Refactor map adapters for improved marker and routing management
- Remove deliveryCheckMarker in favor of ID-tracked markers across all adapters - Add polygon tracking by zone ID in LeafletAdapter for better zone lifecycle management - Refactor LibreAdapter routing to use MapLibre GL Directions with proper cleanup - Improve marker recreation logic and click handler persistence in LibreAdapter - Update updateMarkerPopup interface to accept HTMLElement for type safety - Add routing profile support (driving/walking/cycling) to RouteOptions - Enhance destroy methods to properly clean up all resources 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: David Nguyen <david.nguyen@goutezplanb.com> Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
30cc53f9ad
commit
5205ebff6c
@ -21,6 +21,7 @@
|
|||||||
"@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",
|
"@maplibre/maplibre-gl-directions": "^0.8.0",
|
||||||
|
"@svrnty/ngx-open-map-wrapper": "file:../ngx-open-map-wrapper/dist/ngx-open-map-wrapper",
|
||||||
"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",
|
"leaflet-routing-machine": "^3.2.12",
|
||||||
|
|||||||
@ -18,10 +18,10 @@ import {
|
|||||||
|
|
||||||
export class LeafletAdapter implements IMapAdapter<Marker> {
|
export class LeafletAdapter implements IMapAdapter<Marker> {
|
||||||
private map!: Map;
|
private map!: Map;
|
||||||
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 routes: Record<string, any> = {};
|
||||||
|
private polygons: Record<number, L.Polygon> = {};
|
||||||
|
|
||||||
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);
|
||||||
@ -40,24 +40,22 @@ export class LeafletAdapter implements IMapAdapter<Marker> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addMarker(latLng: LatLng, options?: { id?: string; color?: string ; icon? : DivIcon }) {
|
addMarker(latLng: LatLng, options?: { id?: string; color?: string ; icon? : DivIcon }) {
|
||||||
|
if (!options?.id) {
|
||||||
|
console.warn('addMarker called without id, marker will not be tracked');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const markerOptions: L.MarkerOptions = {};
|
const markerOptions: L.MarkerOptions = {};
|
||||||
if (options?.icon) {
|
if (options?.icon) {
|
||||||
markerOptions.icon = 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, markerOptions);
|
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 {
|
|
||||||
if (this.deliveryCheckMarker) {
|
|
||||||
this.map.removeLayer(this.deliveryCheckMarker);
|
|
||||||
}
|
|
||||||
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];
|
||||||
@ -68,17 +66,15 @@ export class LeafletAdapter implements IMapAdapter<Marker> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeMarker(id?: string): void {
|
removeMarker(id?: string): void {
|
||||||
if (id) {
|
if (!id) {
|
||||||
|
console.warn('removeMarker called without id');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.markers[id]) {
|
if (this.markers[id]) {
|
||||||
this.map.removeLayer(this.markers[id]);
|
this.map.removeLayer(this.markers[id]);
|
||||||
delete this.markers[id];
|
delete this.markers[id];
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (this.deliveryCheckMarker) {
|
|
||||||
this.map.removeLayer(this.deliveryCheckMarker);
|
|
||||||
this.deliveryCheckMarker = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hasMarker(id: string): boolean {
|
hasMarker(id: string): boolean {
|
||||||
@ -92,9 +88,12 @@ export class LeafletAdapter implements IMapAdapter<Marker> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMarkerPopup(id: string, popup: any): void {
|
updateMarkerPopup(id: string, content: HTMLElement): void {
|
||||||
const marker = this.markers[id];
|
const marker = this.markers[id];
|
||||||
if (marker) {
|
if (marker) {
|
||||||
|
const popup = L.popup({
|
||||||
|
offset: [-5, -30],
|
||||||
|
}).setContent(content);
|
||||||
marker.bindPopup(popup);
|
marker.bindPopup(popup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -113,6 +112,11 @@ export class LeafletAdapter implements IMapAdapter<Marker> {
|
|||||||
{
|
{
|
||||||
for(let zone of zones)
|
for(let zone of zones)
|
||||||
{
|
{
|
||||||
|
// Remove old polygon if it exists
|
||||||
|
if (this.polygons[zone.id]) {
|
||||||
|
this.map.removeLayer(this.polygons[zone.id]);
|
||||||
|
}
|
||||||
|
|
||||||
const latlngs = zone.polygon.map((geoPoint) => {
|
const latlngs = zone.polygon.map((geoPoint) => {
|
||||||
return [geoPoint.x, geoPoint.y] as LatLng;
|
return [geoPoint.x, geoPoint.y] as LatLng;
|
||||||
});
|
});
|
||||||
@ -124,8 +128,11 @@ export class LeafletAdapter implements IMapAdapter<Marker> {
|
|||||||
if (zone.opacity)
|
if (zone.opacity)
|
||||||
opacity = zone.opacity;
|
opacity = zone.opacity;
|
||||||
|
|
||||||
polygon(latlngs, { color, fillOpacity: opacity })
|
const poly = polygon(latlngs, { color, fillOpacity: opacity })
|
||||||
.addTo(this.map);
|
.addTo(this.map);
|
||||||
|
|
||||||
|
// Store polygon reference for later removal/update
|
||||||
|
this.polygons[zone.id] = poly;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
openZonePopup(zone: Zone) : void
|
openZonePopup(zone: Zone) : void
|
||||||
@ -189,7 +196,6 @@ export class LeafletAdapter implements IMapAdapter<Marker> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (options?.serviceUrl) {
|
if (options?.serviceUrl) {
|
||||||
console.log('serviceUrl', options);
|
|
||||||
routeOptions.router = L.Routing.osrmv1({
|
routeOptions.router = L.Routing.osrmv1({
|
||||||
serviceUrl: options.serviceUrl
|
serviceUrl: options.serviceUrl
|
||||||
});
|
});
|
||||||
@ -222,6 +228,8 @@ export class LeafletAdapter implements IMapAdapter<Marker> {
|
|||||||
}
|
}
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
this.removeAllRoutes();
|
this.removeAllRoutes();
|
||||||
|
// Clean up polygons
|
||||||
|
Object.values(this.polygons).forEach(poly => this.map.removeLayer(poly));
|
||||||
this.map.remove();
|
this.map.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,11 +3,10 @@ import {getLngLat, IMapAdapter, LatLng, MapOptions, Zone, RouteOptions} from './
|
|||||||
import MapLibreGlDirections from "@maplibre/maplibre-gl-directions";
|
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 markers: Record<string, Marker> = {};
|
private markers: Record<string, Marker> = {};
|
||||||
|
private markerClickHandlers: Record<string, (e: any) => void> = {};
|
||||||
private popup?: Popup;
|
private popup?: Popup;
|
||||||
private routes: Record<string, any> = {};
|
private directions: Record<string, MapLibreGlDirections> = {};
|
||||||
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,
|
||||||
@ -28,6 +27,11 @@ export class LibreAdapter implements IMapAdapter<Marker> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addMarker(latLng: LatLng, options?: { id?: string; color?: string; icon?: HTMLElement }) {
|
addMarker(latLng: LatLng, options?: { id?: string; color?: string; icon?: HTMLElement }) {
|
||||||
|
if (!options?.id) {
|
||||||
|
console.warn('addMarker called without id, marker will not be tracked');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const coords = getLngLat(latLng);
|
const coords = getLngLat(latLng);
|
||||||
|
|
||||||
const markerOptions: any = {};
|
const markerOptions: any = {};
|
||||||
@ -39,7 +43,6 @@ export class LibreAdapter implements IMapAdapter<Marker> {
|
|||||||
markerOptions.color = 'red';
|
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();
|
||||||
}
|
}
|
||||||
@ -48,14 +51,6 @@ export class LibreAdapter implements IMapAdapter<Marker> {
|
|||||||
.setLngLat(coords)
|
.setLngLat(coords)
|
||||||
.addTo(this.map);
|
.addTo(this.map);
|
||||||
this.markers[options.id] = marker;
|
this.markers[options.id] = marker;
|
||||||
} else {
|
|
||||||
if (this.deliveryCheckMarker) {
|
|
||||||
this.deliveryCheckMarker.remove();
|
|
||||||
}
|
|
||||||
this.deliveryCheckMarker = new Marker(markerOptions)
|
|
||||||
.setLngLat(coords)
|
|
||||||
.addTo(this.map);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getMarker(id: string): Marker | undefined {
|
getMarker(id: string): Marker | undefined {
|
||||||
@ -67,19 +62,16 @@ export class LibreAdapter implements IMapAdapter<Marker> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeMarker(id?: string): void {
|
removeMarker(id?: string): void {
|
||||||
if (id) {
|
if (!id) {
|
||||||
|
console.warn('removeMarker called without id');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.markers[id]) {
|
if (this.markers[id]) {
|
||||||
this.markers[id].remove();
|
this.markers[id].remove();
|
||||||
delete this.markers[id];
|
delete this.markers[id];
|
||||||
}
|
// Clean up stored click handler
|
||||||
}
|
delete this.markerClickHandlers[id];
|
||||||
else
|
|
||||||
{
|
|
||||||
if (this.deliveryCheckMarker)
|
|
||||||
{
|
|
||||||
this.deliveryCheckMarker.remove();
|
|
||||||
this.deliveryCheckMarker = undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,13 +82,19 @@ export class LibreAdapter implements IMapAdapter<Marker> {
|
|||||||
updateMarkerIcon(id: string, icon: HTMLElement): void {
|
updateMarkerIcon(id: string, icon: HTMLElement): void {
|
||||||
const marker = this.markers[id];
|
const marker = this.markers[id];
|
||||||
if (marker) {
|
if (marker) {
|
||||||
marker.getElement().replaceWith(icon);
|
this.markers[id] = this.recreateMarker(id, marker, icon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMarkerPopup(id: string, popup: Popup): void {
|
updateMarkerPopup(id: string, content: HTMLElement): void {
|
||||||
const marker = this.markers[id];
|
const marker = this.markers[id];
|
||||||
if (marker) {
|
if (marker) {
|
||||||
|
// Create MapLibre popup with the HTML content
|
||||||
|
const popup = new Popup({
|
||||||
|
offset: [0, -30],
|
||||||
|
maxWidth: '400px',
|
||||||
|
className: 'delivery-popup'
|
||||||
|
}).setDOMContent(content);
|
||||||
marker.setPopup(popup);
|
marker.setPopup(popup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,6 +102,8 @@ export class LibreAdapter implements IMapAdapter<Marker> {
|
|||||||
addMarkerClickHandler(id: string, handler: (e: any) => void): void {
|
addMarkerClickHandler(id: string, handler: (e: any) => void): void {
|
||||||
const marker = this.markers[id];
|
const marker = this.markers[id];
|
||||||
if (marker) {
|
if (marker) {
|
||||||
|
// Store the handler so we can re-apply it when re-adding markers
|
||||||
|
this.markerClickHandlers[id] = handler;
|
||||||
marker.getElement().addEventListener('click', handler);
|
marker.getElement().addEventListener('click', handler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -206,50 +206,101 @@ export class LibreAdapter implements IMapAdapter<Marker> {
|
|||||||
addRoute(id: string, waypoints: LatLng[], options?: RouteOptions): void {
|
addRoute(id: string, waypoints: LatLng[], options?: RouteOptions): void {
|
||||||
this.removeRoute(id);
|
this.removeRoute(id);
|
||||||
|
|
||||||
// Convert waypoints to GeoJSON LineString format [lng, lat]
|
const directionsConfig: any = {
|
||||||
|
sourceName: `maplibre-gl-directions-${id}`, // Unique source name per route
|
||||||
|
requestOptions: {
|
||||||
|
overview: 'full',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options?.serviceUrl) {
|
||||||
|
let apiUrl = options.serviceUrl;
|
||||||
|
|
||||||
|
apiUrl = apiUrl.replace(/\/$/, '');
|
||||||
|
|
||||||
|
if (!apiUrl.endsWith('/route/v1')) {
|
||||||
|
apiUrl = `${apiUrl}/route/v1`;
|
||||||
|
}
|
||||||
|
|
||||||
|
directionsConfig.api = apiUrl;
|
||||||
|
directionsConfig.profile = options.profile || 'driving';
|
||||||
|
}
|
||||||
|
|
||||||
|
const directions = new MapLibreGlDirections(this.map, directionsConfig);
|
||||||
|
|
||||||
|
directions.interactive = false;
|
||||||
|
|
||||||
const coordinates = waypoints.map(wp => getLngLat(wp));
|
const coordinates = waypoints.map(wp => getLngLat(wp));
|
||||||
|
directions.setWaypoints(coordinates);
|
||||||
|
this.directions[id] = directions;
|
||||||
|
|
||||||
const sourceId = `route-${id}`;
|
if (options?.lineStyle) {
|
||||||
const layerId = `route-layer-${id}`;
|
directions.on('fetchroutesend', () => {
|
||||||
|
this.applyRouteLineStyle(directionsConfig.sourceName, options.lineStyle);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Add the route as a GeoJSON source
|
this.readdAllMarkers();
|
||||||
this.map.addSource(sourceId, {
|
}
|
||||||
type: 'geojson',
|
|
||||||
data: {
|
private recreateMarker(id: string, marker: Marker, element?: HTMLElement): Marker {
|
||||||
type: 'Feature',
|
const lngLat = marker.getLngLat();
|
||||||
properties: {},
|
const markerElement = element || marker.getElement();
|
||||||
geometry: {
|
const popup = marker.getPopup();
|
||||||
type: 'LineString',
|
|
||||||
coordinates: coordinates
|
// Remove old marker
|
||||||
|
marker.remove();
|
||||||
|
|
||||||
|
// Create new marker with updated element
|
||||||
|
const newMarker = new Marker({ element: markerElement })
|
||||||
|
.setLngLat(lngLat)
|
||||||
|
.addTo(this.map);
|
||||||
|
|
||||||
|
// Restore popup if exists
|
||||||
|
if (popup) {
|
||||||
|
newMarker.setPopup(popup);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore click handler if exists
|
||||||
|
if (this.markerClickHandlers[id]) {
|
||||||
|
newMarker.getElement().addEventListener('click', this.markerClickHandlers[id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newMarker;
|
||||||
|
}
|
||||||
|
|
||||||
|
private applyRouteLineStyle(sourceName: string, lineStyle?: any): void {
|
||||||
|
if (!lineStyle) return;
|
||||||
|
|
||||||
|
const routeLineLayer = `${sourceName}-routeline`;
|
||||||
|
const routeLineCasingLayer = `${sourceName}-routeline-casing`;
|
||||||
|
|
||||||
|
if (this.map.getLayer(routeLineLayer)) {
|
||||||
|
if (lineStyle.color) {
|
||||||
|
this.map.setPaintProperty(routeLineLayer, 'line-color', lineStyle.color);
|
||||||
|
}
|
||||||
|
if (lineStyle.opacity !== undefined) {
|
||||||
|
this.map.setPaintProperty(routeLineLayer, 'line-opacity', lineStyle.opacity);
|
||||||
|
}
|
||||||
|
if (lineStyle.weight !== undefined) {
|
||||||
|
this.map.setPaintProperty(routeLineLayer, 'line-width', lineStyle.weight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// Add the route layer
|
if (this.map.getLayer(routeLineCasingLayer)) {
|
||||||
this.map.addLayer({
|
if (lineStyle.color) {
|
||||||
id: layerId,
|
this.map.setPaintProperty(routeLineCasingLayer, 'line-color', lineStyle.color);
|
||||||
type: 'line',
|
}
|
||||||
source: sourceId,
|
if (lineStyle.opacity !== undefined) {
|
||||||
layout: {
|
this.map.setPaintProperty(routeLineCasingLayer, 'line-opacity', lineStyle.opacity * 0.6);
|
||||||
'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];
|
private readdAllMarkers(): void {
|
||||||
|
// Re-add all markers to bring them to the front
|
||||||
// Optionally add markers for waypoints
|
for (const [id, marker] of Object.entries(this.markers)) {
|
||||||
if (options?.showMarkers) {
|
this.markers[id] = this.recreateMarker(id, marker);
|
||||||
waypoints.forEach((wp, index) => {
|
|
||||||
const markerId = `${id}-waypoint-${index}`;
|
|
||||||
this.addMarker(wp, { id: markerId, color: options.lineStyle?.color || '#3388ff' });
|
|
||||||
this.routes[markerId] = true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,31 +309,18 @@ export class LibreAdapter implements IMapAdapter<Marker> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeRoute(id: string): void {
|
removeRoute(id: string): void {
|
||||||
if (this.routeLayers[id]) {
|
if (this.directions[id]) {
|
||||||
const [sourceId, layerId] = this.routeLayers[id];
|
try {
|
||||||
|
this.directions[id].destroy();
|
||||||
if (this.map.getLayer(layerId)) {
|
} catch (e) {
|
||||||
this.map.removeLayer(layerId);
|
console.warn('Error clearing directions:', e);
|
||||||
}
|
}
|
||||||
|
delete this.directions[id];
|
||||||
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 {
|
removeAllRoutes(): void {
|
||||||
Object.keys(this.routeLayers).forEach(id => this.removeRoute(id));
|
Object.keys(this.directions).forEach(id => this.removeRoute(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
on(type: string, event: (e: any) => void): void
|
on(type: string, event: (e: any) => void): void
|
||||||
|
|||||||
@ -32,6 +32,7 @@ export interface RouteLineStyle {
|
|||||||
|
|
||||||
export interface RouteOptions {
|
export interface RouteOptions {
|
||||||
serviceUrl?: string;
|
serviceUrl?: string;
|
||||||
|
profile?: 'driving' | 'walking' | 'cycling';
|
||||||
lineStyle?: RouteLineStyle;
|
lineStyle?: RouteLineStyle;
|
||||||
showMarkers?: boolean;
|
showMarkers?: boolean;
|
||||||
draggableWaypoints?: boolean;
|
draggableWaypoints?: boolean;
|
||||||
@ -49,7 +50,7 @@ export interface IMapAdapter<TMarker = any> {
|
|||||||
removeMarker(id?: string): void;
|
removeMarker(id?: string): void;
|
||||||
hasMarker(id: string): boolean;
|
hasMarker(id: string): boolean;
|
||||||
updateMarkerIcon(id: string, icon: any): void;
|
updateMarkerIcon(id: string, icon: any): void;
|
||||||
updateMarkerPopup(id: string, popup: any): void;
|
updateMarkerPopup(id: string, content: HTMLElement): void;
|
||||||
addMarkerClickHandler(id: string, handler: (e: any) => void): void;
|
addMarkerClickHandler(id: string, handler: (e: any) => void): void;
|
||||||
|
|
||||||
// Zone management
|
// Zone management
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user