@page-speed/maps 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +24 -17
- package/README.md +530 -13
- package/dist/hooks/index.cjs +109 -0
- package/dist/hooks/index.cjs.map +1 -0
- package/dist/hooks/index.d.cts +2 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +104 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/useDefaultZoom.cjs +78 -0
- package/dist/hooks/useDefaultZoom.cjs.map +1 -0
- package/dist/hooks/useDefaultZoom.d.cts +30 -0
- package/dist/hooks/useDefaultZoom.d.ts +30 -0
- package/dist/hooks/useDefaultZoom.js +75 -0
- package/dist/hooks/useDefaultZoom.js.map +1 -0
- package/dist/hooks/useGeoCenter.cjs +39 -0
- package/dist/hooks/useGeoCenter.cjs.map +1 -0
- package/dist/hooks/useGeoCenter.d.cts +22 -0
- package/dist/hooks/useGeoCenter.d.ts +22 -0
- package/dist/hooks/useGeoCenter.js +36 -0
- package/dist/hooks/useGeoCenter.js.map +1 -0
- package/dist/index.cjs +101 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +99 -2
- package/dist/index.js.map +1 -1
- package/package.json +17 -2
package/LICENSE
CHANGED
|
@@ -1,21 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
BSD 3-Clause License
|
|
2
2
|
|
|
3
|
-
Copyright (c) OpenSite AI
|
|
3
|
+
Copyright (c) 2025, OpenSite AI. All rights reserved.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
# @page-speed/maps
|
|
2
2
|
|
|
3
|
+
## High-performance MapLibre primitives. An open source tool by [OpenSite AI](https://opensite.ai)
|
|
4
|
+
|
|
3
5
|

|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
<br />
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@page-speed/maps)
|
|
10
|
+
[](https://www.npmjs.com/package/@page-speed/maps)
|
|
11
|
+
[](./LICENSE)
|
|
12
|
+
[](./tsconfig.json)
|
|
13
|
+
[](#tree-shaking)
|
|
6
14
|
|
|
7
15
|
## Install
|
|
8
16
|
|
|
@@ -38,18 +46,527 @@ export function Example() {
|
|
|
38
46
|
|
|
39
47
|
## Why This Package
|
|
40
48
|
|
|
41
|
-
- Explicit Stadia auth
|
|
42
|
-
- Auto-loads MapLibre CSS
|
|
43
|
-
- Keyless fallback map style
|
|
44
|
-
- Tree-shakable exports
|
|
45
|
-
-
|
|
49
|
+
- **Explicit Stadia auth**: no hard-coded keys
|
|
50
|
+
- **Auto-loads MapLibre CSS**: no extra stylesheet import required
|
|
51
|
+
- **Keyless fallback map style**: if no Stadia key is available, roads/landmarks still render via Carto Positron
|
|
52
|
+
- **Tree-shakable exports**: import only what you need
|
|
53
|
+
- **Auto-centering hooks**: compute optimal center and zoom for any set of coordinates
|
|
54
|
+
- **Drop-in API compatibility**: works with the `MapLibre` component used in `dt-cms`
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Table of Contents
|
|
59
|
+
|
|
60
|
+
- [Components](#components)
|
|
61
|
+
- [Hooks](#hooks)
|
|
62
|
+
- [Utilities](#utilities)
|
|
63
|
+
- [Types](#types)
|
|
64
|
+
- [Tree Shaking](#tree-shaking)
|
|
65
|
+
- [Map Styles](#map-styles)
|
|
66
|
+
- [Advanced Usage](#advanced-usage)
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Components
|
|
71
|
+
|
|
72
|
+
### `MapLibre` / `DTMapLibreMap`
|
|
73
|
+
|
|
74
|
+
The main map component. Renders a MapLibre GL map with markers, controls, and full interactivity.
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { MapLibre } from "@page-speed/maps";
|
|
78
|
+
|
|
79
|
+
<MapLibre
|
|
80
|
+
stadiaApiKey="your-api-key"
|
|
81
|
+
mapStyle="osm-bright"
|
|
82
|
+
viewState={{ latitude: 33.4484, longitude: -112.074, zoom: 10 }}
|
|
83
|
+
markers={[
|
|
84
|
+
{ id: "phx", latitude: 33.4484, longitude: -112.074, label: "Phoenix" }
|
|
85
|
+
]}
|
|
86
|
+
showNavigationControl
|
|
87
|
+
showGeolocateControl
|
|
88
|
+
onClick={(coord) => console.log("Clicked:", coord)}
|
|
89
|
+
onMoveEnd={(center, zoom, bounds) => console.log("Moved:", center, zoom)}
|
|
90
|
+
/>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### Props
|
|
94
|
+
|
|
95
|
+
| Prop | Type | Default | Description |
|
|
96
|
+
|------|------|---------|-------------|
|
|
97
|
+
| `stadiaApiKey` | `string` | **required** | Your Stadia Maps API key |
|
|
98
|
+
| `mapStyle` | `string` | `"osm-bright"` | Built-in style name or custom style URL |
|
|
99
|
+
| `viewState` | `Partial<MapViewState>` | - | Controlled view state (lat, lng, zoom) |
|
|
100
|
+
| `onViewStateChange` | `(state) => void` | - | Callback when view state changes |
|
|
101
|
+
| `markers` | `Array<MapLibreMarker \| BasicMarkerInput>` | `[]` | Array of markers to display |
|
|
102
|
+
| `center` | `{ lat, lng }` | - | Initial center (alternative to viewState) |
|
|
103
|
+
| `zoom` | `number` | `14` | Initial zoom level |
|
|
104
|
+
| `styleUrl` | `string` | - | Custom style URL (overrides mapStyle) |
|
|
105
|
+
| `onClick` | `(coord) => void` | - | Map click handler |
|
|
106
|
+
| `onMoveEnd` | `(center, zoom, bounds) => void` | - | Called when map stops moving |
|
|
107
|
+
| `onMarkerDrag` | `(markerId, coord) => void` | - | Called when draggable marker moves |
|
|
108
|
+
| `showNavigationControl` | `boolean` | `true` | Show zoom/rotation controls |
|
|
109
|
+
| `showGeolocateControl` | `boolean` | `false` | Show user location button |
|
|
110
|
+
| `navigationControlPosition` | `MapControlPosition` | `"bottom-right"` | Position of nav controls |
|
|
111
|
+
| `geolocateControlPosition` | `MapControlPosition` | `"top-left"` | Position of geolocate button |
|
|
112
|
+
| `flyToOptions` | `MapLibreFlyToOptions` | `{}` | Animation options for flyTo |
|
|
113
|
+
| `className` | `string` | - | CSS class for wrapper div |
|
|
114
|
+
| `style` | `CSSProperties` | - | Inline styles for wrapper |
|
|
115
|
+
| `children` | `ReactNode` | - | Additional map layers/overlays |
|
|
116
|
+
| `mapLibreCssHref` | `string` | jsDelivr CDN | Custom MapLibre CSS URL |
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Hooks
|
|
121
|
+
|
|
122
|
+
### `useGeoCenter`
|
|
123
|
+
|
|
124
|
+
Computes the geographic center of an array of coordinates using the Cartesian 3D averaging method. Handles antimeridian crossing and polar coordinates correctly.
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
import { useGeoCenter } from "@page-speed/maps/hooks/useGeoCenter";
|
|
128
|
+
// or
|
|
129
|
+
import { useGeoCenter } from "@page-speed/maps";
|
|
130
|
+
|
|
131
|
+
const markers = [
|
|
132
|
+
{ lat: 33.4585, lng: -112.0715 }, // Downtown Phoenix
|
|
133
|
+
{ lat: 33.6510, lng: -111.9244 }, // Scottsdale
|
|
134
|
+
{ lat: 33.3062, lng: -111.8413 }, // Mesa
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
const center = useGeoCenter(markers);
|
|
138
|
+
// Result: { lat: 33.4719, lng: -111.9457 }
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### API
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
function useGeoCenter(coordinates: GeoCoordinate[]): GeoCenterResult | null;
|
|
145
|
+
function computeGeoCenter(coordinates: GeoCoordinate[]): GeoCenterResult | null;
|
|
146
|
+
|
|
147
|
+
interface GeoCoordinate {
|
|
148
|
+
lat: number;
|
|
149
|
+
lng: number;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
interface GeoCenterResult {
|
|
153
|
+
lat: number;
|
|
154
|
+
lng: number;
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### Behavior
|
|
159
|
+
|
|
160
|
+
- **Empty array**: Returns `null`
|
|
161
|
+
- **Single coordinate**: Returns that coordinate
|
|
162
|
+
- **Multiple coordinates**: Returns the geographic midpoint
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
### `useDefaultZoom`
|
|
167
|
+
|
|
168
|
+
Computes the optimal zoom level to fit all coordinates within a given viewport, using Mercator projection math. Uses MapLibre's native 512px tile size.
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
import { useDefaultZoom } from "@page-speed/maps/hooks/useDefaultZoom";
|
|
172
|
+
// or
|
|
173
|
+
import { useDefaultZoom } from "@page-speed/maps";
|
|
174
|
+
|
|
175
|
+
const markers = [
|
|
176
|
+
{ lat: 33.4585, lng: -112.0715 },
|
|
177
|
+
{ lat: 33.6510, lng: -111.9244 },
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
const zoom = useDefaultZoom({
|
|
181
|
+
coordinates: markers,
|
|
182
|
+
mapWidth: 600,
|
|
183
|
+
mapHeight: 400,
|
|
184
|
+
padding: 50,
|
|
185
|
+
maxZoom: 16,
|
|
186
|
+
minZoom: 1,
|
|
187
|
+
});
|
|
188
|
+
// Result: ~10.5 (fits both markers with padding)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### API
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
function useDefaultZoom(options: DefaultZoomOptions): number | null;
|
|
195
|
+
function computeDefaultZoom(options: DefaultZoomOptions): number | null;
|
|
196
|
+
|
|
197
|
+
interface DefaultZoomOptions {
|
|
198
|
+
coordinates: GeoCoordinate[];
|
|
199
|
+
mapWidth: number;
|
|
200
|
+
mapHeight: number;
|
|
201
|
+
padding?: number; // default: 50
|
|
202
|
+
maxZoom?: number; // default: 18
|
|
203
|
+
minZoom?: number; // default: 1
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### Behavior
|
|
208
|
+
|
|
209
|
+
- **Empty array**: Returns `null`
|
|
210
|
+
- **Single coordinate**: Returns `maxZoom`
|
|
211
|
+
- **Multiple coordinates**: Returns the highest zoom that fits all markers with padding
|
|
212
|
+
- **Invalid dimensions**: Returns `null` or `minZoom`
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
### Combined Usage: Auto-Centering Map
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
import { MapLibre, useGeoCenter, useDefaultZoom } from "@page-speed/maps";
|
|
220
|
+
|
|
221
|
+
function AutoCenteringMap({ locations }) {
|
|
222
|
+
const coordinates = locations.map(loc => ({
|
|
223
|
+
lat: loc.latitude,
|
|
224
|
+
lng: loc.longitude,
|
|
225
|
+
}));
|
|
226
|
+
|
|
227
|
+
const center = useGeoCenter(coordinates);
|
|
228
|
+
const zoom = useDefaultZoom({
|
|
229
|
+
coordinates,
|
|
230
|
+
mapWidth: 800,
|
|
231
|
+
mapHeight: 600,
|
|
232
|
+
padding: 60,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
if (!center) return <div>No locations to display</div>;
|
|
236
|
+
|
|
237
|
+
return (
|
|
238
|
+
<div style={{ width: 800, height: 600 }}>
|
|
239
|
+
<MapLibre
|
|
240
|
+
stadiaApiKey={process.env.NEXT_PUBLIC_STADIA_API ?? ""}
|
|
241
|
+
viewState={{
|
|
242
|
+
latitude: center.lat,
|
|
243
|
+
longitude: center.lng,
|
|
244
|
+
zoom: zoom ?? 10,
|
|
245
|
+
}}
|
|
246
|
+
markers={locations.map((loc, i) => ({
|
|
247
|
+
id: loc.id ?? i,
|
|
248
|
+
latitude: loc.latitude,
|
|
249
|
+
longitude: loc.longitude,
|
|
250
|
+
label: loc.name,
|
|
251
|
+
}))}
|
|
252
|
+
/>
|
|
253
|
+
</div>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Utilities
|
|
261
|
+
|
|
262
|
+
### `getMapLibreStyleUrl(style, stadiaApiKey)`
|
|
263
|
+
|
|
264
|
+
Resolves a style name or URL to a fully-qualified MapLibre style URL with authentication.
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
import { getMapLibreStyleUrl } from "@page-speed/maps/utils/style-url";
|
|
268
|
+
|
|
269
|
+
const url = getMapLibreStyleUrl("osm-bright", "your-api-key");
|
|
270
|
+
// "https://tiles.stadiamaps.com/styles/osm_bright.json?api_key=your-api-key"
|
|
271
|
+
|
|
272
|
+
const fallback = getMapLibreStyleUrl("osm-bright", "");
|
|
273
|
+
// "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json" (keyless fallback)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### `appendStadiaApiKey(styleUrl, stadiaApiKey)`
|
|
277
|
+
|
|
278
|
+
Appends the Stadia API key to a style URL if it's a Stadia Maps URL.
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
import { appendStadiaApiKey } from "@page-speed/maps/utils/style-url";
|
|
282
|
+
|
|
283
|
+
const url = appendStadiaApiKey(
|
|
284
|
+
"https://tiles.stadiamaps.com/styles/osm_bright.json",
|
|
285
|
+
"your-api-key"
|
|
286
|
+
);
|
|
287
|
+
// "https://tiles.stadiamaps.com/styles/osm_bright.json?api_key=your-api-key"
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### `generateGoogleMapLink(latitude, longitude, zoom?)`
|
|
291
|
+
|
|
292
|
+
Generates a Google Maps URL for a location.
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
import { generateGoogleMapLink } from "@page-speed/maps/utils/google-links";
|
|
296
|
+
|
|
297
|
+
const url = generateGoogleMapLink(33.4484, -112.074, 15);
|
|
298
|
+
// "https://www.google.com/maps/@33.4484,-112.074,15z"
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### `generateGoogleDirectionsLink(latitude, longitude)`
|
|
302
|
+
|
|
303
|
+
Generates a Google Maps directions URL to a destination.
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import { generateGoogleDirectionsLink } from "@page-speed/maps/utils/google-links";
|
|
307
|
+
|
|
308
|
+
const url = generateGoogleDirectionsLink(33.4484, -112.074);
|
|
309
|
+
// "https://www.google.com/maps/dir/?api=1&destination=33.4484,-112.074"
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Types
|
|
315
|
+
|
|
316
|
+
All types are exported from `@page-speed/maps/types` or the main entry point:
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
import type {
|
|
320
|
+
BasicMarkerInput,
|
|
321
|
+
MapControlPosition,
|
|
322
|
+
MapCoordinate,
|
|
323
|
+
MapLibreFlyToOptions,
|
|
324
|
+
MapLibreMarker,
|
|
325
|
+
MapLibreProps,
|
|
326
|
+
MapViewState,
|
|
327
|
+
GeoCoordinate,
|
|
328
|
+
GeoCenterResult,
|
|
329
|
+
DefaultZoomOptions,
|
|
330
|
+
MapLibreBuiltInStyle,
|
|
331
|
+
} from "@page-speed/maps";
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Key Types
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
type MapViewState = {
|
|
338
|
+
latitude: number;
|
|
339
|
+
longitude: number;
|
|
340
|
+
zoom: number;
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
type MapCoordinate = {
|
|
344
|
+
latitude: number;
|
|
345
|
+
longitude: number;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
type GeoCoordinate = {
|
|
349
|
+
lat: number;
|
|
350
|
+
lng: number;
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
type BasicMarkerInput = {
|
|
354
|
+
id?: string | number;
|
|
355
|
+
latitude: number;
|
|
356
|
+
longitude: number;
|
|
357
|
+
color?: string;
|
|
358
|
+
draggable?: boolean;
|
|
359
|
+
label?: string;
|
|
360
|
+
element?: (() => React.ReactNode) | React.ReactNode;
|
|
361
|
+
onClick?: () => void;
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
type MapControlPosition =
|
|
365
|
+
| "top-left"
|
|
366
|
+
| "top-right"
|
|
367
|
+
| "bottom-left"
|
|
368
|
+
| "bottom-right";
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Tree Shaking
|
|
374
|
+
|
|
375
|
+
This package supports granular tree-shaking. Import only what you need:
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
// Full bundle (all exports)
|
|
379
|
+
import { MapLibre, useGeoCenter, useDefaultZoom } from "@page-speed/maps";
|
|
380
|
+
|
|
381
|
+
// Just the component
|
|
382
|
+
import { MapLibre } from "@page-speed/maps/core";
|
|
383
|
+
|
|
384
|
+
// Just hooks (tree-shakable)
|
|
385
|
+
import { useGeoCenter, useDefaultZoom } from "@page-speed/maps/hooks";
|
|
386
|
+
|
|
387
|
+
// Individual hooks (maximum tree-shaking)
|
|
388
|
+
import { useGeoCenter } from "@page-speed/maps/hooks/useGeoCenter";
|
|
389
|
+
import { useDefaultZoom } from "@page-speed/maps/hooks/useDefaultZoom";
|
|
390
|
+
|
|
391
|
+
// Just utilities
|
|
392
|
+
import { getMapLibreStyleUrl } from "@page-speed/maps/utils/style-url";
|
|
393
|
+
import { generateGoogleMapLink } from "@page-speed/maps/utils/google-links";
|
|
394
|
+
|
|
395
|
+
// Just types
|
|
396
|
+
import type { MapLibreProps } from "@page-speed/maps/types";
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Map Styles
|
|
402
|
+
|
|
403
|
+
Built-in style presets (requires Stadia API key):
|
|
404
|
+
|
|
405
|
+
| Style Name | Description |
|
|
406
|
+
|------------|-------------|
|
|
407
|
+
| `osm-bright` | Clean, bright OpenStreetMap style (default) |
|
|
408
|
+
| `alidade-smooth` | Modern, smooth cartography |
|
|
409
|
+
| `alidade-smooth-dark` | Dark theme variant |
|
|
410
|
+
| `stadia-outdoors` | Outdoor/terrain focused |
|
|
411
|
+
| `stamen-toner` | High-contrast black & white |
|
|
412
|
+
| `stamen-terrain` | Terrain with hillshading |
|
|
413
|
+
| `stamen-watercolor` | Artistic watercolor style |
|
|
414
|
+
| `maplibre-default` | Carto Positron (no API key required) |
|
|
415
|
+
|
|
416
|
+
```tsx
|
|
417
|
+
<MapLibre mapStyle="stamen-terrain" stadiaApiKey="..." />
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Or use a custom style URL:
|
|
421
|
+
|
|
422
|
+
```tsx
|
|
423
|
+
<MapLibre styleUrl="https://your-tiles.com/style.json" stadiaApiKey="..." />
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## Advanced Usage
|
|
429
|
+
|
|
430
|
+
### Custom Markers
|
|
431
|
+
|
|
432
|
+
Pass a custom React element for full control over marker rendering:
|
|
433
|
+
|
|
434
|
+
```tsx
|
|
435
|
+
<MapLibre
|
|
436
|
+
stadiaApiKey="..."
|
|
437
|
+
markers={[
|
|
438
|
+
{
|
|
439
|
+
id: "custom",
|
|
440
|
+
latitude: 33.4484,
|
|
441
|
+
longitude: -112.074,
|
|
442
|
+
element: (
|
|
443
|
+
<div className="custom-marker">
|
|
444
|
+
<img src="/pin.svg" alt="Location" />
|
|
445
|
+
<span>Phoenix HQ</span>
|
|
446
|
+
</div>
|
|
447
|
+
),
|
|
448
|
+
},
|
|
449
|
+
]}
|
|
450
|
+
/>
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### Draggable Markers
|
|
454
|
+
|
|
455
|
+
```tsx
|
|
456
|
+
<MapLibre
|
|
457
|
+
stadiaApiKey="..."
|
|
458
|
+
markers={[
|
|
459
|
+
{
|
|
460
|
+
id: "draggable",
|
|
461
|
+
latitude: 33.4484,
|
|
462
|
+
longitude: -112.074,
|
|
463
|
+
draggable: true,
|
|
464
|
+
label: "Drag me!",
|
|
465
|
+
},
|
|
466
|
+
]}
|
|
467
|
+
onMarkerDrag={(markerId, coord) => {
|
|
468
|
+
console.log(`Marker ${markerId} moved to:`, coord);
|
|
469
|
+
}}
|
|
470
|
+
/>
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### Controlled View State
|
|
474
|
+
|
|
475
|
+
```tsx
|
|
476
|
+
function ControlledMap() {
|
|
477
|
+
const [viewState, setViewState] = useState({
|
|
478
|
+
latitude: 33.4484,
|
|
479
|
+
longitude: -112.074,
|
|
480
|
+
zoom: 12,
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
return (
|
|
484
|
+
<>
|
|
485
|
+
<MapLibre
|
|
486
|
+
stadiaApiKey="..."
|
|
487
|
+
viewState={viewState}
|
|
488
|
+
onViewStateChange={setViewState}
|
|
489
|
+
/>
|
|
490
|
+
<button onClick={() => setViewState(prev => ({ ...prev, zoom: prev.zoom + 1 }))}>
|
|
491
|
+
Zoom In
|
|
492
|
+
</button>
|
|
493
|
+
</>
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### Fly To Animation
|
|
499
|
+
|
|
500
|
+
```tsx
|
|
501
|
+
<MapLibre
|
|
502
|
+
stadiaApiKey="..."
|
|
503
|
+
viewState={viewState}
|
|
504
|
+
flyToOptions={{
|
|
505
|
+
speed: 1.2,
|
|
506
|
+
curve: 1.5,
|
|
507
|
+
easing: (t) => t,
|
|
508
|
+
}}
|
|
509
|
+
/>
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### Composing with UI Libraries
|
|
513
|
+
|
|
514
|
+
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`:
|
|
515
|
+
|
|
516
|
+
```tsx
|
|
517
|
+
// In @opensite/ui (consumer library)
|
|
518
|
+
import { useGeoCenter, useDefaultZoom, type GeoCoordinate } from "@page-speed/maps/hooks";
|
|
519
|
+
|
|
520
|
+
function GeoMap({ markers, clusters, defaultViewState }) {
|
|
521
|
+
// Collect all coordinates
|
|
522
|
+
const allCoordinates: GeoCoordinate[] = [
|
|
523
|
+
...markers.map(m => ({ lat: m.latitude, lng: m.longitude })),
|
|
524
|
+
...clusters.map(c => ({ lat: c.latitude, lng: c.longitude })),
|
|
525
|
+
];
|
|
526
|
+
|
|
527
|
+
// Auto-compute center and zoom
|
|
528
|
+
const geoCenter = useGeoCenter(allCoordinates);
|
|
529
|
+
const defaultZoom = useDefaultZoom({
|
|
530
|
+
coordinates: allCoordinates,
|
|
531
|
+
mapWidth: 600,
|
|
532
|
+
mapHeight: 520,
|
|
533
|
+
padding: 60,
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
return (
|
|
537
|
+
<MapLibre
|
|
538
|
+
viewState={{
|
|
539
|
+
latitude: defaultViewState?.latitude ?? geoCenter?.lat ?? 0,
|
|
540
|
+
longitude: defaultViewState?.longitude ?? geoCenter?.lng ?? 0,
|
|
541
|
+
zoom: defaultViewState?.zoom ?? defaultZoom ?? 10,
|
|
542
|
+
}}
|
|
543
|
+
// ... clustering, custom markers, info windows, etc.
|
|
544
|
+
/>
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
---
|
|
550
|
+
|
|
551
|
+
## API Reference
|
|
552
|
+
|
|
553
|
+
### Exports Summary
|
|
554
|
+
|
|
555
|
+
| Export | Path | Description |
|
|
556
|
+
|--------|------|-------------|
|
|
557
|
+
| `MapLibre` | `@page-speed/maps` | Main map component |
|
|
558
|
+
| `DTMapLibreMap` | `@page-speed/maps` | Alias for MapLibre |
|
|
559
|
+
| `useGeoCenter` | `@page-speed/maps/hooks/useGeoCenter` | Geographic center hook |
|
|
560
|
+
| `computeGeoCenter` | `@page-speed/maps/hooks/useGeoCenter` | Pure function version |
|
|
561
|
+
| `useDefaultZoom` | `@page-speed/maps/hooks/useDefaultZoom` | Auto-zoom hook |
|
|
562
|
+
| `computeDefaultZoom` | `@page-speed/maps/hooks/useDefaultZoom` | Pure function version |
|
|
563
|
+
| `getMapLibreStyleUrl` | `@page-speed/maps/utils/style-url` | Style URL resolver |
|
|
564
|
+
| `appendStadiaApiKey` | `@page-speed/maps/utils/style-url` | API key appender |
|
|
565
|
+
| `generateGoogleMapLink` | `@page-speed/maps/utils/google-links` | Google Maps link |
|
|
566
|
+
| `generateGoogleDirectionsLink` | `@page-speed/maps/utils/google-links` | Google Directions link |
|
|
46
567
|
|
|
47
|
-
|
|
568
|
+
---
|
|
48
569
|
|
|
49
|
-
|
|
50
|
-
- `getMapLibreStyleUrl(value, stadiaApiKey)`
|
|
51
|
-
- `appendStadiaApiKey(styleUrl, stadiaApiKey)`
|
|
52
|
-
- `generateGoogleMapLink(latitude, longitude, zoom?)`
|
|
53
|
-
- `generateGoogleDirectionsLink(latitude, longitude)`
|
|
570
|
+
## License
|
|
54
571
|
|
|
55
|
-
See
|
|
572
|
+
BSD-3-Clause. See [LICENSE](./LICENSE) for details.
|