
Research
Shai-Hulud Descends to Hades: Miasma Worm Campaign Spreads with New PyPI Wave
Socket found 37 malicious PyPI wheels that abuse Python startup hooks to launch a Bun-powered credential stealer tied to Mini Shai-Hulud/Miasma.
@page-speed/maps
Advanced tools
Performance-optimized MapLibre React components and style utilities for DashTrack.

pnpm add @page-speed/maps maplibre-gl react-map-gl
import { MapLibre } from "@page-speed/maps";
export function Example() {
return (
<div style={{ width: "100%", height: 420 }}>
<MapLibre
stadiaApiKey={process.env.NEXT_PUBLIC_STADIA_API ?? ""}
mapStyle="osm-bright"
viewState={{ latitude: 40.7128, longitude: -74.006, zoom: 12 }}
markers={[
{
id: "nyc",
latitude: 40.7128,
longitude: -74.006,
label: "New York"
}
]}
/>
</div>
);
}
MapLibre component used in dt-cmsMapLibre / DTMapLibreMapThe main map component. Renders a MapLibre GL map with markers, controls, and full interactivity.
import { MapLibre } from "@page-speed/maps";
<MapLibre
stadiaApiKey="your-api-key"
mapStyle="osm-bright"
viewState={{ latitude: 33.4484, longitude: -112.074, zoom: 10 }}
markers={[
{ id: "phx", latitude: 33.4484, longitude: -112.074, label: "Phoenix" }
]}
showNavigationControl
showGeolocateControl
onClick={(coord) => console.log("Clicked:", coord)}
onMoveEnd={(center, zoom, bounds) => console.log("Moved:", center, zoom)}
/>
| Prop | Type | Default | Description |
|---|---|---|---|
stadiaApiKey | string | required | Your Stadia Maps API key |
mapStyle | string | "osm-bright" | Built-in style name or custom style URL |
viewState | Partial<MapViewState> | - | Controlled view state (lat, lng, zoom) |
onViewStateChange | (state) => void | - | Callback when view state changes |
markers | Array<MapLibreMarker | BasicMarkerInput> | [] | Array of markers to display |
center | { lat, lng } | - | Initial center (alternative to viewState) |
zoom | number | 14 | Initial zoom level |
styleUrl | string | - | Custom style URL (overrides mapStyle) |
onClick | (coord) => void | - | Map click handler |
onMoveEnd | (center, zoom, bounds) => void | - | Called when map stops moving |
onMarkerDrag | (markerId, coord) => void | - | Called when draggable marker moves |
showNavigationControl | boolean | true | Show zoom/rotation controls |
showGeolocateControl | boolean | false | Show user location button |
navigationControlPosition | MapControlPosition | "bottom-right" | Position of nav controls |
geolocateControlPosition | MapControlPosition | "top-left" | Position of geolocate button |
flyToOptions | MapLibreFlyToOptions | {} | Animation options for flyTo |
className | string | - | CSS class for wrapper div |
style | CSSProperties | - | Inline styles for wrapper |
children | ReactNode | - | Additional map layers/overlays |
mapLibreCssHref | string | jsDelivr CDN | Custom MapLibre CSS URL |
useGeoCenterComputes the geographic center of an array of coordinates using the Cartesian 3D averaging method. Handles antimeridian crossing and polar coordinates correctly.
import { useGeoCenter } from "@page-speed/maps/hooks/useGeoCenter";
// or
import { useGeoCenter } from "@page-speed/maps";
const markers = [
{ lat: 33.4585, lng: -112.0715 }, // Downtown Phoenix
{ lat: 33.6510, lng: -111.9244 }, // Scottsdale
{ lat: 33.3062, lng: -111.8413 }, // Mesa
];
const center = useGeoCenter(markers);
// Result: { lat: 33.4719, lng: -111.9457 }
function useGeoCenter(coordinates: GeoCoordinate[]): GeoCenterResult | null;
function computeGeoCenter(coordinates: GeoCoordinate[]): GeoCenterResult | null;
interface GeoCoordinate {
lat: number;
lng: number;
}
interface GeoCenterResult {
lat: number;
lng: number;
}
nulluseDefaultZoomComputes the optimal zoom level to fit all coordinates within a given viewport, using Mercator projection math. Uses MapLibre's native 512px tile size.
import { useDefaultZoom } from "@page-speed/maps/hooks/useDefaultZoom";
// or
import { useDefaultZoom } from "@page-speed/maps";
const markers = [
{ lat: 33.4585, lng: -112.0715 },
{ lat: 33.6510, lng: -111.9244 },
];
const zoom = useDefaultZoom({
coordinates: markers,
mapWidth: 600,
mapHeight: 400,
padding: 50,
maxZoom: 16,
minZoom: 1,
});
// Result: ~10.5 (fits both markers with padding)
function useDefaultZoom(options: DefaultZoomOptions): number | null;
function computeDefaultZoom(options: DefaultZoomOptions): number | null;
interface DefaultZoomOptions {
coordinates: GeoCoordinate[];
mapWidth: number;
mapHeight: number;
padding?: number; // default: 50
maxZoom?: number; // default: 18
minZoom?: number; // default: 1
}
nullmaxZoomnull or minZoomimport { MapLibre, useGeoCenter, useDefaultZoom } from "@page-speed/maps";
function AutoCenteringMap({ locations }) {
const coordinates = locations.map(loc => ({
lat: loc.latitude,
lng: loc.longitude,
}));
const center = useGeoCenter(coordinates);
const zoom = useDefaultZoom({
coordinates,
mapWidth: 800,
mapHeight: 600,
padding: 60,
});
if (!center) return <div>No locations to display</div>;
return (
<div style={{ width: 800, height: 600 }}>
<MapLibre
stadiaApiKey={process.env.NEXT_PUBLIC_STADIA_API ?? ""}
viewState={{
latitude: center.lat,
longitude: center.lng,
zoom: zoom ?? 10,
}}
markers={locations.map((loc, i) => ({
id: loc.id ?? i,
latitude: loc.latitude,
longitude: loc.longitude,
label: loc.name,
}))}
/>
</div>
);
}
getMapLibreStyleUrl(style, stadiaApiKey)Resolves a style name or URL to a fully-qualified MapLibre style URL with authentication.
import { getMapLibreStyleUrl } from "@page-speed/maps/utils/style-url";
const url = getMapLibreStyleUrl("osm-bright", "your-api-key");
// "https://tiles.stadiamaps.com/styles/osm_bright.json?api_key=your-api-key"
const fallback = getMapLibreStyleUrl("osm-bright", "");
// "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json" (keyless fallback)
appendStadiaApiKey(styleUrl, stadiaApiKey)Appends the Stadia API key to a style URL if it's a Stadia Maps URL.
import { appendStadiaApiKey } from "@page-speed/maps/utils/style-url";
const url = appendStadiaApiKey(
"https://tiles.stadiamaps.com/styles/osm_bright.json",
"your-api-key"
);
// "https://tiles.stadiamaps.com/styles/osm_bright.json?api_key=your-api-key"
generateGoogleMapLink(latitude, longitude, zoom?)Generates a Google Maps URL for a location.
import { generateGoogleMapLink } from "@page-speed/maps/utils/google-links";
const url = generateGoogleMapLink(33.4484, -112.074, 15);
// "https://www.google.com/maps/@33.4484,-112.074,15z"
generateGoogleDirectionsLink(latitude, longitude)Generates a Google Maps directions URL to a destination.
import { generateGoogleDirectionsLink } from "@page-speed/maps/utils/google-links";
const url = generateGoogleDirectionsLink(33.4484, -112.074);
// "https://www.google.com/maps/dir/?api=1&destination=33.4484,-112.074"
All types are exported from @page-speed/maps/types or the main entry point:
import type {
BasicMarkerInput,
MapControlPosition,
MapCoordinate,
MapLibreFlyToOptions,
MapLibreMarker,
MapLibreProps,
MapViewState,
GeoCoordinate,
GeoCenterResult,
DefaultZoomOptions,
MapLibreBuiltInStyle,
} from "@page-speed/maps";
type MapViewState = {
latitude: number;
longitude: number;
zoom: number;
};
type MapCoordinate = {
latitude: number;
longitude: number;
};
type GeoCoordinate = {
lat: number;
lng: number;
};
type BasicMarkerInput = {
id?: string | number;
latitude: number;
longitude: number;
color?: string;
draggable?: boolean;
label?: string;
element?: (() => React.ReactNode) | React.ReactNode;
onClick?: () => void;
};
type MapControlPosition =
| "top-left"
| "top-right"
| "bottom-left"
| "bottom-right";
This package supports granular tree-shaking. Import only what you need:
// Full bundle (all exports)
import { MapLibre, useGeoCenter, useDefaultZoom } from "@page-speed/maps";
// Just the component
import { MapLibre } from "@page-speed/maps/core";
// Just hooks (tree-shakable)
import { useGeoCenter, useDefaultZoom } from "@page-speed/maps/hooks";
// Individual hooks (maximum tree-shaking)
import { useGeoCenter } from "@page-speed/maps/hooks/useGeoCenter";
import { useDefaultZoom } from "@page-speed/maps/hooks/useDefaultZoom";
// Just utilities
import { getMapLibreStyleUrl } from "@page-speed/maps/utils/style-url";
import { generateGoogleMapLink } from "@page-speed/maps/utils/google-links";
// Just types
import type { MapLibreProps } from "@page-speed/maps/types";
Built-in style presets (requires Stadia API key):
| Style Name | Description |
|---|---|
osm-bright | Clean, bright OpenStreetMap style (default) |
alidade-smooth | Modern, smooth cartography |
alidade-smooth-dark | Dark theme variant |
stadia-outdoors | Outdoor/terrain focused |
stamen-toner | High-contrast black & white |
stamen-terrain | Terrain with hillshading |
stamen-watercolor | Artistic watercolor style |
maplibre-default | Carto Positron (no API key required) |
<MapLibre mapStyle="stamen-terrain" stadiaApiKey="..." />
Or use a custom style URL:
<MapLibre styleUrl="https://your-tiles.com/style.json" stadiaApiKey="..." />
Pass a custom React element for full control over marker rendering:
<MapLibre
stadiaApiKey="..."
markers={[
{
id: "custom",
latitude: 33.4484,
longitude: -112.074,
element: (
<div className="custom-marker">
<img src="/pin.svg" alt="Location" />
<span>Phoenix HQ</span>
</div>
),
},
]}
/>
<MapLibre
stadiaApiKey="..."
markers={[
{
id: "draggable",
latitude: 33.4484,
longitude: -112.074,
draggable: true,
label: "Drag me!",
},
]}
onMarkerDrag={(markerId, coord) => {
console.log(`Marker ${markerId} moved to:`, coord);
}}
/>
function ControlledMap() {
const [viewState, setViewState] = useState({
latitude: 33.4484,
longitude: -112.074,
zoom: 12,
});
return (
<>
<MapLibre
stadiaApiKey="..."
viewState={viewState}
onViewStateChange={setViewState}
/>
<button onClick={() => setViewState(prev => ({ ...prev, zoom: prev.zoom + 1 }))}>
Zoom In
</button>
</>
);
}
<MapLibre
stadiaApiKey="..."
viewState={viewState}
flyToOptions={{
speed: 1.2,
curve: 1.5,
easing: (t) => t,
}}
/>
This package provides the core map primitives. For feature-rich components with clustering, info windows, and styled markers, see @opensite/ui which builds on top of @page-speed/maps:
// In @opensite/ui (consumer library)
import { useGeoCenter, useDefaultZoom, type GeoCoordinate } from "@page-speed/maps/hooks";
function GeoMap({ markers, clusters, defaultViewState }) {
// Collect all coordinates
const allCoordinates: GeoCoordinate[] = [
...markers.map(m => ({ lat: m.latitude, lng: m.longitude })),
...clusters.map(c => ({ lat: c.latitude, lng: c.longitude })),
];
// Auto-compute center and zoom
const geoCenter = useGeoCenter(allCoordinates);
const defaultZoom = useDefaultZoom({
coordinates: allCoordinates,
mapWidth: 600,
mapHeight: 520,
padding: 60,
});
return (
<MapLibre
viewState={{
latitude: defaultViewState?.latitude ?? geoCenter?.lat ?? 0,
longitude: defaultViewState?.longitude ?? geoCenter?.lng ?? 0,
zoom: defaultViewState?.zoom ?? defaultZoom ?? 10,
}}
// ... clustering, custom markers, info windows, etc.
/>
);
}
| Export | Path | Description |
|---|---|---|
MapLibre | @page-speed/maps | Main map component |
DTMapLibreMap | @page-speed/maps | Alias for MapLibre |
useGeoCenter | @page-speed/maps/hooks/useGeoCenter | Geographic center hook |
computeGeoCenter | @page-speed/maps/hooks/useGeoCenter | Pure function version |
useDefaultZoom | @page-speed/maps/hooks/useDefaultZoom | Auto-zoom hook |
computeDefaultZoom | @page-speed/maps/hooks/useDefaultZoom | Pure function version |
getMapLibreStyleUrl | @page-speed/maps/utils/style-url | Style URL resolver |
appendStadiaApiKey | @page-speed/maps/utils/style-url | API key appender |
generateGoogleMapLink | @page-speed/maps/utils/google-links | Google Maps link |
generateGoogleDirectionsLink | @page-speed/maps/utils/google-links | Google Directions link |
BSD-3-Clause. See LICENSE for details.
GeoMapFull-featured map component with markers, clusters, rich media panels, and automatic view calculation.
import { GeoMap, createMapMarkerElement } from "@page-speed/maps";
import type { GeoMapMarker } from "@page-speed/maps";
const markers: GeoMapMarker[] = [
{
id: 'office',
latitude: 40.7128,
longitude: -74.0060,
title: 'New York Office',
summary: 'Our headquarters in downtown Manhattan',
locationLine: '123 Broadway, New York, NY 10001',
hoursLine: 'Mon-Fri: 9:00 AM - 6:00 PM',
mediaItems: [
{ id: '1', src: '/office.jpg', alt: 'Office' },
],
markerElement: createMapMarkerElement({ size: 'lg' }),
actions: [
{
label: 'Get Directions',
href: 'https://maps.app.goo.gl/example',
},
],
},
];
<GeoMap
markers={markers}
stadiaApiKey="your-key"
panelPosition="bottom-left"
showNavigationControl
/>
Key Features:
MapMarkerBeautiful concentric circle markers with hover and selection states.
import { MapMarker, NeutralMapMarker, createMapMarkerElement } from "@page-speed/maps";
// Direct usage
<MapMarker
size="lg"
isSelected
dotColor="#1E40AF"
innerRingColor="#3B82F6"
middleRingColor="#93C5FD"
outerRingColor="#DBEAFE"
/>
// With GeoMap
const markers = [{
id: 'loc-1',
latitude: 40.7128,
longitude: -74.0060,
markerElement: createMapMarkerElement({ size: 'lg' }),
}];
Sizes: sm | md | lg
Pre-configured: NeutralMapMarker for neutral gray design
If you're migrating map components from @opensite/ui to @page-speed/maps, see our comprehensive migration guide:
Key Changes:
Made with ❤️ for the DashTrack Platform
FAQs
Performance-optimized MapLibre React components and style utilities for DashTrack.
The npm package @page-speed/maps receives a total of 19 weekly downloads. As such, @page-speed/maps popularity was classified as not popular.
We found that @page-speed/maps demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
Socket found 37 malicious PyPI wheels that abuse Python startup hooks to launch a Bun-powered credential stealer tied to Mini Shai-Hulud/Miasma.

Security News
RubyGems and Bundler 4.0.13 introduced an opt-in cooldown feature that delays newly published gems during dependency resolution.

Security News
pnpm 11.5 now recognizes npm staged publish approvals in release metadata, preventing those releases from being mistaken for lower-trust package publishes.