@fleetbase/fleetops-engine 0.6.44 → 0.6.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/addon/components/admin/map-settings.hbs +26 -0
  2. package/addon/components/admin/map-settings.js +46 -0
  3. package/addon/components/admin/routing-settings.hbs +11 -0
  4. package/addon/components/cell/order-route-type.hbs +46 -0
  5. package/addon/components/cell/order-route-type.js +90 -0
  6. package/addon/components/cell/place-address.hbs +1 -0
  7. package/addon/components/display-place.hbs +45 -56
  8. package/addon/components/display-place.js +21 -0
  9. package/addon/components/entity/form.hbs +24 -0
  10. package/addon/components/entity/form.js +30 -0
  11. package/addon/components/layout/fleet-ops-sidebar/driver-listing.js +5 -5
  12. package/addon/components/layout/fleet-ops-sidebar/fleet-listing.js +5 -5
  13. package/addon/components/layout/fleet-ops-sidebar.js +8 -0
  14. package/addon/components/leaflet-draw-control.js +1 -1
  15. package/addon/components/maintenance-schedule/form.js +2 -1
  16. package/addon/components/map/drawer/driver-listing.js +5 -5
  17. package/addon/components/map/drawer/geofence-event-listing.js +5 -2
  18. package/addon/components/map/drawer/place-listing.js +5 -5
  19. package/addon/components/map/drawer/position-listing.js +102 -21
  20. package/addon/components/map/drawer/vehicle-listing.js +5 -5
  21. package/addon/components/map/google-live-map.hbs +9 -0
  22. package/addon/components/map/google-live-map.js +445 -0
  23. package/addon/components/map/leaflet-live-map.hbs +186 -142
  24. package/addon/components/map/leaflet-live-map.js +121 -41
  25. package/addon/components/map/toolbar/visibility-control-panel.js +2 -2
  26. package/addon/components/map/toolbar/zones-panel.hbs +9 -9
  27. package/addon/components/map/toolbar.hbs +3 -3
  28. package/addon/components/map/toolbar.js +9 -1
  29. package/addon/components/modals/map-layer-form.hbs +132 -63
  30. package/addon/components/modals/orchestrator-import.hbs +4 -11
  31. package/addon/components/modals/orchestrator-import.js +17 -1
  32. package/addon/components/modals/update-order-activity.hbs +1 -1
  33. package/addon/components/orchestrator/order-pool.js +3 -2
  34. package/addon/components/orchestrator-workbench.js +29 -41
  35. package/addon/components/order/details/detail.js +2 -2
  36. package/addon/components/order/details/payload.hbs +5 -5
  37. package/addon/components/order/details/payload.js +18 -1
  38. package/addon/components/order/details/route.hbs +2 -2
  39. package/addon/components/order/details/route.js +76 -3
  40. package/addon/components/order/form/payload.hbs +33 -0
  41. package/addon/components/order/form/payload.js +1 -0
  42. package/addon/components/order/form/route.hbs +8 -8
  43. package/addon/components/order/form/route.js +78 -18
  44. package/addon/components/order/form/service-rate.hbs +1 -1
  45. package/addon/components/order/form.js +2 -2
  46. package/addon/components/order/route-editor.hbs +209 -147
  47. package/addon/components/order/route-editor.js +143 -102
  48. package/addon/components/place/address.hbs +1 -0
  49. package/addon/components/place/address.js +12 -0
  50. package/addon/components/route-list.hbs +43 -55
  51. package/addon/components/route-list.js +47 -0
  52. package/addon/components/route-optimization-wizard-panel.js +3 -4
  53. package/addon/components/service-rate/details.hbs +6 -6
  54. package/addon/components/service-rate/form.hbs +28 -15
  55. package/addon/components/vehicle/card.hbs +4 -4
  56. package/addon/controllers/maintenance/schedules/index.js +2 -1
  57. package/addon/controllers/operations/orders/index/details/virtual.js +5 -0
  58. package/addon/controllers/operations/orders/index/details.js +113 -15
  59. package/addon/controllers/operations/orders/index/new.js +1 -1
  60. package/addon/controllers/operations/orders/index.js +8 -1
  61. package/addon/controllers/operations/service-rates/index/edit.js +1 -0
  62. package/addon/controllers/operations/service-rates/index/new.js +1 -0
  63. package/addon/controllers/settings/map.js +97 -0
  64. package/addon/controllers/settings/routing.js +47 -12
  65. package/addon/extension.js +11 -0
  66. package/addon/helpers/f-to-int.js +6 -0
  67. package/addon/helpers/place-address-html.js +6 -0
  68. package/addon/instance-initializers/register-google-routes.js +17 -0
  69. package/addon/instance-initializers/register-osrm.js +5 -0
  70. package/addon/routes/application.js +9 -3
  71. package/addon/routes/operations/orchestrator.js +13 -0
  72. package/addon/routes/operations/orders/index/details/virtual.js +1 -0
  73. package/addon/routes/operations/orders/index/details.js +47 -24
  74. package/addon/routes/operations/orders/index/new.js +9 -0
  75. package/addon/routes/operations/orders/index.js +0 -1
  76. package/addon/routes/settings/map.js +3 -0
  77. package/addon/routes.js +1 -0
  78. package/addon/services/entity-actions.js +1 -1
  79. package/addon/services/equipment-actions.js +4 -6
  80. package/addon/services/geofence-event-bus.js +2 -1
  81. package/addon/services/geofence.js +204 -168
  82. package/addon/services/google-routes.js +300 -0
  83. package/addon/services/leaflet-draw-restriction.js +2 -1
  84. package/addon/services/leaflet-map-manager.js +21 -0
  85. package/addon/services/maintenance-actions.js +4 -6
  86. package/addon/services/map-adapter/google.js +2205 -0
  87. package/addon/services/map-adapter/leaflet.js +1042 -0
  88. package/addon/services/map-adapter-interface.js +551 -0
  89. package/addon/services/map-manager.js +768 -0
  90. package/addon/services/map-settings.js +75 -0
  91. package/addon/services/movement-tracker.js +83 -32
  92. package/addon/services/order-actions.js +11 -1
  93. package/addon/services/osrm.js +79 -6
  94. package/addon/services/part-actions.js +4 -6
  95. package/addon/services/position-playback.js +135 -62
  96. package/addon/services/route-engine.js +83 -0
  97. package/addon/services/service-areas.js +8 -1
  98. package/addon/services/service-rate-actions.js +8 -3
  99. package/addon/styles/fleetops-engine.css +555 -0
  100. package/addon/templates/management/vehicles/index.hbs +0 -1
  101. package/addon/templates/operations/orders/index/details/virtual.hbs +7 -1
  102. package/addon/templates/operations/orders/index/details.hbs +0 -1
  103. package/addon/templates/settings/map.hbs +53 -0
  104. package/addon/templates/settings/routing.hbs +15 -4
  105. package/addon/utils/fleet-ops-options.js +34 -0
  106. package/addon/utils/has-valid-coordinates.js +6 -0
  107. package/addon/utils/map-provider-options.js +6 -0
  108. package/addon/utils/order-route-editing.js +86 -0
  109. package/addon/utils/order-route-summary.js +20 -0
  110. package/addon/utils/place-address-html.js +41 -0
  111. package/addon/utils/route-colors.js +43 -12
  112. package/addon/utils/route-visualization.js +93 -0
  113. package/addon/utils/serialize-payload.js +1 -1
  114. package/addon/utils/vendor-integration.js +2 -1
  115. package/app/components/admin/map-settings.js +1 -0
  116. package/app/components/admin/routing-settings.js +1 -0
  117. package/app/components/cell/order-route-type.js +1 -0
  118. package/app/components/cell/place-address.js +1 -0
  119. package/app/components/map/google-live-map.js +1 -0
  120. package/app/components/place/address.js +1 -0
  121. package/app/controllers/operations/orders/index/details/virtual.js +1 -0
  122. package/app/controllers/settings/map.js +1 -0
  123. package/app/helpers/f-to-int.js +1 -0
  124. package/app/helpers/place-address-html.js +1 -0
  125. package/app/routes/settings/map.js +1 -0
  126. package/app/services/map-adapter/google.js +1 -0
  127. package/app/services/map-adapter/leaflet.js +1 -0
  128. package/app/services/map-adapter-interface.js +1 -0
  129. package/app/services/map-manager.js +1 -0
  130. package/app/services/map-settings.js +1 -0
  131. package/app/templates/settings/map.js +1 -0
  132. package/app/utils/has-valid-coordinates.js +1 -0
  133. package/app/utils/order-route-editing.js +2 -0
  134. package/app/utils/order-route-summary.js +1 -0
  135. package/app/utils/place-address-html.js +1 -0
  136. package/app/utils/route-colors.js +1 -1
  137. package/app/utils/route-visualization.js +1 -0
  138. package/assets/images/drawing-controls-spritesheet.svg +156 -0
  139. package/composer.json +1 -1
  140. package/extension.json +1 -1
  141. package/package.json +3 -3
  142. package/server/src/Auth/Schemas/FleetOps.php +5 -0
  143. package/server/src/Http/Controllers/Api/v1/OrderController.php +36 -8
  144. package/server/src/Http/Controllers/Api/v1/PayloadController.php +14 -1
  145. package/server/src/Http/Controllers/Api/v1/PurchaseRateController.php +11 -3
  146. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -0
  147. package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +7 -2
  148. package/server/src/Http/Controllers/Api/v1/ServiceRateController.php +6 -1
  149. package/server/src/Http/Controllers/Api/v1/ZoneController.php +2 -0
  150. package/server/src/Http/Controllers/Internal/v1/LiveController.php +2 -1
  151. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +7 -4
  152. package/server/src/Http/Controllers/Internal/v1/OrderController.php +61 -34
  153. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +3 -2
  154. package/server/src/Http/Controllers/Internal/v1/SettingController.php +106 -3
  155. package/server/src/Http/Filter/OrderFilter.php +2 -1
  156. package/server/src/Http/Requests/CreateServiceRateRequest.php +2 -2
  157. package/server/src/Http/Resources/v1/Index/Payload.php +9 -7
  158. package/server/src/Http/Resources/v1/Order.php +20 -2
  159. package/server/src/Http/Resources/v1/ServiceQuote.php +2 -2
  160. package/server/src/Models/Entity.php +7 -0
  161. package/server/src/Models/Order.php +81 -8
  162. package/server/src/Models/OrderConfig.php +15 -0
  163. package/server/src/Models/Payload.php +131 -27
  164. package/server/src/Models/Place.php +182 -48
  165. package/server/src/Models/ServiceRate.php +125 -32
  166. package/server/src/Observers/PurchaseRateObserver.php +3 -5
  167. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +59 -23
  168. package/server/src/Rules/ComputableAlgo.php +1 -3
  169. package/server/src/Support/Algo.php +87 -1
  170. package/server/src/Support/Utils.php +69 -0
  171. package/server/src/routes.php +4 -0
  172. package/translations/en-us.yaml +1937 -1916
@@ -0,0 +1,551 @@
1
+ /**
2
+ * MapAdapterInterface
3
+ *
4
+ * Abstract base class that defines the standardized contract all map provider
5
+ * adapters must implement. This enables FleetOps to support multiple map
6
+ * providers (Leaflet, Google Maps, Mapbox, etc.) without changing any
7
+ * application-level business logic.
8
+ *
9
+ * To add a new map provider:
10
+ * 1. Create `addon/services/map-adapter/<provider-name>.js`
11
+ * 2. Extend this class and implement every method
12
+ * 3. Register the service in `app/services/map-adapter/<provider-name>.js`
13
+ * 4. Set `mapProvider: '<provider-name>'` in `config/environment.js`
14
+ *
15
+ * @module services/map-adapter-interface
16
+ */
17
+ /* eslint-disable no-unused-vars */
18
+ import Service from '@ember/service';
19
+
20
+ export default class MapAdapterInterface extends Service {
21
+ // ─── Internal State ────────────────────────────────────────────────────────
22
+
23
+ /**
24
+ * The raw underlying map instance (e.g. a Leaflet map or google.maps.Map).
25
+ * Consumers should never access this directly; use the adapter API instead.
26
+ * @type {*}
27
+ */
28
+ _map = null;
29
+
30
+ /**
31
+ * Internal marker registry: id → native marker object.
32
+ * @type {Map<string, *>}
33
+ */
34
+ _markers = new Map();
35
+
36
+ /**
37
+ * Internal overlay registry: id → native overlay object (polygon, polyline, circle).
38
+ * @type {Map<string, *>}
39
+ */
40
+ _overlays = new Map();
41
+
42
+ /**
43
+ * Internal routing control registry: id → provider-neutral route handle.
44
+ * @type {Map<string, Object>}
45
+ */
46
+ _routingControls = new Map();
47
+
48
+ // ─── Lifecycle ─────────────────────────────────────────────────────────────
49
+
50
+ /**
51
+ * Initialize the map inside the given DOM element.
52
+ *
53
+ * @param {HTMLElement} element - The container element for the map
54
+ * @param {Object} [options={}] - Provider-specific initialization options
55
+ * @returns {*} The native map instance
56
+ */
57
+ initializeMap(element, options = {}) {
58
+ throw new Error(`${this.constructor.name} must implement initializeMap(element, options)`);
59
+ }
60
+
61
+ /**
62
+ * Destroy the map and release all associated resources.
63
+ */
64
+ destroyMap() {
65
+ throw new Error(`${this.constructor.name} must implement destroyMap()`);
66
+ }
67
+
68
+ /**
69
+ * Bind an already-created native map instance to the adapter.
70
+ * Used by host components that own map creation but still route
71
+ * all behavior through the provider adapter.
72
+ *
73
+ * @param {*} mapInstance
74
+ */
75
+ setMapInstance(mapInstance) {
76
+ this._map = mapInstance;
77
+ return mapInstance;
78
+ }
79
+
80
+ showContextMenu(event, items = []) {
81
+ return { event, items };
82
+ }
83
+
84
+ closeContextMenu() {}
85
+
86
+ removeLayer(layer) {
87
+ return layer;
88
+ }
89
+
90
+ /**
91
+ * Called after the map container is resized. Implementations should
92
+ * trigger the provider's internal resize/invalidate logic.
93
+ */
94
+ invalidateSize() {
95
+ throw new Error(`${this.constructor.name} must implement invalidateSize()`);
96
+ }
97
+
98
+ // ─── Viewport ──────────────────────────────────────────────────────────────
99
+
100
+ /**
101
+ * Set the map center and zoom level immediately (no animation).
102
+ *
103
+ * @param {number} lat
104
+ * @param {number} lng
105
+ * @param {number} [zoom]
106
+ */
107
+ setCenter(lat, lng, zoom) {
108
+ throw new Error(`${this.constructor.name} must implement setCenter(lat, lng, zoom)`);
109
+ }
110
+
111
+ /**
112
+ * Animate the viewport to a new center and zoom level.
113
+ *
114
+ * @param {number} lat
115
+ * @param {number} lng
116
+ * @param {number} [zoom]
117
+ * @param {Object} [options={}]
118
+ */
119
+ flyTo(lat, lng, zoom, options = {}) {
120
+ throw new Error(`${this.constructor.name} must implement flyTo(lat, lng, zoom, options)`);
121
+ }
122
+
123
+ /**
124
+ * Fit the viewport to the given bounds.
125
+ *
126
+ * @param {Array} bounds - [[swLat, swLng], [neLat, neLng]]
127
+ * @param {Object} [options={}]
128
+ */
129
+ fitBounds(bounds, options = {}) {
130
+ throw new Error(`${this.constructor.name} must implement fitBounds(bounds, options)`);
131
+ }
132
+
133
+ /**
134
+ * Smoothly pan the map to a new center without changing zoom.
135
+ *
136
+ * @param {number} lat
137
+ * @param {number} lng
138
+ * @param {Object} [options={}]
139
+ */
140
+ panTo(lat, lng, options = {}) {
141
+ throw new Error(`${this.constructor.name} must implement panTo(lat, lng, options)`);
142
+ }
143
+
144
+ /**
145
+ * Increase the zoom level by one step.
146
+ */
147
+ zoomIn() {
148
+ throw new Error(`${this.constructor.name} must implement zoomIn()`);
149
+ }
150
+
151
+ /**
152
+ * Decrease the zoom level by one step.
153
+ */
154
+ zoomOut() {
155
+ throw new Error(`${this.constructor.name} must implement zoomOut()`);
156
+ }
157
+
158
+ /**
159
+ * Get the current zoom level.
160
+ *
161
+ * @returns {number}
162
+ */
163
+ getZoom() {
164
+ throw new Error(`${this.constructor.name} must implement getZoom()`);
165
+ }
166
+
167
+ /**
168
+ * Get the current map center as { lat, lng }.
169
+ *
170
+ * @returns {{ lat: number, lng: number }}
171
+ */
172
+ getCenter() {
173
+ throw new Error(`${this.constructor.name} must implement getCenter()`);
174
+ }
175
+
176
+ /**
177
+ * Get the current viewport bounds as [[swLat, swLng], [neLat, neLng]].
178
+ *
179
+ * @returns {Array}
180
+ */
181
+ getBounds() {
182
+ throw new Error(`${this.constructor.name} must implement getBounds()`);
183
+ }
184
+
185
+ // ─── Markers ───────────────────────────────────────────────────────────────
186
+
187
+ /**
188
+ * Add a marker to the map and register it by id.
189
+ *
190
+ * @param {string} id - Unique identifier for this marker
191
+ * @param {number} lat
192
+ * @param {number} lng
193
+ * @param {Object} [options={}] - May include: iconUrl, iconSize, title, content (HTML element), zIndexOffset
194
+ * @returns {*} The native marker object
195
+ */
196
+ addMarker(id, lat, lng, options = {}) {
197
+ throw new Error(`${this.constructor.name} must implement addMarker(id, lat, lng, options)`);
198
+ }
199
+
200
+ /**
201
+ * Update a registered marker's position, optionally with smooth animation.
202
+ *
203
+ * @param {string} id
204
+ * @param {number} lat
205
+ * @param {number} lng
206
+ * @param {boolean} [animated=true]
207
+ * @param {number} [duration=500] - Animation duration in ms
208
+ */
209
+ updateMarkerPosition(id, lat, lng, animated = true, duration = 500) {
210
+ throw new Error(`${this.constructor.name} must implement updateMarkerPosition(id, lat, lng, animated, duration)`);
211
+ }
212
+
213
+ /**
214
+ * Rotate a marker to the given heading in degrees.
215
+ *
216
+ * @param {string} id
217
+ * @param {number} degrees
218
+ */
219
+ setMarkerRotation(id, degrees) {
220
+ throw new Error(`${this.constructor.name} must implement setMarkerRotation(id, degrees)`);
221
+ }
222
+
223
+ /**
224
+ * Remove a registered marker from the map.
225
+ *
226
+ * @param {string} id
227
+ */
228
+ removeMarker(id) {
229
+ throw new Error(`${this.constructor.name} must implement removeMarker(id)`);
230
+ }
231
+
232
+ /**
233
+ * Retrieve the native marker object by id.
234
+ *
235
+ * @param {string} id
236
+ * @returns {*|null}
237
+ */
238
+ getMarker(id) {
239
+ return this._markers.get(id) ?? null;
240
+ }
241
+
242
+ /**
243
+ * Check whether a marker with the given id exists.
244
+ *
245
+ * @param {string} id
246
+ * @returns {boolean}
247
+ */
248
+ hasMarker(id) {
249
+ return this._markers.has(id);
250
+ }
251
+
252
+ // ─── Overlays ──────────────────────────────────────────────────────────────
253
+
254
+ /**
255
+ * Add a polygon overlay.
256
+ *
257
+ * @param {string} id
258
+ * @param {Array} coordinates - Array of [lat, lng] pairs
259
+ * @param {Object} [options={}]
260
+ * @returns {*} The native polygon object
261
+ */
262
+ addPolygon(id, coordinates, options = {}) {
263
+ throw new Error(`${this.constructor.name} must implement addPolygon(id, coordinates, options)`);
264
+ }
265
+
266
+ /**
267
+ * Add a polyline overlay.
268
+ *
269
+ * @param {string} id
270
+ * @param {Array} coordinates - Array of [lat, lng] pairs
271
+ * @param {Object} [options={}]
272
+ * @returns {*} The native polyline object
273
+ */
274
+ addPolyline(id, coordinates, options = {}) {
275
+ throw new Error(`${this.constructor.name} must implement addPolyline(id, coordinates, options)`);
276
+ }
277
+
278
+ /**
279
+ * Add a circle overlay.
280
+ *
281
+ * @param {string} id
282
+ * @param {number} lat
283
+ * @param {number} lng
284
+ * @param {number} radiusMeters
285
+ * @param {Object} [options={}]
286
+ * @returns {*} The native circle object
287
+ */
288
+ addCircle(id, lat, lng, radiusMeters, options = {}) {
289
+ throw new Error(`${this.constructor.name} must implement addCircle(id, lat, lng, radiusMeters, options)`);
290
+ }
291
+
292
+ /**
293
+ * Remove a registered overlay from the map.
294
+ *
295
+ * @param {string} id
296
+ */
297
+ removeOverlay(id) {
298
+ throw new Error(`${this.constructor.name} must implement removeOverlay(id)`);
299
+ }
300
+
301
+ /**
302
+ * Remove all registered overlays from the map.
303
+ */
304
+ clearOverlays() {
305
+ for (const id of this._overlays.keys()) {
306
+ this.removeOverlay(id);
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Retrieve the native overlay object by id.
312
+ *
313
+ * @param {string} id
314
+ * @returns {*|null}
315
+ */
316
+ getOverlay(id) {
317
+ return this._overlays.get(id) ?? null;
318
+ }
319
+
320
+ // ─── Routing Controls ─────────────────────────────────────────────────────
321
+
322
+ addRoutingControl(route, options = {}) {
323
+ throw new Error(`${this.constructor.name} must implement addRoutingControl(route, options)`);
324
+ }
325
+
326
+ replaceRoutingControl(route, existingHandle, options = {}) {
327
+ if (existingHandle) {
328
+ this.removeRoutingControl(existingHandle, options.removeOptions ?? {});
329
+ }
330
+
331
+ return this.addRoutingControl(route, options);
332
+ }
333
+
334
+ removeRoutingControl(handle, options = {}) {
335
+ throw new Error(`${this.constructor.name} must implement removeRoutingControl(handle, options)`);
336
+ }
337
+
338
+ clearRoutingControls(filter) {
339
+ for (const handle of this._routingControls.values()) {
340
+ if (typeof filter === 'function' && filter(handle) === false) {
341
+ continue;
342
+ }
343
+ this.removeRoutingControl(handle);
344
+ }
345
+ }
346
+
347
+ positionWaypoints(waypointsOrBounds, options = {}) {
348
+ return { waypointsOrBounds, options };
349
+ }
350
+
351
+ // ─── Drawing Tools ─────────────────────────────────────────────────────────
352
+
353
+ /**
354
+ * Enable the drawing mode for the given shape type.
355
+ *
356
+ * @param {'polygon'|'circle'|'rectangle'|'polyline'|'marker'|null} type
357
+ */
358
+ enableDrawingMode(type, options = {}) {
359
+ throw new Error(`${this.constructor.name} must implement enableDrawingMode(type)`);
360
+ }
361
+
362
+ /**
363
+ * Disable drawing mode and return to normal interaction.
364
+ */
365
+ disableDrawingMode() {
366
+ throw new Error(`${this.constructor.name} must implement disableDrawingMode()`);
367
+ }
368
+
369
+ /**
370
+ * Show the drawing toolbar UI control.
371
+ */
372
+ showDrawControl(config = {}) {
373
+ throw new Error(`${this.constructor.name} must implement showDrawControl()`);
374
+ }
375
+
376
+ /**
377
+ * Hide the drawing toolbar UI control.
378
+ */
379
+ hideDrawControl() {
380
+ throw new Error(`${this.constructor.name} must implement hideDrawControl()`);
381
+ }
382
+
383
+ // ─── Popups / Info Windows ─────────────────────────────────────────────────
384
+
385
+ /**
386
+ * Open a popup at the given coordinates with HTML content.
387
+ *
388
+ * @param {string} id - Unique identifier for this popup
389
+ * @param {number} lat
390
+ * @param {number} lng
391
+ * @param {string|HTMLElement} htmlContent
392
+ * @returns {*} The native popup/info-window object
393
+ */
394
+ openPopup(id, lat, lng, htmlContent) {
395
+ throw new Error(`${this.constructor.name} must implement openPopup(id, lat, lng, htmlContent)`);
396
+ }
397
+
398
+ /**
399
+ * Close and remove a popup by id.
400
+ *
401
+ * @param {string} id
402
+ */
403
+ closePopup(id) {
404
+ throw new Error(`${this.constructor.name} must implement closePopup(id)`);
405
+ }
406
+
407
+ // ─── Context Menus ─────────────────────────────────────────────────────────
408
+
409
+ /**
410
+ * Register a right-click context menu on the map or a specific marker.
411
+ *
412
+ * @param {'map'|string} target - 'map' or a marker id
413
+ * @param {Array<{label: string, action: Function, separator?: boolean}>} items
414
+ */
415
+ registerContextMenu(target, items) {
416
+ throw new Error(`${this.constructor.name} must implement registerContextMenu(target, items)`);
417
+ }
418
+
419
+ /**
420
+ * Remove a previously registered context menu.
421
+ *
422
+ * @param {'map'|string} target
423
+ */
424
+ removeContextMenu(target) {
425
+ throw new Error(`${this.constructor.name} must implement removeContextMenu(target)`);
426
+ }
427
+
428
+ // ─── Events ────────────────────────────────────────────────────────────────
429
+
430
+ /**
431
+ * Register an event listener on the map.
432
+ * Normalized event names: 'click', 'dblclick', 'rightclick', 'moveend',
433
+ * 'zoomend', 'load', 'draw:created', 'draw:edited', 'draw:deleted'
434
+ *
435
+ * @param {string} event
436
+ * @param {Function} handler
437
+ */
438
+ on(event, handler) {
439
+ throw new Error(`${this.constructor.name} must implement on(event, handler)`);
440
+ }
441
+
442
+ /**
443
+ * Remove an event listener from the map.
444
+ *
445
+ * @param {string} event
446
+ * @param {Function} handler
447
+ */
448
+ off(event, handler) {
449
+ throw new Error(`${this.constructor.name} must implement off(event, handler)`);
450
+ }
451
+
452
+ /**
453
+ * Register a one-time event listener on the map.
454
+ *
455
+ * @param {string} event
456
+ * @param {Function} handler
457
+ */
458
+ once(event, handler) {
459
+ throw new Error(`${this.constructor.name} must implement once(event, handler)`);
460
+ }
461
+
462
+ // ─── Utilities ─────────────────────────────────────────────────────────────
463
+
464
+ /**
465
+ * Calculate the distance in metres between two coordinates.
466
+ *
467
+ * @param {number} lat1
468
+ * @param {number} lng1
469
+ * @param {number} lat2
470
+ * @param {number} lng2
471
+ * @returns {number} Distance in metres
472
+ */
473
+ distanceBetween(lat1, lng1, lat2, lng2) {
474
+ throw new Error(`${this.constructor.name} must implement distanceBetween(lat1, lng1, lat2, lng2)`);
475
+ }
476
+
477
+ /**
478
+ * Render a GeoJSON object onto the map.
479
+ *
480
+ * @param {string} id - Unique identifier for this GeoJSON layer
481
+ * @param {Object} geojson - A valid GeoJSON FeatureCollection or Feature
482
+ * @param {Object} [options={}]
483
+ * @returns {*} The native GeoJSON layer object
484
+ */
485
+ addGeoJson(id, geojson, options = {}) {
486
+ throw new Error(`${this.constructor.name} must implement addGeoJson(id, geojson, options)`);
487
+ }
488
+
489
+ /**
490
+ * Remove a GeoJSON layer by id.
491
+ *
492
+ * @param {string} id
493
+ */
494
+ removeGeoJson(id) {
495
+ throw new Error(`${this.constructor.name} must implement removeGeoJson(id)`);
496
+ }
497
+
498
+ /**
499
+ * Set the map's tile/base layer URL (for providers that support custom tiles).
500
+ *
501
+ * @param {string} url
502
+ * @param {Object} [options={}]
503
+ */
504
+ setTileLayer(url, options = {}) {
505
+ // Optional — providers that don't support custom tiles can no-op this.
506
+ }
507
+
508
+ registerMarker(id, markerObject) {
509
+ this._markers.set(id, markerObject);
510
+ return markerObject;
511
+ }
512
+
513
+ registerPolygon(id, polygonObject) {
514
+ this._overlays.set(id, polygonObject);
515
+ return polygonObject;
516
+ }
517
+
518
+ showPolygon(id) {
519
+ const polygon = this.getOverlay(id);
520
+ if (polygon) {
521
+ this.showLayer?.(polygon);
522
+ }
523
+
524
+ return polygon ?? null;
525
+ }
526
+
527
+ hidePolygon(id) {
528
+ const polygon = this.getOverlay(id);
529
+ if (polygon) {
530
+ this.hideLayer?.(polygon);
531
+ }
532
+
533
+ return polygon ?? null;
534
+ }
535
+
536
+ getContextMenuItems() {
537
+ return [];
538
+ }
539
+
540
+ showCoordinates() {}
541
+
542
+ centerMap() {}
543
+
544
+ toggleDrawControl() {}
545
+
546
+ panBy() {}
547
+
548
+ editPolygon() {
549
+ return Promise.resolve({ type: 'unsupported' });
550
+ }
551
+ }