@aapicoits/powermap-web-sdk 1.0.0

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.
package/README.md ADDED
@@ -0,0 +1,1138 @@
1
+ # PowerMap Web SDK πŸš€
2
+
3
+ A professional-grade, high-performance Map SDK. Optimized for logistics, real-time tracking, indoor mapping, and 3D vehicle simulations.
4
+
5
+ <!-- [![npm version](https://img.shields.io/npm/v/@powermap/web-sdk.svg?style=flat-square)](https://www.npmjs.com/package/@powermap/web-sdk) -->
6
+
7
+ ---
8
+
9
+ ## 2. πŸ›οΈ Architecture Overview
10
+
11
+ The PowerMap Web SDK is built with a **Layered Facade Architecture**, ensuring that high-performance map rendering is wrapped in an easy-to-use, fluent JS API.
12
+
13
+ ```mermaid
14
+ graph TD
15
+ App[Web Application] --> PM[PowerMap Instance]
16
+
17
+ subgraph "Facade Layer (Fluent API)"
18
+ PM
19
+ end
20
+
21
+ subgraph "Manager Layer (Lazy Loading)"
22
+ PM --> Registry[Component Registry]
23
+ Registry --> MarkerMgr[MarkerManager]
24
+ Registry --> SearchMgr[SearchManager]
25
+ Registry --> RouteMgr[RoutingManager]
26
+ Registry --> GeomMgr[GeometryManager]
27
+ Registry --> EditorMgr[EditorManager]
28
+ Registry --> LayerMgr[LayerManager]
29
+ end
30
+
31
+ subgraph "Service Layer"
32
+ PM --> AuthMgr[AuthManager]
33
+ PM --> Events[EventBus]
34
+ AuthMgr --> Cache[Token Cache]
35
+ end
36
+
37
+ subgraph "Visual Engine"
38
+ PM --> ML[PowerMap Visual Engine]
39
+ end
40
+
41
+ Cache -.-> Storage[(Local Storage)]
42
+ AuthMgr -.-> API((PowerMap APIs))
43
+ ```
44
+
45
+ ---
46
+
47
+ ## 3. πŸ“¦ Installation
48
+
49
+ Install the package via npm or yarn:
50
+
51
+ ```bash
52
+ npm install @powermap/web-sdk
53
+ ```
54
+
55
+ ---
56
+
57
+ ## 4. πŸš€ Quick Start
58
+
59
+ Get a map up and running in less than 2 minutes. The PowerMap SDK is designed for chaining and ease of use.
60
+
61
+ ### Step A: Prepare your HTML
62
+
63
+ ```html
64
+ <div id="map" style="width: 100%; height: 500px; border-radius: 8px; overflow: hidden;"></div>
65
+ ```
66
+
67
+ ### Step B: Initialize and Style
68
+
69
+ ```javascript
70
+ import { PowerMap } from "@powermap/web-sdk";
71
+
72
+ // 1. Initialize with dual-auth system
73
+ const map = new PowerMap({
74
+ container: "map",
75
+ center: [100.5018, 13.7563], // Bangkok [Longitude, Latitude]
76
+ zoom: 14,
77
+ mapApiKey: "YOUR_MAP_API_KEY", // Public key for tiles and base layers
78
+ auth: {
79
+ clientId: "YOUR_CLIENT_ID", // Service Credentials for backend APIs
80
+ clientSecret: "YOUR_CLIENT_SECRET"
81
+ },
82
+ previewOnly: true // Optional: Lock all user interactions (Static View)
83
+ });
84
+
85
+ // 2. Chaining for smooth DX
86
+ map.onMapLoaded((e) => {
87
+ console.log("PowerMap is ready!", e.map);
88
+
89
+ // Add a marker as soon as the map loads
90
+ map.addMarker({
91
+ position: [100.5018, 13.7563],
92
+ type: "start",
93
+ popupContent: "<strong>Origin Point</strong>"
94
+ });
95
+ })
96
+ .setStyle('th') // Options: 'th', 'en', 'dark', 'gray'
97
+ .flyTo({ zoom: 16, pitch: 45 });
98
+ ```
99
+
100
+ > [!TIP]
101
+ > **Performance Note**: The PowerMap Web SDK uses a lazy-loading registry pattern. Managers (Routing, Search, etc.) are only initialized once you first call them, keeping your app's initial memory footprint minimal.
102
+
103
+ ---
104
+
105
+ ## 5. πŸ”— Method Chaining
106
+
107
+ The PowerMap SDK is built with a **fluent interface**, allowing you to link multiple configuration steps and event listeners together in a single statement.
108
+
109
+ ### Supported Methods
110
+
111
+ Most methods that perform an action on the map (styling, camera movement, or event registration) return the `PowerMap` instance to facilitate chaining.
112
+
113
+ | Group | Chainable Methods |
114
+ | :--- | :--- |
115
+ | **Events** | `on`, `off`, `onMapLoaded`, `onClick`, `onMapLongClick`, `onMarkerClick`, `onPoiClick`, `onMarkerDrag`, `onMarkerDragEnd`, `onRouteCalculated`, `onError` |
116
+ | **Camera** | `flyTo`, `easeTo`, `fitBounds`, `jumpTo`, `setPitch`, `setBearing`, `zoomIn`, `zoomOut`, `rotateLeft`, `rotateRight` |
117
+ | **Map Style** | `setStyle` |
118
+ | **Markers** | `removeMarker`, `updatePopup`, `clearMarkers` |
119
+ | **Search** | `search` |
120
+ | **Routing** | `clearRoutes` |
121
+ | **Layers** | `showLayer`, `hideLayer`, `toggleLayer` |
122
+ <!-- | **Sim/Nav** | `startSimulation`, `stopSimulation`, `startNavigation`, `stopNavigation` | -->
123
+
124
+ > [!NOTE]
125
+ > Methods that return a **Promise** (like `addRoute`) or a **new Object** (like `addMarker` or `getRouteDetails`) interrupt the chain.
126
+
127
+ ### Chaining Example
128
+
129
+ ```javascript
130
+ map.onMapLoaded(() => console.log("Ready!"))
131
+ .onMarkerClick(data => console.log("Clicked:", data))
132
+ .setStyle('dark')
133
+ .flyTo({ center: [100.5, 13.7], zoom: 12 })
134
+ .setPitch(45);
135
+ ```
136
+
137
+ ---
138
+
139
+ ## 6. πŸ› οΈ Configuration Reference
140
+
141
+ When creating a `new PowerMap(options)`, the following options are available:
142
+
143
+ | Option | Type | Default | Required | Description |
144
+ | :--- | :--- | :--- | :--- | :--- |
145
+ | `container` | `string` | - | **Yes** | The HTML element ID where the map will be rendered. |
146
+ | `mapApiKey` | `string` | - | **Yes** | Your PowerMap Public API Key for map tiles. |
147
+ | `auth` | `Object` | - | **Yes** | **Service Credentials**. Used for connecting to backend APIs of PowerMap services (e.g., Routing, Optimization). Contains `clientId` and `clientSecret`. |
148
+ | `center` | `[lng, lat]` | `[100, 13]` | No | The initial center point of the map. |
149
+ | `zoom` | `number` | `14` | No | Total zoom level (0-22). |
150
+ | `baseUrl` | `string` | - | No | Optional proxy URL (e.g., `/api/proxy`). If set, the SDK will route all API requests through this URL. |
151
+ | `maptilerKey` | `string` | - | No | Your MapTiler API Key. Required if you intend to use high-resolution satellite layers. |
152
+ | `previewOnly` | `boolean` | `false` | No | If **true**, the map will be non-interactive (no zoom, pan, rotate, or pitch). Perfect for static map previews. |
153
+
154
+ ### Core Instance Methods
155
+
156
+ These methods are available on the `PowerMap` instance to manage the map state and lifecycle.
157
+
158
+ | Method | Arguments | Description |
159
+ | :--- | :--- | :--- |
160
+ | `on(event, cb)` | `string, function` | Subscribe to any map event (see Section 9). |
161
+ | `off(event, cb)` | `string, function` | Unsubscribe from a previously registered event. |
162
+ | `setMode(mode, opts)` | `string, Object` | Switch between `'standard'` and `'indoor'` modes. |
163
+ | `setStyle(name)` | `string` | Change the map style (e.g., `'th'`, `'dark'`). |
164
+ | `setNavigationControl(opts)` | `boolean\|Object` | Enable/disable or customize zoom/compass controls. |
165
+ | `setFullscreenControl(opts)` | `boolean\|Object` | Enable/disable fullscreen control. |
166
+ | `debug()` | - | Access the Debug Manager for telemetry and performance tools. |
167
+ | `onError(callback)` | `function` | Global error listener for Auth, Map, or Routing issues. |
168
+
169
+ ---
170
+
171
+ ## 7. πŸ“ Marker Management
172
+
173
+ Our marker system simplifies standard map markers with built-in presets for logistics and easy event handling.
174
+
175
+ ### `addMarker(options)` Options
176
+
177
+ | Option | Type | Default | Description |
178
+ | :--- | :--- | :--- | :--- |
179
+ | `position` | `[lng, lat]` | **Required** | Coordinates for the marker. |
180
+ | `type` | `string` | `'default'` | Preset icons: `'start'`, `'destination'`, `'waypoint'`. |
181
+ | `iconUrl` | `string` | - | Custom image URL to use as the icon. Overrides `type`. |
182
+ | `width`/`height` | `string` | `'30px'`/`'38px'` | CSS dimensions for the marker icon. |
183
+ | `anchor` | `string` | `'bottom'` | Where the marker is anchored to the coordinate. |
184
+ | `draggable` | `boolean` | `false` | If true, user can drag the marker. Fires `onMarkerDrag` events. |
185
+ | `rotation` | `number` | `0` | Icon rotation in degrees. |
186
+ | `popupContent` | `string|Object` | - | HTML string or **Structured Object** to display in a premium popup. |
187
+ | `id` | `string` | - | **Unique ID** to track this marker. Allows retrieval via `getMarker(id)`. |
188
+ | `color` | `string` | - | Custom hex color for pinpoint marker (e.g. `'#ff4444'`). Uses **High-Accuracy GL Rendering**. |
189
+ | `icon` | `string` | - | FontAwesome icon class for pinpoint marker (e.g. `'fas fa-car'`). |
190
+ | `element` | `HTMLElement` | - | Use a raw HTML element as a fully custom marker. |
191
+
192
+ > [!IMPORTANT]
193
+ > **High-Accuracy Rendering**: Markers with a `color` property use our new **GL Symbol Layer** system. These markers are rendered directly on the map canvas, ensuring **perfect alignment** and **zero-lag** during zoom or pan. Ordinary preset markers (`type`) use standard DOM elements for maximum compatibility.
194
+
195
+ ### Marker Examples
196
+
197
+ #### Standard Presets
198
+
199
+ ```javascript
200
+ map.addMarker({
201
+ position: [100.5, 13.7],
202
+ type: 'start', // 'start', 'destination', 'waypoint'
203
+ popupContent: "Start Point"
204
+ });
205
+ ```
206
+
207
+ #### Premium Colored Markers (No Image Required)
208
+
209
+ ```javascript
210
+ map.addMarker({
211
+ position: [100.52, 13.78],
212
+ color: '#f97316', // Vibrant Orange
213
+ icon: 'fas fa-store', // FontAwesome Icon
214
+ draggable: true, // Enable high-accuracy dragging
215
+ popupContent: {
216
+ title: "Power Shop",
217
+ tag: "Retail",
218
+ body: "Custom colored marker with icon."
219
+ }
220
+ });
221
+ ```
222
+
223
+ #### Draggable Markers & Events
224
+
225
+ You can enable dragging for any marker. For GL-rendered markers (those with `color`), dragging is handled with sub-pixel precision.
226
+
227
+ ```javascript
228
+ const marker = map.addMarker({
229
+ position: [100.5, 13.7],
230
+ color: '#3b82f6',
231
+ draggable: true
232
+ });
233
+
234
+ // 1. Listen via Proxy (Recommended)
235
+ marker.on('drag', (e) => console.log('Current:', e.lngLat));
236
+ marker.on('dragend', (e) => console.log('Final:', e.lngLat));
237
+
238
+ // 2. Listen via Global Event Bus
239
+ map.onMarkerDrag(({ marker, lngLat }) => {
240
+ console.log(`Marker is moving to: ${lngLat.lng}, ${lngLat.lat}`);
241
+ });
242
+ ```
243
+
244
+ #### Structured Popup Content & Actions
245
+
246
+ Instead of raw HTML, you can pass a structured object to `popupContent` for a premium, consistent look. You can also define interactive buttons using the `action` or `actions` property:
247
+
248
+ ```javascript
249
+ map.addMarker({
250
+ position: [100.5, 13.7],
251
+ popupContent: {
252
+ title: "Siam Paragon",
253
+ tag: "Mall",
254
+ body: "The pride of Bangkok with world-class shopping.",
255
+ footer: "Open: 10:00 - 22:00",
256
+ actions: [
257
+ { label: "View Details", url: "https://example.com/details", target: "_blank" },
258
+ { label: "Navigate Here", url: "https://example.com/nav", color: "#10b981" }
259
+ ]
260
+ }
261
+ });
262
+ ```
263
+
264
+ ### Marker Methods
265
+
266
+ | Method | Arguments | Description |
267
+ | :--- | :--- | :--- |
268
+ | `addMarker(options)` | `Object` | Create and add a marker. Returns marker instance. |
269
+ | `getMarker(id)` | `string` | Retrieve a marker instance by its unique ID. |
270
+ | `removeMarker(markerOrId)` | `Object\|string` | Remove a marker using instance or ID. |
271
+ | `updatePopup(marker, content)` | `Object, string\|Object` | Update the popup content (HTML or Structured Object). |
272
+ | `clearMarkers()` | - | Remove all markers from the map. |
273
+
274
+ ---
275
+
276
+ ## 8. 🎨 CSS Customization
277
+
278
+ The PowerMap SDK automatically wraps all map elements in a global `.powermap-sdk` class and provides branded aliases for all internal UI components.
279
+
280
+ ### The Global Wrapper
281
+
282
+ Any HTML element you provide as a `container` will automatically have the `powermap-sdk` class applied at runtime. This provides a clean scope for all PowerMap styles.
283
+
284
+ ```html
285
+ <!-- Input -->
286
+ <div id="map"></div>
287
+
288
+ <!-- Output at runtime -->
289
+ <div id="map" class="powermap-sdk">...</div>
290
+ ```
291
+
292
+ ### Premium Class Aliases
293
+
294
+ To make customization even more intuitive, we've provided branded CSS classes that you can target directly:
295
+
296
+ | Feature | PowerMap Class | Description |
297
+ | :--- | :--- | :--- |
298
+ | **Marker** | `.pm-marker` | The main container for any marker. |
299
+ | **Popup** | `.pm-popup` | The root container of a popup. |
300
+ | **Popup Content** | `.pm-popup-content` | The main content area (glassmorphism). |
301
+ | **Popup Header** | `.pm-popup-header` | The header (part of structured content). |
302
+ | **Popup Body** | `.pm-popup-body` | The body text area. |
303
+ | **Popup Tag** | `.pm-popup-tag` | The category tag. |
304
+ | **Popup Footer** | `.pm-popup-footer` | The bottom meta info area. |
305
+ | **Popup Close** | `.pm-popup-close` | The close button element. |
306
+ | **Popup Tip** | `.pm-popup-tip` | The triangular tip pointing to the marker. |
307
+ | **Nav Group** | `.pm-ctrl-group` | The container of zoom/compass buttons. |
308
+ | **Zoom In** | `.pm-ctrl-zoom-in` | The zoom in button. |
309
+ | **Zoom Out** | `.pm-ctrl-zoom-out` | The zoom out button. |
310
+ | **Compass** | `.pm-ctrl-compass` | The compass/rotation button. |
311
+
312
+ ### Premium Overrides
313
+
314
+ Target your own CSS using the scoped wrapper or branded aliases:
315
+
316
+ ```css
317
+ /* Change the header color of structured popups */
318
+ .powermap-sdk .pm-popup-header {
319
+ background: linear-gradient(135deg, #000 0%, #333 100%);
320
+ }
321
+
322
+ /* Customize the glassmorphism background of all popups */
323
+ .powermap-sdk .pm-popup-content {
324
+ background: rgba(255, 255, 255, 0.95);
325
+ backdrop-filter: blur(20px);
326
+ }
327
+
328
+ /* Custom Marker Scale */
329
+ .powermap-sdk .pm-marker {
330
+ transform: scale(1.1);
331
+ }
332
+ ```
333
+
334
+ ### `setNavigationControl(options)` Options
335
+
336
+ | Option | Type | Default | Description |
337
+ | :--- | :--- | :--- | :--- |
338
+ | `position` | `string` | `'top-right'` | Position on the map: `'top-left'`, `'top-right'`, `'bottom-left'`, `'bottom-right'`. |
339
+ | `showZoom` | `boolean` | `true` | If `true`, display the zoom in and zoom out buttons. |
340
+ | `showCompass` | `boolean` | `true` | If `true`, display the compass (rotation) button. |
341
+ | `visualizePitch` | `boolean` | `true` | If `true`, the compass button will rotate on the Y-axis to visualize map pitch (3D view). |
342
+
343
+ ### Map Control Examples
344
+
345
+ ```javascript
346
+ // Simple: Enable default branded controls (top-right)
347
+ map.setNavigationControl(true);
348
+
349
+ // Advanced: Custom position and visibility
350
+ map.setNavigationControl({
351
+ position: 'bottom-right',
352
+ showZoom: true,
353
+ showCompass: false
354
+ });
355
+
356
+ // Disable controls
357
+ map.setNavigationControl(false);
358
+ ```
359
+
360
+ ---
361
+
362
+ ## 9. πŸ›£οΈ Routing & Optimization
363
+
364
+ Generate high-precision routes with automatic visualization. The SDK automatically fits the map view to the resulting route.
365
+
366
+ ### `addRoute(options)` Options
367
+
368
+ | Option | Type | Default | Description |
369
+ | :--- | :--- | :--- | :--- |
370
+ | `start` | `[lng, lat]` | **Required** | Start point coordinates. |
371
+ | `destination` | `[lng, lat]` | **Required** | End point coordinates. |
372
+ | `waypoints` | `Array<[lng, lat]>` | `[]` | List of points to hit between start and end. |
373
+ | `optimize` | `boolean` | `false` | If true, reorders waypoints to find the shortest total trip (TSP). **Requires at least 1 waypoint**. |
374
+ | `profile` | `string` | `'driving'` | Routing engine profile: `'driving'`, `'walking'`, `'cycling'`. |
375
+ | `language` | `string` | `'th'` | Return instructions/names in `'th'` or `'en'`. |
376
+ | `routeColor` | `string` | `'#3b82f6'` | Hex color code for the main route line. |
377
+ | `routeWidth` | `number` | `5` | Thickness of the main route line. |
378
+ | `routeOpacity` | `number` | `1.0` | Opacity of the route line (0.0 to 1.0). |
379
+ | `casingColor` | `string` | `'#1d4ed8'` | Hex color for the route's outer border (shadow). |
380
+ | `casingWidth` | `number` | `8` | Thickness of the casing line. |
381
+
382
+ ### Route Methods
383
+
384
+ | Method | Arguments | Description |
385
+ | :--- | :--- | :--- |
386
+ | `addRoute(options)` | `Object` | Calculate, draw route and fit view. Returns Promise. |
387
+ | `clearRoutes()` | - | Remove all route lines and markers from map. |
388
+ | `getRouteDetails()` | - | Returns distance, duration, and formatted strings. |
389
+ | `getRouteSummary()` | - | Returns top-level distance/duration strings. |
390
+
391
+ ### `addRoute(options)` Return Object
392
+
393
+ The `addRoute` method returns a Promise that resolves to a `RouteResult` object containing:
394
+
395
+ | Property | Type | Description |
396
+ | :--- | :--- | :--- |
397
+ | `distance` | `number` | Total distance of the route in **meters**. |
398
+ | `duration` | `number` | Estimated travel time in **seconds**. |
399
+ | `steps` | `Array<Object>` | Turn-by-turn instructions (text, distance, duration, location). |
400
+ | `geometry` | `Object` | GeoJSON `LineString` of the route. |
401
+ | `raw` | `Object` | Raw response from the PowerMap routing backend. |
402
+
403
+ #### `steps` Object Structure
404
+
405
+ Each step in the `steps` array represents a turn or maneuver:
406
+
407
+ | Field | Type | Description |
408
+ | :--- | :--- | :--- |
409
+ | `text` | `string` | The maneuver instruction (e.g., "ΰΉ€ΰΈ₯ΰΈ΅ΰΉ‰ΰΈ’ΰΈ§ΰΈ‚ΰΈ§ΰΈ²ΰΈ—ΰΈ΅ΰΉˆΰΈ–ΰΈ™ΰΈ™ΰΈͺΰΈΈΰΈ‚ΰΈΈΰΈ‘ΰΈ§ΰΈ΄ΰΈ—"). |
410
+ | `distance` | `number` | Distance to travel for this step (meters). |
411
+ | `duration` | `number` | Estimated time for this step (seconds). |
412
+ | `location` | `[lng, lat]` | Geographic coordinate where the maneuver starts. |
413
+
414
+ ### Routing Example
415
+
416
+ ```javascript
417
+ // Simple route from A to B
418
+ const route = await map.addRoute({
419
+ start: [100.5018, 13.7563],
420
+ destination: [100.5118, 13.7663],
421
+ language: "en"
422
+ });
423
+
424
+ // Complex optimized route with waypoints
425
+ const optimizedRoute = await map.addRoute({
426
+ start: [100.50, 13.75],
427
+ destination: [100.60, 13.85],
428
+ waypoints: [
429
+ [100.55, 13.80],
430
+ [100.52, 13.78]
431
+ ],
432
+ optimize: true, // Will reorder the 2 waypoints for the best efficiency
433
+ language: "th"
434
+ });
435
+
436
+ console.log(`Total Distance: ${optimizedRoute.distance}m`);
437
+ console.log(`Total Duration: ${optimizedRoute.duration}s`);
438
+
439
+ // Custom red route for heavy traffic visualization
440
+ const redRoute = await map.addRoute({
441
+ start: [100.5, 13.7],
442
+ destination: [100.6, 13.8],
443
+ routeColor: "#ff0000",
444
+ routeWidth: 10,
445
+ casingColor: "#990000"
446
+ });
447
+
448
+ // Clear all routes from visibility
449
+ map.clearRoutes();
450
+ ```
451
+
452
+ #### πŸ“ Route Summary Helpers
453
+
454
+ Quickly access formatted summaries (distance in km/m, time in hours/mins) for the total route and individual steps:
455
+
456
+ ```javascript
457
+ // Get enhanced details (Recommended)
458
+ const details = map.getRouteDetails();
459
+ if (details) {
460
+ console.log(`Total: ${details.distanceStr}, Time: ${details.durationStr}`);
461
+
462
+ // Each step also contains formatted strings
463
+ details.steps.forEach(step => {
464
+ console.log(`${step.text} (${step.distanceStr})`);
465
+ });
466
+ }
467
+
468
+ // Get just the top-level summary
469
+ const summary = map.getRouteSummary(); // { distanceStr, durationStr }
470
+ ```
471
+
472
+ ---
473
+
474
+ ## πŸ—οΈ 10. Map Layers Management
475
+
476
+ The `LayerManager` provides a robust API to toggle built-in map layers (e.g., Roads, Railways, BTS/MRT, City Plans) on and off. The SDK dynamically fetches these layers from the catalog and applies them to your current map style.
477
+
478
+ ### Built-in Layer Groups
479
+
480
+ Map layers are grouped into 3 main categories:
481
+
482
+ 1. **CITY PLAN** (`city`): City plans for various provinces.
483
+ 2. **TRANSPORT** (`transport`): Roads, Current/Future Railways, Current/Future BTS/MRT.
484
+ 3. **INSIGHT DATA** (`insight`): Night lights, Satellite imagery.
485
+
486
+ ### Layer APIs
487
+
488
+ | Method | Arguments | Description |
489
+ | :--- | :--- | :--- |
490
+ | `map.layers.getCatalog()` | - | Returns the full list of available layer groups and IDs. |
491
+ | `map.layers.ensure(layerId)` | `string` | Ensures a layer's sources are created (idempotent). |
492
+ | `map.layers.show(layerId)` | `string` | Ensures and shows a specific layer. |
493
+ | `map.layers.hide(layerId)` | `string` | Hides a specific layer. |
494
+ | `map.layers.toggle(layerId)` | `string` | Toggles the visibility of a layer. |
495
+ | `map.layers.getLayerState(id)` | `string` | Returns `{loaded, visible}` status. |
496
+ | `map.layers.setOpacity(id, val)` | `string, number` | Adjusts the transparency (0.0 - 1.0). |
497
+ | `map.layers.showGroup(key)` | `string` | Shows all layers within a specific group. |
498
+ | `map.layers.hideGroup(key)` | `string` | Hides all layers within a specific group. |
499
+ | `map.layers.hideAll()` | - | Hides every active layer from the catalog. |
500
+
501
+ ### Usage Example
502
+
503
+ ```javascript
504
+ map.onMapLoaded(async () => {
505
+ // 1. View all available layers
506
+ const catalog = map.layers.getCatalog();
507
+ console.log(catalog);
508
+
509
+ // 2. Show the Road layer
510
+ await map.layers.show('pm-road-vt');
511
+
512
+ // 3. Toggle the current Railway layer
513
+ await map.layers.toggle('pm-current-railway-th-vt');
514
+
515
+ // 4. Adjust opacity
516
+ map.layers.setOpacity('pm-road-vt', 0.5);
517
+
518
+ // 5. Hide an entire group
519
+ map.layers.hideGroup('city');
520
+ });
521
+ ```
522
+
523
+ You can also use convenience methods directly on the `PowerMap` instance:
524
+ ```javascript
525
+ await map.showLayer('pm-road-vt');
526
+ map.hideLayer('pm-road-vt');
527
+ await map.toggleLayer('pm-road-vt');
528
+ ```
529
+
530
+ > [!IMPORTANT]
531
+ > **Satellite Layer**: High-resolution satellite imagery (`pm-maptiler-satellite-layer`) requires a `maptilerKey` provided in the initial `PowerMap` configuration.
532
+
533
+ ---
534
+
535
+ ## 11. πŸŽ₯ Camera & Viewport Control
536
+
537
+ Take full control of the user's perspective with smooth transitions or direct manipulation.
538
+
539
+ ### Movement Functions
540
+
541
+ | Method | Arguments | Description |
542
+ | :--- | :--- | :--- |
543
+ | `flyTo(options)` | `Object` | Smooth cinematic movement with options: `center`, `zoom`, `pitch`, `bearing`, `duration`. |
544
+ | `easeTo(options)` | `Object` | Linear transition movement. Same options as `flyTo`. |
545
+ | `jumpTo(options)` | `Object` | Instant teleportation without animation. |
546
+ | `fitBounds(bounds, opts)` | `Array, Object` | Fits view to show all coords. Options: `padding`. |
547
+ | `zoomIn()` | - | Smoothly zoom in by level 1. |
548
+ | `zoomOut()` | - | Smoothly zoom out by level 1. |
549
+ | `rotateLeft()` | - | Smoothly rotate left by 45 degrees. |
550
+ | `rotateRight()` | - | Smoothly rotate right by 45 degrees. |
551
+
552
+ ### State Functions
553
+
554
+ | Method | Arguments | Description |
555
+ | :--- | :--- | :--- |
556
+ | `setPitch(deg, anim)` | `number, bool` | Set tilt angle (0-85). Smooth by default. |
557
+ | `setBearing(deg, anim)` | `number, bool` | Set direction (0-360). Smooth by default. |
558
+ | `getCenter()` | - | Returns `{lng, lat}` of the current map center. |
559
+ | `getZoom()` | - | Returns the current zoom level as a `number`. |
560
+ | `getPitch()` | - | Returns the current pitch (tilt) as a `number`. |
561
+ | `getBearing()` | - | Returns the current bearing (rotation) as a `number`. |
562
+
563
+ ### Camera Examples
564
+
565
+ ```javascript
566
+ // Fly into a specific warehouse view
567
+ map.flyTo({
568
+ center: [100.5, 13.8],
569
+ zoom: 17,
570
+ pitch: 60,
571
+ bearing: 45,
572
+ duration: 4000
573
+ });
574
+
575
+ // Fit view to a specific region
576
+ map.fitBounds([
577
+ [100.4, 13.7],
578
+ [100.6, 13.9]
579
+ ], { padding: 40 });
580
+ ```
581
+
582
+ <!--
583
+ ---
584
+
585
+ ## 8. πŸš— Simulation & Navigation
586
+
587
+ Advanced tracking simulation with 2D/3D model support. This is ideal for visualizing real-time vehicle movement.
588
+
589
+ ### `startSimulation(route, options)` Options
590
+
591
+ | Option | Type | Default | Description |
592
+ | :--- | :--- | :--- | :--- |
593
+ | `route` | `Object` | **Required** | The route result object returned by `addRoute`. |
594
+ | `use3D` | `boolean` | `false` | If true, renders a 3D GLB model. If false, renders a 2D marker. |
595
+ | `modelUrl` | `string` | (Internal) | URL to a GLB model file for 3D simulation. |
596
+ | `scale` | `number` | `0.5` | Scale factor for the 3D model. |
597
+
598
+ ### Simulation & Navigation Methods
599
+
600
+ | Method | Arguments | Description |
601
+ | :--- | :--- | :--- |
602
+ | `startSimulation(route, opts)` | `Object, Object` | Start 2D or 3D vehicle animation along a route. |
603
+ | `stopSimulation()` | - | Terminate current running simulation. |
604
+ | `startNavigation()` | - | Enable "Follow" camera mode during simulation. |
605
+ | `stopNavigation()` | - | Disable camera following. |
606
+
607
+ ### Simulation & Navigation Example
608
+
609
+ ```javascript
610
+ const myRoute = await map.addRoute({ ... });
611
+
612
+ // 1. Start simulation: Moving along the path
613
+ map.startSimulation(myRoute, {
614
+ use3D: true, // Awesome 3D car mode!
615
+ scale: 0.8,
616
+ modelUrl: "/assets/my-truck.glb"
617
+ });
618
+
619
+ // 2. Navigation Mode: Make the camera follow the car
620
+ map.startNavigation();
621
+
622
+ // 3. Listen to updates
623
+ map.onSimulationUpdate(({ position, step }) => {
624
+ console.log(`Car is at ${position}. Progress: ${step}`);
625
+ });
626
+
627
+ // 4. Terminate
628
+ // map.stopNavigation();
629
+ // map.stopSimulation();
630
+ ```
631
+ -->
632
+
633
+ ---
634
+
635
+ ## 11. πŸ”₯ Event System
636
+
637
+ The SDK uses a semantic event system for a modern developer experience. All events can be chained.
638
+
639
+ ### Event List & Arguments
640
+
641
+ | Method | Callback Arguments | Triggered When... |
642
+ | :--- | :--- | :--- |
643
+ | `onMapLoaded` | `{map}` | The map is fully initialized and layers can be added. |
644
+ | `onClick` | `{lngLat, point, ...}` | The map is clicked. |
645
+ | `onMapLongClick` | `{lngLat, point, ...}` | The map is long-pressed or right-clicked. |
646
+ | `onMarkerClick` | `{marker, originalEvent, options}` | A user clicks on any marker added via SDK. |
647
+ | `onPoiClick` | `{feature, lngLat, point}` | A user clicks on a Point of Interest (POI) on the base map. |
648
+ | `onModeChanged` | `{mode, options}` | The SDK operational mode (standard/indoor) has changed. |
649
+ | `onMarkerDrag` | `{marker, lngLat, options}` | A draggable marker is currently being moved. |
650
+ | `onMarkerDragEnd` | `{marker, lngLat, options}` | A user releases a draggable marker. |
651
+ | `onRouteCalculated` | `route` | A new route has been fetched and drawn. |
652
+ | `onGeometryExported` | `{data}` | GeoJSON data has been exported from the editor. |
653
+ | `layerEnsured` | `{layerId, group}` | A catalog layer has finished initializing sources. |
654
+ | `onError` | `{source, message}` | Any internal error (Auth, Routing, Map) occurs. |
655
+
656
+ ### Event Subscription Example
657
+
658
+ ```javascript
659
+ map.onMapLoaded((e) => {
660
+ console.log("Ready!");
661
+ })
662
+ .onClick((e) => {
663
+ console.log(`Map clicked at: ${e.lngLat}`);
664
+ })
665
+ .onMarkerClick((data) => {
666
+ alert(`You clicked marker: ${data.options.popupContent}`);
667
+ })
668
+ .onMarkerDragEnd((data) => {
669
+ console.log(`New Coordinate: ${data.lngLat.lng}, ${data.lngLat.lat}`);
670
+ });
671
+ ```
672
+
673
+ ---
674
+
675
+ <!-- ## 9. πŸ” Security & Production Best Practices
676
+
677
+ For production deployments, **NEVER** expose your `clientSecret` in client-side code. This exposes your account to unauthorized usage.
678
+
679
+ **Recommended Proxy Setup:**
680
+
681
+ 1. Your Frontend calls your own Backend (e.g., `POST /api/get-route`).
682
+ 2. Your Backend appends `clientId` and `clientSecret` and calls PowerMap API.
683
+ 3. Your Backend returns the result to your Frontend.
684
+
685
+ **SDK Setup for Proxy:**
686
+ ```javascript
687
+ const map = new PowerMap({
688
+ baseUrl: "/api/my-proxy", // Point to your backend route
689
+ mapApiKey: "PUBLIC_MAP_KEY"
690
+ // No clientSecret needed here; the backend handles it!
691
+ });
692
+ ```
693
+
694
+ --- -->
695
+
696
+ ## 12. πŸ” Search & Geocoding
697
+
698
+ The Web SDK provides built-in search capabilities using the PowerMap Search API.
699
+
700
+ ### `search(query, options)`
701
+
702
+ Available options:
703
+
704
+ - `lang`: `'th'` (default) or `'en'`.
705
+ - `location`: `[latitude, longitude]` to prioritize nearby results.
706
+
707
+ ```javascript
708
+ const results = await map.search("Central World", { lang: 'en' });
709
+
710
+ results.forEach(item => {
711
+ console.log(`${item.name}: ${item.address}`);
712
+ console.log(`Position: ${item.position}`); // [lat, lng]
713
+ });
714
+ ```
715
+
716
+ ---
717
+
718
+ ## 13. πŸ•ΉοΈ Interactive Metadata (POI)
719
+
720
+ The SDK can detect when a user clicks on a Point of Interest (POI) on the base map (e.g., shopping malls, parks, gas stations).
721
+
722
+ ```javascript
723
+ map.onPoiClick(({ feature, lngLat }) => {
724
+ const name = feature.properties.name_t;
725
+ console.log(`User clicked on: ${name}`);
726
+
727
+ // Highlighting the POI with a popup
728
+ map.addMarker({
729
+ id: 'poi-temp',
730
+ position: [lngLat.lng, lngLat.lat],
731
+ popupContent: { title: name, tag: 'POI' }
732
+ });
733
+ });
734
+ ```
735
+
736
+ ---
737
+
738
+ ## 14. 🧰 Utility Functions
739
+
740
+ SDK provides handy formatters for displaying routing data.
741
+
742
+ | Method | Arguments | Description |
743
+ | :--- | :--- | :--- |
744
+ | `formatDistance(meters)` | `number` | Converts meters to string (e.g., "1.25 km" or "500 m"). |
745
+ | `formatDuration(seconds)` | `number` | Converts seconds to string (e.g., "1h 30m" or "5 min"). |
746
+
747
+ ---
748
+
749
+ ## 15. πŸ“ Geometry Engine & Editor
750
+
751
+ The PowerMap SDK includes a professional-grade geometry engine and a built-in UI for creating, editing, and managing spatial data. This is optimized for both simple outdoor measuring and complex indoor mapping.
752
+
753
+ ### 15.1 The Editor Toolbar
754
+
755
+ Enable a premium, built-in toolbar that handles the entire drawing lifecycle.
756
+
757
+ ```javascript
758
+ map.editor.enable({
759
+ position: 'bottom-left', // 'top-left', 'top-right', 'bottom-left', 'bottom-right'
760
+ mode: 'standard' // 'standard' (Logistics) or 'indoor' (Indoor Mapping)
761
+ });
762
+ ```
763
+
764
+ #### **Interactive Tools**
765
+
766
+ - **Standard Mode**: Point, Line (Measure), Polygon (Area), Rectangle.
767
+ - **Indoor Mode**: Beacon, POI, Walkway, Floor, Obstacle.
768
+
769
+ ### 15.2 Measurement & Info Panel
770
+
771
+ When a feature is selected, a real-time measurement panel appears, providing:
772
+
773
+ - **Distance**: For lines and walkways.
774
+ - **Area**: For polygons and floors.
775
+ - **3D Height**: For extruded features.
776
+ - **Live Coordinates**: For Points (POIs and Beacons), showing relative X/Y metrics dynamically during placement inside buildings, or Latitude/Longitude for outdoor placements.
777
+
778
+ ### 15.3 Property Editor UI
779
+
780
+ The PowerMap Editor includes a functional **Property Editor** panel (available in `indoor` mode or via the settings icon). It allows you to manage:
781
+
782
+ - **Core Metadata**: ID and Name.
783
+ - **Classification**: Independent `feature_type` (e.g., level, building, venue), explicit Categories, and Ordinal/Level (for stacking floors).
784
+ - **Visual Styling**: Choose colors and icons (for Beacons and POIs).
785
+ - **Custom Properties**: Add unlimited key-value pairs for your own business logic.
786
+
787
+ ### 15.4 3D & Advanced Tools
788
+
789
+ Take drawing to the next dimension:
790
+
791
+ - **Extrude**: Transform 2D polygons into 3D buildings or obstacles by setting the `height` property.
792
+ - **Rotate & Clone**: Rapidly duplicate and align features.
793
+ - **Vertical Alignment**: Manage `ordinal` properties to ensure data belongs to the correct floor level.
794
+
795
+ ### 15.5 Data Lifecycle (Import/Export)
796
+
797
+ The Editor facilitates easy data exchange via GeoJSON:
798
+
799
+ - **Export**: Built-in "Download" button in the toolbar, or call `map.geometry.export()`.
800
+ - **Import**: Built-in "Upload" button, or call `map.geometry.import(geojson)`.
801
+ - **IMDF Compliance**: Indoor fields (`feature_type`, `category`, `ordinal`) are mapped to IMDF format automatically.
802
+
803
+ ### 15.6 Unified Programmatic CRUD API
804
+
805
+ > For the full reference, see [GEOMETRY.md](./GEOMETRY.md)
806
+
807
+ All operations β€” whether from draw tool or code β€” emit the same `geometryChanged` event.
808
+
809
+ ```javascript
810
+ // CREATE (standard)
811
+ const id = map.geometry.create('polygon', {
812
+ coordinates: [[[100.5,13.7],[100.6,13.7],[100.6,13.8],[100.5,13.7]]],
813
+ properties: { fillColor: '#4f46e5', height: 10 }
814
+ });
815
+
816
+ // CREATE (indoor)
817
+ const beaconId = map.geometry.create('beacon', {
818
+ lngLat: [100.52, 13.75],
819
+ properties: { ordinal: 1, name: 'BKK-01' }
820
+ });
821
+
822
+ // READ
823
+ map.geometry.getById(id); // Feature | null
824
+ map.geometry.getByKind('beacon'); // Feature[]
825
+ map.geometry.query().byKind('poi').byFloor(1).toArray(); // Feature[]
826
+
827
+ // UPDATE
828
+ map.geometry.update(id, {
829
+ properties: { name: 'Updated Name', color: '#ff0000' }
830
+ });
831
+
832
+ // DELETE
833
+ map.geometry.remove(id);
834
+ map.geometry.clear();
835
+ ```
836
+
837
+ **All CRUD Methods:**
838
+
839
+ | Method | Arguments | Description |
840
+ | :--- | :--- | :--- |
841
+ | `create(kind, options)` | `string, Object` | Create a standard or indoor feature programmatically |
842
+ | `update(id, patch)` | `string, Object` | Update properties and/or geometry of an existing feature |
843
+ | `getById(id)` | `string` | Returns a single GeoJSON Feature or null |
844
+ | `getByKind(kind)` | `string` | Returns all indoor features matching the given kind |
845
+ | `query()` | - | Returns a chainable `GeometryQuery` builder |
846
+ | `add(geojson)` | `Object` | Manually add a raw GeoJSON feature (legacy) |
847
+ | `remove(id)` | `string\|string[]` | Remove one or multiple features by ID |
848
+ | `clear()` | - | Remove all geometry from the map |
849
+ | `setFeatureHeight(id, m)` | `string, number` | Extrude a polygon into a 3D volume |
850
+ | `getGeoJSON(id?, opts)` | `string, Object` | Returns feature(s) as GeoJSON (`options.format: 'imdf' \| 'standard'`) |
851
+ | `export(opts)` | `Object` | Returns all features as GeoJSON |
852
+ | `import(geojson, opts)` | `Object, Object` | Loads GeoJSON/IMDF data |
853
+
854
+ ### 15.7 GeometryStore β€” Placeholder API Bridge
855
+
856
+ `map.geometry.store` provides placeholder hooks that fire on every geometry change regardless of origin (draw tool or code). Use this to connect your own backend API.
857
+
858
+ ```javascript
859
+ // Register before the map loads
860
+ map.geometry.store.onCreate(async (payload) => {
861
+ console.log('[Placeholder] CREATE', payload);
862
+ // await fetch('/api/features', { method: 'POST', body: JSON.stringify(payload) })
863
+ });
864
+ map.geometry.store.onUpdate(async (payload) => {
865
+ console.log('[Placeholder] UPDATE', payload);
866
+ // await fetch(`/api/features/${payload.featureId}`, { method: 'PUT', body: JSON.stringify(payload) })
867
+ });
868
+ map.geometry.store.onDelete(async (payload) => {
869
+ console.log('[Placeholder] DELETE', payload);
870
+ // await fetch(`/api/features/${payload.featureId}`, { method: 'DELETE' })
871
+ });
872
+
873
+ // Used when a developer clicks the "Save" icon on the draw toolbar
874
+ map.geometry.store.onSave(async (data) => {
875
+ console.log('[Placeholder] MANUAL SAVE TRIGGERED', data);
876
+ // const success = await saveAllToDatabase(data);
877
+ // if (success) map.geometry.store.setDirty(false); // Clear unsaved warnings
878
+ });
879
+ ```
880
+
881
+ <!--
882
+ #### Unsaved Changes Protection
883
+ The SDK tracks if there are unsaved drawing changes (`isDirty = true`):
884
+ 1. **Closing Tab or Refreshing**: The browser will display its Native Alert (this cannot be styled due to browser security).
885
+ 2. **SPA / In-App Navigation**: You can trigger a **Custom UI Modal** (with blue/white buttons) using our built-in method before changing menus:
886
+
887
+ ```javascript
888
+ const action = await map.geometry.store.promptUnsavedChanges();
889
+ if (action === 'save') {
890
+ // await api.saveData();
891
+ } else if (action === 'cancel') {
892
+ return; // Stay on page
893
+ }
894
+ ```
895
+
896
+ Clear the protection manually once your API saves successfully:
897
+ ```javascript
898
+ map.geometry.store.setDirty(false);
899
+ ```
900
+ -->
901
+
902
+ Each callback receives a clean `payload` with `user_` prefixes stripped:
903
+
904
+ ```javascript
905
+ {
906
+ operation: 'create' | 'update' | 'delete',
907
+ source: 'draw' | 'programmatic',
908
+ format: 'standard' | 'indoor',
909
+ featureId: 'abc123',
910
+ kind: 'beacon' | 'poi' | ... | null, // null for standard
911
+ floor: 1 | null, // ordinal, null for standard
912
+ timestamp: '2026-03-27T...',
913
+ geometry: { type: 'Point', coordinates: [...] },
914
+ properties: { name: 'BKK-01', color: '#fbbf24' }
915
+ }
916
+ ```
917
+
918
+ > [!NOTE]
919
+ > If no callback is registered, the SDK logs all events to the console automatically with πŸ“¦ CREATE / ✏️ UPDATE / πŸ—‘οΈ DELETE icons.
920
+
921
+ ### 15.8 SDK Events
922
+
923
+ ```javascript
924
+ // Subscribe to every geometry change (create/update/delete)
925
+ map.on('geometryChanged', ({ operation, source, feature }) => {
926
+ // operation: 'create' | 'update' | 'delete'
927
+ // source: 'draw' | 'programmatic'
928
+ // feature: GeoJSON Feature (null for delete)
929
+ });
930
+
931
+ // Subscribe to live coordinate/property updates from the UI Editor
932
+ // Use this to refresh UI panels instantly without waiting for or triggering a backend save.
933
+ map.on('geometryLiveUpdate', ({ type, features }) => {
934
+ // type: 'update'
935
+ // features: Array of GeoJSON Features that are currently being edited
936
+ });
937
+
938
+ // Listen to selection changes
939
+ map.on('geometrySelectionChanged', ({ features }) => {
940
+ // Array of currently selected feature objects
941
+ });
942
+ ```
943
+
944
+ ---
945
+
946
+ ## 16. 🏠 Indoor Mapping
947
+
948
+ The PowerMap SDK provides a robust API for managing indoor environments, including floor switching, category filtering, and specialized geometry handling.
949
+
950
+ ### 16.1 Enabling Indoor Mode
951
+
952
+ Switch to indoor mode to enable building-specific logic and specialized drawing tools (e.g., Beacons, Walkways).
953
+
954
+ ```javascript
955
+ map.setMode('indoor');
956
+ ```
957
+
958
+ ### 16.2 Floor Management (`map.indoor`)
959
+
960
+ When in indoor mode, use the `map.indoor` facade to control the vertical context of the map.
961
+
962
+ | Method | Arguments | Description |
963
+ | :--- | :--- | :--- |
964
+ | `setFloor(ordinal, opts)` | `number, Object` | Switch to a specific floor level (e.g., 1, 2, -1). |
965
+ | `getAvailableFloors()` | - | Returns a sorted array of all unique floor ordinals found in the map data. |
966
+ | `getCurrentFloor()` | - | Returns the ordinal of the currently active floor. |
967
+
968
+ #### Switching Floors with Animation
969
+
970
+ ```javascript
971
+ // Switch to Floor 2 with a smooth fade transition
972
+ map.indoor.setFloor(2, { animate: true });
973
+ ```
974
+
975
+ #### Building a Dynamic Floor Selector UI
976
+
977
+ ```javascript
978
+ // Get all available floors to populate a dropdown
979
+ const floors = map.indoor.getAvailableFloors();
980
+ // Returns: [1, 2, 3] or [] if no indoor data is found
981
+ ```
982
+
983
+ ### 16.3 Visibility Control
984
+
985
+ Toggle the visibility of specific indoor data categories. These settings persist across floor changes.
986
+
987
+ ```javascript
988
+ // Toggle individual categories
989
+ map.indoor.setCategoryVisible('poi', true); // Points of Interest
990
+ map.indoor.setCategoryVisible('beacon', false); // Beacons/Sensors
991
+ map.indoor.setCategoryVisible('walkway', true); // Navigation Paths
992
+ map.indoor.setCategoryVisible('obstacle', true); // 3D Obstacles/Buildings
993
+ ```
994
+
995
+ ### 16.4 Indoor Data Kinds
996
+
997
+ The SDK categorizes indoor features using the `user_kind` (or `kind`) property:
998
+
999
+ - **floor**: The walkable surface area of a floor (Polygon).
1000
+ - **obstacle**: Non-walkable areas. Can be extruded into 3D.
1001
+ - **walkway**: Navigable path lines for indoor routing.
1002
+ - **poi**: Indoor points of interest with custom icons.
1003
+ - **beacon**: Positioning hardware or logical points.
1004
+
1005
+ ### 16.5 Event Synchronization
1006
+
1007
+ ```javascript
1008
+ // Listen for mode changes
1009
+ map.on('onModeChanged', ({ mode }) => {
1010
+ console.log(`Mode switched to: ${mode}`); // 'standard' or 'indoor'
1011
+ });
1012
+
1013
+ // Sync UI when data is added/deleted or imported
1014
+ map.on('geometryChanged', () => {
1015
+ const updatedFloors = map.indoor.getAvailableFloors();
1016
+ // Refresh your floor selector here
1017
+ });
1018
+ ```
1019
+
1020
+ ---
1021
+
1022
+ ## 17. ⌨️ Keyboard Shortcuts
1023
+
1024
+ The SDK provides built-in productivity shortcuts whenever the geometry engine is active:
1025
+
1026
+ - **Ctrl + C**: Copy selected geometry.
1027
+ - **Ctrl + V**: Paste geometry at the current cursor position.
1028
+ - **Delete / Backspace**: Remove selected features.
1029
+
1030
+ ---
1031
+
1032
+ ## 18. 🩺 Debug & Telemetry
1033
+
1034
+ Enable the Debug HUD during development to monitor performance and coordinates.
1035
+
1036
+ ```javascript
1037
+ // Enable debug panel
1038
+ map.debug().enable({ position: 'top-right' });
1039
+
1040
+ // Toggle specific metrics
1041
+ map.debug().showFPS(true);
1042
+ map.debug().showCoordinate(false);
1043
+
1044
+ // Disable
1045
+ map.debug().disable();
1046
+ ```
1047
+
1048
+ ---
1049
+
1050
+ ## 19. πŸ›  Build & Development
1051
+
1052
+ For contributors to the SDK core:
1053
+
1054
+ ### Contributor Guide
1055
+
1056
+ 1. **Setup Environment**:
1057
+
1058
+ ```bash
1059
+ npm install
1060
+ ```
1061
+
1062
+ 2. **Local Development**:
1063
+
1064
+ ```bash
1065
+ npm run dev
1066
+ ```
1067
+
1068
+ 3. **Production Build**:
1069
+
1070
+ ```bash
1071
+ npm run build
1072
+ ```
1073
+
1074
+ ### Project Structure
1075
+
1076
+ - `/src/core`: Map lifecycle and shared managers.
1077
+ - `/src/geometry`: Drawing engine and coordinate logic.
1078
+ - `/src/editor`: Toolbar UI and control components.
1079
+ - `/src/routing`: Navigation and path-finding.
1080
+ - `/src/auth`: Security and API tokens.
1081
+
1082
+ ---
1083
+
1084
+ ## πŸ“Œ Appendix A: Complete SDK Event List
1085
+
1086
+ The SDK emits the following events through its central `EventBus`. You can subscribe to them via `map.on(eventName, callback)`.
1087
+
1088
+ ### 🌍 Core / Global
1089
+ | Event | Payload | Description |
1090
+ |---|---|---|
1091
+ | `mapLoaded` | `{ map }` | Fired when PowerMap finishes loading the style and is ready. |
1092
+ | `mapStyleChanged` | `{ styleKey }` | Fired after switching the base map style. |
1093
+ | `modeChanged` | `{ mode, options }` | Fired when SDK interactive mode changes (e.g. `mode_free` β†’ `mode_routing`). |
1094
+ | `error` | `PowerMapError` | Fired globally for SDK internal errors. |
1095
+
1096
+ ### πŸ“ Geometry & Draw
1097
+ | Event | Payload | Description |
1098
+ |---|---|---|
1099
+ | `geometryChanged` | `{ operation/type, source, feature(s) }` | Fired upon completion of a CRUD operation or drawing action. |
1100
+ | `geometryLiveUpdate` | `{ type: 'update', features }` | Fired rapidly during property/coordinate editing for real-time UI mapping. |
1101
+ | `geometrySelectionChanged` | `{ features }` | Fired when the active selection changes in drawing mode. |
1102
+
1103
+ ### πŸ“ Markers
1104
+ | Event | Payload | Description |
1105
+ |---|---|---|
1106
+ | `markerAdded` | `{ marker, options }` | Fired when a new marker is added to the map. |
1107
+ | `markerRemoved` | `{ marker\|id }` | Fired when a marker is explicitly removed. |
1108
+ | `markerClick` | `{ marker, originalEvent, options }` | Fired when a marker is clicked. |
1109
+ | `markerDrag` | `{ marker, lngLat, options }` | Fired continuously while a draggable marker is being dragged. |
1110
+ | `markerDragEnd` | `{ marker, lngLat, options }` | Fired when a marker is dropped after dragging. |
1111
+ | `markersCleared`| `-` | Fired when all markers on the map are cleared. |
1112
+
1113
+ ### 🏒 Indoor
1114
+ | Event | Payload | Description |
1115
+ |---|---|---|
1116
+ | `floorChanged` | `ordinal (number)` | Fired when the active indoor floor changes. |
1117
+
1118
+ ### πŸ—ΊοΈ Routing & Navigation
1119
+ | Event | Payload | Description |
1120
+ |---|---|---|
1121
+ | `routeRequestStart`| `options` | Fired when a route calculation request is initiated. |
1122
+ | `routeCalculated` | `GeoJSON Feature` | Fired upon successful calculation of a MultiLineString route. |
1123
+ | `routeCleared` | `-` | Fired when the active route is removed from the map. |
1124
+ | `navigationStart` | `-` | Fired when Turn-by-Turn navigation is initiated. |
1125
+ | `navigationEnd` | `-` | Fired when Turn-by-Turn navigation stops. |
1126
+ | `simulationStart` | `-` | Fired when GPS Route Simulator starts running. |
1127
+ | `simulationUpdate`| `{ position, step }` | Fired at every tick of the GPS simulator with the interpolated position. |
1128
+ | `simulationEnd` | `-` | Fired when GPS simulation halts or reaches the destination. |
1129
+
1130
+ ### 🎨 Layers
1131
+ | Event | Payload | Description |
1132
+ |---|---|---|
1133
+ | `layerVisibilityChanged`| `{ layerId, visible }` | Fired when a system layer's visibility is toggled. |
1134
+ | `layerEnsured` | `{ layerId, group }` | Fired when the SDK auto-creates a missing required layer group. |
1135
+
1136
+ ---
1137
+
1138
+ Β© 2026 PowerMap Development Team. All rights reserved.