UNPKG

@fleetbase/fleetops-engine

Version:

Fleet & Transport Management Extension for Fleetbase

552 lines (475 loc) 17.3 kB
/** * MapAdapterInterface * * Abstract base class that defines the standardized contract all map provider * adapters must implement. This enables Fleet-Ops to support multiple map * providers (Leaflet, Google Maps, Mapbox, etc.) without changing any * application-level business logic. * * To add a new map provider: * 1. Create `addon/services/map-adapter/<provider-name>.js` * 2. Extend this class and implement every method * 3. Register the service in `app/services/map-adapter/<provider-name>.js` * 4. Set `mapProvider: '<provider-name>'` in `config/environment.js` * * @module services/map-adapter-interface */ /* eslint-disable no-unused-vars */ import Service from '@ember/service'; export default class MapAdapterInterface extends Service { // ─── Internal State ──────────────────────────────────────────────────────── /** * The raw underlying map instance (e.g. a Leaflet map or google.maps.Map). * Consumers should never access this directly; use the adapter API instead. * @type {*} */ _map = null; /** * Internal marker registry: id → native marker object. * @type {Map<string, *>} */ _markers = new Map(); /** * Internal overlay registry: id → native overlay object (polygon, polyline, circle). * @type {Map<string, *>} */ _overlays = new Map(); /** * Internal routing control registry: id → provider-neutral route handle. * @type {Map<string, Object>} */ _routingControls = new Map(); // ─── Lifecycle ───────────────────────────────────────────────────────────── /** * Initialize the map inside the given DOM element. * * @param {HTMLElement} element - The container element for the map * @param {Object} [options={}] - Provider-specific initialization options * @returns {*} The native map instance */ initializeMap(element, options = {}) { throw new Error(`${this.constructor.name} must implement initializeMap(element, options)`); } /** * Destroy the map and release all associated resources. */ destroyMap() { throw new Error(`${this.constructor.name} must implement destroyMap()`); } /** * Bind an already-created native map instance to the adapter. * Used by host components that own map creation but still route * all behavior through the provider adapter. * * @param {*} mapInstance */ setMapInstance(mapInstance) { this._map = mapInstance; return mapInstance; } showContextMenu(event, items = []) { return { event, items }; } closeContextMenu() {} removeLayer(layer) { return layer; } /** * Called after the map container is resized. Implementations should * trigger the provider's internal resize/invalidate logic. */ invalidateSize() { throw new Error(`${this.constructor.name} must implement invalidateSize()`); } // ─── Viewport ────────────────────────────────────────────────────────────── /** * Set the map center and zoom level immediately (no animation). * * @param {number} lat * @param {number} lng * @param {number} [zoom] */ setCenter(lat, lng, zoom) { throw new Error(`${this.constructor.name} must implement setCenter(lat, lng, zoom)`); } /** * Animate the viewport to a new center and zoom level. * * @param {number} lat * @param {number} lng * @param {number} [zoom] * @param {Object} [options={}] */ flyTo(lat, lng, zoom, options = {}) { throw new Error(`${this.constructor.name} must implement flyTo(lat, lng, zoom, options)`); } /** * Fit the viewport to the given bounds. * * @param {Array} bounds - [[swLat, swLng], [neLat, neLng]] * @param {Object} [options={}] */ fitBounds(bounds, options = {}) { throw new Error(`${this.constructor.name} must implement fitBounds(bounds, options)`); } /** * Smoothly pan the map to a new center without changing zoom. * * @param {number} lat * @param {number} lng * @param {Object} [options={}] */ panTo(lat, lng, options = {}) { throw new Error(`${this.constructor.name} must implement panTo(lat, lng, options)`); } /** * Increase the zoom level by one step. */ zoomIn() { throw new Error(`${this.constructor.name} must implement zoomIn()`); } /** * Decrease the zoom level by one step. */ zoomOut() { throw new Error(`${this.constructor.name} must implement zoomOut()`); } /** * Get the current zoom level. * * @returns {number} */ getZoom() { throw new Error(`${this.constructor.name} must implement getZoom()`); } /** * Get the current map center as { lat, lng }. * * @returns {{ lat: number, lng: number }} */ getCenter() { throw new Error(`${this.constructor.name} must implement getCenter()`); } /** * Get the current viewport bounds as [[swLat, swLng], [neLat, neLng]]. * * @returns {Array} */ getBounds() { throw new Error(`${this.constructor.name} must implement getBounds()`); } // ─── Markers ─────────────────────────────────────────────────────────────── /** * Add a marker to the map and register it by id. * * @param {string} id - Unique identifier for this marker * @param {number} lat * @param {number} lng * @param {Object} [options={}] - May include: iconUrl, iconSize, title, content (HTML element), zIndexOffset * @returns {*} The native marker object */ addMarker(id, lat, lng, options = {}) { throw new Error(`${this.constructor.name} must implement addMarker(id, lat, lng, options)`); } /** * Update a registered marker's position, optionally with smooth animation. * * @param {string} id * @param {number} lat * @param {number} lng * @param {boolean} [animated=true] * @param {number} [duration=500] - Animation duration in ms */ updateMarkerPosition(id, lat, lng, animated = true, duration = 500) { throw new Error(`${this.constructor.name} must implement updateMarkerPosition(id, lat, lng, animated, duration)`); } /** * Rotate a marker to the given heading in degrees. * * @param {string} id * @param {number} degrees */ setMarkerRotation(id, degrees) { throw new Error(`${this.constructor.name} must implement setMarkerRotation(id, degrees)`); } /** * Remove a registered marker from the map. * * @param {string} id */ removeMarker(id) { throw new Error(`${this.constructor.name} must implement removeMarker(id)`); } /** * Retrieve the native marker object by id. * * @param {string} id * @returns {*|null} */ getMarker(id) { return this._markers.get(id) ?? null; } /** * Check whether a marker with the given id exists. * * @param {string} id * @returns {boolean} */ hasMarker(id) { return this._markers.has(id); } // ─── Overlays ────────────────────────────────────────────────────────────── /** * Add a polygon overlay. * * @param {string} id * @param {Array} coordinates - Array of [lat, lng] pairs * @param {Object} [options={}] * @returns {*} The native polygon object */ addPolygon(id, coordinates, options = {}) { throw new Error(`${this.constructor.name} must implement addPolygon(id, coordinates, options)`); } /** * Add a polyline overlay. * * @param {string} id * @param {Array} coordinates - Array of [lat, lng] pairs * @param {Object} [options={}] * @returns {*} The native polyline object */ addPolyline(id, coordinates, options = {}) { throw new Error(`${this.constructor.name} must implement addPolyline(id, coordinates, options)`); } /** * Add a circle overlay. * * @param {string} id * @param {number} lat * @param {number} lng * @param {number} radiusMeters * @param {Object} [options={}] * @returns {*} The native circle object */ addCircle(id, lat, lng, radiusMeters, options = {}) { throw new Error(`${this.constructor.name} must implement addCircle(id, lat, lng, radiusMeters, options)`); } /** * Remove a registered overlay from the map. * * @param {string} id */ removeOverlay(id) { throw new Error(`${this.constructor.name} must implement removeOverlay(id)`); } /** * Remove all registered overlays from the map. */ clearOverlays() { for (const id of this._overlays.keys()) { this.removeOverlay(id); } } /** * Retrieve the native overlay object by id. * * @param {string} id * @returns {*|null} */ getOverlay(id) { return this._overlays.get(id) ?? null; } // ─── Routing Controls ───────────────────────────────────────────────────── addRoutingControl(route, options = {}) { throw new Error(`${this.constructor.name} must implement addRoutingControl(route, options)`); } replaceRoutingControl(route, existingHandle, options = {}) { if (existingHandle) { this.removeRoutingControl(existingHandle, options.removeOptions ?? {}); } return this.addRoutingControl(route, options); } removeRoutingControl(handle, options = {}) { throw new Error(`${this.constructor.name} must implement removeRoutingControl(handle, options)`); } clearRoutingControls(filter) { for (const handle of this._routingControls.values()) { if (typeof filter === 'function' && filter(handle) === false) { continue; } this.removeRoutingControl(handle); } } positionWaypoints(waypointsOrBounds, options = {}) { return { waypointsOrBounds, options }; } // ─── Drawing Tools ───────────────────────────────────────────────────────── /** * Enable the drawing mode for the given shape type. * * @param {'polygon'|'circle'|'rectangle'|'polyline'|'marker'|null} type */ enableDrawingMode(type, options = {}) { throw new Error(`${this.constructor.name} must implement enableDrawingMode(type)`); } /** * Disable drawing mode and return to normal interaction. */ disableDrawingMode() { throw new Error(`${this.constructor.name} must implement disableDrawingMode()`); } /** * Show the drawing toolbar UI control. */ showDrawControl(config = {}) { throw new Error(`${this.constructor.name} must implement showDrawControl()`); } /** * Hide the drawing toolbar UI control. */ hideDrawControl() { throw new Error(`${this.constructor.name} must implement hideDrawControl()`); } // ─── Popups / Info Windows ───────────────────────────────────────────────── /** * Open a popup at the given coordinates with HTML content. * * @param {string} id - Unique identifier for this popup * @param {number} lat * @param {number} lng * @param {string|HTMLElement} htmlContent * @returns {*} The native popup/info-window object */ openPopup(id, lat, lng, htmlContent) { throw new Error(`${this.constructor.name} must implement openPopup(id, lat, lng, htmlContent)`); } /** * Close and remove a popup by id. * * @param {string} id */ closePopup(id) { throw new Error(`${this.constructor.name} must implement closePopup(id)`); } // ─── Context Menus ───────────────────────────────────────────────────────── /** * Register a right-click context menu on the map or a specific marker. * * @param {'map'|string} target - 'map' or a marker id * @param {Array<{label: string, action: Function, separator?: boolean}>} items */ registerContextMenu(target, items) { throw new Error(`${this.constructor.name} must implement registerContextMenu(target, items)`); } /** * Remove a previously registered context menu. * * @param {'map'|string} target */ removeContextMenu(target) { throw new Error(`${this.constructor.name} must implement removeContextMenu(target)`); } // ─── Events ──────────────────────────────────────────────────────────────── /** * Register an event listener on the map. * Normalized event names: 'click', 'dblclick', 'rightclick', 'moveend', * 'zoomend', 'load', 'draw:created', 'draw:edited', 'draw:deleted' * * @param {string} event * @param {Function} handler */ on(event, handler) { throw new Error(`${this.constructor.name} must implement on(event, handler)`); } /** * Remove an event listener from the map. * * @param {string} event * @param {Function} handler */ off(event, handler) { throw new Error(`${this.constructor.name} must implement off(event, handler)`); } /** * Register a one-time event listener on the map. * * @param {string} event * @param {Function} handler */ once(event, handler) { throw new Error(`${this.constructor.name} must implement once(event, handler)`); } // ─── Utilities ───────────────────────────────────────────────────────────── /** * Calculate the distance in metres between two coordinates. * * @param {number} lat1 * @param {number} lng1 * @param {number} lat2 * @param {number} lng2 * @returns {number} Distance in metres */ distanceBetween(lat1, lng1, lat2, lng2) { throw new Error(`${this.constructor.name} must implement distanceBetween(lat1, lng1, lat2, lng2)`); } /** * Render a GeoJSON object onto the map. * * @param {string} id - Unique identifier for this GeoJSON layer * @param {Object} geojson - A valid GeoJSON FeatureCollection or Feature * @param {Object} [options={}] * @returns {*} The native GeoJSON layer object */ addGeoJson(id, geojson, options = {}) { throw new Error(`${this.constructor.name} must implement addGeoJson(id, geojson, options)`); } /** * Remove a GeoJSON layer by id. * * @param {string} id */ removeGeoJson(id) { throw new Error(`${this.constructor.name} must implement removeGeoJson(id)`); } /** * Set the map's tile/base layer URL (for providers that support custom tiles). * * @param {string} url * @param {Object} [options={}] */ setTileLayer(url, options = {}) { // Optional — providers that don't support custom tiles can no-op this. } registerMarker(id, markerObject) { this._markers.set(id, markerObject); return markerObject; } registerPolygon(id, polygonObject) { this._overlays.set(id, polygonObject); return polygonObject; } showPolygon(id) { const polygon = this.getOverlay(id); if (polygon) { this.showLayer?.(polygon); } return polygon ?? null; } hidePolygon(id) { const polygon = this.getOverlay(id); if (polygon) { this.hideLayer?.(polygon); } return polygon ?? null; } getContextMenuItems() { return []; } showCoordinates() {} centerMap() {} toggleDrawControl() {} panBy() {} editPolygon() { return Promise.resolve({ type: 'unsupported' }); } }