Add custom icon support and routing functionality to map adapters

- Add icon parameter to IMapAdapter.addMarker() supporting DivIcon (Leaflet) and HTMLElement (MapLibre)
- Implement routing with leaflet-routing-machine and @maplibre/maplibre-gl-directions
- Add marker management methods: hasMarker, updateMarkerIcon, updateMarkerPopup, addMarkerClickHandler
- Add route management methods: addRoute, updateRoute, removeRoute, removeAllRoutes
- Update MapFacade to proxy all new marker and route management methods
- Add RouteOptions interface with customizable line styles and waypoint options

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: David Nguyen <david.nguyen@goutezplanb.com>
This commit is contained in:
David Nguyen 2025-10-30 23:01:58 -04:00
parent 2cc110adc3
commit 30cc53f9ad
Signed by: david.nguyen
GPG Key ID: D5FB5A5715829326
8 changed files with 438 additions and 21 deletions

90
package-lock.json generated
View File

@ -17,8 +17,10 @@
"@angular/platform-server": "^19.2.0", "@angular/platform-server": "^19.2.0",
"@angular/router": "^19.2.0", "@angular/router": "^19.2.0",
"@angular/ssr": "^19.2.15", "@angular/ssr": "^19.2.15",
"@maplibre/maplibre-gl-directions": "^0.8.0",
"express": "^4.18.2", "express": "^4.18.2",
"leaflet": "^2.0.0-alpha.1", "leaflet": "^2.0.0-alpha.1",
"leaflet-routing-machine": "^3.2.12",
"maplibre-gl": "^5.7.0", "maplibre-gl": "^5.7.0",
"rxjs": "~7.8.0", "rxjs": "~7.8.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
@ -31,6 +33,7 @@
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/jasmine": "~5.1.0", "@types/jasmine": "~5.1.0",
"@types/leaflet": "^1.9.20", "@types/leaflet": "^1.9.20",
"@types/leaflet-routing-machine": "^3.2.9",
"@types/node": "^18.18.0", "@types/node": "^18.18.0",
"jasmine-core": "~5.6.0", "jasmine-core": "~5.6.0",
"karma": "~6.4.0", "karma": "~6.4.0",
@ -3940,6 +3943,12 @@
"win32" "win32"
] ]
}, },
"node_modules/@mapbox/corslite": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@mapbox/corslite/-/corslite-0.0.7.tgz",
"integrity": "sha512-w/uS474VFjmqQ7fFWIMZINQM1BAQxDLuoJaZZIPES1BmeYpCtlh9MtbFxKGGDAsfvut8/HircIsVvEYRjQ+iMg==",
"license": "BSD"
},
"node_modules/@mapbox/geojson-rewind": { "node_modules/@mapbox/geojson-rewind": {
"version": "0.5.2", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz",
@ -3967,6 +3976,17 @@
"integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==", "integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/@mapbox/polyline": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@mapbox/polyline/-/polyline-0.2.0.tgz",
"integrity": "sha512-GCddO0iw6AzOQqZgBmjEQI9Pgo40/yRgkTkikGctE01kNBN0ThWYuAnTD+hRWrAWMV6QJ0rNm4m8DAsaAXE7Pg==",
"bin": {
"polyline": "bin/polyline.bin.js"
},
"engines": {
"node": "*"
}
},
"node_modules/@mapbox/tiny-sdf": { "node_modules/@mapbox/tiny-sdf": {
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.7.tgz", "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.7.tgz",
@ -3999,6 +4019,37 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/@maplibre/maplibre-gl-directions": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-directions/-/maplibre-gl-directions-0.8.0.tgz",
"integrity": "sha512-C0f+Nomj+vVPnTANJaKW7q3I+DqXfQT/huY8h9d22IFK6xEYvfe93ksX/PwNV39wp/PLOqNVcm3yWpQJfK4JVQ==",
"license": "MIT",
"dependencies": {
"@placemarkio/polyline": "^1.2.0",
"nanoid": "^5.0.6"
},
"peerDependencies": {
"maplibre-gl": "^5.0.0"
}
},
"node_modules/@maplibre/maplibre-gl-directions/node_modules/nanoid": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz",
"integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.js"
},
"engines": {
"node": "^18 || >=20"
}
},
"node_modules/@maplibre/maplibre-gl-style-spec": { "node_modules/@maplibre/maplibre-gl-style-spec": {
"version": "24.3.0", "version": "24.3.0",
"resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-24.3.0.tgz", "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-24.3.0.tgz",
@ -5115,6 +5166,18 @@
"node": ">=14" "node": ">=14"
} }
}, },
"node_modules/@placemarkio/polyline": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@placemarkio/polyline/-/polyline-1.2.0.tgz",
"integrity": "sha512-PjXntwUKQFTM/MgXIZHBOtuU2rAkmPgfrIxweOJEf1vyytQJNLDMI4YIRO3LUkt++F4TyAQHjPoRsteYa+gtVQ==",
"license": "MIT",
"dependencies": {
"@types/geojson": "*"
},
"peerDependencies": {
"@types/geojson": "*"
}
},
"node_modules/@rollup/plugin-json": { "node_modules/@rollup/plugin-json": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz",
@ -5845,6 +5908,16 @@
"@types/geojson": "*" "@types/geojson": "*"
} }
}, },
"node_modules/@types/leaflet-routing-machine": {
"version": "3.2.9",
"resolved": "https://registry.npmjs.org/@types/leaflet-routing-machine/-/leaflet-routing-machine-3.2.9.tgz",
"integrity": "sha512-5c42q5R/8MoZyu7OmeUF8ARg37YLn1abFO3BXXVHvGK3/QEI9VattMYvGbEvjpS9+9MwE4l1iDrdZ+YT9Bs7cg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/leaflet": "^1.9"
}
},
"node_modules/@types/mime": { "node_modules/@types/mime": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
@ -10270,6 +10343,17 @@
"integrity": "sha512-2EJU27z/wljOgQCyybRkfrm5Xc3uy6huKehh0UAPsrAdwnSMxaplsqCl9cXPAuDm6D7uL6PCznYMDVIsaAdSdA==", "integrity": "sha512-2EJU27z/wljOgQCyybRkfrm5Xc3uy6huKehh0UAPsrAdwnSMxaplsqCl9cXPAuDm6D7uL6PCznYMDVIsaAdSdA==",
"license": "BSD-2-Clause" "license": "BSD-2-Clause"
}, },
"node_modules/leaflet-routing-machine": {
"version": "3.2.12",
"resolved": "https://registry.npmjs.org/leaflet-routing-machine/-/leaflet-routing-machine-3.2.12.tgz",
"integrity": "sha512-HLde58G1YtD9xSIzZavJ6BPABZaV1hHeGst8ouhzuxmSC3s32NVtADT+njbIUMW1maHRCrsgTk/E4hz5QH7FrA==",
"license": "ISC",
"dependencies": {
"@mapbox/corslite": "0.0.7",
"@mapbox/polyline": "^0.2.0",
"osrm-text-instructions": "^0.13.2"
}
},
"node_modules/less": { "node_modules/less": {
"version": "4.2.2", "version": "4.2.2",
"resolved": "https://registry.npmjs.org/less/-/less-4.2.2.tgz", "resolved": "https://registry.npmjs.org/less/-/less-4.2.2.tgz",
@ -11939,6 +12023,12 @@
"license": "MIT", "license": "MIT",
"optional": true "optional": true
}, },
"node_modules/osrm-text-instructions": {
"version": "0.13.4",
"resolved": "https://registry.npmjs.org/osrm-text-instructions/-/osrm-text-instructions-0.13.4.tgz",
"integrity": "sha512-ge4ZTIetMQKAHKq2MwWf83ntzdJN20ndRKRaVNoZ3SkDkBNO99Qddz7r6+hrVx38I+ih6Rk5T1yslczAB6Q9Pg==",
"license": "BSD-2-Clause"
},
"node_modules/p-limit": { "node_modules/p-limit": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",

View File

@ -20,8 +20,10 @@
"@angular/platform-server": "^19.2.0", "@angular/platform-server": "^19.2.0",
"@angular/router": "^19.2.0", "@angular/router": "^19.2.0",
"@angular/ssr": "^19.2.15", "@angular/ssr": "^19.2.15",
"@maplibre/maplibre-gl-directions": "^0.8.0",
"express": "^4.18.2", "express": "^4.18.2",
"leaflet": "^2.0.0-alpha.1", "leaflet": "^2.0.0-alpha.1",
"leaflet-routing-machine": "^3.2.12",
"maplibre-gl": "^5.7.0", "maplibre-gl": "^5.7.0",
"rxjs": "~7.8.0", "rxjs": "~7.8.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
@ -34,6 +36,7 @@
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/jasmine": "~5.1.0", "@types/jasmine": "~5.1.0",
"@types/leaflet": "^1.9.20", "@types/leaflet": "^1.9.20",
"@types/leaflet-routing-machine": "^3.2.9",
"@types/node": "^18.18.0", "@types/node": "^18.18.0",
"jasmine-core": "~5.6.0", "jasmine-core": "~5.6.0",
"karma": "~6.4.0", "karma": "~6.4.0",

View File

@ -20,7 +20,9 @@
"@angular/common": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "@angular/common": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"@angular/core": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "@angular/core": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"maplibre-gl": "^5.0.0", "maplibre-gl": "^5.0.0",
"leaflet": "^1.0.0 || ^2.0.0" "leaflet": "^1.0.0 || ^2.0.0",
"leaflet-routing-machine": "^3.0.0",
"@maplibre/maplibre-gl-directions": "^0.8.0"
}, },
"dependencies": { "dependencies": {
"tslib": "^2.3.0" "tslib": "^2.3.0"

View File

@ -1,16 +1,19 @@
import { import L,{
Map, Map,
TileLayer, TileLayer,
Marker, Marker,
Popup, Popup,
polygon polygon, DivIcon
} from 'leaflet'; } from 'leaflet';
import 'leaflet-routing-machine';
import { import {
getLngLat, getLngLat,
IMapAdapter, IMapAdapter,
LatLng, LatLng,
MapOptions, Zone, MapOptions,
Zone,
RouteOptions,
} from './map-adapter.interface'; } from './map-adapter.interface';
export class LeafletAdapter implements IMapAdapter<Marker> { export class LeafletAdapter implements IMapAdapter<Marker> {
@ -18,6 +21,7 @@ export class LeafletAdapter implements IMapAdapter<Marker> {
private deliveryCheckMarker?: Marker; private deliveryCheckMarker?: Marker;
private markers: Record<string, Marker> = {}; private markers: Record<string, Marker> = {};
private popup?: Popup; private popup?: Popup;
private routes: Record<string, any> = {};
init(container: HTMLElement, options: MapOptions): void { init(container: HTMLElement, options: MapOptions): void {
this.map = new Map(container).setView(options.center, options.zoom); this.map = new Map(container).setView(options.center, options.zoom);
@ -35,22 +39,26 @@ export class LeafletAdapter implements IMapAdapter<Marker> {
this.map.setZoom(zoom); this.map.setZoom(zoom);
} }
addMarker(latLng: LatLng, options: { id?: string; color?: string }) { addMarker(latLng: LatLng, options?: { id?: string; color?: string ; icon? : DivIcon }) {
if (options.id) { const markerOptions: L.MarkerOptions = {};
if (options?.icon) {
markerOptions.icon = options.icon;
}
if (options?.id) {
if (this.markers[options.id]) { if (this.markers[options.id]) {
this.map.removeLayer(this.markers[options.id]); this.map.removeLayer(this.markers[options.id]);
} }
const marker = new Marker(latLng) const marker = new Marker(latLng, markerOptions);
marker.addTo(this.map); marker.addTo(this.map);
this.markers[options.id] = marker; this.markers[options.id] = marker;
} else { } else {
if (this.deliveryCheckMarker) { if (this.deliveryCheckMarker) {
this.map.removeLayer(this.deliveryCheckMarker); this.map.removeLayer(this.deliveryCheckMarker);
} }
this.deliveryCheckMarker = new Marker(latLng).addTo(this.map); this.deliveryCheckMarker = new Marker(latLng, markerOptions).addTo(this.map);
} }
} }
getMarker(id: string): Marker | undefined { getMarker(id: string): Marker | undefined {
return this.markers[id]; return this.markers[id];
} }
@ -72,6 +80,31 @@ export class LeafletAdapter implements IMapAdapter<Marker> {
} }
} }
} }
hasMarker(id: string): boolean {
return !!this.markers[id];
}
updateMarkerIcon(id: string, icon: any): void {
const marker = this.markers[id];
if (marker) {
marker.setIcon(icon);
}
}
updateMarkerPopup(id: string, popup: any): void {
const marker = this.markers[id];
if (marker) {
marker.bindPopup(popup);
}
}
addMarkerClickHandler(id: string, handler: (e: any) => void): void {
const marker = this.markers[id];
if (marker) {
marker.on('click', handler);
}
}
addZone(zones: Zone[]): void addZone(zones: Zone[]): void
{ {
this.updateZone(zones); this.updateZone(zones);
@ -128,12 +161,67 @@ export class LeafletAdapter implements IMapAdapter<Marker> {
this.popup = undefined; this.popup = undefined;
} }
} }
addRoute(id: string, waypoints: LatLng[], options?: RouteOptions): void {
this.removeRoute(id);
const routeOptions: any = {
waypoints: waypoints.map(wp => L.latLng(wp[0], wp[1])),
lineOptions: {
styles: [
{
color: options?.lineStyle?.color || '#3388ff',
opacity: options?.lineStyle?.opacity ?? 1,
weight: options?.lineStyle?.weight || 4
}
],
extendToWaypoints: false,
addWaypoints: false,
missingRouteTolerance: 10
},
createMarker: options?.showMarkers ? undefined : () => false,
draggableWaypoints: options?.draggableWaypoints ?? false,
routeWhileDragging: false,
summaryTemplate: '',
distanceTemplate: '',
timeTemplate: '',
show: true
};
if (options?.serviceUrl) {
console.log('serviceUrl', options);
routeOptions.router = L.Routing.osrmv1({
serviceUrl: options.serviceUrl
});
}
const control = L.Routing
.control(routeOptions)
.addTo(this.map);
this.routes[id] = control;
}
updateRoute(id: string, waypoints: LatLng[], options?: RouteOptions): void {
this.addRoute(id, waypoints, options);
}
removeRoute(id: string): void {
if (this.routes[id]) {
this.routes[id].remove();
delete this.routes[id];
}
}
removeAllRoutes(): void {
Object.keys(this.routes).forEach(id => this.removeRoute(id));
}
on(type: string, event: (e: any) => void): void on(type: string, event: (e: any) => void): void
{ {
this.map.on(type, event); this.map.on(type, event);
} }
destroy(): void { destroy(): void {
this.removeAllRoutes();
this.map.remove(); this.map.remove();
} }
} }

View File

@ -1,11 +1,13 @@
import { Map, Marker, NavigationControl, GeoJSONSource, Popup } from 'maplibre-gl'; import { Map, Marker, NavigationControl, GeoJSONSource, Popup } from 'maplibre-gl';
import {getLngLat, IMapAdapter, LatLng, MapOptions, Zone} from './map-adapter.interface'; import {getLngLat, IMapAdapter, LatLng, MapOptions, Zone, RouteOptions} from './map-adapter.interface';
import MapLibreGlDirections from "@maplibre/maplibre-gl-directions";
export class LibreAdapter implements IMapAdapter<Marker> { export class LibreAdapter implements IMapAdapter<Marker> {
private map!: Map; private map!: Map;
private deliveryCheckMarker?: Marker; private deliveryCheckMarker?: Marker;
private markers: Record<string, Marker> = {}; private markers: Record<string, Marker> = {};
private popup?: Popup; private popup?: Popup;
private routes: Record<string, any> = {};
private routeLayers: Record<string, [string, string]> = {};
init(container: HTMLElement, options: MapOptions): void { init(container: HTMLElement, options: MapOptions): void {
this.map = new Map({ this.map = new Map({
container, container,
@ -25,15 +27,24 @@ export class LibreAdapter implements IMapAdapter<Marker> {
this.map.setZoom(zoom); this.map.setZoom(zoom);
} }
addMarker(latLng: LatLng, options: { id?: string; color?: string }) { addMarker(latLng: LatLng, options?: { id?: string; color?: string; icon?: HTMLElement }) {
const coords = getLngLat(latLng); const coords = getLngLat(latLng);
if (options.id) { const markerOptions: any = {};
if (options?.icon) {
markerOptions.element = options.icon;
} else if (options?.color) {
markerOptions.color = options.color;
} else {
markerOptions.color = 'red';
}
if (options?.id) {
if (this.markers[options.id]) { if (this.markers[options.id]) {
this.markers[options.id].remove(); this.markers[options.id].remove();
} }
const marker = new Marker({ color: options.color || 'red' }) const marker = new Marker(markerOptions)
.setLngLat(coords) .setLngLat(coords)
.addTo(this.map); .addTo(this.map);
this.markers[options.id] = marker; this.markers[options.id] = marker;
@ -41,7 +52,7 @@ export class LibreAdapter implements IMapAdapter<Marker> {
if (this.deliveryCheckMarker) { if (this.deliveryCheckMarker) {
this.deliveryCheckMarker.remove(); this.deliveryCheckMarker.remove();
} }
this.deliveryCheckMarker = new Marker({ color: options?.color || 'red' }) this.deliveryCheckMarker = new Marker(markerOptions)
.setLngLat(coords) .setLngLat(coords)
.addTo(this.map); .addTo(this.map);
} }
@ -71,6 +82,31 @@ export class LibreAdapter implements IMapAdapter<Marker> {
} }
} }
} }
hasMarker(id: string): boolean {
return !!this.markers[id];
}
updateMarkerIcon(id: string, icon: HTMLElement): void {
const marker = this.markers[id];
if (marker) {
marker.getElement().replaceWith(icon);
}
}
updateMarkerPopup(id: string, popup: Popup): void {
const marker = this.markers[id];
if (marker) {
marker.setPopup(popup);
}
}
addMarkerClickHandler(id: string, handler: (e: any) => void): void {
const marker = this.markers[id];
if (marker) {
marker.getElement().addEventListener('click', handler);
}
}
addZone(zones: Zone[]): void addZone(zones: Zone[]): void
{ {
this.updateZone(zones); this.updateZone(zones);
@ -166,11 +202,95 @@ export class LibreAdapter implements IMapAdapter<Marker> {
this.popup = undefined; this.popup = undefined;
} }
} }
addRoute(id: string, waypoints: LatLng[], options?: RouteOptions): void {
this.removeRoute(id);
// Convert waypoints to GeoJSON LineString format [lng, lat]
const coordinates = waypoints.map(wp => getLngLat(wp));
const sourceId = `route-${id}`;
const layerId = `route-layer-${id}`;
// Add the route as a GeoJSON source
this.map.addSource(sourceId, {
type: 'geojson',
data: {
type: 'Feature',
properties: {},
geometry: {
type: 'LineString',
coordinates: coordinates
}
}
});
// Add the route layer
this.map.addLayer({
id: layerId,
type: 'line',
source: sourceId,
layout: {
'line-join': 'round',
'line-cap': 'round'
},
paint: {
'line-color': options?.lineStyle?.color || '#3388ff',
'line-width': options?.lineStyle?.weight || 4,
'line-opacity': options?.lineStyle?.opacity ?? 1
}
});
this.routeLayers[id] = [sourceId, layerId];
// Optionally add markers for waypoints
if (options?.showMarkers) {
waypoints.forEach((wp, index) => {
const markerId = `${id}-waypoint-${index}`;
this.addMarker(wp, { id: markerId, color: options.lineStyle?.color || '#3388ff' });
this.routes[markerId] = true;
});
}
}
updateRoute(id: string, waypoints: LatLng[], options?: RouteOptions): void {
this.addRoute(id, waypoints, options);
}
removeRoute(id: string): void {
if (this.routeLayers[id]) {
const [sourceId, layerId] = this.routeLayers[id];
if (this.map.getLayer(layerId)) {
this.map.removeLayer(layerId);
}
if (this.map.getSource(sourceId)) {
this.map.removeSource(sourceId);
}
delete this.routeLayers[id];
}
// Remove waypoint markers if they exist
Object.keys(this.routes).forEach(key => {
if (key.startsWith(`${id}-waypoint-`)) {
this.removeMarker(key);
delete this.routes[key];
}
});
}
removeAllRoutes(): void {
Object.keys(this.routeLayers).forEach(id => this.removeRoute(id));
}
on(type: string, event: (e: any) => void): void on(type: string, event: (e: any) => void): void
{ {
this.map.on(type, event); this.map.on(type, event);
} }
destroy(): void { destroy(): void {
this.removeAllRoutes();
this.map.remove(); this.map.remove();
} }
} }

View File

@ -24,18 +24,47 @@ export function getLngLat(latLng: LatLng): [number, number] {
return [latLng[1], latLng[0]]; return [latLng[1], latLng[0]];
} }
export interface RouteLineStyle {
color?: string;
opacity?: number;
weight?: number;
}
export interface RouteOptions {
serviceUrl?: string;
lineStyle?: RouteLineStyle;
showMarkers?: boolean;
draggableWaypoints?: boolean;
}
export interface IMapAdapter<TMarker = any> { export interface IMapAdapter<TMarker = any> {
init(container: HTMLElement, options: MapOptions): void; init(container: HTMLElement, options: MapOptions): void;
setCenter(latLng: LatLng): void; setCenter(latLng: LatLng): void;
setZoom(zoom: number): void; setZoom(zoom: number): void;
addMarker(latLng: LatLng, options?: { color?: string }): void;
// Marker management
addMarker(latLng: LatLng, options?: {id?: string; color?: string; icon?: any}): void;
getMarker(id: string): TMarker | undefined; getMarker(id: string): TMarker | undefined;
getAllMarkers(): Record<string, TMarker>; getAllMarkers(): Record<string, TMarker>;
removeMarker(id?: string): void; removeMarker(id?: string): void;
hasMarker(id: string): boolean;
updateMarkerIcon(id: string, icon: any): void;
updateMarkerPopup(id: string, popup: any): void;
addMarkerClickHandler(id: string, handler: (e: any) => void): void;
// Zone management
addZone(zone: Zone[]): void; addZone(zone: Zone[]): void;
updateZone(zone: Zone[]): void; updateZone(zone: Zone[]): void;
openZonePopup(zone: Zone) : void openZonePopup(zone: Zone) : void
closePopup(): void; closePopup(): void;
// Route management
addRoute(id: string, waypoints: LatLng[], options?: RouteOptions): void;
updateRoute(id: string, waypoints: LatLng[], options?: RouteOptions): void;
removeRoute(id: string): void;
removeAllRoutes(): void;
// Utilities
destroy(): void; destroy(): void;
on( on(
type: string, type: string,

View File

@ -1,4 +1,4 @@
import {IMapAdapter, MapOptions, LatLng, getLngLat, Zone} from './map-adapter.interface'; import {IMapAdapter, MapOptions, LatLng, getLngLat, Zone, RouteOptions} from './map-adapter.interface';
import { LibreAdapter } from './libre-adapter'; import { LibreAdapter } from './libre-adapter';
import { LeafletAdapter } from './leaflet-adapter'; import { LeafletAdapter } from './leaflet-adapter';
@ -30,7 +30,7 @@ export class MapFacade implements IMapAdapter<any> {
this.adapter.setZoom(zoom); this.adapter.setZoom(zoom);
} }
addMarker(latLng: LatLng, options: { id?: string; color?: string }): void { addMarker(latLng: LatLng, options: { id?: string; color?: string; icon?: any}): void {
this.adapter.addMarker(latLng, options); this.adapter.addMarker(latLng, options);
} }
@ -45,6 +45,23 @@ export class MapFacade implements IMapAdapter<any> {
removeMarker(id?: string): void { removeMarker(id?: string): void {
this.adapter.removeMarker(id); this.adapter.removeMarker(id);
} }
hasMarker(id: string): boolean {
return this.adapter.hasMarker(id);
}
updateMarkerIcon(id: string, icon: any): void {
this.adapter.updateMarkerIcon(id, icon);
}
updateMarkerPopup(id: string, popup: any): void {
this.adapter.updateMarkerPopup(id, popup);
}
addMarkerClickHandler(id: string, handler: (e: any) => void): void {
this.adapter.addMarkerClickHandler(id, handler);
}
addZone(zones: Zone[]): void addZone(zones: Zone[]): void
{ {
this.adapter.addZone(zones); this.adapter.addZone(zones);
@ -61,6 +78,23 @@ export class MapFacade implements IMapAdapter<any> {
{ {
this.adapter.closePopup(); this.adapter.closePopup();
} }
addRoute(id: string, waypoints: LatLng[], options?: RouteOptions): void {
this.adapter.addRoute(id, waypoints, options);
}
updateRoute(id: string, waypoints: LatLng[], options?: RouteOptions): void {
this.adapter.updateRoute(id, waypoints, options);
}
removeRoute(id: string): void {
this.adapter.removeRoute(id);
}
removeAllRoutes(): void {
this.adapter.removeAllRoutes();
}
on(type: string, event: (e: any) => void): void on(type: string, event: (e: any) => void): void
{ {
this.adapter.on(type, event); this.adapter.on(type, event);

View File

@ -1524,6 +1524,11 @@
resolved "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.2.6.tgz" resolved "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.2.6.tgz"
integrity sha512-XlqVtILonQnG+9fH2N3Aytria7P/1fwDgDhl29rde96uH2sLB8CHORIf2PfuLVzFQJ7Uqp8py9AYwr3ZUCFfWg== integrity sha512-XlqVtILonQnG+9fH2N3Aytria7P/1fwDgDhl29rde96uH2sLB8CHORIf2PfuLVzFQJ7Uqp8py9AYwr3ZUCFfWg==
"@mapbox/corslite@0.0.7":
version "0.0.7"
resolved "https://registry.npmjs.org/@mapbox/corslite/-/corslite-0.0.7.tgz"
integrity sha512-w/uS474VFjmqQ7fFWIMZINQM1BAQxDLuoJaZZIPES1BmeYpCtlh9MtbFxKGGDAsfvut8/HircIsVvEYRjQ+iMg==
"@mapbox/geojson-rewind@^0.5.2": "@mapbox/geojson-rewind@^0.5.2":
version "0.5.2" version "0.5.2"
resolved "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz" resolved "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz"
@ -1542,6 +1547,11 @@
resolved "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz" resolved "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz"
integrity sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ== integrity sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==
"@mapbox/polyline@^0.2.0":
version "0.2.0"
resolved "https://registry.npmjs.org/@mapbox/polyline/-/polyline-0.2.0.tgz"
integrity sha512-GCddO0iw6AzOQqZgBmjEQI9Pgo40/yRgkTkikGctE01kNBN0ThWYuAnTD+hRWrAWMV6QJ0rNm4m8DAsaAXE7Pg==
"@mapbox/tiny-sdf@^2.0.7": "@mapbox/tiny-sdf@^2.0.7":
version "2.0.7" version "2.0.7"
resolved "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.7.tgz" resolved "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.7.tgz"
@ -1566,6 +1576,14 @@
resolved "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz" resolved "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz"
integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q== integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==
"@maplibre/maplibre-gl-directions@^0.8.0":
version "0.8.0"
resolved "https://registry.npmjs.org/@maplibre/maplibre-gl-directions/-/maplibre-gl-directions-0.8.0.tgz"
integrity sha512-C0f+Nomj+vVPnTANJaKW7q3I+DqXfQT/huY8h9d22IFK6xEYvfe93ksX/PwNV39wp/PLOqNVcm3yWpQJfK4JVQ==
dependencies:
"@placemarkio/polyline" "^1.2.0"
nanoid "^5.0.6"
"@maplibre/maplibre-gl-style-spec@^24.3.0": "@maplibre/maplibre-gl-style-spec@^24.3.0":
version "24.3.0" version "24.3.0"
resolved "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-24.3.0.tgz" resolved "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-24.3.0.tgz"
@ -1932,6 +1950,13 @@
resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz"
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
"@placemarkio/polyline@^1.2.0":
version "1.2.0"
resolved "https://registry.npmjs.org/@placemarkio/polyline/-/polyline-1.2.0.tgz"
integrity sha512-PjXntwUKQFTM/MgXIZHBOtuU2rAkmPgfrIxweOJEf1vyytQJNLDMI4YIRO3LUkt++F4TyAQHjPoRsteYa+gtVQ==
dependencies:
"@types/geojson" "*"
"@rollup/plugin-json@^6.1.0": "@rollup/plugin-json@^6.1.0":
version "6.1.0" version "6.1.0"
resolved "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz" resolved "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz"
@ -2272,7 +2297,14 @@
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
"@types/leaflet@^1.9.20": "@types/leaflet-routing-machine@^3.2.9":
version "3.2.9"
resolved "https://registry.npmjs.org/@types/leaflet-routing-machine/-/leaflet-routing-machine-3.2.9.tgz"
integrity sha512-5c42q5R/8MoZyu7OmeUF8ARg37YLn1abFO3BXXVHvGK3/QEI9VattMYvGbEvjpS9+9MwE4l1iDrdZ+YT9Bs7cg==
dependencies:
"@types/leaflet" "^1.9"
"@types/leaflet@^1.9", "@types/leaflet@^1.9.20":
version "1.9.21" version "1.9.21"
resolved "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.21.tgz" resolved "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.21.tgz"
integrity sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w== integrity sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==
@ -4637,6 +4669,15 @@ launch-editor@^2.6.1:
picocolors "^1.1.1" picocolors "^1.1.1"
shell-quote "^1.8.3" shell-quote "^1.8.3"
leaflet-routing-machine@^3.2.12:
version "3.2.12"
resolved "https://registry.npmjs.org/leaflet-routing-machine/-/leaflet-routing-machine-3.2.12.tgz"
integrity sha512-HLde58G1YtD9xSIzZavJ6BPABZaV1hHeGst8ouhzuxmSC3s32NVtADT+njbIUMW1maHRCrsgTk/E4hz5QH7FrA==
dependencies:
"@mapbox/corslite" "0.0.7"
"@mapbox/polyline" "^0.2.0"
osrm-text-instructions "^0.13.2"
leaflet@^2.0.0-alpha.1: leaflet@^2.0.0-alpha.1:
version "2.0.0-alpha.1" version "2.0.0-alpha.1"
resolved "https://registry.npmjs.org/leaflet/-/leaflet-2.0.0-alpha.1.tgz" resolved "https://registry.npmjs.org/leaflet/-/leaflet-2.0.0-alpha.1.tgz"
@ -4842,7 +4883,7 @@ make-fetch-happen@^14.0.0, make-fetch-happen@^14.0.2, make-fetch-happen@^14.0.3:
promise-retry "^2.0.1" promise-retry "^2.0.1"
ssri "^12.0.0" ssri "^12.0.0"
maplibre-gl@^5.7.0: maplibre-gl@^5.0.0, maplibre-gl@^5.7.0:
version "5.10.0" version "5.10.0"
resolved "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.10.0.tgz" resolved "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.10.0.tgz"
integrity sha512-eLhlX8Fnpaoo7+uGqggZmXmZld6WrbzOJUPB7G8JB8XpminlTnrQTtXilMHduR8fsNVxrzD8yRRqEoajONc8LQ== integrity sha512-eLhlX8Fnpaoo7+uGqggZmXmZld6WrbzOJUPB7G8JB8XpminlTnrQTtXilMHduR8fsNVxrzD8yRRqEoajONc8LQ==
@ -5150,6 +5191,11 @@ nanoid@^3.3.11, nanoid@^3.3.8:
resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz" resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz"
integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
nanoid@^5.0.6:
version "5.1.6"
resolved "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz"
integrity sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==
needle@^3.1.0: needle@^3.1.0:
version "3.3.1" version "3.3.1"
resolved "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz" resolved "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz"
@ -5418,6 +5464,11 @@ ordered-binary@^1.5.3:
resolved "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.0.tgz" resolved "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.0.tgz"
integrity sha512-IQh2aMfMIDbPjI/8a3Edr+PiOpcsB7yo8NdW7aHWVaoR/pcDldunMvnnwbk/auPGqmKeAdxtZl7MHX/QmPwhvQ== integrity sha512-IQh2aMfMIDbPjI/8a3Edr+PiOpcsB7yo8NdW7aHWVaoR/pcDldunMvnnwbk/auPGqmKeAdxtZl7MHX/QmPwhvQ==
osrm-text-instructions@^0.13.2:
version "0.13.4"
resolved "https://registry.npmjs.org/osrm-text-instructions/-/osrm-text-instructions-0.13.4.tgz"
integrity sha512-ge4ZTIetMQKAHKq2MwWf83ntzdJN20ndRKRaVNoZ3SkDkBNO99Qddz7r6+hrVx38I+ih6Rk5T1yslczAB6Q9Pg==
p-limit@^2.2.0: p-limit@^2.2.0:
version "2.3.0" version "2.3.0"
resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz"