@tracktor/map 1.6.4 β 1.6.5
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/README.md +640 -73
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,29 +1,62 @@
|
|
|
1
|
-
#
|
|
1
|
+
# πΊοΈ @tracktor/map
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A modern, lightweight React map library built on top of Mapbox GL JS and react-map-gl. Designed for simplicity, flexibility, and visual elegance.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Easily combine markers, routes, GeoJSON features, isochrones, and nearest-point calculations β all with a declarative, type-safe API.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@tracktor/map)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
|
|
10
|
+
---
|
|
6
11
|
|
|
12
|
+
## π Installation
|
|
7
13
|
```bash
|
|
8
14
|
npm install @tracktor/map
|
|
9
15
|
```
|
|
10
16
|
|
|
11
17
|
or
|
|
12
|
-
|
|
13
18
|
```bash
|
|
14
19
|
yarn add @tracktor/map
|
|
15
20
|
```
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
or
|
|
23
|
+
```bash
|
|
24
|
+
bun add @tracktor/map
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## βοΈ Requirements
|
|
30
|
+
|
|
31
|
+
| Dependency | Version | Purpose |
|
|
32
|
+
|------------|---------|---------|
|
|
33
|
+
| `react` | 17+ / 18+ / 19+ | Core React runtime |
|
|
34
|
+
| `react-dom` | 17+ / 18+ / 19+ | React DOM rendering |
|
|
35
|
+
| `mapbox-gl` | β₯3.0.0 | Map rendering engine |
|
|
36
|
+
| `@tracktor/design-system` | β₯4.0.0 | UI theming and components |
|
|
37
|
+
| `@mui/icons-material` | * | Material UI icons |
|
|
38
|
+
| `@mui/x-license` | * | MUI X license integration |
|
|
18
39
|
|
|
19
|
-
|
|
40
|
+
πͺΆ **You'll also need a Mapbox access token** to render maps. Get one at [mapbox.com](https://account.mapbox.com/access-tokens/).
|
|
20
41
|
|
|
21
|
-
|
|
22
|
-
- `mapbox-gl` for map rendering
|
|
23
|
-
- React 19+
|
|
42
|
+
---
|
|
24
43
|
|
|
25
|
-
##
|
|
44
|
+
## β¨ Features
|
|
26
45
|
|
|
46
|
+
β
**Declarative API** β manage complex map interactions with simple props
|
|
47
|
+
β
**Markers & Popups** β customizable React components or image-based icons
|
|
48
|
+
β
**Routing & Isochrones** β visualize travel-time areas or compute optimal routes
|
|
49
|
+
β
**GeoJSON Layers** β render vector data dynamically
|
|
50
|
+
β
**Nearest Marker Search** β find and highlight closest points instantly
|
|
51
|
+
β
**Type-safe API** β full TypeScript support with smart IntelliSense
|
|
52
|
+
β
**Responsive Design** β automatically adapts to any container or screen size
|
|
53
|
+
β
**Built for performance** β minimal re-renders, efficient map updates
|
|
54
|
+
β
**Multiple Routing Engines** β supports both OSRM (free) and Mapbox Directions API
|
|
55
|
+
β
**Flexible Map Styles** β works with Mapbox styles, OpenStreetMap, or custom raster tiles
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## π§© Quick Start
|
|
27
60
|
```tsx
|
|
28
61
|
import { MapProvider, MarkerMap } from "@tracktor/map";
|
|
29
62
|
|
|
@@ -33,82 +66,616 @@ const markers = [
|
|
|
33
66
|
lng: 2.3522,
|
|
34
67
|
lat: 48.8566,
|
|
35
68
|
Tooltip: <div>Paris</div>,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
69
|
+
color: "primary",
|
|
70
|
+
variant: "default",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 2,
|
|
74
|
+
lng: -0.1276,
|
|
75
|
+
lat: 51.5074,
|
|
76
|
+
Tooltip: <div>London</div>,
|
|
77
|
+
color: "secondary",
|
|
78
|
+
variant: "default",
|
|
39
79
|
},
|
|
40
80
|
];
|
|
41
81
|
|
|
42
|
-
|
|
43
|
-
|
|
82
|
+
function App() {
|
|
83
|
+
return (
|
|
84
|
+
<MapProvider
|
|
85
|
+
licenseMuiX="your-muix-license"
|
|
86
|
+
licenceMapbox="your-mapbox-token"
|
|
87
|
+
>
|
|
88
|
+
<MarkerMap
|
|
89
|
+
markers={markers}
|
|
90
|
+
center={[2.3522, 48.8566]}
|
|
91
|
+
zoom={5}
|
|
92
|
+
fitBounds
|
|
93
|
+
height={500}
|
|
94
|
+
width="100%"
|
|
95
|
+
onMapClick={(lng, lat, marker) => {
|
|
96
|
+
console.log("Clicked at:", lng, lat);
|
|
97
|
+
if (marker) console.log("Marker clicked:", marker);
|
|
98
|
+
}}
|
|
99
|
+
/>
|
|
100
|
+
</MapProvider>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## π§ Components Overview
|
|
108
|
+
|
|
109
|
+
### `MapProvider`
|
|
110
|
+
|
|
111
|
+
Wraps your map components and injects required providers (theme, tokens, MUI X license).
|
|
112
|
+
|
|
113
|
+
**Required Props:**
|
|
114
|
+
- `licenseMuiX` β Your MUI X license key
|
|
115
|
+
- `licenceMapbox` β Your Mapbox access token
|
|
116
|
+
```tsx
|
|
117
|
+
<MapProvider
|
|
118
|
+
licenseMuiX="your-license"
|
|
119
|
+
licenceMapbox="your-token"
|
|
120
|
+
>
|
|
121
|
+
{/* Your map components */}
|
|
122
|
+
</MapProvider>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `MapView` / `MarkerMap`
|
|
126
|
+
|
|
127
|
+
Main map component that handles:
|
|
128
|
+
- Marker rendering with custom icons or React components
|
|
129
|
+
- Interactive popups with hover/click modes
|
|
130
|
+
- Automatic bounds fitting
|
|
131
|
+
- Map click events
|
|
132
|
+
- Responsive container sizing
|
|
133
|
+
|
|
134
|
+
### Specialized Components
|
|
135
|
+
|
|
136
|
+
- **`RouteMap`** β Draw routes between two points using OSRM or Mapbox
|
|
137
|
+
- **`IsochroneMap`** β Compute and display travel-time polygons
|
|
138
|
+
- **`FeatureMap`** β Display custom GeoJSON layers with styling
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## π§± Props Reference
|
|
143
|
+
|
|
144
|
+
### `MapView` Props
|
|
145
|
+
|
|
146
|
+
#### Core Map Props
|
|
147
|
+
|
|
148
|
+
| Prop | Type | Default | Description |
|
|
149
|
+
|------|------|---------|-------------|
|
|
150
|
+
| `center` | `[lng, lat]` | `[2.3522, 48.8566]` | Initial map center coordinates |
|
|
151
|
+
| `zoom` | `number` | `5` | Initial zoom level (0-22) |
|
|
152
|
+
| `width` | `string \| number` | `"100%"` | Map container width |
|
|
153
|
+
| `height` | `string \| number` | `300` | Map container height |
|
|
154
|
+
| `loading` | `boolean` | `false` | Show skeleton loader |
|
|
155
|
+
| `square` | `boolean` | `false` | Enforce 1:1 aspect ratio |
|
|
156
|
+
| `containerStyle` | `SxProps` | `undefined` | Custom MUI sx styles |
|
|
157
|
+
|
|
158
|
+
#### Map Appearance
|
|
159
|
+
|
|
160
|
+
| Prop | Type | Default | Description |
|
|
161
|
+
|------|------|---------|-------------|
|
|
162
|
+
| `theme` | `"light" \| "dark"` | `"light"` | Map color theme |
|
|
163
|
+
| `baseMapView` | `"street" \| "satellite"` | `"street"` | Base map layer type |
|
|
164
|
+
| `mapStyle` | `string` | - | Custom Mapbox style URL |
|
|
165
|
+
| `projection` | `ProjectionSpecification` | `"mercator"` | Map projection system |
|
|
166
|
+
|
|
167
|
+
#### Interaction Props
|
|
168
|
+
|
|
169
|
+
| Prop | Type | Default | Description |
|
|
170
|
+
|------|------|---------|-------------|
|
|
171
|
+
| `cooperativeGestures` | `boolean` | `true` | Require modifier key for zoom/pan |
|
|
172
|
+
| `doubleClickZoom` | `boolean` | `true` | Enable double-click to zoom |
|
|
173
|
+
| `onMapClick` | `(lng, lat, marker?) => void` | - | Callback for map clicks |
|
|
174
|
+
|
|
175
|
+
#### Marker Props
|
|
176
|
+
|
|
177
|
+
| Prop | Type | Default | Description |
|
|
178
|
+
|------|------|---------|-------------|
|
|
179
|
+
| `markers` | `MarkerProps[]` | `[]` | Array of markers to display |
|
|
180
|
+
| `openPopup` | `string \| number` | `undefined` | ID of marker with open popup |
|
|
181
|
+
| `openPopupOnHover` | `boolean` | `false` | Open popups on hover instead of click |
|
|
182
|
+
| `popupMaxWidth` | `string` | `"300px"` | Maximum popup width |
|
|
183
|
+
|
|
184
|
+
#### Bounds & Animation
|
|
185
|
+
|
|
186
|
+
| Prop | Type | Default | Description |
|
|
187
|
+
|------|------|---------|-------------|
|
|
188
|
+
| `fitBounds` | `boolean` | `true` | Auto-fit map to show all markers |
|
|
189
|
+
| `fitBoundsPadding` | `number` | `0` | Padding (px) around fitted bounds |
|
|
190
|
+
| `fitBoundDuration` | `number` | `500` | Animation duration (ms) |
|
|
191
|
+
| `disableAnimation` | `boolean` | `false` | Disable all animations |
|
|
192
|
+
| `fitBoundsAnimationKey` | `unknown` | - | Change to re-trigger fit bounds |
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
### Marker Props (`MarkerProps`)
|
|
197
|
+
|
|
198
|
+
| Prop | Type | Required | Description |
|
|
199
|
+
|------|------|----------|-------------|
|
|
200
|
+
| `id` | `string \| number` | β
| Unique marker identifier |
|
|
201
|
+
| `lng` | `number` | β
| Longitude coordinate |
|
|
202
|
+
| `lat` | `number` | β
| Latitude coordinate |
|
|
203
|
+
| `Tooltip` | `ReactNode` | - | Content for popup/tooltip |
|
|
204
|
+
| `IconComponent` | `React.ComponentType` | - | Custom React icon component |
|
|
205
|
+
| `iconProps` | `object` | - | Props passed to IconComponent |
|
|
206
|
+
| `color` | `string` | - | Marker color (MUI palette) |
|
|
207
|
+
| `variant` | `string` | - | Marker style variant |
|
|
208
|
+
|
|
209
|
+
**Example with custom icon:**
|
|
210
|
+
```tsx
|
|
211
|
+
import LocationOnIcon from '@mui/icons-material/LocationOn';
|
|
212
|
+
|
|
213
|
+
const marker = {
|
|
214
|
+
id: 1,
|
|
215
|
+
lng: 2.3522,
|
|
216
|
+
lat: 48.8566,
|
|
217
|
+
IconComponent: LocationOnIcon,
|
|
218
|
+
iconProps: { fontSize: 'large', color: 'error' },
|
|
219
|
+
Tooltip: <div>Custom Icon Marker</div>
|
|
220
|
+
};
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
### Routing Props
|
|
226
|
+
|
|
227
|
+
Add a route between two points by providing `from` and `to` coordinates.
|
|
228
|
+
|
|
229
|
+
| Prop | Type | Default | Description |
|
|
230
|
+
|------|------|---------|-------------|
|
|
231
|
+
| `from` | `[lng, lat]` | - | Route starting point |
|
|
232
|
+
| `to` | `[lng, lat]` | - | Route ending point |
|
|
233
|
+
| `profile` | `"driving" \| "walking" \| "cycling"` | `"driving"` | Transportation mode |
|
|
234
|
+
| `engine` | `"OSRM" \| "Mapbox"` | `"OSRM"` | Routing service to use |
|
|
235
|
+
| `itineraryLineStyle` | `{ color, width, opacity }` | `{ color: "#3b82f6", width: 4, opacity: 0.8 }` | Route line appearance |
|
|
236
|
+
|
|
237
|
+
**Example:**
|
|
238
|
+
```tsx
|
|
239
|
+
<MapView
|
|
240
|
+
from={[2.3522, 48.8566]} // Paris
|
|
241
|
+
to={[-0.1276, 51.5074]} // London
|
|
242
|
+
profile="driving"
|
|
243
|
+
engine="OSRM"
|
|
244
|
+
itineraryLineStyle={{
|
|
245
|
+
color: "#10b981",
|
|
246
|
+
width: 5,
|
|
247
|
+
opacity: 0.9
|
|
248
|
+
}}
|
|
249
|
+
/>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
### Nearest Marker Search
|
|
255
|
+
|
|
256
|
+
Find and highlight the closest marker to a given point within a maximum distance.
|
|
257
|
+
|
|
258
|
+
| Prop | Type | Description |
|
|
259
|
+
|------|------|-------------|
|
|
260
|
+
| `findNearestMarker.origin` | `[lng, lat]` | Starting point for search |
|
|
261
|
+
| `findNearestMarker.destinations` | `Array<{lng, lat, id}>` | Candidate destinations |
|
|
262
|
+
| `findNearestMarker.maxDistanceMeters` | `number` | Maximum search radius |
|
|
263
|
+
| `onNearestFound` | `(id, coords, distance) => void` | Callback with nearest result |
|
|
264
|
+
|
|
265
|
+
**Example:**
|
|
266
|
+
```tsx
|
|
267
|
+
<MapView
|
|
268
|
+
findNearestMarker={{
|
|
269
|
+
origin: [2.3522, 48.8566],
|
|
270
|
+
destinations: markers.map(m => ({
|
|
271
|
+
lng: m.lng,
|
|
272
|
+
lat: m.lat,
|
|
273
|
+
id: m.id
|
|
274
|
+
})),
|
|
275
|
+
maxDistanceMeters: 5000,
|
|
276
|
+
}}
|
|
277
|
+
onNearestFound={(id, coords, distance) => {
|
|
278
|
+
console.log(`Nearest: ${id} at ${distance}m`);
|
|
279
|
+
}}
|
|
280
|
+
/>
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
### Isochrone Props
|
|
286
|
+
|
|
287
|
+
Compute and display areas reachable within specific time intervals.
|
|
288
|
+
|
|
289
|
+
| Prop | Type | Description |
|
|
290
|
+
|------|------|-------------|
|
|
291
|
+
| `isochrone.origin` | `[lng, lat]` | Center point for isochrone |
|
|
292
|
+
| `isochrone.profile` | `"driving" \| "walking" \| "cycling"` | Transportation mode |
|
|
293
|
+
| `isochrone.intervals` | `number[]` | Time intervals in minutes |
|
|
294
|
+
| `isochrone.onIsochroneLoaded` | `(data) => void` | Callback with GeoJSON result |
|
|
295
|
+
|
|
296
|
+
**Example:**
|
|
297
|
+
```tsx
|
|
298
|
+
<MapView
|
|
299
|
+
isochrone={{
|
|
300
|
+
origin: [2.3522, 48.8566],
|
|
301
|
+
profile: "driving",
|
|
302
|
+
intervals: [5, 10, 15, 20], // 5, 10, 15, 20 minutes
|
|
303
|
+
onIsochroneLoaded: (geojson) => {
|
|
304
|
+
console.log("Isochrone data:", geojson);
|
|
305
|
+
}
|
|
306
|
+
}}
|
|
307
|
+
/>
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
### GeoJSON Features
|
|
313
|
+
|
|
314
|
+
Display custom vector features like polygons, lines, or points.
|
|
315
|
+
|
|
316
|
+
| Prop | Type | Description |
|
|
317
|
+
|------|------|-------------|
|
|
318
|
+
| `features` | `Feature \| Feature[] \| FeatureCollection` | GeoJSON data to render |
|
|
319
|
+
|
|
320
|
+
**Example:**
|
|
321
|
+
```tsx
|
|
322
|
+
<MapView
|
|
323
|
+
features={{
|
|
324
|
+
type: "Feature",
|
|
325
|
+
geometry: {
|
|
326
|
+
type: "LineString",
|
|
327
|
+
coordinates: [
|
|
328
|
+
[2.3, 48.8],
|
|
329
|
+
[2.4, 48.9],
|
|
330
|
+
[2.5, 48.85]
|
|
331
|
+
]
|
|
332
|
+
},
|
|
333
|
+
properties: {
|
|
334
|
+
color: "#ef4444"
|
|
335
|
+
}
|
|
336
|
+
}}
|
|
337
|
+
/>
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## π§ Advanced Use Cases
|
|
343
|
+
|
|
344
|
+
### π§ Real-time GPS Tracking
|
|
345
|
+
```tsx
|
|
346
|
+
function LiveTracking() {
|
|
347
|
+
const [position, setPosition] = useState([2.3522, 48.8566]);
|
|
348
|
+
|
|
349
|
+
useEffect(() => {
|
|
350
|
+
const watchId = navigator.geolocation.watchPosition((pos) => {
|
|
351
|
+
setPosition([pos.coords.longitude, pos.coords.latitude]);
|
|
352
|
+
});
|
|
353
|
+
return () => navigator.geolocation.clearWatch(watchId);
|
|
354
|
+
}, []);
|
|
355
|
+
|
|
356
|
+
return (
|
|
357
|
+
<MapView
|
|
358
|
+
markers={[{
|
|
359
|
+
id: 'current',
|
|
360
|
+
lng: position[0],
|
|
361
|
+
lat: position[1],
|
|
362
|
+
Tooltip: <div>You are here</div>
|
|
363
|
+
}]}
|
|
364
|
+
center={position}
|
|
365
|
+
zoom={15}
|
|
366
|
+
fitBounds={false}
|
|
367
|
+
/>
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### π Dynamic Data with React Query
|
|
373
|
+
```tsx
|
|
374
|
+
import { useQuery } from '@tanstack/react-query';
|
|
375
|
+
|
|
376
|
+
function DynamicMarkers() {
|
|
377
|
+
const { data: markers } = useQuery({
|
|
378
|
+
queryKey: ['locations'],
|
|
379
|
+
queryFn: fetchLocations,
|
|
380
|
+
refetchInterval: 5000 // Refresh every 5s
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
return (
|
|
384
|
+
<MapView
|
|
44
385
|
markers={markers}
|
|
45
|
-
center={[2.3522, 48.8566]}
|
|
46
|
-
zoom={10}
|
|
47
386
|
fitBounds
|
|
48
|
-
|
|
49
|
-
width="100%"
|
|
387
|
+
fitBoundsAnimationKey={markers?.length}
|
|
50
388
|
/>
|
|
51
|
-
|
|
389
|
+
);
|
|
390
|
+
}
|
|
52
391
|
```
|
|
53
392
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
| `openPopupOnHover` | `boolean` | Automatically open popups on marker hover |
|
|
78
|
-
| `onMapClick` | `(lng: number, lat: number) => void` | Callback triggered when clicking on the map |
|
|
79
|
-
|
|
80
|
-
### MarkerProps
|
|
81
|
-
|
|
82
|
-
| Prop | Type | Description |
|
|
83
|
-
|-------------|----------------------|---------------------------------------------------------------|
|
|
84
|
-
| `id` | `number` or `string` | Unique marker identifier |
|
|
85
|
-
| `lng` | `number` | Longitude |
|
|
86
|
-
| `lat` | `number` | Latitude |
|
|
87
|
-
| `Tooltip` | `ReactNode` | Tooltip content displayed in a popup |
|
|
88
|
-
| `iconImage` | `string` | Image URL used as marker icon |
|
|
89
|
-
| `size` | `number` | Marker size in pixels |
|
|
90
|
-
| `zIndex` | `number` | Z-index to control stacking order |
|
|
91
|
-
| `onClick` | `() => void` | Function triggered on marker click |
|
|
92
|
-
| `type` | `string` | Custom marker type (optional, for filtering or styling) |
|
|
93
|
-
| `name` | `string` | Name of the marker |
|
|
94
|
-
| `Icon` | `ReactNode` | Custom React component to render instead of default image |
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
## π§βπ» Contributing
|
|
98
|
-
|
|
99
|
-
Contributions are welcome! Please follow the coding conventions and include tests when necessary.
|
|
393
|
+
### π¨ Custom Marker Components
|
|
394
|
+
```tsx
|
|
395
|
+
function CustomMarker({ isActive, count }) {
|
|
396
|
+
return (
|
|
397
|
+
<Box
|
|
398
|
+
sx={{
|
|
399
|
+
width: 40,
|
|
400
|
+
height: 40,
|
|
401
|
+
borderRadius: '50%',
|
|
402
|
+
bgcolor: isActive ? 'success.main' : 'grey.500',
|
|
403
|
+
display: 'flex',
|
|
404
|
+
alignItems: 'center',
|
|
405
|
+
justifyContent: 'center',
|
|
406
|
+
color: 'white',
|
|
407
|
+
fontWeight: 'bold',
|
|
408
|
+
border: '2px solid white',
|
|
409
|
+
boxShadow: 2
|
|
410
|
+
}}
|
|
411
|
+
>
|
|
412
|
+
{count}
|
|
413
|
+
</Box>
|
|
414
|
+
);
|
|
415
|
+
}
|
|
100
416
|
|
|
101
|
-
|
|
417
|
+
<MapView
|
|
418
|
+
markers={[{
|
|
419
|
+
id: 1,
|
|
420
|
+
lng: 2.3522,
|
|
421
|
+
lat: 48.8566,
|
|
422
|
+
IconComponent: CustomMarker,
|
|
423
|
+
iconProps: { isActive: true, count: 5 }
|
|
424
|
+
}]}
|
|
425
|
+
/>
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### πΊοΈ Multi-Route Comparison
|
|
429
|
+
```tsx
|
|
430
|
+
<MapView
|
|
431
|
+
features={[
|
|
432
|
+
{
|
|
433
|
+
type: "Feature",
|
|
434
|
+
geometry: {
|
|
435
|
+
type: "LineString",
|
|
436
|
+
coordinates: route1
|
|
437
|
+
},
|
|
438
|
+
properties: { color: "#3b82f6" }
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
type: "Feature",
|
|
442
|
+
geometry: {
|
|
443
|
+
type: "LineString",
|
|
444
|
+
coordinates: route2
|
|
445
|
+
},
|
|
446
|
+
properties: { color: "#ef4444" }
|
|
447
|
+
}
|
|
448
|
+
]}
|
|
449
|
+
/>
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### π― Click-to-Add Markers
|
|
453
|
+
```tsx
|
|
454
|
+
function InteractiveMap() {
|
|
455
|
+
const [markers, setMarkers] = useState([]);
|
|
456
|
+
|
|
457
|
+
const handleMapClick = (lng, lat) => {
|
|
458
|
+
setMarkers(prev => [...prev, {
|
|
459
|
+
id: Date.now(),
|
|
460
|
+
lng,
|
|
461
|
+
lat,
|
|
462
|
+
Tooltip: <div>Point {markers.length + 1}</div>
|
|
463
|
+
}]);
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
return (
|
|
467
|
+
<MapView
|
|
468
|
+
markers={markers}
|
|
469
|
+
onMapClick={handleMapClick}
|
|
470
|
+
/>
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## π‘ Tips & Best Practices
|
|
478
|
+
|
|
479
|
+
### Performance Optimization
|
|
480
|
+
|
|
481
|
+
- **Memoize marker data** to prevent unnecessary re-renders
|
|
482
|
+
- **Use `fitBoundsAnimationKey`** to control when bounds recalculate
|
|
483
|
+
- **Disable animations** for large datasets: `disableAnimation={true}`
|
|
484
|
+
- **Debounce dynamic updates** when tracking real-time data
|
|
485
|
+
|
|
486
|
+
### UX Improvements
|
|
487
|
+
|
|
488
|
+
- Combine `openPopupOnHover` and `disableAnimation` for smooth interactions
|
|
489
|
+
- Use `fitBoundsPadding` to ensure markers aren't at screen edges
|
|
490
|
+
- Set appropriate `popupMaxWidth` for mobile responsiveness
|
|
491
|
+
- Provide visual feedback with custom `IconComponent` states
|
|
492
|
+
|
|
493
|
+
### Routing Best Practices
|
|
494
|
+
|
|
495
|
+
- **Use OSRM** (free) for basic routing needs
|
|
496
|
+
- **Use Mapbox** for production apps requiring SLA and support
|
|
497
|
+
- Cache route results to minimize API calls
|
|
498
|
+
- Handle network errors gracefully
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
## π§βπ» Development
|
|
503
|
+
|
|
504
|
+
### Prerequisites
|
|
505
|
+
|
|
506
|
+
- **Bun** β₯1.1.0 (recommended) or Node.js 18+
|
|
507
|
+
- Git
|
|
508
|
+
|
|
509
|
+
### Setup
|
|
510
|
+
```bash
|
|
511
|
+
# Clone the repository
|
|
512
|
+
git clone https://github.com/tracktor-tech/tracktor-map.git
|
|
513
|
+
cd tracktor-map
|
|
514
|
+
|
|
515
|
+
# Install dependencies
|
|
516
|
+
bun install
|
|
517
|
+
|
|
518
|
+
# Start development sandbox
|
|
519
|
+
bun run sandbox
|
|
520
|
+
# or
|
|
521
|
+
bun run dev:sandbox
|
|
522
|
+
```
|
|
102
523
|
|
|
103
|
-
|
|
524
|
+
### Available Scripts
|
|
104
525
|
|
|
526
|
+
| Command | Description |
|
|
527
|
+
|---------|-------------|
|
|
528
|
+
| `bun run sandbox` | Start interactive development playground |
|
|
529
|
+
| `bun run build` | Build library for production |
|
|
530
|
+
| `bun run build:sandbox` | Build sandbox demo site |
|
|
531
|
+
| `bun run deploy:sandbox` | Deploy sandbox to GitHub Pages |
|
|
532
|
+
| `bun run lint` | Check code quality and types |
|
|
533
|
+
| `bun run lint:fix` | Auto-fix linting issues |
|
|
534
|
+
| `bun run test` | Run test suite |
|
|
535
|
+
| `bun run test:watch` | Run tests in watch mode |
|
|
536
|
+
| `bun run version` | Bump version with changelog |
|
|
537
|
+
| `bun run release` | Build and publish to npm |
|
|
105
538
|
|
|
106
|
-
|
|
107
|
-
|
|
539
|
+
### Project Structure
|
|
540
|
+
```
|
|
541
|
+
@tracktor/map/
|
|
542
|
+
βββ src/
|
|
543
|
+
β βββ components/ # Reusable map and UI components (Marker, Popup, etc.)
|
|
544
|
+
β βββ constants/ # Shared configuration values and styling constants
|
|
545
|
+
β βββ context/ # React context providers (e.g. map state)
|
|
546
|
+
β βββ features/ # Core map features (routes, isochrones, nearest, etc.)
|
|
547
|
+
β βββ services/ # External APIs and utility services
|
|
548
|
+
β βββ types/ # TypeScript interfaces and types
|
|
549
|
+
β βββ utils/ # Generic helpers and formatting functions
|
|
550
|
+
β βββ main.ts # Library entry point
|
|
551
|
+
β
|
|
552
|
+
βββ sandbox/ # Development playground (example app & live demos)
|
|
553
|
+
β βββ context/ # Demo context providers
|
|
554
|
+
β βββ examples/ # Interactive usage examples
|
|
555
|
+
β βββ features/ # Components used in the docs/demo
|
|
556
|
+
β βββ public/ # Static assets (images, previews, etc.)
|
|
557
|
+
β βββ App.tsx # Sandbox root component
|
|
558
|
+
β βββ index.tsx # Sandbox entry file
|
|
559
|
+
β
|
|
560
|
+
βββ test/ # Unit and integration tests
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### Testing
|
|
564
|
+
```bash
|
|
565
|
+
# Run all tests
|
|
566
|
+
bun test
|
|
567
|
+
|
|
568
|
+
# Watch mode
|
|
569
|
+
bun test:watch
|
|
570
|
+
|
|
571
|
+
# Run specific test file
|
|
572
|
+
bun test src/components/MapView.test.tsx
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
### Contributing
|
|
576
|
+
|
|
577
|
+
We welcome contributions! Please:
|
|
578
|
+
|
|
579
|
+
1. Fork the repository
|
|
580
|
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
581
|
+
3. Make your changes
|
|
582
|
+
4. Run tests and linting (`bun run test && bun run lint`)
|
|
583
|
+
5. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
584
|
+
6. Push to the branch (`git push origin feature/amazing-feature`)
|
|
585
|
+
7. Open a Pull Request
|
|
586
|
+
|
|
587
|
+
**Code Style:**
|
|
588
|
+
- Follow existing patterns and conventions
|
|
589
|
+
- Use TypeScript for all new code
|
|
590
|
+
- Add tests for new features
|
|
591
|
+
- Update documentation as needed
|
|
592
|
+
|
|
593
|
+
---
|
|
594
|
+
|
|
595
|
+
## π Documentation & Examples
|
|
596
|
+
|
|
597
|
+
Explore interactive examples and comprehensive API documentation:
|
|
598
|
+
|
|
599
|
+
π **[Live Documentation & Sandbox](https://tracktor.github.io/map)**
|
|
600
|
+
|
|
601
|
+
The sandbox includes:
|
|
602
|
+
- Interactive code examples
|
|
603
|
+
- Live preview of all features
|
|
604
|
+
- Copy-paste ready snippets
|
|
605
|
+
- API reference with search
|
|
606
|
+
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
## π¦ Publishing & Deployment
|
|
610
|
+
|
|
611
|
+
### Publish to npm
|
|
612
|
+
```bash
|
|
613
|
+
# Update version and generate changelog
|
|
614
|
+
bun run version
|
|
108
615
|
|
|
616
|
+
# Build and publish
|
|
617
|
+
bun run release
|
|
109
618
|
```
|
|
110
|
-
|
|
619
|
+
|
|
620
|
+
### Deploy Sandbox to GitHub Pages
|
|
621
|
+
```bash
|
|
622
|
+
bun run deploy:sandbox
|
|
111
623
|
```
|
|
112
|
-
This will build the documentation and push it to the `gh-pages` branch.
|
|
113
624
|
|
|
114
|
-
|
|
625
|
+
This will:
|
|
626
|
+
1. Build the sandbox with production optimizations
|
|
627
|
+
2. Generate a 404.html for client-side routing
|
|
628
|
+
3. Push to the `gh-pages` branch
|
|
629
|
+
4. Update the live documentation site
|
|
630
|
+
|
|
631
|
+
---
|
|
632
|
+
|
|
633
|
+
## π§ Troubleshooting
|
|
634
|
+
|
|
635
|
+
### Common Issues
|
|
636
|
+
|
|
637
|
+
**Map not rendering:**
|
|
638
|
+
- Verify your Mapbox token is valid
|
|
639
|
+
- Check browser console for errors
|
|
640
|
+
- Ensure mapbox-gl CSS is imported
|
|
641
|
+
|
|
642
|
+
**TypeScript errors:**
|
|
643
|
+
- Run `bun install` to update type definitions
|
|
644
|
+
- Check peer dependency versions match
|
|
645
|
+
|
|
646
|
+
**Performance issues:**
|
|
647
|
+
- Reduce marker count or use clustering
|
|
648
|
+
- Disable animations for large datasets
|
|
649
|
+
- Memoize marker data
|
|
650
|
+
|
|
651
|
+
### Getting Help
|
|
652
|
+
|
|
653
|
+
- π Check the [documentation](https://tracktor.github.io/map)
|
|
654
|
+
- π [Report bugs](https://github.com/tracktor-tech/tracktor-map/issues)
|
|
655
|
+
- π¬ Join discussions in GitHub Discussions
|
|
656
|
+
|
|
657
|
+
---
|
|
658
|
+
|
|
659
|
+
## π License
|
|
660
|
+
|
|
661
|
+
**UNLICENSED** β This package is proprietary software.
|
|
662
|
+
Β© [Tracktor β Kevin Graff]
|
|
663
|
+
|
|
664
|
+
---
|
|
665
|
+
|
|
666
|
+
## π§ Links
|
|
667
|
+
|
|
668
|
+
- π¦ **npm**: [@tracktor/map](https://www.npmjs.com/package/@tracktor/map)
|
|
669
|
+
- π» **GitHub**: [tracktor-tech/tracktor-map](https://github.com/tracktor-tech/tracktor-map)
|
|
670
|
+
- π **Docs**: [tracktor.github.io/map](https://tracktor.github.io/map)
|
|
671
|
+
- π¨ **Design System**: [@tracktor/design-system](https://www.npmjs.com/package/@tracktor/design-system)
|
|
672
|
+
|
|
673
|
+
---
|
|
674
|
+
|
|
675
|
+
## π Acknowledgments
|
|
676
|
+
|
|
677
|
+
Built with:
|
|
678
|
+
- [Mapbox GL JS](https://docs.mapbox.com/mapbox-gl-js/) β Powerful map rendering
|
|
679
|
+
- [react-map-gl](https://visgl.github.io/react-map-gl/) β React wrapper for Mapbox
|
|
680
|
+
- [OSRM](http://project-osrm.org/) β Free routing engine
|
|
681
|
+
- [@tracktor/design-system](https://www.npmjs.com/package/@tracktor/design-system) β UI components
|
package/package.json
CHANGED