@tracktor/map 1.6.5 → 1.7.1

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 CHANGED
@@ -61,44 +61,44 @@ bun add @tracktor/map
61
61
  import { MapProvider, MarkerMap } from "@tracktor/map";
62
62
 
63
63
  const markers = [
64
- {
65
- id: 1,
66
- lng: 2.3522,
67
- lat: 48.8566,
68
- Tooltip: <div>Paris</div>,
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",
79
- },
64
+ {
65
+ id: 1,
66
+ lng: 2.3522,
67
+ lat: 48.8566,
68
+ Tooltip: <div>Paris</div>,
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",
79
+ },
80
80
  ];
81
81
 
82
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
- );
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
102
  }
103
103
  ```
104
104
 
@@ -115,10 +115,10 @@ Wraps your map components and injects required providers (theme, tokens, MUI X l
115
115
  - `licenceMapbox` — Your Mapbox access token
116
116
  ```tsx
117
117
  <MapProvider
118
- licenseMuiX="your-license"
119
- licenceMapbox="your-token"
118
+ licenseMuiX="your-license"
119
+ licenceMapbox="your-token"
120
120
  >
121
- {/* Your map components */}
121
+ {/* Your map components */}
122
122
  </MapProvider>
123
123
  ```
124
124
 
@@ -147,7 +147,7 @@ Main map component that handles:
147
147
 
148
148
  | Prop | Type | Default | Description |
149
149
  |------|------|---------|-------------|
150
- | `center` | `[lng, lat]` | `[2.3522, 48.8566]` | Initial map center coordinates |
150
+ | `center` | `LngLatLike \| number[]` | `[2.3522, 48.8566]` | Initial map center coordinates [lng, lat] |
151
151
  | `zoom` | `number` | `5` | Initial zoom level (0-22) |
152
152
  | `width` | `string \| number` | `"100%"` | Map container width |
153
153
  | `height` | `string \| number` | `300` | Map container height |
@@ -170,13 +170,14 @@ Main map component that handles:
170
170
  |------|------|---------|-------------|
171
171
  | `cooperativeGestures` | `boolean` | `true` | Require modifier key for zoom/pan |
172
172
  | `doubleClickZoom` | `boolean` | `true` | Enable double-click to zoom |
173
- | `onMapClick` | `(lng, lat, marker?) => void` | - | Callback for map clicks |
173
+ | `onMapClick` | `(lng, lat, marker?) => void` | - | Callback for map clicks (includes clicked marker if applicable) |
174
174
 
175
175
  #### Marker Props
176
176
 
177
177
  | Prop | Type | Default | Description |
178
178
  |------|------|---------|-------------|
179
179
  | `markers` | `MarkerProps[]` | `[]` | Array of markers to display |
180
+ | `markerImageURL` | `string` | - | Custom marker icon URL |
180
181
  | `openPopup` | `string \| number` | `undefined` | ID of marker with open popup |
181
182
  | `openPopupOnHover` | `boolean` | `false` | Open popups on hover instead of click |
182
183
  | `popupMaxWidth` | `string` | `"300px"` | Maximum popup width |
@@ -222,45 +223,68 @@ const marker = {
222
223
 
223
224
  ---
224
225
 
225
- ### Routing Props
226
+ ### Itinerary Props (`itineraryParams`)
226
227
 
227
- Add a route between two points by providing `from` and `to` coordinates.
228
+ Draw a route between two points with customizable styling and routing engines.
228
229
 
229
230
  | Prop | Type | Default | Description |
230
231
  |------|------|---------|-------------|
231
- | `from` | `[lng, lat]` | - | Route starting point |
232
- | `to` | `[lng, lat]` | - | Route ending point |
232
+ | `from` | `[number, number]` | - | Route starting point [lng, lat] |
233
+ | `to` | `[number, number]` | - | Route ending point [lng, lat] |
233
234
  | `profile` | `"driving" \| "walking" \| "cycling"` | `"driving"` | Transportation mode |
234
235
  | `engine` | `"OSRM" \| "Mapbox"` | `"OSRM"` | Routing service to use |
235
- | `itineraryLineStyle` | `{ color, width, opacity }` | `{ color: "#3b82f6", width: 4, opacity: 0.8 }` | Route line appearance |
236
+ | `itineraryLineStyle` | `Partial<ItineraryLineStyle>` | `{ color: "#3b82f6", width: 4, opacity: 0.8 }` | Route line appearance |
237
+ | `initialRoute` | `Feature<LineString>` | - | Precomputed GeoJSON route |
238
+ | `onRouteComputed` | `(route) => void` | - | Callback fired when route is computed |
239
+ | `itineraryLabel` | `ReactNode` | - | Label displayed along the route (e.g., "12 min") |
236
240
 
237
241
  **Example:**
238
242
  ```tsx
239
243
  <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
244
+ itineraryParams={{
245
+ from: [2.3522, 48.8566], // Paris
246
+ to: [-0.1276, 51.5074], // London
247
+ profile: "driving",
248
+ engine: "OSRM",
249
+ itineraryLineStyle: {
250
+ color: "#10b981",
251
+ width: 5,
252
+ opacity: 0.9
253
+ },
254
+ itineraryLabel: <span>Route principale</span>,
255
+ onRouteComputed: (route) => {
256
+ console.log("Route computed:", route);
257
+ }
248
258
  }}
249
259
  />
250
260
  ```
251
261
 
252
262
  ---
253
263
 
254
- ### Nearest Marker Search
264
+ ### Nearest Marker Search (`findNearestMarker`)
255
265
 
256
266
  Find and highlight the closest marker to a given point within a maximum distance.
257
267
 
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 |
268
+ | Prop | Type | Default | Description |
269
+ |------|------|---------|-------------|
270
+ | `origin` | `[number, number]` | - | Starting point for search [lng, lat] |
271
+ | `destinations` | `Array<{id, lng, lat}>` | - | Candidate destinations |
272
+ | `maxDistanceMeters` | `number` | - | Maximum search radius in meters |
273
+ | `profile` | `"driving" \| "walking" \| "cycling"` | `"driving"` | Routing profile for distance calculation |
274
+ | `engine` | `"OSRM" \| "Mapbox"` | `"OSRM"` | Routing engine to use |
275
+ | `onNearestFound` | `(results) => void` | - | Callback with all nearest results |
276
+ | `initialNearestResults` | `NearestResult[]` | - | Precomputed nearest results |
277
+ | `itineraryLineStyle` | `Partial<ItineraryLineStyle>` | - | Style override for auto-generated itinerary |
278
+
279
+ **NearestResult Type:**
280
+ ```tsx
281
+ interface NearestResult {
282
+ id: number | string;
283
+ point: [number, number]; // [lng, lat]
284
+ distance: number; // in meters
285
+ routeFeature?: Feature<LineString> | null;
286
+ }
287
+ ```
264
288
 
265
289
  **Example:**
266
290
  ```tsx
@@ -268,30 +292,35 @@ Find and highlight the closest marker to a given point within a maximum distance
268
292
  findNearestMarker={{
269
293
  origin: [2.3522, 48.8566],
270
294
  destinations: markers.map(m => ({
295
+ id: m.id,
271
296
  lng: m.lng,
272
- lat: m.lat,
273
- id: m.id
297
+ lat: m.lat
274
298
  })),
275
299
  maxDistanceMeters: 5000,
276
- }}
277
- onNearestFound={(id, coords, distance) => {
278
- console.log(`Nearest: ${id} at ${distance}m`);
300
+ profile: "walking",
301
+ engine: "OSRM",
302
+ onNearestFound: (results) => {
303
+ console.log(`Found ${results.length} markers within range`);
304
+ results.forEach(r => {
305
+ console.log(`Marker ${r.id} at ${r.distance}m`);
306
+ });
307
+ }
279
308
  }}
280
309
  />
281
310
  ```
282
311
 
283
312
  ---
284
313
 
285
- ### Isochrone Props
314
+ ### Isochrone Props (`isochrone`)
286
315
 
287
316
  Compute and display areas reachable within specific time intervals.
288
317
 
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 |
318
+ | Prop | Type | Default | Description |
319
+ |------|------|---------|-------------|
320
+ | `origin` | `[number, number]` | - | Center point for isochrone [lng, lat] |
321
+ | `profile` | `"driving" \| "walking" \| "cycling"` | `"driving"` | Transportation mode |
322
+ | `intervals` | `number[]` | `[5, 10, 15]` | Time intervals in minutes |
323
+ | `onIsochroneLoaded` | `(data) => void` | - | Callback with GeoJSON result |
295
324
 
296
325
  **Example:**
297
326
  ```tsx
@@ -309,7 +338,7 @@ Compute and display areas reachable within specific time intervals.
309
338
 
310
339
  ---
311
340
 
312
- ### GeoJSON Features
341
+ ### GeoJSON Features (`features`)
313
342
 
314
343
  Display custom vector features like polygons, lines, or points.
315
344
 
@@ -472,6 +501,44 @@ function InteractiveMap() {
472
501
  }
473
502
  ```
474
503
 
504
+ ### 🚗 Combined Routing & Nearest Search
505
+ ```tsx
506
+ function DeliveryMap() {
507
+ const [origin] = useState([2.3522, 48.8566]);
508
+ const [destinations] = useState([
509
+ { id: 1, lng: 2.35, lat: 48.86 },
510
+ { id: 2, lng: 2.36, lat: 48.85 },
511
+ { id: 3, lng: 2.34, lat: 48.87 }
512
+ ]);
513
+
514
+ return (
515
+ <MapView
516
+ markers={destinations.map(d => ({
517
+ id: d.id,
518
+ lng: d.lng,
519
+ lat: d.lat,
520
+ Tooltip: <div>Destination {d.id}</div>
521
+ }))}
522
+ findNearestMarker={{
523
+ origin,
524
+ destinations,
525
+ maxDistanceMeters: 10000,
526
+ profile: "driving",
527
+ engine: "OSRM",
528
+ itineraryLineStyle: {
529
+ color: "#22c55e",
530
+ width: 4,
531
+ opacity: 0.8
532
+ },
533
+ onNearestFound: (results) => {
534
+ console.log("Nearest destinations:", results);
535
+ }
536
+ }}
537
+ />
538
+ );
539
+ }
540
+ ```
541
+
475
542
  ---
476
543
 
477
544
  ## 💡 Tips & Best Practices
@@ -482,6 +549,7 @@ function InteractiveMap() {
482
549
  - **Use `fitBoundsAnimationKey`** to control when bounds recalculate
483
550
  - **Disable animations** for large datasets: `disableAnimation={true}`
484
551
  - **Debounce dynamic updates** when tracking real-time data
552
+ - **Use `initialRoute` and `initialNearestResults`** to avoid redundant API calls
485
553
 
486
554
  ### UX Improvements
487
555
 
@@ -489,13 +557,15 @@ function InteractiveMap() {
489
557
  - Use `fitBoundsPadding` to ensure markers aren't at screen edges
490
558
  - Set appropriate `popupMaxWidth` for mobile responsiveness
491
559
  - Provide visual feedback with custom `IconComponent` states
560
+ - Use `itineraryLabel` to display route duration or distance
492
561
 
493
562
  ### Routing Best Practices
494
563
 
495
564
  - **Use OSRM** (free) for basic routing needs
496
565
  - **Use Mapbox** for production apps requiring SLA and support
497
- - Cache route results to minimize API calls
498
- - Handle network errors gracefully
566
+ - Cache route results with `initialRoute` to minimize API calls
567
+ - Handle network errors gracefully with `onRouteComputed` callback
568
+ - Combine `findNearestMarker` with `itineraryParams` for optimal routing workflows
499
569
 
500
570
  ---
501
571
 
@@ -647,10 +717,17 @@ This will:
647
717
  - Reduce marker count or use clustering
648
718
  - Disable animations for large datasets
649
719
  - Memoize marker data
720
+ - Use `initialRoute` and `initialNearestResults` for cached data
721
+
722
+ **Routing not working:**
723
+ - Verify coordinates are in [lng, lat] format (not lat, lng)
724
+ - Check that routing engine is accessible
725
+ - Ensure profile matches your use case
726
+ - Verify maxDistanceMeters is reasonable for nearest search
650
727
 
651
728
  ### Getting Help
652
729
 
653
- - 📖 Check the [documentation](https://tracktor.github.io/map)
730
+ - 📖 Check the [documentation](https://github.com/Tracktor/map)
654
731
  - 🐛 [Report bugs](https://github.com/tracktor-tech/tracktor-map/issues)
655
732
  - 💬 Join discussions in GitHub Discussions
656
733
 
@@ -666,9 +743,10 @@ This will:
666
743
  ## 🧭 Links
667
744
 
668
745
  - 📦 **npm**: [@tracktor/map](https://www.npmjs.com/package/@tracktor/map)
669
- - 💻 **GitHub**: [tracktor-tech/tracktor-map](https://github.com/tracktor-tech/tracktor-map)
746
+ - 💻 **GitHub**: [@tracktor/map](https://github.com/Tracktor/map)
670
747
  - 🌐 **Docs**: [tracktor.github.io/map](https://tracktor.github.io/map)
671
748
  - 🎨 **Design System**: [@tracktor/design-system](https://www.npmjs.com/package/@tracktor/design-system)
749
+ - Sandbox Demo: [tracktor.github.io/map/sandbox](https://tracktor.github.io/map)
672
750
 
673
751
  ---
674
752
 
@@ -1,6 +1,14 @@
1
1
  import { Feature, FeatureCollection } from 'geojson';
2
- interface RenderFeaturesProps {
2
+ import { ReactNode } from 'react';
3
+ export interface LineStyle {
4
+ color?: string;
5
+ width?: number;
6
+ opacity?: number;
7
+ }
8
+ export interface RenderFeaturesProps {
3
9
  features?: Feature | Feature[] | FeatureCollection;
10
+ lineLabel?: ReactNode;
11
+ lineStyle?: LineStyle;
4
12
  }
5
- declare const RenderFeatures: ({ features }: RenderFeaturesProps) => import("react/jsx-runtime").JSX.Element | null;
13
+ declare const RenderFeatures: ({ features, lineLabel, lineStyle }: RenderFeaturesProps) => import("react/jsx-runtime").JSX.Element | null;
6
14
  export default RenderFeatures;
@@ -0,0 +1,7 @@
1
+ import { Feature } from 'geojson';
2
+ import { ReactNode } from 'react';
3
+ declare const LineLabel: ({ route, children }: {
4
+ route?: Feature | null;
5
+ children: ReactNode;
6
+ }) => import("react/jsx-runtime").JSX.Element | null;
7
+ export default LineLabel;
@@ -1,33 +1,16 @@
1
- import { Engine, ItineraryLineStyle, Profile } from '../../types/MapViewProps.ts';
2
- type ItineraryProps = {
3
- from?: [number, number];
4
- to?: [number, number];
5
- profile?: Profile;
6
- engine?: Engine;
7
- itineraryLineStyle?: Partial<ItineraryLineStyle>;
8
- };
1
+ import { ItineraryParams } from '../../types/MapViewProps';
9
2
  /**
10
3
  * Itinerary Component
11
4
  * -------------------
12
- * Renders a route line between two geographical points on a Mapbox map.
13
- *
14
- * Workflow:
15
- * 1. Fetches the route geometry between `from` and `to` coordinates.
16
- * 2. Uses either OSRM or Mapbox routing services depending on the `routeService` prop.
17
- * 3. Displays the resulting route as a line layer via `react-map-gl`.
18
- *
19
- * Props:
20
- * - `from`: starting point [lng, lat].
21
- * - `to`: destination point [lng, lat].
22
- * - `profile`: routing mode ("driving", "walking", or "cycling").
23
- * - `routeService`: which routing engine to use ("OSRM" or "Mapbox").
24
- * - `itineraryLineStyle`: optional line style overrides (color, opacity, width).
25
- *
26
- * Dependencies:
27
- * - `OSRMRoute`: returns a GeoJSON LineString from OSRM.
28
- * - `mapboxRoute`: returns a GeoJSON LineString from Mapbox Directions API.
29
- * - `react-map-gl`: used for rendering the map layers.
5
+ * Renders a route on a map, either:
6
+ * - using a precomputed GeoJSON route (initialRoute), or
7
+ * - by fetching a route from a routing engine (OSRM or Mapbox).
30
8
  *
9
+ * Responsibilities:
10
+ * 1. Load the itinerary from props if precomputed (`initialRoute`).
11
+ * 2. Otherwise fetch the route dynamically based on `from` + `to`.
12
+ * 3. Optionally notify parent when a route is computed (`onRouteComputed`).
13
+ * 4. Delegate visual rendering to <RenderFeatures /> for consistency across map features.
31
14
  */
32
- declare const Itinerary: ({ profile, engine, to, from, itineraryLineStyle }: ItineraryProps) => import("react/jsx-runtime").JSX.Element | null;
15
+ declare const Itinerary: ({ from, to, profile, engine, itineraryLineStyle, initialRoute, onRouteComputed, itineraryLabel }: ItineraryParams) => import("react/jsx-runtime").JSX.Element | null;
33
16
  export default Itinerary;
@@ -1,4 +1,4 @@
1
1
  import { ReactElement } from 'react';
2
2
  import { MapViewProps } from '../../types/MapViewProps.ts';
3
- declare const _default: import('react').MemoExoticComponent<({ containerStyle, square, loading, height, width, center, zoom, popupMaxWidth, openPopup, openPopupOnHover, markers, fitBounds, fitBoundsPadding, fitBoundDuration, fitBoundsAnimationKey, disableAnimation, mapStyle: baseMapStyle, onMapClick, baseMapView, cooperativeGestures, doubleClickZoom, projection, theme: themeOverride, features, from, to, profile, itineraryLineStyle, engine, findNearestMarker, onNearestFound, isochrone, }: MapViewProps) => ReactElement>;
3
+ declare const _default: import('react').MemoExoticComponent<({ containerStyle, square, loading, height, width, center, zoom, popupMaxWidth, openPopup, openPopupOnHover, markers, fitBounds, fitBoundsPadding, fitBoundDuration, fitBoundsAnimationKey, disableAnimation, mapStyle: baseMapStyle, onMapClick, baseMapView, cooperativeGestures, doubleClickZoom, projection, theme: themeOverride, features, itineraryParams, findNearestMarker, isochrone, }: MapViewProps) => ReactElement>;
4
4
  export default _default;
@@ -1,10 +1,3 @@
1
- import { Engine, FindNearestMarkerParams } from '../../types/MapViewProps.ts';
2
- export interface NearestResult {
3
- id: number | string;
4
- point: [number, number];
5
- distance: number;
6
- }
7
- declare const _default: import('react').MemoExoticComponent<({ origin, maxDistanceMeters, destinations, onNearestFound, profile, engine, }: FindNearestMarkerParams & {
8
- engine: Engine;
9
- }) => import("react/jsx-runtime").JSX.Element | null>;
1
+ import { FindNearestMarkerParams } from '../../types/MapViewProps';
2
+ declare const _default: import('react').MemoExoticComponent<({ origin, maxDistanceMeters, destinations, onNearestFound, initialNearestResults, itineraryLineStyle, profile, engine, }: FindNearestMarkerParams) => import("react/jsx-runtime").JSX.Element | null>;
10
3
  export default _default;