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
JavaScript
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}`));