{"_id":"@aapicoits/powermap-web-sdk","_rev":"8-cb14d5ea43890c8f529ec08ffa0d174b","name":"@aapicoits/powermap-web-sdk","dist-tags":{"latest":"1.2.3"},"versions":{"1.0.0":{"name":"@aapicoits/powermap-web-sdk","version":"1.0.0","_id":"@aapicoits/powermap-web-sdk@1.0.0","maintainers":[{"name":"aits-dev","email":"app.aapico@gmail.com"}],"dist":{"shasum":"05efb2fb952aeecd98c7552dfb8b42326b3b59d2","tarball":"https://registry.npmjs.org/@aapicoits/powermap-web-sdk/-/powermap-web-sdk-1.0.0.tgz","fileCount":6,"integrity":"sha512-AFN41zQkib8hybS+i6Jm5bFB509/4FW+KMksZMSbZC4TmfVY3gjx6LLM7yLh6Nr+UuU/Hf5YhxllPXA6zE/k1Q==","signatures":[{"sig":"MEUCIA9evPWhSXWqCbhV6IRbpssxx+tBEfX6zQIYTAfnAilDAiEA031LWkhPcmKToRDFZEktriV5VeZuLnYmH0ufDlSg15c=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2900119},"main":"./dist/powermap-web-sdk.umd.cjs","type":"module","module":"./dist/powermap-web-sdk.js","exports":{".":{"import":"./dist/powermap-web-sdk.js","require":"./dist/powermap-web-sdk.umd.cjs"},"./dist/*":"./dist/*"},"gitHead":"62a22b6afc5e4667a58256fb3328260c41aa63fb","scripts":{"dev":"vite","build":"vite build","preview":"vite preview"},"_npmUser":{"name":"aits-dev","email":"app.aapico@gmail.com"},"_npmVersion":"11.6.2","description":"A professional-grade, high-performance Map SDK. Optimized for logistics, real-time tracking, indoor mapping, and 3D vehicle simulations.","directories":{},"_nodeVersion":"24.12.0","dependencies":{"three":"^0.162.0","lodash":"^4.17.21","@turf/turf":"^7.3.4","maplibre-gl":"^4.0.0","@mapbox/mapbox-gl-draw":"^1.5.1","@fortawesome/fontawesome-free":"^7.2.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^5.1.4"},"_npmOperationalInternal":{"tmp":"tmp/powermap-web-sdk_1.0.0_1778134623893_0.46044662847093565","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@aapicoits/powermap-web-sdk","version":"1.0.1","_id":"@aapicoits/powermap-web-sdk@1.0.1","maintainers":[{"name":"aits-dev","email":"app.aapico@gmail.com"}],"//":"@powermap/web-sdk","dist":{"shasum":"fc0c5eb2ff31753f51c773c31fdfa57adb9adc5e","tarball":"https://registry.npmjs.org/@aapicoits/powermap-web-sdk/-/powermap-web-sdk-1.0.1.tgz","fileCount":7,"integrity":"sha512-gVtWYUsUAtgRT8M3ozNweTCjNC2DBaY/fgomI0/aHu+EITEHashp6TcFpBn/0glwMiN5VX2A3XHo5SD7JXuFJA==","signatures":[{"sig":"MEUCIHZQz7ol8vTxREQsx3YT8ZftWV8+CBx4LwfxR0pbTZFzAiEAgfc9Tr5cFhPoAIpidixTfXDOHktMTS9vWNelFLXeqEY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2906836},"main":"./dist/powermap-web-sdk.umd.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/powermap-web-sdk.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/powermap-web-sdk.js","require":"./dist/powermap-web-sdk.umd.cjs"},"./dist/*":"./dist/*"},"gitHead":"62a22b6afc5e4667a58256fb3328260c41aa63fb","scripts":{"dev":"vite","build":"vite build","preview":"vite preview"},"_npmUser":{"name":"aits-dev","email":"app.aapico@gmail.com"},"_npmVersion":"11.6.2","description":"A professional-grade, high-performance Map SDK. Optimized for logistics, real-time tracking, indoor mapping, and 3D vehicle simulations.","directories":{},"_nodeVersion":"24.12.0","dependencies":{"three":"^0.162.0","lodash":"^4.17.21","@turf/turf":"^7.3.4","maplibre-gl":"^4.0.0","@mapbox/mapbox-gl-draw":"^1.5.1","@fortawesome/fontawesome-free":"^7.2.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^5.1.4","vite-plugin-dts":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/powermap-web-sdk_1.0.1_1778143068732_0.5893058497952837","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@aapicoits/powermap-web-sdk","version":"1.1.0","_id":"@aapicoits/powermap-web-sdk@1.1.0","maintainers":[{"name":"aits-dev","email":"app.aapico@gmail.com"}],"//":"@powermap/web-sdk","dist":{"shasum":"c8bb330019997e3305f692e89677a0a99159f290","tarball":"https://registry.npmjs.org/@aapicoits/powermap-web-sdk/-/powermap-web-sdk-1.1.0.tgz","fileCount":7,"integrity":"sha512-JSNgBFZVRVv1jMhlppLIL4OUegI5SSxPJmcioyTKHgPaJmv8f+wQWvWlQvRqMdvY2AMg0Xg9QZLQv5PasBP6UA==","signatures":[{"sig":"MEUCIQD7CjtUhMey/TfViHs+rQWfja8xY5c4ly+lnbntcYpETAIgMWkKWT/qPiYiTNTL7d5ooPJipncVS0HUE7zSB9xTZPg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2952304},"main":"./dist/powermap-web-sdk.umd.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/powermap-web-sdk.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/powermap-web-sdk.js","require":"./dist/powermap-web-sdk.umd.cjs"},"./dist/*":"./dist/*"},"gitHead":"daa0278ba73e08869ca5498965beac4d28a64fc8","scripts":{"dev":"vite","build":"vite build","preview":"vite preview"},"_npmUser":{"name":"aits-dev","email":"app.aapico@gmail.com"},"_npmVersion":"11.6.2","description":"A professional-grade, high-performance Map SDK. Optimized for logistics, real-time tracking, indoor mapping, and 3D vehicle simulations.","directories":{},"_nodeVersion":"24.12.0","dependencies":{"three":"^0.162.0","lodash":"^4.17.21","@turf/turf":"^7.3.4","maplibre-gl":"^4.0.0","@mapbox/mapbox-gl-draw":"^1.5.1","@fortawesome/fontawesome-free":"^7.2.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^5.1.4","vite-plugin-dts":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/powermap-web-sdk_1.1.0_1781507953417_0.06052659943107286","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@aapicoits/powermap-web-sdk","version":"1.1.1","_id":"@aapicoits/powermap-web-sdk@1.1.1","maintainers":[{"name":"aits-dev","email":"app.aapico@gmail.com"}],"//":"@powermap/web-sdk","dist":{"shasum":"e21ffe7aa8e4b2ec3df48f11991699e75a1db704","tarball":"https://registry.npmjs.org/@aapicoits/powermap-web-sdk/-/powermap-web-sdk-1.1.1.tgz","fileCount":7,"integrity":"sha512-T33ROV3KR4h81BLvNQSr1tEFfgusEXFBUAdBBR1A6dGojXWwIcBB7Mk/dIatohiu4nJOdBwz2X1s1fmuyCiF8A==","signatures":[{"sig":"MEUCIQCf7reLwOVVez07I8wio2MgUoPFUCnPdPDTEypEEiXFHAIgTQ3mG1pbPYfJ5vUNx5xm4PxpFI2wX+dxxcLTm/JeAMg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2959353},"main":"./dist/powermap-web-sdk.umd.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/powermap-web-sdk.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/powermap-web-sdk.js","require":"./dist/powermap-web-sdk.umd.cjs"},"./dist/*":"./dist/*"},"gitHead":"5ff1beacbdeeab1da8a6ab401b800f608b596fe2","scripts":{"dev":"vite","build":"vite build","preview":"vite preview"},"_npmUser":{"name":"aits-dev","email":"app.aapico@gmail.com"},"_npmVersion":"11.6.2","description":"A professional-grade, high-performance Map SDK. Optimized for logistics, real-time tracking, indoor mapping, and 3D vehicle simulations.","directories":{},"_nodeVersion":"24.12.0","dependencies":{"three":"^0.162.0","lodash":"^4.17.21","@turf/turf":"^7.3.4","maplibre-gl":"^4.0.0","@mapbox/mapbox-gl-draw":"^1.5.1","@fortawesome/fontawesome-free":"^7.2.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^5.1.4","vite-plugin-dts":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/powermap-web-sdk_1.1.1_1785904905029_0.003705581076413722","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@aapicoits/powermap-web-sdk","version":"1.2.0","_id":"@aapicoits/powermap-web-sdk@1.2.0","maintainers":[{"name":"aits-dev","email":"app.aapico@gmail.com"}],"//":"@powermap/web-sdk","dist":{"shasum":"110ce71e2fa9718735ba56cffd65e9223970e57d","tarball":"https://registry.npmjs.org/@aapicoits/powermap-web-sdk/-/powermap-web-sdk-1.2.0.tgz","fileCount":7,"integrity":"sha512-wiNLO13ynH0W4ufilrZ1iWLCDQkEu9kYTuUkm46V/xPMB4q7MH24hFf5RhijgC4KAENKPJGDtXsC9V7/KjHwnA==","signatures":[{"sig":"MEQCID97gBblxPbIDLEmtTbcWeL1v97r6wBrbOfPgcsozygCAiB/bTV2mD1u7PnRuNUfGTPXd6QLAKlLOlt0wUXClUQOaQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":3322887},"main":"./dist/powermap-web-sdk.umd.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/powermap-web-sdk.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/powermap-web-sdk.js","require":"./dist/powermap-web-sdk.umd.cjs"},"./dist/*":"./dist/*"},"gitHead":"7510fb08b3848716c3ea3c45406a57f49f6426f7","scripts":{"dev":"vite","build":"vite build","preview":"vite preview"},"_npmUser":{"name":"aits-dev","email":"app.aapico@gmail.com"},"_npmVersion":"11.6.2","description":"A professional-grade, high-performance Map SDK. Optimized for logistics, real-time tracking, indoor mapping, and 3D vehicle simulations.","directories":{},"_nodeVersion":"24.12.0","dependencies":{"three":"^0.162.0","lodash":"^4.17.21","@turf/turf":"^7.3.4","maplibre-gl":"^4.0.0","@mapbox/mapbox-gl-draw":"^1.5.1","@fortawesome/fontawesome-free":"^7.2.0","@mapbox/mapbox-gl-draw-static-mode":"^1.0.1"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^5.1.4","vite-plugin-dts":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/powermap-web-sdk_1.2.0_1786347041102_0.02313268786625322","host":"s3://npm-registry-packages-npm-production"}},"1.2.1":{"name":"@aapicoits/powermap-web-sdk","version":"1.2.1","_id":"@aapicoits/powermap-web-sdk@1.2.1","maintainers":[{"name":"aits-dev","email":"app.aapico@gmail.com"}],"//":"@powermap/web-sdk","dist":{"shasum":"debcf2514d9d739df92fc932fdb4b9e4dd9ff41b","tarball":"https://registry.npmjs.org/@aapicoits/powermap-web-sdk/-/powermap-web-sdk-1.2.1.tgz","fileCount":7,"integrity":"sha512-RO+rpgIusujfAu56l55GwmcPKOMV9IzCGU6MkZ0M5yoK8+rIqyXVBX5ZpeIx+Qv4KVZdDtm39vPaT+/aEO1TcA==","signatures":[{"sig":"MEYCIQDpYU0EgzKopiSXL7HYZL0MH5cQ5WF1Wiq2M93YzhT/OAIhAIad9mFxvf894J8S7kdubPd3ogQ0+VOBaoHt35hPX1f0","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":3322941},"main":"./dist/powermap-web-sdk.umd.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/powermap-web-sdk.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/powermap-web-sdk.js","require":"./dist/powermap-web-sdk.umd.cjs"},"./dist/*":"./dist/*"},"gitHead":"5b47a7c7c878c315bd291a12b4696ec87f33905d","scripts":{"dev":"vite","build":"vite build","preview":"vite preview"},"_npmUser":{"name":"aits-dev","email":"app.aapico@gmail.com"},"_npmVersion":"11.6.2","description":"A professional-grade, high-performance Map SDK. Optimized for logistics, real-time tracking, indoor mapping, and 3D vehicle simulations.","directories":{},"_nodeVersion":"24.12.0","dependencies":{"three":"^0.162.0","lodash":"^4.17.21","@turf/turf":"^7.3.4","maplibre-gl":"^4.0.0","@mapbox/mapbox-gl-draw":"^1.5.1","@fortawesome/fontawesome-free":"^7.2.0","@mapbox/mapbox-gl-draw-static-mode":"^1.0.1"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^5.1.4","vite-plugin-dts":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/powermap-web-sdk_1.2.1_1786603498890_0.543975139481967","host":"s3://npm-registry-packages-npm-production"}},"1.2.2":{"name":"@aapicoits/powermap-web-sdk","version":"1.2.2","_id":"@aapicoits/powermap-web-sdk@1.2.2","maintainers":[{"name":"aits-dev","email":"app.aapico@gmail.com"}],"//":"@powermap/web-sdk","dist":{"shasum":"b8fa0c28eeca68fa60936dae4f79b51acad4c8ff","tarball":"https://registry.npmjs.org/@aapicoits/powermap-web-sdk/-/powermap-web-sdk-1.2.2.tgz","fileCount":7,"integrity":"sha512-hU1fi0cqyYUlFa0o8qVnt/zy+TvGAGcO8Z+vN2MhCT4B8ZteuBmJ3CXy7R3WYlDyFP6/7aK3qG7XrLQGM4TPQw==","signatures":[{"sig":"MEUCIQCk3TnfTqAOzf1XQLhZjwJ9fBloLrZAhjctu1JUx//BaQIgIjTIGd0TpHZGBemZ6DbDeGy6uLaCLYBKovBVaAlt9I0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":3331643},"main":"./dist/powermap-web-sdk.umd.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/powermap-web-sdk.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/powermap-web-sdk.js","require":"./dist/powermap-web-sdk.umd.cjs"},"./dist/*":"./dist/*"},"gitHead":"e222b6728c077838573091747c8db65e7c81fb4b","scripts":{"dev":"vite","build":"vite build","preview":"vite preview"},"_npmUser":{"name":"aits-dev","email":"app.aapico@gmail.com"},"_npmVersion":"11.6.2","description":"A professional-grade, high-performance Map SDK. Optimized for logistics, real-time tracking, indoor mapping, and 3D vehicle simulations.","directories":{},"_nodeVersion":"24.12.0","dependencies":{"three":"^0.162.0","lodash":"^4.17.21","@turf/turf":"^7.3.4","maplibre-gl":"^4.0.0","@mapbox/mapbox-gl-draw":"^1.5.1","@fortawesome/fontawesome-free":"^7.2.0","@mapbox/mapbox-gl-draw-static-mode":"^1.0.1"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^5.1.4","vite-plugin-dts":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/powermap-web-sdk_1.2.2_1788164373248_0.5664581188109106","host":"s3://npm-registry-packages-npm-production"}},"1.2.3":{"//":"@powermap/web-sdk","name":"@aapicoits/powermap-web-sdk","version":"1.2.3","type":"module","main":"./dist/powermap-web-sdk.umd.cjs","module":"./dist/powermap-web-sdk.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/powermap-web-sdk.js","require":"./dist/powermap-web-sdk.umd.cjs"},"./dist/*":"./dist/*"},"scripts":{"dev":"vite","build":"vite build","preview":"vite preview"},"dependencies":{"@fortawesome/fontawesome-free":"^7.2.0","@mapbox/mapbox-gl-draw":"^1.5.1","@mapbox/mapbox-gl-draw-static-mode":"^1.0.1","@turf/turf":"^7.3.4","lodash":"^4.17.21","maplibre-gl":"^4.0.0","three":"^0.162.0"},"devDependencies":{"vite":"^5.1.4","vite-plugin-dts":"^5.0.0"},"gitHead":"ee3c0f3d0fd9b78e68e2f7412db2d93a3aea2b67","_id":"@aapicoits/powermap-web-sdk@1.2.3","description":"A professional-grade, high-performance Map SDK. Optimized for logistics, real-time tracking, indoor mapping, and 3D vehicle simulations.","_nodeVersion":"24.12.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-uxn0LHvW7CXzGaP8c62Fb37kWgeCys6yrIfhnLfJlp01EYfJUxqnOjApw0EgmJ598J5KkPQH81Z9WOycgInt0w==","shasum":"64273004e9182f7cda566ce6283cdc5f64453e3d","tarball":"https://registry.npmjs.org/@aapicoits/powermap-web-sdk/-/powermap-web-sdk-1.2.3.tgz","fileCount":7,"unpackedSize":3369777,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQD9RmyEGVUX/Q7BTYEV8gf8FuRiTVYLdRLF7FfP314zXgIhANgz50GFGGJrgGdO96K5AtnQy3jUaJqfZMea9Cmi7PBo"}]},"_npmUser":{"name":"aits-dev","email":"app.aapico@gmail.com"},"directories":{},"maintainers":[{"name":"aits-dev","email":"app.aapico@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/powermap-web-sdk_1.2.3_1788245380880_0.024376863765650736"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-07T06:17:03.809Z","modified":"2026-09-01T06:49:41.260Z","1.0.0":"2026-05-07T06:17:04.120Z","1.0.1":"2026-05-07T08:37:49.039Z","1.1.0":"2026-06-15T07:19:13.638Z","1.1.1":"2026-08-05T04:41:45.260Z","1.2.0":"2026-08-10T07:30:41.352Z","1.2.1":"2026-08-13T06:44:59.105Z","1.2.2":"2026-08-31T08:19:33.483Z","1.2.3":"2026-09-01T06:49:41.083Z"},"description":"A professional-grade, high-performance Map SDK. Optimized for logistics, real-time tracking, indoor mapping, and 3D vehicle simulations.","maintainers":[{"name":"aits-dev","email":"app.aapico@gmail.com"}],"readme":"# PowerMap Web SDK 🚀\r\n\r\nA professional-grade, high-performance Map SDK. Optimized for logistics, real-time tracking, indoor mapping, and 3D vehicle simulations.\r\n\r\n<!-- [![npm version](https://img.shields.io/npm/v/@powermap/web-sdk.svg?style=flat-square)](https://www.npmjs.com/package/@powermap/web-sdk) -->\r\n\r\n---\r\n\r\n## 2. 🏛️ Architecture Overview\r\n\r\nThe 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.\r\n\r\n```mermaid\r\ngraph TD\r\n    App[Web Application] --> PM[PowerMap Instance]\r\n    \r\n    subgraph \"Facade Layer (Fluent API)\"\r\n        PM\r\n    end\r\n\r\n    subgraph \"Manager Layer (Lazy Loading)\"\r\n        PM --> Registry[Component Registry]\r\n        Registry --> MarkerMgr[MarkerManager]\r\n        Registry --> SearchMgr[SearchManager]\r\n        Registry --> RouteMgr[RoutingManager]\r\n        Registry --> GeomMgr[GeometryManager]\r\n        Registry --> EditorMgr[EditorManager]\r\n        Registry --> LayerMgr[LayerManager]\r\n    end\r\n\r\n    subgraph \"Service Layer\"\r\n        PM --> AuthMgr[AuthManager]\r\n        PM --> Events[EventBus]\r\n        AuthMgr --> Cache[Token Cache]\r\n    end\r\n\r\n    subgraph \"Visual Engine\"\r\n        PM --> ML[PowerMap Visual Engine]\r\n    end\r\n\r\n    Cache -.-> Storage[(Local Storage)]\r\n    AuthMgr -.-> API((PowerMap APIs))\r\n```\r\n\r\n---\r\n\r\n## 3. 📦 Installation\r\n\r\nInstall the package via npm or yarn:\r\n\r\n```bash\r\nnpm install @powermap/web-sdk\r\n```\r\n\r\n---\r\n\r\n## 4. 🚀 Quick Start\r\n\r\nGet a map up and running in less than 2 minutes. The PowerMap SDK is designed for chaining and ease of use.\r\n\r\n### Step A: Prepare your HTML\r\n\r\n```html\r\n<div id=\"map\" style=\"width: 100%; height: 500px; border-radius: 8px; overflow: hidden;\"></div>\r\n```\r\n\r\n### Step B: Initialize and Style\r\n\r\n```javascript\r\nimport { PowerMap } from \"@powermap/web-sdk\";\r\n\r\n// 1. Initialize with dual-auth system\r\nconst map = new PowerMap({\r\n  container: \"map\",\r\n  center: [100.5018, 13.7563], // Bangkok [Longitude, Latitude]\r\n  zoom: 14,\r\n  mapApiKey: \"YOUR_MAP_API_KEY\", // Public key for tiles and base layers\r\n  auth: {\r\n    clientId: \"YOUR_CLIENT_ID\",     // Service Credentials for backend APIs\r\n    clientSecret: \"YOUR_CLIENT_SECRET\"\r\n  },\r\n  previewOnly: true // Optional: Lock all user interactions (Static View)\r\n});\r\n\r\n// 2. Chaining for smooth DX\r\nmap.onMapLoaded((e) => {\r\n  console.log(\"PowerMap is ready!\", e.map);\r\n  \r\n  // Add a marker as soon as the map loads\r\n  map.addMarker({\r\n    position: [100.5018, 13.7563],\r\n    type: \"start\",\r\n    popupContent: \"<strong>Origin Point</strong>\"\r\n  });\r\n})\r\n.setStyle('th') // Options: 'th', 'en', 'dark', 'gray'\r\n.flyTo({ zoom: 16, pitch: 45 });\r\n```\r\n\r\n> [!TIP]\r\n> **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.\r\n\r\n---\r\n\r\n## 5. 🔗 Method Chaining\r\n\r\nThe PowerMap SDK is built with a **fluent interface**, allowing you to link multiple configuration steps and event listeners together in a single statement.\r\n\r\n### Supported Methods\r\n\r\nMost methods that perform an action on the map (styling, camera movement, or event registration) return the `PowerMap` instance to facilitate chaining.\r\n\r\n| Group | Chainable Methods |\r\n| :--- | :--- |\r\n| **Events** | `on`, `off`, `onMapLoaded`, `onClick`, `onMapLongClick`, `onMarkerClick`, `onPoiClick`, `onMarkerDrag`, `onMarkerDragEnd`, `onRouteCalculated`, `onError` |\r\n| **Camera** | `flyTo`, `easeTo`, `fitBounds`, `jumpTo`, `setPitch`, `setBearing`, `zoomIn`, `zoomOut`, `rotateLeft`, `rotateRight` |\r\n| **Map Style** | `setStyle` |\r\n| **Markers** | `removeMarker`, `updatePopup`, `clearMarkers` |\r\n| **Search** | `search` |\r\n| **Routing** | `clearRoutes` |\r\n| **Layers** | `showLayer`, `hideLayer`, `toggleLayer` |\r\n<!-- | **Sim/Nav** | `startSimulation`, `stopSimulation`, `startNavigation`, `stopNavigation` | -->\r\n\r\n> [!NOTE]\r\n> Methods that return a **Promise** (like `addRoute`) or a **new Object** (like `addMarker` or `getRouteDetails`) interrupt the chain.\r\n\r\n### Chaining Example\r\n\r\n```javascript\r\nmap.onMapLoaded(() => console.log(\"Ready!\"))\r\n  .onMarkerClick(data => console.log(\"Clicked:\", data))\r\n  .setStyle('dark')\r\n  .flyTo({ center: [100.5, 13.7], zoom: 12 })\r\n  .setPitch(45);\r\n```\r\n\r\n---\r\n\r\n## 6. 🛠️ Configuration Reference\r\n\r\nWhen creating a `new PowerMap(options)`, the following options are available:\r\n\r\n| Option | Type | Default | Required | Description |\r\n| :--- | :--- | :--- | :--- | :--- |\r\n| `container` | `string` | - | **Yes** | The HTML element ID where the map will be rendered. |\r\n| `mapApiKey` | `string` | - | **Yes** | Your PowerMap Public API Key for map tiles. |\r\n| `auth` | `Object` | - | **Yes** | **Service Credentials**. Used for connecting to backend APIs of PowerMap services (e.g., Routing, Optimization). Contains `clientId` and `clientSecret`. |\r\n| `center` | `[lng, lat]` | `[100, 13]` | No | The initial center point of the map. |\r\n| `zoom` | `number` | `14` | No | Total zoom level (0-22). |\r\n| `baseUrl` | `string` | - | No | Optional proxy URL (e.g., `/api/proxy`). If set, the SDK will route all API requests through this URL. |\r\n| `maptilerKey` | `string` | - | No | Your MapTiler API Key. Required if you intend to use high-resolution satellite layers. |\r\n| `previewOnly` | `boolean` | `false` | No | If **true**, the map will be non-interactive (no zoom, pan, rotate, or pitch). Perfect for static map previews. |\r\n\r\n### Core Instance Methods\r\n\r\nThese methods are available on the `PowerMap` instance to manage the map state and lifecycle.\r\n\r\n| Method | Arguments | Description |\r\n| :--- | :--- | :--- |\r\n| `on(event, cb)` | `string, function` | Subscribe to any map event (see Section 9). |\r\n| `off(event, cb)` | `string, function` | Unsubscribe from a previously registered event. |\r\n| `setMode(mode, opts)` | `string, Object` | Switch between `'standard'` and `'indoor'` modes. |\r\n| `setStyle(name)` | `string` | Change the map style (e.g., `'th'`, `'dark'`). |\r\n| `setNavigationControl(opts)` | `boolean\\|Object` | Enable/disable or customize zoom/compass controls. |\r\n| `setFullscreenControl(opts)` | `boolean\\|Object` | Enable/disable fullscreen control. |\r\n| `debug()` | - | Access the Debug Manager for telemetry and performance tools. |\r\n| `onError(callback)` | `function` | Global error listener for Auth, Map, or Routing issues. |\r\n\r\n---\r\n\r\n## 7. 📍 Marker Management\r\n\r\nOur marker system simplifies standard map markers with built-in presets for logistics and easy event handling.\r\n\r\n### `addMarker(options)` Options\r\n\r\n| Option | Type | Default | Description |\r\n| :--- | :--- | :--- | :--- |\r\n| `position` | `[lng, lat]` | **Required** | Coordinates for the marker. |\r\n| `type` | `string` | `'default'` | Preset icons: `'start'`, `'destination'`, `'waypoint'`. |\r\n| `iconUrl` | `string` | - | Custom image URL to use as the icon. Overrides `type`. |\r\n| `width`/`height` | `string` | `'30px'`/`'38px'` | CSS dimensions for the marker icon. |\r\n| `anchor` | `string` | `'bottom'` | Where the marker is anchored to the coordinate. |\r\n| `draggable` | `boolean` | `false` | If true, user can drag the marker. Fires `onMarkerDrag` events. |\r\n| `rotation` | `number` | `0` | Icon rotation in degrees. |\r\n| `popupContent` | `string|Object` | - | HTML string or **Structured Object** to display in a premium popup. |\r\n| `id` | `string` | - | **Unique ID** to track this marker. Allows retrieval via `getMarker(id)`. |\r\n| `color` | `string` | - | Custom hex color for pinpoint marker (e.g. `'#ff4444'`). Uses **High-Accuracy GL Rendering**. |\r\n| `icon` | `string` | - | FontAwesome icon class for pinpoint marker (e.g. `'fas fa-car'`). |\r\n| `element` | `HTMLElement` | - | Use a raw HTML element as a fully custom marker. |\r\n\r\n> [!IMPORTANT]\r\n> **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.\r\n\r\n### Marker Examples\r\n\r\n#### Standard Presets\r\n\r\n```javascript\r\nmap.addMarker({\r\n  position: [100.5, 13.7],\r\n  type: 'start', // 'start', 'destination', 'waypoint'\r\n  popupContent: \"Start Point\"\r\n});\r\n```\r\n\r\n#### Premium Colored Markers (No Image Required)\r\n\r\n```javascript\r\nmap.addMarker({\r\n  position: [100.52, 13.78],\r\n  color: '#f97316',      // Vibrant Orange\r\n  icon: 'fas fa-store',  // FontAwesome Icon\r\n  draggable: true,      // Enable high-accuracy dragging\r\n  popupContent: {\r\n    title: \"Power Shop\",\r\n    tag: \"Retail\",\r\n    body: \"Custom colored marker with icon.\"\r\n  }\r\n});\r\n```\r\n\r\n#### Draggable Markers & Events\r\n\r\nYou can enable dragging for any marker. For GL-rendered markers (those with `color`), dragging is handled with sub-pixel precision.\r\n\r\n```javascript\r\nconst marker = map.addMarker({\r\n  position: [100.5, 13.7],\r\n  color: '#3b82f6',\r\n  draggable: true\r\n});\r\n\r\n// 1. Listen via Proxy (Recommended)\r\nmarker.on('drag', (e) => console.log('Current:', e.lngLat));\r\nmarker.on('dragend', (e) => console.log('Final:', e.lngLat));\r\n\r\n// 2. Listen via Global Event Bus\r\nmap.onMarkerDrag(({ marker, lngLat }) => {\r\n  console.log(`Marker is moving to: ${lngLat.lng}, ${lngLat.lat}`);\r\n});\r\n```\r\n\r\n#### Structured Popup Content & Actions\r\n\r\nInstead 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:\r\n\r\n```javascript\r\nmap.addMarker({\r\n  position: [100.5, 13.7],\r\n  popupContent: {\r\n    title: \"Siam Paragon\",\r\n    tag: \"Mall\",\r\n    body: \"The pride of Bangkok with world-class shopping.\",\r\n    footer: \"Open: 10:00 - 22:00\",\r\n    actions: [\r\n      { label: \"View Details\", url: \"https://example.com/details\", target: \"_blank\" },\r\n      { label: \"Navigate Here\", url: \"https://example.com/nav\", color: \"#10b981\" }\r\n    ]\r\n  }\r\n});\r\n```\r\n\r\n### Marker Methods\r\n\r\n| Method | Arguments | Description |\r\n| :--- | :--- | :--- |\r\n| `addMarker(options)` | `Object` | Create and add a marker. Returns marker instance. |\r\n| `getMarker(id)` | `string` | Retrieve a marker instance by its unique ID. |\r\n| `removeMarker(markerOrId)` | `Object\\|string` | Remove a marker using instance or ID. |\r\n| `updatePopup(marker, content)` | `Object, string\\|Object` | Update the popup content (HTML or Structured Object). |\r\n| `clearMarkers()` | - | Remove all markers from the map. |\r\n\r\n---\r\n\r\n## 8. 🎨 CSS Customization\r\n\r\nThe PowerMap SDK automatically wraps all map elements in a global `.powermap-sdk` class and provides branded aliases for all internal UI components.\r\n\r\n### The Global Wrapper\r\n\r\nAny 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.\r\n\r\n```html\r\n<!-- Input -->\r\n<div id=\"map\"></div>\r\n\r\n<!-- Output at runtime -->\r\n<div id=\"map\" class=\"powermap-sdk\">...</div>\r\n```\r\n\r\n### Premium Class Aliases\r\n\r\nTo make customization even more intuitive, we've provided branded CSS classes that you can target directly:\r\n\r\n| Feature | PowerMap Class | Description |\r\n| :--- | :--- | :--- |\r\n| **Marker** | `.pm-marker` | The main container for any marker. |\r\n| **Popup** | `.pm-popup` | The root container of a popup. |\r\n| **Popup Content** | `.pm-popup-content` | The main content area (glassmorphism). |\r\n| **Popup Header** | `.pm-popup-header` | The header (part of structured content). |\r\n| **Popup Body** | `.pm-popup-body` | The body text area. |\r\n| **Popup Tag** | `.pm-popup-tag` | The category tag. |\r\n| **Popup Footer** | `.pm-popup-footer` | The bottom meta info area. |\r\n| **Popup Close** | `.pm-popup-close` | The close button element. |\r\n| **Popup Tip** | `.pm-popup-tip` | The triangular tip pointing to the marker. |\r\n| **Nav Group** | `.pm-ctrl-group` | The container of zoom/compass buttons. |\r\n| **Zoom In** | `.pm-ctrl-zoom-in` | The zoom in button. |\r\n| **Zoom Out** | `.pm-ctrl-zoom-out` | The zoom out button. |\r\n| **Compass** | `.pm-ctrl-compass` | The compass/rotation button. |\r\n\r\n### Premium Overrides\r\n\r\nTarget your own CSS using the scoped wrapper or branded aliases:\r\n\r\n```css\r\n/* Change the header color of structured popups */\r\n.powermap-sdk .pm-popup-header {\r\n  background: linear-gradient(135deg, #000 0%, #333 100%);\r\n}\r\n\r\n/* Customize the glassmorphism background of all popups */\r\n.powermap-sdk .pm-popup-content {\r\n  background: rgba(255, 255, 255, 0.95);\r\n  backdrop-filter: blur(20px);\r\n}\r\n\r\n/* Custom Marker Scale */\r\n.powermap-sdk .pm-marker {\r\n  transform: scale(1.1);\r\n}\r\n```\r\n\r\n### `setNavigationControl(options)` Options\r\n\r\n| Option | Type | Default | Description |\r\n| :--- | :--- | :--- | :--- |\r\n| `position` | `string` | `'top-right'` | Position on the map: `'top-left'`, `'top-right'`, `'bottom-left'`, `'bottom-right'`. |\r\n| `showZoom` | `boolean` | `true` | If `true`, display the zoom in and zoom out buttons. |\r\n| `showCompass` | `boolean` | `true` | If `true`, display the compass (rotation) button. |\r\n| `visualizePitch` | `boolean` | `true` | If `true`, the compass button will rotate on the Y-axis to visualize map pitch (3D view). |\r\n\r\n### Map Control Examples\r\n\r\n```javascript\r\n// Simple: Enable default branded controls (top-right)\r\nmap.setNavigationControl(true);\r\n\r\n// Advanced: Custom position and visibility\r\nmap.setNavigationControl({\r\n  position: 'bottom-right',\r\n  showZoom: true,\r\n  showCompass: false\r\n});\r\n\r\n// Disable controls\r\nmap.setNavigationControl(false);\r\n```\r\n\r\n---\r\n\r\n## 9. 🛣️ Routing & Optimization\r\n\r\nGenerate high-precision routes with automatic visualization. The SDK automatically fits the map view to the resulting route.\r\n\r\n### `addRoute(options)` Options\r\n\r\n| Option | Type | Default | Description |\r\n| :--- | :--- | :--- | :--- |\r\n| `start` | `[lng, lat]` | **Required** | Start point coordinates. |\r\n| `destination` | `[lng, lat]` | **Required** | End point coordinates. |\r\n| `waypoints` | `Array<[lng, lat]>` | `[]` | List of points to hit between start and end. |\r\n| `optimize` | `boolean` | `false` | If true, reorders waypoints to find the shortest total trip (TSP). **Requires at least 1 waypoint**. |\r\n| `profile` | `string` | `'driving'` | Routing engine profile: `'driving'`, `'walking'`, `'cycling'`. |\r\n| `language` | `string` | `'th'` | Return instructions/names in `'th'` or `'en'`. |\r\n| `routeColor` | `string` | `'#3b82f6'` | Hex color code for the main route line. |\r\n| `routeWidth` | `number` | `5` | Thickness of the main route line. |\r\n| `routeOpacity` | `number` | `1.0` | Opacity of the route line (0.0 to 1.0). |\r\n| `casingColor` | `string` | `'#1d4ed8'` | Hex color for the route's outer border (shadow). |\r\n| `casingWidth` | `number` | `8` | Thickness of the casing line. |\r\n\r\n### Route Methods\r\n\r\n| Method | Arguments | Description |\r\n| :--- | :--- | :--- |\r\n| `addRoute(options)` | `Object` | Calculate, draw route and fit view. Returns Promise. |\r\n| `clearRoutes()` | - | Remove all route lines and markers from map. |\r\n| `getRouteDetails()` | - | Returns distance, duration, and formatted strings. |\r\n| `getRouteSummary()` | - | Returns top-level distance/duration strings. |\r\n\r\n### `addRoute(options)` Return Object\r\n\r\nThe `addRoute` method returns a Promise that resolves to a `RouteResult` object containing:\r\n\r\n| Property | Type | Description |\r\n| :--- | :--- | :--- |\r\n| `distance` | `number` | Total distance of the route in **meters**. |\r\n| `duration` | `number` | Estimated travel time in **seconds**. |\r\n| `steps` | `Array<Object>` | Turn-by-turn instructions (text, distance, duration, location). |\r\n| `geometry` | `Object` | GeoJSON `LineString` of the route. |\r\n| `raw` | `Object` | Raw response from the PowerMap routing backend. |\r\n\r\n#### `steps` Object Structure\r\n\r\nEach step in the `steps` array represents a turn or maneuver:\r\n\r\n| Field | Type | Description |\r\n| :--- | :--- | :--- |\r\n| `text` | `string` | The maneuver instruction (e.g., \"เลี้ยวขวาที่ถนนสุขุมวิท\"). |\r\n| `distance` | `number` | Distance to travel for this step (meters). |\r\n| `duration` | `number` | Estimated time for this step (seconds). |\r\n| `location` | `[lng, lat]` | Geographic coordinate where the maneuver starts. |\r\n\r\n### Routing Example\r\n\r\n```javascript\r\n// Simple route from A to B\r\nconst route = await map.addRoute({\r\n  start: [100.5018, 13.7563],\r\n  destination: [100.5118, 13.7663],\r\n  language: \"en\"\r\n});\r\n\r\n// Complex optimized route with waypoints\r\nconst optimizedRoute = await map.addRoute({\r\n  start: [100.50, 13.75],\r\n  destination: [100.60, 13.85],\r\n  waypoints: [\r\n    [100.55, 13.80],\r\n    [100.52, 13.78]\r\n  ],\r\n  optimize: true, // Will reorder the 2 waypoints for the best efficiency\r\n  language: \"th\"\r\n});\r\n\r\nconsole.log(`Total Distance: ${optimizedRoute.distance}m`);\r\nconsole.log(`Total Duration: ${optimizedRoute.duration}s`);\r\n\r\n// Custom red route for heavy traffic visualization\r\nconst redRoute = await map.addRoute({\r\n  start: [100.5, 13.7],\r\n  destination: [100.6, 13.8],\r\n  routeColor: \"#ff0000\",\r\n  routeWidth: 10,\r\n  casingColor: \"#990000\"\r\n});\r\n\r\n// Clear all routes from visibility\r\nmap.clearRoutes();\r\n```\r\n\r\n#### 📍 Route Summary Helpers\r\n\r\nQuickly access formatted summaries (distance in km/m, time in hours/mins) for the total route and individual steps:\r\n\r\n```javascript\r\n// Get enhanced details (Recommended)\r\nconst details = map.getRouteDetails();\r\nif (details) {\r\n    console.log(`Total: ${details.distanceStr}, Time: ${details.durationStr}`);\r\n    \r\n    // Each step also contains formatted strings\r\n    details.steps.forEach(step => {\r\n        console.log(`${step.text} (${step.distanceStr})`);\r\n    });\r\n}\r\n\r\n// Get just the top-level summary\r\nconst summary = map.getRouteSummary(); // { distanceStr, durationStr }\r\n```\r\n\r\n### 9.1 Built-in Routing UI (Routing Panel)\r\n\r\nThe SDK provides a built-in outdoor routing panel that automatically handles searching, waypoint management, and turn-by-turn navigation UI.\r\n\r\n```javascript\r\n// Add the routing panel to the map\r\nconst routingControl = new PowerMap.RoutingPanelControl({\r\n  defaultProfile: 'driving', // 'driving', 'foot', 'bicycle', 'motorcycle'\r\n  onLocationSelect: (location) => {\r\n    console.log(\"Selected Location:\", location);\r\n  },\r\n  onRouteCalculated: (routeResult) => {\r\n    console.log(\"Route calculated:\", routeResult);\r\n  },\r\n  onClear: () => {\r\n    console.log(\"Route cleared\");\r\n  }\r\n});\r\n\r\nmap.addControl(routingControl);\r\n\r\n// Programmatically clear the routing panel and map route\r\n// routingControl.clear();\r\n```\r\n\r\n---\r\n\r\n## 🏗️ 10. Map Layers Management\r\n\r\nThe `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.\r\n\r\n### Built-in Layer Groups\r\n\r\nMap layers are grouped into 3 main categories:\r\n\r\n1. **CITY PLAN** (`city`): City plans for various provinces.\r\n2. **TRANSPORT** (`transport`): Roads, Current/Future Railways, Current/Future BTS/MRT.\r\n3. **INSIGHT DATA** (`insight`): Night lights, Satellite imagery.\r\n\r\n### Layer APIs\r\n\r\n| Method | Arguments | Description |\r\n| :--- | :--- | :--- |\r\n| `map.layers.getCatalog()` | - | Returns the full list of available layer groups and IDs. |\r\n| `map.layers.ensure(layerId)` | `string` | Ensures a layer's sources are created (idempotent). |\r\n| `map.layers.show(layerId)` | `string` | Ensures and shows a specific layer. |\r\n| `map.layers.hide(layerId)` | `string` | Hides a specific layer. |\r\n| `map.layers.toggle(layerId)` | `string` | Toggles the visibility of a layer. |\r\n| `map.layers.getLayerState(id)` | `string` | Returns `{loaded, visible}` status. |\r\n| `map.layers.setOpacity(id, val)` | `string, number` | Adjusts the transparency (0.0 - 1.0). |\r\n| `map.layers.showGroup(key)` | `string` | Shows all layers within a specific group. |\r\n| `map.layers.hideGroup(key)` | `string` | Hides all layers within a specific group. |\r\n| `map.layers.hideAll()` | - | Hides every active layer from the catalog. |\r\n\r\n### Usage Example\r\n\r\n```javascript\r\nmap.onMapLoaded(async () => {\r\n  // 1. View all available layers\r\n  const catalog = map.layers.getCatalog();\r\n  console.log(catalog);\r\n\r\n  // 2. Show the Road layer\r\n  await map.layers.show('pm-road-vt');\r\n\r\n  // 3. Toggle the current Railway layer\r\n  await map.layers.toggle('pm-current-railway-th-vt');\r\n\r\n  // 4. Adjust opacity\r\n  map.layers.setOpacity('pm-road-vt', 0.5);\r\n\r\n  // 5. Hide an entire group\r\n  map.layers.hideGroup('city');\r\n});\r\n```\r\n\r\nYou can also use convenience methods directly on the `PowerMap` instance:\r\n```javascript\r\nawait map.showLayer('pm-road-vt');\r\nmap.hideLayer('pm-road-vt');\r\nawait map.toggleLayer('pm-road-vt');\r\n```\r\n\r\n> [!IMPORTANT]\r\n> **Satellite Layer**: High-resolution satellite imagery (`pm-maptiler-satellite-layer`) requires a `maptilerKey` provided in the initial `PowerMap` configuration.\r\n\r\n---\r\n\r\n## 11. 🎥 Camera & Viewport Control\r\n\r\nTake full control of the user's perspective with smooth transitions or direct manipulation.\r\n\r\n### Movement Functions\r\n\r\n| Method | Arguments | Description |\r\n| :--- | :--- | :--- |\r\n| `flyTo(options)` | `Object` | Smooth cinematic movement with options: `center`, `zoom`, `pitch`, `bearing`, `duration`. |\r\n| `easeTo(options)` | `Object` | Linear transition movement. Same options as `flyTo`. |\r\n| `jumpTo(options)` | `Object` | Instant teleportation without animation. |\r\n| `fitBounds(bounds, opts)` | `Array, Object` | Fits view to show all coords. Options: `padding`. |\r\n| `zoomIn()` | - | Smoothly zoom in by level 1. |\r\n| `zoomOut()` | - | Smoothly zoom out by level 1. |\r\n| `rotateLeft()` | - | Smoothly rotate left by 45 degrees. |\r\n| `rotateRight()` | - | Smoothly rotate right by 45 degrees. |\r\n\r\n### State Functions\r\n\r\n| Method | Arguments | Description |\r\n| :--- | :--- | :--- |\r\n| `setPitch(deg, anim)` | `number, bool` | Set tilt angle (0-85). Smooth by default. |\r\n| `setBearing(deg, anim)` | `number, bool` | Set direction (0-360). Smooth by default. |\r\n| `getCenter()` | - | Returns `{lng, lat}` of the current map center. |\r\n| `getZoom()` | - | Returns the current zoom level as a `number`. |\r\n| `getPitch()` | - | Returns the current pitch (tilt) as a `number`. |\r\n| `getBearing()` | - | Returns the current bearing (rotation) as a `number`. |\r\n\r\n### Camera Examples\r\n\r\n```javascript\r\n// Fly into a specific warehouse view\r\nmap.flyTo({\r\n  center: [100.5, 13.8],\r\n  zoom: 17,\r\n  pitch: 60,\r\n  bearing: 45,\r\n  duration: 4000\r\n});\r\n\r\n// Fit view to a specific region\r\nmap.fitBounds([\r\n  [100.4, 13.7], \r\n  [100.6, 13.9]\r\n], { padding: 40 });\r\n```\r\n\r\n<!-- \r\n---\r\n\r\n## 8. 🚗 Simulation & Navigation\r\n\r\nAdvanced tracking simulation with 2D/3D model support. This is ideal for visualizing real-time vehicle movement.\r\n\r\n### `startSimulation(route, options)` Options\r\n\r\n| Option | Type | Default | Description |\r\n| :--- | :--- | :--- | :--- |\r\n| `route` | `Object` | **Required** | The route result object returned by `addRoute`. |\r\n| `use3D` | `boolean` | `false` | If true, renders a 3D GLB model. If false, renders a 2D marker. |\r\n| `modelUrl` | `string` | (Internal) | URL to a GLB model file for 3D simulation. |\r\n| `scale` | `number` | `0.5` | Scale factor for the 3D model. |\r\n\r\n### Simulation & Navigation Methods\r\n\r\n| Method | Arguments | Description |\r\n| :--- | :--- | :--- |\r\n| `startSimulation(route, opts)` | `Object, Object` | Start 2D or 3D vehicle animation along a route. |\r\n| `stopSimulation()` | - | Terminate current running simulation. |\r\n| `startNavigation()` | - | Enable \"Follow\" camera mode during simulation. |\r\n| `stopNavigation()` | - | Disable camera following. |\r\n\r\n### Simulation & Navigation Example\r\n\r\n```javascript\r\nconst myRoute = await map.addRoute({ ... });\r\n\r\n// 1. Start simulation: Moving along the path\r\nmap.startSimulation(myRoute, {\r\n  use3D: true, // Awesome 3D car mode!\r\n  scale: 0.8,\r\n  modelUrl: \"/assets/my-truck.glb\"\r\n});\r\n\r\n// 2. Navigation Mode: Make the camera follow the car\r\nmap.startNavigation();\r\n\r\n// 3. Listen to updates\r\nmap.onSimulationUpdate(({ position, step }) => {\r\n  console.log(`Car is at ${position}. Progress: ${step}`);\r\n});\r\n\r\n// 4. Terminate\r\n// map.stopNavigation();\r\n// map.stopSimulation();\r\n```\r\n-->\r\n\r\n---\r\n\r\n## 11. 🔥 Event System\r\n\r\nThe SDK uses a semantic event system for a modern developer experience. All events can be chained.\r\n\r\n### Event List & Arguments\r\n\r\n| Method | Callback Arguments | Triggered When... |\r\n| :--- | :--- | :--- |\r\n| `onMapLoaded` | `{map}` | The map is fully initialized and layers can be added. |\r\n| `onClick` | `{lngLat, point, ...}` | The map is clicked. |\r\n| `onMapLongClick` | `{lngLat, point, ...}` | The map is long-pressed or right-clicked. |\r\n| `onMarkerClick` | `{marker, originalEvent, options}` | A user clicks on any marker added via SDK. |\r\n| `onPoiClick` | `{feature, lngLat, point}` | A user clicks on a Point of Interest (POI) on the base map. |\r\n| `onModeChanged` | `{mode, options}` | The SDK operational mode (standard/indoor) has changed. |\r\n| `onMarkerDrag` | `{marker, lngLat, options}` | A draggable marker is currently being moved. |\r\n| `onMarkerDragEnd` | `{marker, lngLat, options}` | A user releases a draggable marker. |\r\n| `onRouteCalculated` | `route` | A new route has been fetched and drawn. |\r\n| `onGeometryExported` | `{data}` | GeoJSON data has been exported from the editor. |\r\n| `layerEnsured` | `{layerId, group}` | A catalog layer has finished initializing sources. |\r\n| `onError` | `{source, message}` | Any internal error (Auth, Routing, Map) occurs. |\r\n\r\n### Event Subscription Example\r\n\r\n```javascript\r\nmap.onMapLoaded((e) => {\r\n  console.log(\"Ready!\");\r\n})\r\n.onClick((e) => {\r\n  console.log(`Map clicked at: ${e.lngLat}`);\r\n})\r\n.onMarkerClick((data) => {\r\n  alert(`You clicked marker: ${data.options.popupContent}`);\r\n})\r\n.onMarkerDragEnd((data) => {\r\n  console.log(`New Coordinate: ${data.lngLat.lng}, ${data.lngLat.lat}`);\r\n});\r\n```\r\n\r\n---\r\n\r\n<!-- ## 9. 🔐 Security & Production Best Practices\r\n\r\nFor production deployments, **NEVER** expose your `clientSecret` in client-side code. This exposes your account to unauthorized usage.\r\n\r\n**Recommended Proxy Setup:**\r\n\r\n1. Your Frontend calls your own Backend (e.g., `POST /api/get-route`).\r\n2. Your Backend appends `clientId` and `clientSecret` and calls PowerMap API.\r\n3. Your Backend returns the result to your Frontend.\r\n\r\n**SDK Setup for Proxy:**\r\n```javascript\r\nconst map = new PowerMap({\r\n  baseUrl: \"/api/my-proxy\", // Point to your backend route\r\n  mapApiKey: \"PUBLIC_MAP_KEY\"\r\n  // No clientSecret needed here; the backend handles it!\r\n});\r\n```\r\n\r\n--- -->\r\n\r\n## 12. 🔍 Search & Geocoding\r\n\r\nThe Web SDK provides built-in search capabilities using the PowerMap Search API with automatic script detection, smart field fallbacks, and cross-endpoint query handling.\r\n\r\n### `search(query, options)`\r\n\r\nAvailable options:\r\n\r\n| Option | Type | Default | Description |\r\n| :--- | :--- | :--- | :--- |\r\n| `lang` | `string` | `'auto'` | Language mode: `'auto'`, `'th'`, or `'en'`. In `'auto'` mode, script type is detected automatically from Unicode ranges. |\r\n| `location` | `[lat, lng]` | `-` | `[latitude, longitude]` coordinate pair to prioritize nearby search results. |\r\n\r\n#### Automatic Script Detection & Fallbacks\r\n- **`lang: 'auto'`**: Automatically inspects query characters. Thai queries (`[\\u0E00-\\u0E7F]`) select Thai search services, while English/Latin queries (`[a-zA-Z]`) select English search services.\r\n- **Mixed Script Handling**: For mixed search terms (e.g. `\"Central บางนา\"` or `\"BTS อโศก\"`), if primary search yields no results or contains mixed scripts, the SDK automatically executes a cross-endpoint fallback search and merges unique results.\r\n- **Smart Field Mapping**: `name` and `address` automatically default to the preferred query language with automatic fallback (`nameEn` -> `nameTh` -> `name` or `nameTh` -> `nameEn` -> `name`), ensuring titles and addresses are never returned as empty strings.\r\n\r\n#### Returned `SearchResult` Properties\r\n\r\nEach item in the returned array contains:\r\n\r\n| Field | Type | Description |\r\n| :--- | :--- | :--- |\r\n| `id` / `gid` | `string` | Unique identifier for the POI. |\r\n| `name` | `string` | Display name in the preferred language with automatic fallback. |\r\n| `nameEn` | `string` | English name of the place (with Thai fallback). |\r\n| `nameTh` | `string` | Thai name of the place (with English fallback). |\r\n| `address` | `string` | Formatted address in the preferred language with fallback. |\r\n| `addressEn` | `string` | Formatted English address string. |\r\n| `addressTh` | `string` | Formatted Thai address string. |\r\n| `position` | `[lat, lng]` | Coordinate pair `[latitude, longitude]` (compatible with Mobile SDK). |\r\n| `positionLngLat` | `[lng, lat]` | Coordinate pair `[longitude, latitude]` for Web map view manipulation. |\r\n| `province` / `amphoe` / `tambon` | `string` | Administrative boundary details. |\r\n| `postCode` | `string` | Postal code if available. |\r\n| `rawData` | `Object` | Raw Elasticsearch GeoJSON source payload. |\r\n\r\n### Search Examples\r\n\r\n```javascript\r\n// 1. Auto language detection (Recommended)\r\nconst results = await map.search(\"Central World\");\r\n\r\nresults.forEach(item => {\r\n  console.log(`${item.name} (${item.nameTh}): ${item.address}`);\r\n  console.log(`Position [lat, lng]:`, item.position);\r\n});\r\n\r\n// 2. Explicit English search with location prioritization\r\nconst enResults = await map.search(\"Siam Paragon\", {\r\n  lang: 'en',\r\n  location: [13.746, 100.534] // [lat, lng]\r\n});\r\n\r\n// 3. Mixed query (Thai + English)\r\nconst mixedResults = await map.search(\"BTS อโศก\", { lang: 'auto' });\r\n```\r\n\r\n---\r\n\r\n## 13. 🕹️ Interactive Metadata (POI)\r\n\r\nThe SDK can detect when a user clicks on a Point of Interest (POI) on the base map (e.g., shopping malls, parks, gas stations).\r\n\r\n```javascript\r\nmap.onPoiClick(({ feature, lngLat }) => {\r\n  const name = feature.properties.name_t;\r\n  console.log(`User clicked on: ${name}`);\r\n  \r\n  // Highlighting the POI with a popup\r\n  map.addMarker({\r\n    id: 'poi-temp',\r\n    position: [lngLat.lng, lngLat.lat],\r\n    popupContent: { title: name, tag: 'POI' }\r\n  });\r\n});\r\n```\r\n\r\n---\r\n\r\n## 14. 🧰 Utility Functions\r\n\r\nSDK provides handy formatters for displaying routing data.\r\n\r\n| Method | Arguments | Description |\r\n| :--- | :--- | :--- |\r\n| `formatDistance(meters)` | `number` | Converts meters to string (e.g., \"1.25 km\" or \"500 m\"). |\r\n| `formatDuration(seconds)` | `number` | Converts seconds to string (e.g., \"1h 30m\" or \"5 min\"). |\r\n\r\n---\r\n\r\n## 15. 📐 Geometry Engine & Editor\r\n\r\nThe 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.\r\n\r\n### 15.1 The Editor Toolbar\r\n\r\nEnable a premium, built-in toolbar that handles the entire drawing lifecycle.\r\n\r\n```javascript\r\nmap.editor.enable({\r\n  position: 'bottom-left', // 'top-left', 'top-right', 'bottom-left', 'bottom-right'\r\n  mode: 'standard'         // 'standard' (Logistics) or 'indoor' (Indoor Mapping)\r\n});\r\n```\r\n\r\n#### **Interactive Tools**\r\n\r\n- **Standard Mode**: Point, Line (Measure), Polygon (Area), Rectangle.\r\n- **Indoor Mode**: Beacon, POI, Walkway, Floor, Obstacle.\r\n\r\n### 15.2 Measurement & Info Panel\r\n\r\nWhen a feature is selected, a real-time measurement panel appears, providing:\r\n\r\n- **Distance**: For lines and walkways.\r\n- **Area**: For polygons and floors.\r\n- **3D Height**: For extruded features.\r\n- **Live Coordinates**: For Points (POIs and Beacons), showing relative X/Y metrics dynamically during placement inside buildings, or Latitude/Longitude for outdoor placements.\r\n\r\n### 15.3 Property Editor UI\r\n\r\nThe PowerMap Editor includes a functional **Property Editor** panel (available in `indoor` mode or via the settings icon). It allows you to manage:\r\n\r\n- **Core Metadata**: ID and Name.\r\n- **Classification**: Independent `feature_type` (e.g., level, building, venue), explicit Categories, and Ordinal/Level (for stacking floors).\r\n- **Visual Styling**: Choose colors and icons (for Beacons and POIs).\r\n- **Custom Properties**: Add unlimited key-value pairs for your own business logic.\r\n\r\n### 15.4 3D & Advanced Tools\r\n\r\nTake drawing to the next dimension:\r\n\r\n- **Extrude**: Transform 2D polygons into 3D buildings or obstacles by setting the `height` property.\r\n- **Rotate & Clone**: Rapidly duplicate and align features.\r\n- **Vertical Alignment**: Manage `ordinal` properties to ensure data belongs to the correct floor level.\r\n\r\n### 15.5 Data Lifecycle (Import/Export)\r\n\r\nThe Editor facilitates easy data exchange via GeoJSON:\r\n\r\n- **Export**: Built-in \"Download\" button in the toolbar, or call `map.geometry.export()`.\r\n- **Import**: Built-in \"Upload\" button, or call `map.geometry.import(geojson)`.\r\n- **IMDF Compliance**: Indoor fields (`feature_type`, `category`, `ordinal`) are mapped to IMDF format automatically.\r\n\r\n### 15.6 Unified Programmatic CRUD API\r\n\r\n> For the full reference, see [GEOMETRY.md](./GEOMETRY.md)\r\n\r\nAll operations — whether from draw tool or code — emit the same `geometryChanged` event.\r\n\r\n```javascript\r\n// CREATE (standard)\r\nconst id = map.geometry.create('polygon', {\r\n  coordinates: [[[100.5,13.7],[100.6,13.7],[100.6,13.8],[100.5,13.7]]],\r\n  properties: { fillColor: '#4f46e5', height: 10 }\r\n});\r\n\r\n// CREATE (indoor)\r\nconst beaconId = map.geometry.create('beacon', {\r\n  lngLat: [100.52, 13.75],\r\n  properties: { ordinal: 1, name: 'BKK-01' }\r\n});\r\n\r\n// READ\r\nmap.geometry.getById(id);                          // Feature | null\r\nmap.geometry.getByKind('beacon');                  // Feature[]\r\nmap.geometry.query().byKind('poi').byFloor(1).toArray();  // Feature[]\r\n\r\n// UPDATE\r\nmap.geometry.update(id, {\r\n  properties: { name: 'Updated Name', color: '#ff0000' }\r\n});\r\n\r\n// DELETE\r\nmap.geometry.remove(id);\r\nmap.geometry.clear();\r\n```\r\n\r\n**All CRUD Methods:**\r\n\r\n| Method | Arguments | Description |\r\n| :--- | :--- | :--- |\r\n| `create(kind, options)` | `string, Object` | Create a standard or indoor feature programmatically |\r\n| `update(id, patch)` | `string, Object` | Update properties and/or geometry of an existing feature |\r\n| `getById(id)` | `string` | Returns a single GeoJSON Feature or null |\r\n| `getByKind(kind)` | `string` | Returns all indoor features matching the given kind |\r\n| `query()` | - | Returns a chainable `GeometryQuery` builder |\r\n| `add(geojson)` | `Object` | Manually add a raw GeoJSON feature (legacy) |\r\n| `remove(id)` | `string\\|string[]` | Remove one or multiple features by ID |\r\n| `clear()` | - | Remove all geometry from the map |\r\n| `setFeatureHeight(id, m)` | `string, number` | Extrude a polygon into a 3D volume |\r\n| `getGeoJSON(id?, opts)` | `string, Object` | Returns feature(s) as GeoJSON (`options.format: 'imdf' \\| 'standard'`) |\r\n| `export(opts)` | `Object` | Returns all features as GeoJSON |\r\n| `import(geojson, opts)` | `Object, Object` | Loads GeoJSON/IMDF data |\r\n\r\n### 15.7 GeometryStore — Placeholder API Bridge\r\n\r\n`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.\r\n\r\n```javascript\r\n// Register before the map loads\r\nmap.geometry.store.onCreate(async (payload) => {\r\n  console.log('[Placeholder] CREATE', payload);\r\n  // await fetch('/api/features', { method: 'POST', body: JSON.stringify(payload) })\r\n});\r\nmap.geometry.store.onUpdate(async (payload) => {\r\n  console.log('[Placeholder] UPDATE', payload);\r\n  // await fetch(`/api/features/${payload.featureId}`, { method: 'PUT', body: JSON.stringify(payload) })\r\n});\r\nmap.geometry.store.onDelete(async (payload) => {\r\n  console.log('[Placeholder] DELETE', payload);\r\n  // await fetch(`/api/features/${payload.featureId}`, { method: 'DELETE' })\r\n});\r\n\r\n// Used when a developer clicks the \"Save\" icon on the draw toolbar\r\nmap.geometry.store.onSave(async (data) => {\r\n  console.log('[Placeholder] MANUAL SAVE TRIGGERED', data);\r\n  // const success = await saveAllToDatabase(data);\r\n  // if (success) map.geometry.store.setDirty(false); // Clear unsaved warnings\r\n});\r\n```\r\n\r\n<!--\r\n#### Unsaved Changes Protection\r\nThe SDK tracks if there are unsaved drawing changes (`isDirty = true`):\r\n1. **Closing Tab or Refreshing**: The browser will display its Native Alert (this cannot be styled due to browser security).\r\n2. **SPA / In-App Navigation**: You can trigger a **Custom UI Modal** (with blue/white buttons) using our built-in method before changing menus:\r\n\r\n```javascript\r\nconst action = await map.geometry.store.promptUnsavedChanges();\r\nif (action === 'save') { \r\n  // await api.saveData(); \r\n} else if (action === 'cancel') { \r\n  return; // Stay on page\r\n}\r\n```\r\n\r\nClear the protection manually once your API saves successfully:\r\n```javascript\r\nmap.geometry.store.setDirty(false);\r\n```\r\n-->\r\n\r\nEach callback receives a clean `payload` with `user_` prefixes stripped:\r\n\r\n```javascript\r\n{\r\n  operation:  'create' | 'update' | 'delete',\r\n  source:     'draw' | 'programmatic',\r\n  format:     'standard' | 'indoor',\r\n  featureId:  'abc123',\r\n  kind:       'beacon' | 'poi' | ... | null,   // null for standard\r\n  floor:      1 | null,                         // ordinal, null for standard\r\n  timestamp:  '2026-03-27T...',\r\n  geometry:   { type: 'Point', coordinates: [...] },\r\n  properties: { name: 'BKK-01', color: '#fbbf24' }\r\n}\r\n```\r\n\r\n> [!NOTE]\r\n> If no callback is registered, the SDK logs all events to the console automatically with 📦 CREATE / ✏️ UPDATE / 🗑️ DELETE icons.\r\n\r\n### 15.8 SDK Events\r\n\r\n```javascript\r\n// Subscribe to every geometry change (create/update/delete)\r\nmap.on('geometryChanged', ({ operation, source, feature }) => {\r\n  // operation: 'create' | 'update' | 'delete'\r\n  // source:    'draw' | 'programmatic'\r\n  // feature:   GeoJSON Feature (null for delete)\r\n});\r\n\r\n// Subscribe to live coordinate/property updates from the UI Editor\r\n// Use this to refresh UI panels instantly without waiting for or triggering a backend save.\r\nmap.on('geometryLiveUpdate', ({ type, features }) => {\r\n  // type: 'update'\r\n  // features: Array of GeoJSON Features that are currently being edited\r\n});\r\n\r\n// Listen to selection changes\r\nmap.on('geometrySelectionChanged', ({ features }) => {\r\n  // Array of currently selected feature objects\r\n});\r\n```\r\n\r\n---\r\n\r\n## 16. 🏠 Indoor Mapping\r\n\r\nThe PowerMap SDK provides a robust API for managing indoor environments with **OGC IMDF 1.0.0 Full Compliance** — supporting all 16 standard feature types, ZIP archive import/export, and schema validation.\r\n\r\n> [!TIP]\r\n> For the complete IMDF reference including all feature types, properties, archive format, and validation — see [GEOMETRY.md](./GEOMETRY.md#ogc-imdf-100-compliance)\r\n\r\n### 16.1 Indoor Venue Selection (NEW)\r\n\r\nThe new `loadIndoorMap()` API lets you select and activate a venue from pre-loaded data. It handles mode switching, floor detection, and camera fitting automatically.\r\n\r\n```javascript\r\n// 1. Load data from your API\r\nconst data = await fetch('/api/venues/siam-paragon/features').then(r => r.json());\r\nmap.geometry.import(data);\r\n\r\n// 2. Select venue → auto enters indoor mode\r\nconst result = map.loadIndoorMap('siam_paragon');\r\nconsole.log(result.floors);       // [0, 1, 2, 3, 4]\r\nconsole.log(result.currentFloor); // 0\r\n\r\n// 3. Switch floors\r\nmap.setFloor(2);  // Convenience alias for map.indoor.setFloor()\r\n\r\n// 4. Query venue metadata\r\nmap.indoor.getVenueInfo();   // { id, name, floors, bounds, buildingCount }\r\nmap.indoor.getFloorInfo(2);  // { name, unitCount, poiCount, beaconCount }\r\n\r\n// 5. Exit\r\nmap.unloadIndoorMap();\r\n```\r\n\r\n| Method | Returns | Description |\r\n| :--- | :--- | :--- |\r\n| `loadIndoorMap(venueId, opts)` | `{ venue, floors, currentFloor }` | Select venue, enter indoor mode, fit bounds |\r\n| `setFloor(level, opts)` | `PowerMap` | Convenience alias for `map.indoor.setFloor()` |\r\n| `unloadIndoorMap()` | `PowerMap` | Exit indoor mode, clear selection |\r\n\r\n### 16.2 Floor Management (`map.indoor`)\r\n\r\nWhen in indoor mode, use the `map.indoor` facade to control the vertical context of the map.\r\n\r\n| Method | Arguments | Description |\r\n| :--- | :--- | :--- |\r\n| `setFloor(ordinal, opts)` | `number, Object` | Switch to a specific floor level (e.g., 1, 2, -1). |\r\n| `getAvailableFloors()` | - | Returns a sorted array of all unique floor ordinals found in the map data. |\r\n| `getCurrentFloor()` | - | Returns the ordinal of the currently active floor. |\r\n| `getActiveVenue()` | - | Returns the currently selected venue feature or `null`. |\r\n| `getVenues()` | - | Lists all venue features in loaded data. |\r\n| `getVenueInfo()` | - | Summary of active venue (name, floors, bounds, building count). |\r\n| `getFloorInfo(ordinal)` | `number` | Floor details (unit count, POI count, beacon count). |\r\n| `getBuildings()` | - | Lists all building features in loaded data. |\r\n\r\n#### Switching Floors with Animation\r\n\r\n```javascript\r\n// Switch to Floor 2 with a smooth fade transition\r\nmap.indoor.setFloor(2, { animate: true });\r\n```\r\n\r\n#### Building a Dynamic Floor Selector UI\r\n\r\n```javascript\r\n// Get all available floors to populate a dropdown\r\nconst floors = map.indoor.getAvailableFloors(); \r\n// Returns: [1, 2, 3] or [] if no indoor data is found\r\n```\r\n\r\n### 16.4 Indoor Data Kinds (IMDF Compliant)\r\n\r\nThe SDK categorizes indoor features using `user_kind` (or `kind`) mapped to OGC IMDF `feature_type`:\r\n\r\n| SDK Kind | IMDF Feature Type | Geometry | Description |\r\n| :--- | :--- | :--- | :--- |\r\n| `venue` | venue | Polygon | Main venue (mall, airport) |\r\n| `building` | building | Polygon | Building within venue |\r\n| `floor` | level | Polygon | Floor plan surface |\r\n| `unit` | unit | Polygon | Room, corridor, area |\r\n| `opening` | opening | LineString | Door, gate, passage |\r\n| `amenity` | amenity | Point | ATM, restroom, etc. |\r\n| `anchor` | anchor | Point | Reference point |\r\n| `footprint` | footprint | Polygon | Building shadow |\r\n| `fixture` | fixture | Polygon | Furniture, permanent |\r\n| `section` | section | Polygon | Zone, terminal |\r\n| `geofence` | geofence | Polygon | Virtual fence |\r\n| `kiosk` | kiosk | Polygon | Service booth |\r\n| `detail` | detail | LineString | Crosswalk, railing |\r\n| `beacon` | anchor (beacon) | Point | BLE beacon device |\r\n| `poi` | anchor (poi) | Point | Point of interest |\r\n| `walkway` | pathway | LineString | Indoor navigation path |\r\n| `obstacle` | unit (restricted) | Polygon | Non-walkable area |\r\n\r\n### 16.5 IMDF Archive & Validation\r\n\r\n```javascript\r\nimport { IMDFArchive, IMDFValidator } from '@powermap/web-sdk';\r\n\r\n// Export as IMDF ZIP archive\r\nconst blob = await IMDFArchive.toArchive(map.geometry.export());\r\n\r\n// Import IMDF ZIP archive\r\nconst fc = await IMDFArchive.fromArchive(zipFile);\r\nmap.geometry.import(fc);\r\n\r\n// Validate IMDF data\r\nconst result = IMDFValidator.validate(map.geometry.export());\r\nif (!result.valid) console.error(result.errors);\r\n```\r\n\r\n> [!NOTE]\r\n> IMDF Archive requires [JSZip](https://stuk.github.io/jszip/) as an optional dependency.\r\n\r\n### 16.6 Event Synchronization\r\n\r\n```javascript\r\n// Listen for venue selection\r\nmap.on('indoorMapLoaded', ({ venue, floors, currentFloor }) => {\r\n  console.log(`Entered: ${venue.properties?.name?.en}`);\r\n});\r\n\r\n// Listen for mode changes\r\nmap.on('onModeChanged', ({ mode }) => {\r\n  console.log(`Mode switched to: ${mode}`); // 'standard' or 'indoor'\r\n});\r\n\r\n// Sync UI when data is added/deleted or imported\r\nmap.on('geometryChanged', () => {\r\n   const updatedFloors = map.indoor.getAvailableFloors();\r\n   // Refresh your floor selector here\r\n});\r\n```\r\n\r\n---\r\n\r\n## 17. ⌨️ Keyboard Shortcuts\r\n\r\nThe SDK provides built-in productivity shortcuts whenever the geometry engine is active:\r\n\r\n- **Ctrl + C**: Copy selected geometry.\r\n- **Ctrl + V**: Paste geometry at the current cursor position.\r\n- **Delete / Backspace**: Remove selected features.\r\n\r\n---\r\n\r\n## 17. 🏢 Indoor API & Mapping\r\n\r\nThe PowerMap SDK includes a dedicated `IndoorAPI` and UI controls to easily build indoor navigation and mapping applications. The indoor modules are initialized lazily and are available under `map.indoor`.\r\n\r\n### 17.1 The `IndoorAPI`\r\n\r\nThe `IndoorAPI` automatically utilizes the SDK's `AuthManager` to handle authentication securely and allows you to fetch hierarchy data and perform searches within venues.\r\n\r\n```javascript\r\n// Fetch all venues available for the user\r\nconst venues = await map.indoor.api.getVenues();\r\n\r\n// Fetch buildings in a specific venue\r\nconst buildings = await map.indoor.api.getBuildings(venueId);\r\n\r\n// Fetch floors in a specific building\r\nconst floors = await map.indoor.api.getFloors(buildingId);\r\n\r\n// Fetch raw GeoJSON for a specific floor to render on the map\r\nconst geojson = await map.indoor.api.getFloorGeoJSON(floorId);\r\n```\r\n\r\n### 17.2 Indoor Search\r\n\r\nYou can search for Points of Interest (POIs) across your indoor map using the built-in search API. **Note: Searching requires a `buildingId` to ensure performance and accuracy.**\r\n\r\n```javascript\r\n// Search within a specific building (buildingId is REQUIRED)\r\nconst results = await map.indoor.api.search('coffee', { buildingId: 'bld-1234' });\r\n\r\n// Search with a limit\r\nconst limitedResults = await map.indoor.api.search('coffee', { \r\n    buildingId: 'bld-1234',\r\n    limit: 10 \r\n});\r\n\r\n// Example output\r\n// [\r\n//   { \r\n//      id: 'poi-1', name: 'Starbucks', category: 'cafe', \r\n//      x: 100.5, y: 13.7, floorId: 'fl-1', floorNumber: 'G' \r\n//   }\r\n// ]\r\n```\r\n\r\n### 17.3 Indoor UI Controls\r\n\r\nThe SDK provides ready-to-use UI components such as the `IndoorFloorControl`, which renders a sleek, floating floor selector that automatically updates when you switch buildings.\r\n\r\n```javascript\r\nimport { IndoorFloorControl } from '@powermap/web-sdk';\r\n\r\n// 1. Initialize the control\r\nconst floorControl = new IndoorFloorControl({\r\n    onChange: (floorId, floorInfo) => {\r\n        console.log(\"Selected floor:\", floorInfo.name);\r\n        // Load the floor GeoJSON onto the map here...\r\n    }\r\n});\r\n\r\n// 2. Add to the map (top-right corner)\r\nmap.mapManager.map.addControl(floorControl, 'top-right');\r\n\r\n// 3. Feed it data from the IndoorAPI\r\nconst floors = await map.indoor.api.getFloors('bld-1234');\r\nconst formattedFloors = floors.map(f => ({\r\n    id: f.id,\r\n    name: f.floorNumber,\r\n    score: parseInt(f.floorNumber) || 0\r\n}));\r\n\r\n// Sort top to bottom, then pass to control\r\nformattedFloors.sort((a, b) => b.score - a.score);\r\nfloorControl.setFloors(formattedFloors);\r\n\r\n// 4. Update the active floor programmatically\r\nfloorControl.setActiveFloor('fl-1');\r\n```\r\n\r\n### 17.4 Indoor Routing (Premium UI)\r\n\r\nThe SDK provides a built-in `IndoorRouter` and a complete `IndoorRoutingPanelControl` UI for searching POIs and calculating indoor paths across different floors. \r\n\r\nThe router integrates directly with the Indoor API and automatically manages multi-floor rendering, step-by-step instructions, and origin/destination markers.\r\n\r\n```javascript\r\nimport { IndoorRoutingPanelControl } from '@powermap/web-sdk';\r\n\r\n// 1. Initialize the Routing Panel Control\r\nconst routingPanel = new IndoorRoutingPanelControl({\r\n    buildingId: 'bld-1234',\r\n    router: map.indoor.router, // Provided automatically by the SDK\r\n    onFloorChange: (floorId) => {\r\n        // Automatically called when a user clicks a route step on a different floor\r\n        // Use this callback to change your visual background maps or floor selector UI\r\n        console.log(\"Routing requested switch to floor:\", floorId);\r\n        floorControl.setActiveFloor(floorId);\r\n    }\r\n});\r\n\r\n// 2. Add to the map (top-right corner, below the floor selector)\r\nmap.mapManager.map.addControl(routingPanel, 'top-right');\r\n```\r\n\r\nThe `IndoorRoutingPanelControl` features:\r\n- **Built-in POI Search**: Users can type in the input fields to search for POIs via the backend.\r\n- **Auto-Switching Floors**: When a route is calculated, the SDK automatically switches to the origin floor and pans the camera.\r\n- **Layer Management**: The `IndoorRouter` handles drawing the route lines and rendering start/end markers on the correct floor.\r\n\r\n---\r\n\r\n## 18. 🩺 Debug & Telemetry\r\n\r\nEnable the Debug HUD during development to monitor performance and coordinates.\r\n\r\n```javascript\r\n// Enable debug panel\r\nmap.debug().enable({ position: 'top-right' });\r\n\r\n// Toggle specific metrics\r\nmap.debug().showFPS(true);\r\nmap.debug().showCoordinate(false);\r\n\r\n// Disable\r\nmap.debug().disable();\r\n```\r\n\r\n---\r\n\r\n## 19. 🛠 Build & Development\r\n\r\nFor contributors to the SDK core:\r\n\r\n### Contributor Guide\r\n\r\n1. **Setup Environment**:\r\n\r\n   ```bash\r\n   npm install\r\n   ```\r\n\r\n2. **Local Development**:\r\n\r\n   ```bash\r\n   npm run dev\r\n   ```\r\n\r\n3. **Production Build**:\r\n\r\n   ```bash\r\n   npm run build\r\n   ```\r\n\r\n### Project Structure\r\n\r\n- `/src/core`: Map lifecycle and shared managers.\r\n- `/src/geometry`: Drawing engine and coordinate logic.\r\n- `/src/editor`: Toolbar UI and control components.\r\n- `/src/routing`: Navigation and path-finding.\r\n- `/src/auth`: Security and API tokens.\r\n\r\n---\r\n\r\n## 16. 🏢 Indoor Map UI Controls\r\n\r\nThe SDK provides ready-to-use UI controls specifically designed for indoor mapping, ensuring a seamless user experience when navigating inside buildings.\r\n\r\n### 16.1 Indoor Floor Selector (`IndoorFloorControl`)\r\n\r\nA control that allows users to manually switch between floors in a building. It automatically updates its UI when `setFloors` or `setActiveFloor` is called.\r\n\r\n```javascript\r\nconst floorControl = new PowerMap.IndoorFloorControl({\r\n  onChange: (floorId) => {\r\n    console.log(`User changed to floor: ${floorId}`);\r\n    // Your logic to switch the map's visible floor\r\n  }\r\n});\r\nmap.addControl(floorControl, 'bottom-right');\r\n\r\n// Set available floors (e.g., from your venue data)\r\nfloorControl.setFloors([\r\n  { id: '1', name: 'G' },\r\n  { id: '2', name: 'M' },\r\n  { id: '3', name: '1' }\r\n]);\r\n\r\n// Set the active highlighted floor\r\nfloorControl.setActiveFloor('1');\r\n```\r\n\r\n### 16.2 Indoor Routing Panel (`IndoorRoutingPanelControl`)\r\n\r\nA premium UI control for indoor navigation. It handles searching for indoor POIs, selecting start/end points within a building, and displaying turn-by-turn indoor directions (including elevators and escalators).\r\n\r\n```javascript\r\nconst indoorRoutingControl = new PowerMap.IndoorRoutingPanelControl({\r\n  buildingId: 'bldg-12345',\r\n  router: map.indoor.router, // Your IndoorRouter instance\r\n  onLocationSelect: (location) => {\r\n    console.log(\"Selected indoor location:\", location);\r\n  }\r\n});\r\n\r\nmap.addControl(indoorRoutingControl, 'top-left');\r\n\r\n// Programmatically clear the indoor routing panel\r\n// indoorRoutingControl.clear();\r\n```\r\n\r\n---\r\n\r\n## 📌 Appendix A: Complete SDK Event List\r\n\r\nThe SDK emits the following events through its central `EventBus`. You can subscribe to them via `map.on(eventName, callback)`.\r\n\r\n### 🌍 Core / Global\r\n| Event | Payload | Description |\r\n|---|---|---|\r\n| `mapLoaded` | `{ map }` | Fired when PowerMap finishes loading the style and is ready. |\r\n| `mapStyleChanged` | `{ styleKey }` | Fired after switching the base map style. |\r\n| `modeChanged` | `{ mode, options }` | Fired when SDK interactive mode changes (e.g. `mode_free` → `mode_routing`). |\r\n| `error` | `PowerMapError` | Fired globally for SDK internal errors. |\r\n\r\n### 📐 Geometry & Draw\r\n| Event | Payload | Description |\r\n|---|---|---|\r\n| `geometryChanged` | `{ operation/type, source, feature(s) }` | Fired upon completion of a CRUD operation or drawing action. |\r\n| `geometryLiveUpdate` | `{ type: 'update', features }` | Fired rapidly during property/coordinate editing for real-time UI mapping. |\r\n| `geometrySelectionChanged` | `{ features }` | Fired when the active selection changes in drawing mode. |\r\n\r\n### 📍 Markers\r\n| Event | Payload | Description |\r\n|---|---|---|\r\n| `markerAdded` | `{ marker, options }` | Fired when a new marker is added to the map. |\r\n| `markerRemoved` | `{ marker\\|id }` | Fired when a marker is explicitly removed. |\r\n| `markerClick` | `{ marker, originalEvent, options }` | Fired when a marker is clicked. |\r\n| `markerDrag` | `{ marker, lngLat, options }` | Fired continuously while a draggable marker is being dragged. |\r\n| `markerDragEnd` | `{ marker, lngLat, options }` | Fired when a marker is dropped after dragging. |\r\n| `markersCleared`| `-` | Fired when all markers on the map are cleared. |\r\n\r\n### 🏢 Indoor\r\n| Event | Payload | Description |\r\n|---|---|---|\r\n| `floorChanged` | `ordinal (number)` | Fired when the active indoor floor changes. |\r\n| `venueChanged` | `VenueInfo` | Fired when the active venue selection changes. |\r\n| `indoorMapLoaded` | `{ venue, floors, currentFloor }` | Fired when a venue is successfully selected via `loadIndoorMap()`. |\r\n| `indoorMapUnloaded` | `-` | Fired when indoor mode is exited via `unloadIndoorMap()`. |\r\n\r\n### 🗺️ Routing & Navigation\r\n| Event | Payload | Description |\r\n|---|---|---|\r\n| `routeRequestStart`| `options` | Fired when a route calculation request is initiated. |\r\n| `routeCalculated` | `GeoJSON Feature` | Fired upon successful calculation of a MultiLineString route. |\r\n| `routeCleared` | `-` | Fired when the active route is removed from the map. |\r\n| `navigationStart` | `-` | Fired when Turn-by-Turn navigation is initiated. |\r\n| `navigationEnd` | `-` | Fired when Turn-by-Turn navigation stops. |\r\n| `simulationStart` | `-` | Fired when GPS Route Simulator starts running. |\r\n| `simulationUpdate`| `{ position, step }` | Fired at every tick of the GPS simulator with the interpolated position. |\r\n| `simulationEnd` | `-` | Fired when GPS simulation halts or reaches the destination. |\r\n\r\n### 🎨 Layers\r\n| Event | Payload | Description |\r\n|---|---|---|\r\n| `layerVisibilityChanged`| `{ layerId, visible }` | Fired when a system layer's visibility is toggled. |\r\n| `layerEnsured` | `{ layerId, group }` | Fired when the SDK auto-creates a missing required layer group. |\r\n\r\n---\r\n\r\n© 2026 PowerMap Development Team. All rights reserved.\r\n","readmeFilename":"README.md"}