UNPKG

unimap

Version:

Unified mapping library for multiple map service providers - Google Maps, Mapbox, Bing Maps, OpenStreetMap, and more

1,457 lines (1,449 loc) 167 kB
var UniMap = (() => { var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // unimap.js var unimap_exports = {}; __export(unimap_exports, { UniMap: () => UniMap }); // utils/constants.js var SUPPORTED_PROVIDERS = [ "google", "mapbox", "osm", "here", "tomtom", "bing", "yandex", "carto", "azure", "mapmyindia" ]; // utils/common.js function throwIfMissing(paramName) { throw new Error(`Missing required parameter: ${paramName}`); } // adapters/BaseAdapter.js var BaseAdapter = class { constructor(apiKey, containerId, options = {}) { this.apiKey = apiKey; this.containerId = containerId; this.options = options; this.map = null; this.markers = /* @__PURE__ */ new Map(); this.polylines = /* @__PURE__ */ new Map(); this.polygons = /* @__PURE__ */ new Map(); this.heatmaps = /* @__PURE__ */ new Map(); this.layers = /* @__PURE__ */ new Map(); } async init() { throw new Error("init() method must be implemented by the adapter"); } addMarker(options) { throw new Error("addMarker() method must be implemented by the adapter"); } removeMarker(markerId) { throw new Error("removeMarker() method must be implemented by the adapter"); } updateMarker(markerId, options) { throw new Error("updateMarker() method must be implemented by the adapter"); } setCenter(coords) { throw new Error("setCenter() method must be implemented by the adapter"); } getCenter() { throw new Error("getCenter() method must be implemented by the adapter"); } setZoom(level) { throw new Error("setZoom() method must be implemented by the adapter"); } getZoom() { throw new Error("getZoom() method must be implemented by the adapter"); } zoomIn() { throw new Error("zoomIn() method must be implemented by the adapter"); } zoomOut() { throw new Error("zoomOut() method must be implemented by the adapter"); } panTo(coords) { throw new Error("panTo() method must be implemented by the adapter"); } fitBounds(bounds) { throw new Error("fitBounds() method must be implemented by the adapter"); } geocode(address) { throw new Error("geocode() method must be implemented by the adapter"); } reverseGeocode(lat, lng) { throw new Error("reverseGeocode() method must be implemented by the adapter"); } drawRoute(coords, options = {}) { throw new Error("drawRoute() method must be implemented by the adapter"); } getDirections(origin, destination, options = {}) { throw new Error("getDirections() method must be implemented by the adapter"); } drawPolygon(coords, options = {}) { throw new Error("drawPolygon() method must be implemented by the adapter"); } drawPolyline(coords, options = {}) { throw new Error("drawPolyline() method must be implemented by the adapter"); } drawCircle(center, radius, options = {}) { throw new Error("drawCircle() method must be implemented by the adapter"); } drawRectangle(bounds, options = {}) { throw new Error("drawRectangle() method must be implemented by the adapter"); } enableTrafficLayer() { throw new Error("enableTrafficLayer() method must be implemented by the adapter"); } disableTrafficLayer() { throw new Error("disableTrafficLayer() method must be implemented by the adapter"); } addHeatMap(points, options = {}) { throw new Error("addHeatMap() method must be implemented by the adapter"); } addTileLayer(url, options = {}) { throw new Error("addTileLayer() method must be implemented by the adapter"); } removeLayer(layerId) { throw new Error("removeLayer() method must be implemented by the adapter"); } trackUserLocation(callback, options = {}) { throw new Error("trackUserLocation() method must be implemented by the adapter"); } getUserLocation() { throw new Error("getUserLocation() method must be implemented by the adapter"); } indoorMaps(enable) { throw new Error("indoorMaps() method must be implemented by the adapter"); } applyMapStyle(style) { throw new Error("applyMapStyle() method must be implemented by the adapter"); } enable3D(enable) { throw new Error("enable3D() method must be implemented by the adapter"); } on(event, callback) { throw new Error("on() method must be implemented by the adapter"); } off(event, callback) { throw new Error("off() method must be implemented by the adapter"); } getBounds() { throw new Error("getBounds() method must be implemented by the adapter"); } getContainer() { return document.getElementById(this.containerId); } destroy() { throw new Error("destroy() method must be implemented by the adapter"); } _validateCoordinates(lat, lng) { return typeof lat === "number" && typeof lng === "number" && lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180; } _generateId() { return `id_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } }; // adapters/GoogleMapsAdapter.js var GoogleMapsAdapter = class extends BaseAdapter { constructor(apiKey, containerId, options = {}) { super(apiKey, containerId, options); this.trafficLayer = null; this.eventListeners = /* @__PURE__ */ new Map(); } async init() { try { await this.loadGoogleMapsScript(); await this.validateApiKey(); const mapElement = this.getContainer(); if (!mapElement) { throw new Error(`Container element with ID '${this.containerId}' not found.`); } this.map = new google.maps.Map(mapElement, { center: this.options.center || { lat: 0, lng: 0 }, zoom: this.options.zoom || 10, styles: this.options.styles || [], mapTypeId: this.options.mapTypeId || "roadmap", disableDefaultUI: this.options.disableDefaultUI || false, zoomControl: this.options.zoomControl !== false, mapTypeControl: this.options.mapTypeControl !== false, scaleControl: this.options.scaleControl !== false, streetViewControl: this.options.streetViewControl !== false, rotateControl: this.options.rotateControl !== false, fullscreenControl: this.options.fullscreenControl !== false }); await new Promise((resolve) => { google.maps.event.addListenerOnce(this.map, "idle", resolve); }); } catch (error) { console.error("Google Maps initialization error:", error); throw new Error(`Failed to initialize Google Maps: ${error.message}`); } } loadGoogleMapsScript() { return new Promise((resolve, reject) => { if (window.google && window.google.maps) { return resolve(); } const script = document.createElement("script"); script.src = `https://maps.googleapis.com/maps/api/js?key=${this.apiKey}&libraries=places,visualization,geometry&callback=initGoogleMaps`; script.async = true; script.defer = true; window.initGoogleMaps = () => { resolve(); }; script.onerror = (error) => { reject(new Error("Failed to load Google Maps script")); }; setTimeout(() => { if (!window.google || !window.google.maps) { reject(new Error("Google Maps script loading timeout")); } }, 1e4); document.head.appendChild(script); }); } async validateApiKey() { if (!this.apiKey || this.apiKey.trim() === "") { throw new Error("Google Maps API key is required"); } try { const testGeocoder = new google.maps.Geocoder(); await new Promise((resolve, reject) => { testGeocoder.geocode({ address: "New York" }, (results, status) => { if (status === "OK") { resolve(); } else if (status === "REQUEST_DENIED") { reject(new Error("API key is invalid or restricted")); } else if (status === "OVER_QUERY_LIMIT") { reject(new Error("API key has exceeded quota")); } else { reject(new Error(`API key validation failed: ${status}`)); } }); }); } catch (error) { throw new Error(`API key validation failed: ${error.message}`); } } addMarker(options) { if (!this.map) { throw new Error("Map not initialized"); } const markerId = this._generateId(); const marker = new google.maps.Marker({ position: { lat: options.lat, lng: options.lng }, map: this.map, title: options.title || "", label: options.label || "", icon: options.icon || null, draggable: options.draggable || false, clickable: options.clickable !== false }); this.markers.set(markerId, marker); return markerId; } removeMarker(markerId) { const marker = this.markers.get(markerId); if (marker) { marker.setMap(null); this.markers.delete(markerId); return true; } return false; } updateMarker(markerId, options) { const marker = this.markers.get(markerId); if (marker) { if (options.position) { marker.setPosition({ lat: options.position.lat, lng: options.position.lng }); } if (options.title !== void 0) marker.setTitle(options.title); if (options.label !== void 0) marker.setLabel(options.label); if (options.icon !== void 0) marker.setIcon(options.icon); if (options.draggable !== void 0) marker.setDraggable(options.draggable); return true; } return false; } setCenter(coords) { if (!this.map) { throw new Error("Map not initialized"); } if (this._validateCoordinates(coords.lat, coords.lng)) { this.map.setCenter({ lat: coords.lat, lng: coords.lng }); } } getCenter() { if (!this.map) { throw new Error("Map not initialized"); } const center = this.map.getCenter(); return { lat: center.lat(), lng: center.lng() }; } setZoom(level) { if (!this.map) { throw new Error("Map not initialized"); } this.map.setZoom(level); } getZoom() { if (!this.map) { throw new Error("Map not initialized"); } return this.map.getZoom(); } zoomIn() { if (!this.map) { throw new Error("Map not initialized"); } this.map.setZoom(this.map.getZoom() + 1); } zoomOut() { if (!this.map) { throw new Error("Map not initialized"); } this.map.setZoom(this.map.getZoom() - 1); } panTo(coords) { if (!this.map) { throw new Error("Map not initialized"); } if (this._validateCoordinates(coords.lat, coords.lng)) { this.map.panTo({ lat: coords.lat, lng: coords.lng }); } } fitBounds(bounds) { if (!this.map) { throw new Error("Map not initialized"); } const googleBounds = new google.maps.LatLngBounds( { lat: bounds.southwest.lat, lng: bounds.southwest.lng }, { lat: bounds.northeast.lat, lng: bounds.northeast.lng } ); this.map.fitBounds(googleBounds); } geocode(address) { if (!this.map) { throw new Error("Map not initialized"); } const geocoder = new google.maps.Geocoder(); return new Promise((resolve, reject) => { geocoder.geocode({ address }, (results, status) => { if (status === "OK") { const location = results[0].geometry.location; resolve({ lat: location.lat(), lng: location.lng(), formattedAddress: results[0].formatted_address }); } else { reject(`Geocode failed: ${status}`); } }); }); } reverseGeocode(lat, lng) { if (!this.map) { throw new Error("Map not initialized"); } if (!this._validateCoordinates(lat, lng)) { return Promise.reject("Invalid coordinates"); } const geocoder = new google.maps.Geocoder(); return new Promise((resolve, reject) => { geocoder.geocode({ location: { lat, lng } }, (results, status) => { if (status === "OK") { resolve({ formattedAddress: results[0].formatted_address, components: results[0].address_components }); } else { reject(`Reverse geocode failed: ${status}`); } }); }); } drawRoute(coords, options = {}) { if (!this.map) { throw new Error("Map not initialized"); } const path = coords.map((coord) => ({ lat: coord.lat, lng: coord.lng })); const polyline = new google.maps.Polyline({ path, geodesic: true, strokeColor: options.strokeColor || "#FF0000", strokeOpacity: options.strokeOpacity || 1, strokeWeight: options.strokeWeight || 2 }); const polylineId = this._generateId(); polyline.setMap(this.map); this.polylines.set(polylineId, polyline); return polylineId; } getDirections(origin, destination, options = {}) { if (!this.map) { throw new Error("Map not initialized"); } const directionsService = new google.maps.DirectionsService(); const directionsRenderer = new google.maps.DirectionsRenderer({ map: this.map, suppressMarkers: options.suppressMarkers || false }); return new Promise((resolve, reject) => { directionsService.route({ origin, destination, travelMode: options.travelMode || google.maps.TravelMode.DRIVING, waypoints: options.waypoints || [], optimizeWaypoints: options.optimizeWaypoints || false }, (result, status) => { if (status === "OK") { directionsRenderer.setDirections(result); resolve(result); } else { reject(`Directions failed: ${status}`); } }); }); } drawPolygon(coords, options = {}) { if (!this.map) { throw new Error("Map not initialized"); } const polygon = new google.maps.Polygon({ paths: coords.map((coord) => ({ lat: coord.lat, lng: coord.lng })), strokeColor: options.strokeColor || "#FF0000", strokeOpacity: options.strokeOpacity || 0.8, strokeWeight: options.strokeWeight || 2, fillColor: options.fillColor || "#FF0000", fillOpacity: options.fillOpacity || 0.35 }); const polygonId = this._generateId(); polygon.setMap(this.map); this.polygons.set(polygonId, polygon); return polygonId; } drawPolyline(coords, options = {}) { return this.drawRoute(coords, options); } drawCircle(center, radius, options = {}) { if (!this.map) { throw new Error("Map not initialized"); } const circle = new google.maps.Circle({ center: { lat: center.lat, lng: center.lng }, radius, strokeColor: options.strokeColor || "#FF0000", strokeOpacity: options.strokeOpacity || 0.8, strokeWeight: options.strokeWeight || 2, fillColor: options.fillColor || "#FF0000", fillOpacity: options.fillOpacity || 0.35 }); const circleId = this._generateId(); circle.setMap(this.map); this.polygons.set(circleId, circle); return circleId; } drawRectangle(bounds, options = {}) { if (!this.map) { throw new Error("Map not initialized"); } const rectangle = new google.maps.Rectangle({ bounds: { south: bounds.southwest.lat, west: bounds.southwest.lng, north: bounds.northeast.lat, east: bounds.northeast.lng }, strokeColor: options.strokeColor || "#FF0000", strokeOpacity: options.strokeOpacity || 0.8, strokeWeight: options.strokeWeight || 2, fillColor: options.fillColor || "#FF0000", fillOpacity: options.fillOpacity || 0.35 }); const rectangleId = this._generateId(); rectangle.setMap(this.map); this.polygons.set(rectangleId, rectangle); return rectangleId; } enableTrafficLayer() { if (!this.map) { throw new Error("Map not initialized"); } if (!this.trafficLayer) { this.trafficLayer = new google.maps.TrafficLayer(); } this.trafficLayer.setMap(this.map); } disableTrafficLayer() { if (this.trafficLayer) { this.trafficLayer.setMap(null); } } addHeatMap(points, options = {}) { if (!this.map) { throw new Error("Map not initialized"); } const heatmapData = points.map((point) => new google.maps.LatLng(point.lat, point.lng)); this.heatmap = new google.maps.visualization.HeatmapLayer({ data: heatmapData, radius: options.radius || 20, opacity: options.opacity || 0.6 }); const heatmapId = this._generateId(); this.heatmap.setMap(this.map); this.heatmaps.set(heatmapId, this.heatmap); return heatmapId; } addTileLayer(url, options = {}) { if (!this.map) { throw new Error("Map not initialized"); } const tileLayer = new google.maps.ImageMapType({ getTileUrl: function(coord, zoom) { return url.replace("{x}", coord.x).replace("{y}", coord.y).replace("{z}", zoom); }, tileSize: new google.maps.Size(256, 256), opacity: options.opacity || 1 }); const layerId = this._generateId(); this.layers.set(layerId, tileLayer); return layerId; } removeLayer(layerId) { const layer = this.layers.get(layerId); if (layer) { if (layer.setMap) { layer.setMap(null); } this.layers.delete(layerId); return true; } return false; } trackUserLocation(callback, options = {}) { if (navigator.geolocation) { const watchId = navigator.geolocation.watchPosition( (position) => { const coords = { lat: position.coords.latitude, lng: position.coords.longitude, accuracy: position.coords.accuracy }; callback(coords); }, (error) => console.error("Geolocation error:", error), { enableHighAccuracy: options.enableHighAccuracy || false, timeout: options.timeout || 5e3, maximumAge: options.maximumAge || 0 } ); return watchId; } else { console.error("Geolocation is not supported by this browser."); return null; } } getUserLocation() { return new Promise((resolve, reject) => { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( (position) => { resolve({ lat: position.coords.latitude, lng: position.coords.longitude, accuracy: position.coords.accuracy }); }, (error) => reject(error), { enableHighAccuracy: true, timeout: 1e4, maximumAge: 6e4 } ); } else { reject(new Error("Geolocation is not supported by this browser.")); } }); } indoorMaps(enable) { console.info("Indoor maps are enabled by default in Google Maps."); } applyMapStyle(style) { if (!this.map) { throw new Error("Map not initialized"); } this.map.setOptions({ styles: style }); } enable3D(enable) { if (!this.map) { throw new Error("Map not initialized"); } if (enable) { this.map.setTilt(45); } else { this.map.setTilt(0); } } on(event, callback) { if (!this.map) { throw new Error("Map not initialized"); } if (!this.eventListeners.has(event)) { this.eventListeners.set(event, []); } const listener = this.map.addListener(event, callback); this.eventListeners.get(event).push({ callback, listener }); } off(event, callback) { const listeners = this.eventListeners.get(event); if (listeners) { const index = listeners.findIndex((item) => item.callback === callback); if (index > -1) { const { listener } = listeners[index]; google.maps.event.removeListener(listener); listeners.splice(index, 1); } } } getBounds() { if (!this.map) { throw new Error("Map not initialized"); } const bounds = this.map.getBounds(); if (bounds) { return { southwest: { lat: bounds.getSouthWest().lat(), lng: bounds.getSouthWest().lng() }, northeast: { lat: bounds.getNorthEast().lat(), lng: bounds.getNorthEast().lng() } }; } return null; } destroy() { this.eventListeners.forEach((listeners, event) => { listeners.forEach(({ listener }) => { google.maps.event.removeListener(listener); }); }); this.eventListeners.clear(); this.markers.forEach((marker) => marker.setMap(null)); this.polylines.forEach((polyline) => polyline.setMap(null)); this.polygons.forEach((polygon) => polygon.setMap(null)); this.heatmaps.forEach((heatmap) => heatmap.setMap(null)); this.layers.forEach((layer) => { if (layer.setMap) layer.setMap(null); }); this.markers.clear(); this.polylines.clear(); this.polygons.clear(); this.heatmaps.clear(); this.layers.clear(); const container = this.getContainer(); if (container) { while (container.firstChild) { container.removeChild(container.firstChild); } } this.map = null; this.trafficLayer = null; } }; // adapters/MapboxAdapter.js var MapboxAdapter = class extends BaseAdapter { constructor(apiKey, containerId, options = {}) { super(apiKey, containerId, options); this.eventListeners = /* @__PURE__ */ new Map(); this.sources = /* @__PURE__ */ new Map(); this.layers = /* @__PURE__ */ new Map(); this._isLoaded = false; } async init() { await this.loadMapboxScript(); const mapElement = this.getContainer(); if (!mapElement) { throw new Error(`Container element with ID '${this.containerId}' not found.`); } if (!this.apiKey || typeof this.apiKey !== "string") { throw new Error("Mapbox access token is required"); } mapboxgl.accessToken = this.apiKey; const centerLat = typeof this.options.center?.lat === "number" ? this.options.center.lat : 0; const centerLng = typeof this.options.center?.lng === "number" ? this.options.center.lng : 0; this.map = new mapboxgl.Map({ container: this.containerId, style: this.options.style || "mapbox://styles/mapbox/streets-v11", center: [centerLng, centerLat], zoom: this.options.zoom || 10 }); await new Promise((resolve) => { this.map.on("load", () => { this._isLoaded = true; resolve(); }); }); } isReady() { if (!this.map) return false; if (typeof this.map.isStyleLoaded === "function") { return this.map.isStyleLoaded(); } return this._isLoaded; } executeWhenReady(callback) { if (this.isReady()) { callback(); } else { this.map.once("load", callback); } } loadMapboxScript() { return new Promise((resolve, reject) => { if (window.mapboxgl) { return resolve(); } const link = document.createElement("link"); link.href = "https://api.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.css"; link.rel = "stylesheet"; document.head.appendChild(link); const script = document.createElement("script"); script.src = "https://api.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.js"; script.async = true; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } addMarker(options) { const markerId = this._generateId(); const el = document.createElement("div"); el.className = "mapbox-marker"; el.style.width = "20px"; el.style.height = "20px"; el.style.backgroundColor = options.color || "#FF0000"; el.style.borderRadius = "50%"; el.style.border = "2px solid white"; el.style.cursor = "pointer"; if (options.label) { el.setAttribute("title", options.label); } const marker = new mapboxgl.Marker(el).setLngLat([options.lng, options.lat]).addTo(this.map); this.markers.set(markerId, marker); return markerId; } removeMarker(markerId) { const marker = this.markers.get(markerId); if (marker) { marker.remove(); this.markers.delete(markerId); return true; } return false; } updateMarker(markerId, options) { const marker = this.markers.get(markerId); if (marker) { if (options.position) { marker.setLngLat([options.position.lng, options.position.lat]); } return true; } return false; } setCenter(coords) { if (this._validateCoordinates(coords.lat, coords.lng)) { this.map.setCenter([coords.lng, coords.lat]); } } getCenter() { const center = this.map.getCenter(); return { lat: center.lat, lng: center.lng }; } setZoom(level) { this.map.setZoom(level); } getZoom() { return this.map.getZoom(); } zoomIn() { this.map.zoomIn(); } zoomOut() { this.map.zoomOut(); } panTo(coords) { if (this._validateCoordinates(coords.lat, coords.lng)) { this.map.panTo([coords.lng, coords.lat]); } } fitBounds(bounds) { const mapboxBounds = [ [bounds.southwest.lng, bounds.southwest.lat], [bounds.northeast.lng, bounds.northeast.lat] ]; this.map.fitBounds(mapboxBounds); } async geocode(address) { const query = encodeURIComponent(address); const response = await fetch( `https://api.mapbox.com/geocoding/v5/mapbox.places/${query}.json?access_token=${this.apiKey}&limit=1` ); if (!response.ok) { const text = await response.text().catch(() => ""); throw new Error(`Geocoding failed: ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`); } const data = await response.json(); if (data.features && data.features.length > 0) { const [lng, lat] = data.features[0].center; return { lat, lng, formattedAddress: data.features[0].place_name }; } else { throw new Error("No results found"); } } async reverseGeocode(lat, lng) { if (!this._validateCoordinates(lat, lng)) { return Promise.reject("Invalid coordinates"); } const response = await fetch( `https://api.mapbox.com/geocoding/v5/mapbox.places/${lng},${lat}.json?access_token=${this.apiKey}&limit=1` ); if (!response.ok) { const text = await response.text().catch(() => ""); throw new Error(`Reverse geocoding failed: ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`); } const data = await response.json(); if (data.features && data.features.length > 0) { return { formattedAddress: data.features[0].place_name, components: data.features[0].context || [] }; } else { throw new Error("No results found"); } } drawRoute(coords, options = {}) { const routeId = this._generateId(); const sourceId = `route-source-${routeId}`; const layerId = `route-layer-${routeId}`; const coordinates = coords.map((coord) => [coord.lng, coord.lat]); this.executeWhenReady(() => { if (!this.map.getSource(sourceId)) { this.map.addSource(sourceId, { type: "geojson", data: { type: "Feature", properties: {}, geometry: { type: "LineString", coordinates } } }); } if (!this.map.getLayer(layerId)) { this.map.addLayer({ id: layerId, type: "line", source: sourceId, layout: { "line-join": "round", "line-cap": "round" }, paint: { "line-color": options.strokeColor || "#FF0000", "line-width": options.strokeWeight || 3, "line-opacity": options.strokeOpacity || 1 } }); } }); this.sources.set(sourceId, sourceId); this.polylines.set(routeId, { sourceId, layerId }); return routeId; } async getDirections(origin, destination, options = {}) { const profile = options.travelMode || "driving"; const coordinates = `${origin.lng},${origin.lat};${destination.lng},${destination.lat}`; const response = await fetch( `https://api.mapbox.com/directions/v5/mapbox/${profile}/${coordinates}?access_token=${this.apiKey}&geometries=geojson` ); if (!response.ok) { const text = await response.text().catch(() => ""); throw new Error(`Directions failed: ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`); } const data = await response.json(); if (data.routes && data.routes.length > 0) { const route = data.routes[0]; const routeId = this.drawRoute(route.geometry.coordinates.map((coord) => ({ lng: coord[0], lat: coord[1] })), options); return { routeId, duration: route.duration, distance: route.distance }; } else { throw new Error("No route found"); } } drawPolygon(coords, options = {}) { const polygonId = this._generateId(); const sourceId = `polygon-source-${polygonId}`; const layerId = `polygon-layer-${polygonId}`; const coordinates = coords.map((coord) => [coord.lng, coord.lat]); this.executeWhenReady(() => { if (!this.map.getSource(sourceId)) { this.map.addSource(sourceId, { type: "geojson", data: { type: "Feature", properties: {}, geometry: { type: "Polygon", coordinates: [coordinates] } } }); } if (!this.map.getLayer(layerId)) { this.map.addLayer({ id: layerId, type: "fill", source: sourceId, paint: { "fill-color": options.fillColor || "#FF0000", "fill-opacity": options.fillOpacity || 0.35 } }); } const outlineId = `${layerId}-outline`; if (!this.map.getLayer(outlineId)) { this.map.addLayer({ id: outlineId, type: "line", source: sourceId, paint: { "line-color": options.strokeColor || "#FF0000", "line-width": options.strokeWeight || 2, "line-opacity": options.strokeOpacity || 0.8 } }); } }); this.sources.set(sourceId, sourceId); this.polygons.set(polygonId, { sourceId, layerId }); return polygonId; } drawPolyline(coords, options = {}) { return this.drawRoute(coords, options); } drawCircle(center, radius, options = {}) { const points = 64; const coords = []; for (let i = 0; i < points; i++) { const angle = i / points * 2 * Math.PI; const lat = center.lat + radius / 111320 * Math.cos(angle); const lng = center.lng + radius / (111320 * Math.cos(center.lat * Math.PI / 180)) * Math.sin(angle); coords.push({ lat, lng }); } return this.drawPolygon(coords, options); } drawRectangle(bounds, options = {}) { const coords = [ { lat: bounds.southwest.lat, lng: bounds.southwest.lng }, { lat: bounds.southwest.lat, lng: bounds.northeast.lng }, { lat: bounds.northeast.lat, lng: bounds.northeast.lng }, { lat: bounds.northeast.lat, lng: bounds.southwest.lng } ]; return this.drawPolygon(coords, options); } enableTrafficLayer() { console.info("Traffic layer not available in Mapbox. Consider using Mapbox Traffic API."); } disableTrafficLayer() { } addHeatMap(points, options = {}) { const heatmapId = this._generateId(); const sourceId = `heatmap-source-${heatmapId}`; const layerId = `heatmap-layer-${heatmapId}`; const features = points.map((point) => ({ type: "Feature", properties: { weight: point.weight || 1 }, geometry: { type: "Point", coordinates: [point.lng, point.lat] } })); this.executeWhenReady(() => { if (!this.map.getSource(sourceId)) { this.map.addSource(sourceId, { type: "geojson", data: { type: "FeatureCollection", features } }); } if (!this.map.getLayer(layerId)) { this.map.addLayer({ id: layerId, type: "heatmap", source: sourceId, paint: { "heatmap-weight": ["get", "weight"], "heatmap-intensity": options.intensity || 1, "heatmap-color": [ "interpolate", ["linear"], ["heatmap-density"], 0, "rgba(0, 0, 255, 0)", 0.5, "rgba(0, 0, 255, 1)", 1, "rgba(255, 0, 0, 1)" ], "heatmap-radius": options.radius || 20, "heatmap-opacity": options.opacity || 0.6 } }); } }); this.sources.set(sourceId, sourceId); this.heatmaps.set(heatmapId, { sourceId, layerId }); return heatmapId; } addTileLayer(url, options = {}) { const layerId = this._generateId(); this.executeWhenReady(() => { if (!this.map.getSource(layerId)) { this.map.addSource(layerId, { type: "raster", tiles: [url], tileSize: options.tileSize || 256 }); } if (!this.map.getLayer(layerId)) { this.map.addLayer({ id: layerId, type: "raster", source: layerId, paint: { "raster-opacity": options.opacity || 1 } }); } }); this.sources.set(layerId, layerId); this.layers.set(layerId, layerId); return layerId; } removeLayer(layerId) { const layer = this.layers.get(layerId); if (layer) { if (this.map.getLayer(layer)) { this.map.removeLayer(layer); } if (this.map.getSource(layer)) { this.map.removeSource(layer); } this.layers.delete(layerId); return true; } return false; } trackUserLocation(callback, options = {}) { if (navigator.geolocation) { const watchId = navigator.geolocation.watchPosition( (position) => { const coords = { lat: position.coords.latitude, lng: position.coords.longitude, accuracy: position.coords.accuracy }; callback(coords); }, (error) => console.error("Geolocation error:", error), { enableHighAccuracy: options.enableHighAccuracy || false, timeout: options.timeout || 5e3, maximumAge: options.maximumAge || 0 } ); return watchId; } else { console.error("Geolocation is not supported by this browser."); return null; } } getUserLocation() { return new Promise((resolve, reject) => { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( (position) => { resolve({ lat: position.coords.latitude, lng: position.coords.longitude, accuracy: position.coords.accuracy }); }, (error) => reject(error), { enableHighAccuracy: true, timeout: 1e4, maximumAge: 6e4 } ); } else { reject(new Error("Geolocation is not supported by this browser.")); } }); } indoorMaps(enable) { console.info("Indoor maps not available in Mapbox."); } applyMapStyle(style) { if (typeof style === "string") { this.map.setStyle(style); } else if (typeof style === "object") { Object.keys(style).forEach((key) => { this.map.setPaintProperty(key, style[key]); }); } } enable3D(enable) { if (enable) { this.map.addLayer({ id: "3d-buildings", source: "composite", "source-layer": "building", filter: ["==", "extrude", "true"], type: "fill-extrusion", minzoom: 15, paint: { "fill-extrusion-color": "#aaa", "fill-extrusion-height": ["get", "height"], "fill-extrusion-base": ["get", "min_height"], "fill-extrusion-opacity": 0.6 } }); } else { if (this.map.getLayer("3d-buildings")) { this.map.removeLayer("3d-buildings"); } } } on(event, callback) { if (!this.eventListeners.has(event)) { this.eventListeners.set(event, []); } this.eventListeners.get(event).push(callback); this.map.on(event, callback); } off(event, callback) { const listeners = this.eventListeners.get(event); if (listeners) { const index = listeners.indexOf(callback); if (index > -1) { listeners.splice(index, 1); this.map.off(event, callback); } } } getBounds() { const bounds = this.map.getBounds(); return { southwest: { lat: bounds.getSouthWest().lat, lng: bounds.getSouthWest().lng }, northeast: { lat: bounds.getNorthEast().lat, lng: bounds.getNorthEast().lng } }; } destroy() { this.eventListeners.forEach((listeners, event) => { listeners.forEach((callback) => { this.map.off(event, callback); }); }); this.eventListeners.clear(); this.sources.forEach((sourceId, key) => { if (this.map.getSource(sourceId)) { this.map.removeSource(sourceId); } }); this.markers.clear(); this.polylines.clear(); this.polygons.clear(); this.heatmaps.clear(); this.layers.clear(); this.sources.clear(); if (this.map) { this.map.remove(); this.map = null; } } }; // adapters/BingMapsAdapter.js var BingMapsAdapter = class extends BaseAdapter { constructor(apiKey, containerId, options = {}) { super(apiKey, containerId, options); this.eventListeners = /* @__PURE__ */ new Map(); this.entities = /* @__PURE__ */ new Map(); } async init() { await this.loadBingMapsScript(); const mapElement = this.getContainer(); if (!mapElement) { throw new Error(`Container element with ID '${this.containerId}' not found.`); } const centerLat = typeof this.options.center?.lat === "number" ? this.options.center.lat : 0; const centerLng = typeof this.options.center?.lng === "number" ? this.options.center.lng : 0; this.map = new Microsoft.Maps.Map(mapElement, { credentials: this.apiKey, center: new Microsoft.Maps.Location(centerLat, centerLng), zoom: this.options.zoom || 10, mapTypeId: this.options.mapTypeId || Microsoft.Maps.MapTypeId.road, showMapTypeSelector: this.options.showMapTypeSelector !== false, showZoomButton: this.options.showZoomButton !== false, showScalebar: this.options.showScalebar !== false, showBreadcrumb: this.options.showBreadcrumb !== false, enableCORS: this.options.enableCORS !== false, enableHighDpi: this.options.enableHighDpi !== false, enableInertia: this.options.enableInertia !== false }); } loadBingMapsScript() { return new Promise((resolve, reject) => { if (window.Microsoft && window.Microsoft.Maps && window.Microsoft.Maps.Location) { return resolve(); } const callbackName = "bingMapsLoadCallback"; window[callbackName] = () => { if (window.Microsoft && window.Microsoft.Maps && window.Microsoft.Maps.Location) { resolve(); } else { reject(new Error("Bing Maps failed to load properly")); } delete window[callbackName]; }; const script = document.createElement("script"); script.src = `https://www.bing.com/api/maps/mapcontrol?key=${this.apiKey}&callback=${callbackName}`; script.async = true; script.onerror = () => { delete window[callbackName]; reject(new Error("Failed to load Bing Maps script")); }; document.head.appendChild(script); }); } addMarker(options) { const markerId = this._generateId(); const location = new Microsoft.Maps.Location(options.lat, options.lng); const pushpinOptions = { title: options.title || "", text: options.label || "", draggable: options.draggable || false }; if (options.icon) { pushpinOptions.icon = options.icon; } if (options.color !== void 0) { pushpinOptions.color = options.color; } else if (Microsoft.Maps.PushpinColor && Microsoft.Maps.PushpinColor.red) { pushpinOptions.color = Microsoft.Maps.PushpinColor.red; } const pushpin = new Microsoft.Maps.Pushpin(location, pushpinOptions); this.map.entities.push(pushpin); this.markers.set(markerId, pushpin); return markerId; } removeMarker(markerId) { const marker = this.markers.get(markerId); if (marker) { this.map.entities.remove(marker); this.markers.delete(markerId); return true; } return false; } updateMarker(markerId, options) { const marker = this.markers.get(markerId); if (marker) { if (options.position) { marker.setLocation(new Microsoft.Maps.Location(options.position.lat, options.position.lng)); } if (options.title !== void 0) marker.setTitle(options.title); if (options.text !== void 0) marker.setText(options.text); if (options.color !== void 0) marker.setColor(options.color); return true; } return false; } setCenter(coords) { if (this._validateCoordinates(coords.lat, coords.lng)) { this.map.setView({ center: new Microsoft.Maps.Location(coords.lat, coords.lng) }); } } getCenter() { const center = this.map.getCenter(); return { lat: center.latitude, lng: center.longitude }; } setZoom(level) { this.map.setView({ zoom: level }); } getZoom() { return this.map.getZoom(); } zoomIn() { this.map.setView({ zoom: this.map.getZoom() + 1 }); } zoomOut() { this.map.setView({ zoom: this.map.getZoom() - 1 }); } panTo(coords) { if (this._validateCoordinates(coords.lat, coords.lng)) { this.map.panTo(new Microsoft.Maps.Location(coords.lat, coords.lng)); } } fitBounds(bounds) { const locations = [ new Microsoft.Maps.Location(bounds.southwest.lat, bounds.southwest.lng), new Microsoft.Maps.Location(bounds.northeast.lat, bounds.northeast.lng) ]; const viewBox = Microsoft.Maps.LocationRect.fromLocations(locations); this.map.setView({ bounds: viewBox }); } async geocode(address) { if (!address || typeof address !== "string") { return Promise.reject(new Error("Address must be a non-empty string")); } return new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error("Geocoding request timeout")); }, 3e4); try { Microsoft.Maps.loadModule("Microsoft.Maps.Search", () => { try { const searchManager = new Microsoft.Maps.Search.SearchManager(this.map); const request = { where: address.trim(), callback: (result) => { clearTimeout(timeout); if (result && result.results && result.results.length > 0) { const best = result.results[0]; resolve({ lat: best.location.latitude, lng: best.location.longitude, formattedAddress: best.address?.formattedAddress || best.name || address }); } else { reject(new Error("No results found")); } }, errorCallback: async (err) => { clearTimeout(timeout); const message = err && err.message ? err.message : err ? JSON.stringify(err) : "Unknown geocoding error"; try { const rest = await this._bingRestGeocode(address); if (rest) { resolve(rest); return; } } catch { } reject(new Error(`Geocoding failed: ${message}`)); } }; searchManager.geocode(request); } catch (err) { clearTimeout(timeout); reject(new Error(`Geocoding setup error: ${err.message}`)); } }); } catch (err) { clearTimeout(timeout); reject(new Error(`Geocoding initialization error: ${err.message}`)); } }); } async reverseGeocode(lat, lng) { if (!this._validateCoordinates(lat, lng)) { return Promise.reject(new Error("Invalid coordinates")); } return new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error("Reverse geocoding request timeout")); }, 3e4); try { Microsoft.Maps.loadModule("Microsoft.Maps.Search", () => { try { const searchManager = new Microsoft.Maps.Search.SearchManager(this.map); const location = new Microsoft.Maps.Location(lat, lng); const request = { location, callback: (result) => { clearTimeout(timeout); if (result && result.address) { resolve({ formattedAddress: result.address.formattedAddress, components: result.address }); } else { reject(new Error("No results found")); } }, errorCallback: async (err) => { clearTimeout(timeout); const message = err && err.message ? err.message : err ? JSON.stringify(err) : "Unknown reverse geocoding error"; try { const rest = await this._bingRestReverseGeocode(lat, lng); if (rest) { resolve(rest); return; } } catch { } reject(new Error(`Reverse geocoding failed: ${message}`)); } }; searchManager.reverseGeocode(request); } catch (err) { clearTimeout(timeout); reject(new Error(`Reverse geocoding setup error: ${err.message}`)); } }); } catch (err) { clearTimeout(timeout); reject(new Error(`Reverse geocoding initialization error: ${err.message}`));