PowerMap Web SDK
High-performance Map SDK optimized for logistics, tracking, and indoor mapping.
Install the package via npm or yarn:
npm install @aapicoits/powermap-web-sdk
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.
graph TD
App[Web Application] --> PM[PowerMap Instance]
subgraph "Facade Layer (Fluent API)"
PM
end
subgraph "Manager Layer (Lazy Loading)"
PM --> Registry[Component Registry]
Registry --> MarkerMgr[MarkerManager]
Registry --> SearchMgr[SearchManager]
Registry --> RouteMgr[RoutingManager]
Registry --> GeomMgr[GeometryManager]
Registry --> EditorMgr[EditorManager]
Registry --> LayerMgr[LayerManager]
end
subgraph "Service Layer"
PM --> AuthMgr[AuthManager]
PM --> Events[EventBus]
AuthMgr --> Cache[Token Cache]
end
subgraph "Visual Engine"
PM --> ML[PowerMap Visual Engine]
end
Cache -.-> Storage[(Local Storage)]
AuthMgr -.-> API((PowerMap APIs))
Get a map up and running in less than 2 minutes. The PowerMap SDK is designed for chaining and ease of use.
Step A: Prepare your HTML
<div id="map" style="width: 100%; height: 500px; border-radius: 8px; overflow: hidden;"></div>
Step B: Initialize and Style
import { PowerMap } from "@powermap/web-sdk";
// 1. Initialize with dual-auth system
const map = new PowerMap({
container: "map",
center: [100.5018, 13.7563], // Bangkok [Longitude, Latitude]
zoom: 14,
mapApiKey: "YOUR_MAP_API_KEY", // Public key for tiles and base layers
auth: {
clientId: "YOUR_CLIENT_ID", // Service Credentials for backend APIs
clientSecret: "YOUR_CLIENT_SECRET"
},
previewOnly: true // Optional: Lock all user interactions (Static View)
});
// 2. Chaining for smooth DX
map.onMapLoaded((e) => {
console.log("PowerMap is ready!", e.map);
// Add a marker as soon as the map loads
map.addMarker({
position: [100.5018, 13.7563],
type: "start",
popupContent: "<strong>Origin Point</strong>"
});
})
.setStyle('th') // Options: 'th', 'en', 'dark', 'gray', 'hydro', 'satellite'
.flyTo({ zoom: 16, pitch: 45 });
The PowerMap SDK is built with a fluent interface, allowing you to link multiple configuration steps and event listeners together in a single statement.
Supported Methods
Most methods that perform an action on the map (styling, camera movement, or event registration) return the PowerMap instance to facilitate chaining.
| Group | Chainable Methods |
|---|---|
| Events | on, off, onMapLoaded, onClick, onMapLongClick, onMarkerClick, onPoiClick, onMarkerDrag, onMarkerDragEnd, onRouteCalculated, onError |
| Camera | flyTo, easeTo, fitBounds, jumpTo, setPitch, setBearing, zoomIn, zoomOut, rotateLeft, rotateRight |
| Map Style | setStyle |
| Markers | removeMarker, updatePopup, clearMarkers |
| Search | search |
| Routing | clearRoutes |
| Layers | showLayer, hideLayer, toggleLayer |
[!NOTE] Methods that return a Promise (like
addRoute) or a new Object (likeaddMarkerorgetRouteDetails) interrupt the chain.
Chaining Example
map.onMapLoaded(() => console.log("Ready!"))
.onMarkerClick(data => console.log("Clicked:", data))
.setStyle('dark')
.flyTo({ center: [100.5, 13.7], zoom: 12 })
.setPitch(45);
When creating a new PowerMap(options), the following options are available:
| Option | Type | Default | Required | Description |
|---|---|---|---|---|
container |
string |
- | Yes | The HTML element ID where the map will be rendered. |
mapApiKey |
string |
- | Yes | Your PowerMap Public API Key for map tiles. |
auth |
Object |
- | Yes | Service Credentials. Used for connecting to backend APIs of PowerMap services (e.g., Routing, Optimization). Contains clientId and clientSecret. |
center |
[lng, lat] |
[100, 13] |
No | The initial center point of the map. |
zoom |
number |
14 |
No | Total zoom level (0-22). |
style |
string |
'th' |
No | The initial map style. Supported values: 'th' | 'en' | 'dark' | 'gray' | 'hydro'| 'satellite'. |
baseUrl |
string |
- | No | Optional proxy URL (e.g., /api/proxy). If set, the SDK will route all API requests through this URL. |
previewOnly |
boolean |
false |
No | If true, the map will be non-interactive (no zoom, pan, rotate, or pitch). Perfect for static map previews. |
Core Instance Methods
These methods are available on the PowerMap instance to manage the map state and lifecycle.
| Method | Arguments | Description |
|---|---|---|
on(event, cb) |
string, function |
Subscribe to any map event (see Section 9). |
off(event, cb) |
string, function |
Unsubscribe from a previously registered event. |
setMode(mode, opts) |
string, Object |
Switch between 'standard' and 'indoor' modes. |
setStyle(name) |
string |
Change the map style (e.g., 'th', 'en', 'dark', 'gray', 'hydro', 'satellite'). |
setNavigationControl(opts) |
boolean|Object |
Enable/disable or customize zoom/compass controls. |
setFullscreenControl(opts) |
boolean|Object |
Enable/disable fullscreen control. |
debug() |
- | Access the Debug Manager for telemetry and performance tools. |
onError(callback) |
function |
Global error listener for Auth, Map, or Routing issues. |
Our marker system simplifies standard map markers with built-in presets for logistics and easy event handling.
addMarker(options) Options
| Option | Type | Default | Description |
|---|---|---|---|
position |
[lng, lat] |
Required | Coordinates for the marker. |
type |
string |
'default' |
Preset icons: 'start', 'destination', 'waypoint'. |
iconUrl |
string |
- | Custom image URL to use as the icon. Overrides type. |
width/height |
string |
'30px'/'38px' |
CSS dimensions for the marker icon. |
anchor |
string |
'bottom' |
Where the marker is anchored to the coordinate. |
draggable |
boolean |
false |
If true, user can drag the marker. Fires onMarkerDrag events. |
rotation |
number |
0 |
Icon rotation in degrees. |
popupContent |
string|Object |
- | HTML string or Structured Object to display in a premium popup. |
id |
string |
- | Unique ID to track this marker. Allows retrieval via getMarker(id). |
color |
string |
- | Custom hex color for pinpoint marker (e.g. '#ff4444'). Uses High-Accuracy GL Rendering. |
icon |
string |
- | FontAwesome icon class for pinpoint marker (e.g. 'fas fa-car'). |
element |
HTMLElement |
- | Use a raw HTML element as a fully custom marker. |
[!IMPORTANT] High-Accuracy Rendering: Markers with a
colorproperty 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.
Marker Examples
Standard Presets
map.addMarker({
position: [100.5, 13.7],
type: 'start', // 'start', 'destination', 'waypoint'
popupContent: "Start Point"
});
Premium Colored Markers (No Image Required)
map.addMarker({
position: [100.52, 13.78],
color: '#f97316', // Vibrant Orange
icon: 'fas fa-store', // FontAwesome Icon
draggable: true, // Enable high-accuracy dragging
popupContent: {
title: "Power Shop",
tag: "Retail",
body: "Custom colored marker with icon."
}
});
Draggable Markers & Events
You can enable dragging for any marker. For GL-rendered markers (those with color), dragging is handled with sub-pixel precision.
const marker = map.addMarker({
position: [100.5, 13.7],
color: '#3b82f6',
draggable: true
});
// 1. Listen via Proxy (Recommended)
marker.on('drag', (e) => console.log('Current:', e.lngLat));
marker.on('dragend', (e) => console.log('Final:', e.lngLat));
// 2. Listen via Global Event Bus
map.onMarkerDrag(({ marker, lngLat }) => {
console.log(`Marker is moving to: ${lngLat.lng}, ${lngLat.lat}`);
});
Structured Popup Content & Actions
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:
map.addMarker({
position: [100.5, 13.7],
popupContent: {
title: "Siam Paragon",
tag: "Mall",
body: "The pride of Bangkok with world-class shopping.",
footer: "Open: 10:00 - 22:00",
actions: [
{ label: "View Details", url: "https://example.com/details", target: "_blank" },
{ label: "Navigate Here", url: "https://example.com/nav", color: "#10b981" }
]
}
});
Marker Methods
| Method | Arguments | Description |
|---|---|---|
addMarker(options) |
Object |
Create and add a marker. Returns marker instance. |
getMarker(id) |
string |
Retrieve a marker instance by its unique ID. |
removeMarker(markerOrId) |
Object|string |
Remove a marker using instance or ID. |
updatePopup(marker, content) |
Object, string|Object |
Update the popup content (HTML or Structured Object). |
clearMarkers() |
- | Remove all markers from the map. |
The PowerMap SDK automatically wraps all map elements in a global .powermap-sdk class and provides branded aliases for all internal UI components.
The Global Wrapper
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.
<!-- Input -->
<div id="map"></div>
<!-- Output at runtime -->
<div id="map" class="powermap-sdk">...</div>
Premium Class Aliases
To make customization even more intuitive, we've provided branded CSS classes that you can target directly:
| Feature | PowerMap Class | Description |
|---|---|---|
| Marker | .pm-marker |
The main container for any marker. |
| Popup | .pm-popup |
The root container of a popup. |
| Popup Content | .pm-popup-content |
The main content area (glassmorphism). |
| Popup Header | .pm-popup-header |
The header (part of structured content). |
| Popup Body | .pm-popup-body |
The body text area. |
| Popup Tag | .pm-popup-tag |
The category tag. |
| Popup Footer | .pm-popup-footer |
The bottom meta info area. |
| Popup Close | .pm-popup-close |
The close button element. |
| Popup Tip | .pm-popup-tip |
The triangular tip pointing to the marker. |
| Nav Group | .pm-ctrl-group |
The container of zoom/compass buttons. |
| Zoom In | .pm-ctrl-zoom-in |
The zoom in button. |
| Zoom Out | .pm-ctrl-zoom-out |
The zoom out button. |
| Compass | .pm-ctrl-compass |
The compass/rotation button. |
Premium Overrides
Target your own CSS using the scoped wrapper or branded aliases:
/* Change the header color of structured popups */
.powermap-sdk .pm-popup-header {
background: linear-gradient(135deg, #000 0%, #333 100%);
}
/* Customize the glassmorphism background of all popups */
.powermap-sdk .pm-popup-content {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
}
/* Custom Marker Scale */
.powermap-sdk .pm-marker {
transform: scale(1.1);
}
setNavigationControl(options) Options
| Option | Type | Default | Description |
|---|---|---|---|
position |
string |
'top-right' |
Position on the map: 'top-left', 'top-right', 'bottom-left', 'bottom-right'. |
showZoom |
boolean |
true |
If true, display the zoom in and zoom out buttons. |
showCompass |
boolean |
true |
If true, display the compass (rotation) button. |
visualizePitch |
boolean |
true |
If true, the compass button will rotate on the Y-axis to visualize map pitch (3D view). |
Map Control Examples
// Simple: Enable default branded controls (top-right)
map.setNavigationControl(true);
// Advanced: Custom position and visibility
map.setNavigationControl({
position: 'bottom-right',
showZoom: true,
showCompass: false
});
// Disable controls
map.setNavigationControl(false);
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.
Built-in Layer Groups
Map layers are grouped into 3 main categories:
- CITY PLAN (
city): City plans for various provinces. - TRANSPORT (
transport): Roads, Current/Future Railways, Current/Future BTS/MRT. - INSIGHT DATA (
insight): Night lights, Satellite imagery.
Layer APIs
| Method | Arguments | Description |
|---|---|---|
map.layers.getCatalog() |
- | Returns the full list of available layer groups and IDs. |
map.layers.ensure(layerId) |
string |
Ensures a layer's sources are created (idempotent). |
map.layers.show(layerId) |
string |
Ensures and shows a specific layer. |
map.layers.hide(layerId) |
string |
Hides a specific layer. |
map.layers.toggle(layerId) |
string |
Toggles the visibility of a layer. |
map.layers.getLayerState(id) |
string |
Returns {loaded, visible} status. |
map.layers.setOpacity(id, val) |
string, number |
Adjusts the transparency (0.0 - 1.0). |
map.layers.showGroup(key) |
string |
Shows all layers within a specific group. |
map.layers.hideGroup(key) |
string |
Hides all layers within a specific group. |
map.layers.hideAll() |
- | Hides every active layer from the catalog. |
Usage Example
map.onMapLoaded(async () => {
// 1. View all available layers
const catalog = map.layers.getCatalog();
console.log(catalog);
// 2. Show the Road layer
await map.layers.show('pm-road-vt');
// 3. Toggle the current Railway layer
await map.layers.toggle('pm-current-railway-th-vt');
// 4. Adjust opacity
map.layers.setOpacity('pm-road-vt', 0.5);
// 5. Hide an entire group
map.layers.hideGroup('city');
});
You can also use convenience methods directly on the PowerMap instance:
await map.showLayer('pm-road-vt');
map.hideLayer('pm-road-vt');
await map.toggleLayer('pm-road-vt');
Satellite Layer
Toggle or display satellite imagery as an overlay over vector basemaps using Layer ID pm-satellite-layer (or alias 'satellite'):
// Toggle satellite overlay
await map.toggleLayer('pm-satellite-layer');
// Or show explicitly using the alias
await map.showLayer('satellite');
When enabled as an overlay layer, satellite imagery tiles are automatically positioned beneath road networks and labels so places and street names remain readable on top.
Take full control of the user's perspective with smooth transitions or direct manipulation.
Movement Functions
| Method | Arguments | Description |
|---|---|---|
flyTo(options) |
Object |
Smooth cinematic movement with options: center, zoom, pitch, bearing, duration. |
easeTo(options) |
Object |
Linear transition movement. Same options as flyTo. |
jumpTo(options) |
Object |
Instant teleportation without animation. |
fitBounds(bounds, opts) |
Array, Object |
Fits view to show all coords. Options: padding. |
zoomIn() |
- | Smoothly zoom in by level 1. |
zoomOut() |
- | Smoothly zoom out by level 1. |
rotateLeft() |
- | Smoothly rotate left by 45 degrees. |
rotateRight() |
- | Smoothly rotate right by 45 degrees. |
State Functions
| Method | Arguments | Description |
|---|---|---|
setPitch(deg, anim) |
number, bool |
Set tilt angle (0-85). Smooth by default. |
setBearing(deg, anim) |
number, bool |
Set direction (0-360). Smooth by default. |
getCenter() |
- | Returns {lng, lat} of the current map center. |
getZoom() |
- | Returns the current zoom level as a number. |
getPitch() |
- | Returns the current pitch (tilt) as a number. |
getBearing() |
- | Returns the current bearing (rotation) as a number. |
Camera Examples
// Fly into a specific warehouse view
map.flyTo({
center: [100.5, 13.8],
zoom: 17,
pitch: 60,
bearing: 45,
duration: 4000
});
// Fit view to a specific region
map.fitBounds([
[100.4, 13.7],
[100.6, 13.9]
], { padding: 40 });
The SDK uses a semantic event system for a modern developer experience. All events can be chained.
Event List & Arguments
| Method | Callback Arguments | Triggered When... |
|---|---|---|
onMapLoaded |
{map} |
The map is fully initialized and layers can be added. |
onClick |
{lngLat, point, ...} |
The map is clicked. |
onMapLongClick |
{lngLat, point, ...} |
The map is long-pressed or right-clicked. |
onMarkerClick |
{marker, originalEvent, options} |
A user clicks on any marker added via SDK. |
onPoiClick |
{feature, lngLat, point} |
A user clicks on a Point of Interest (POI) on the base map. |
onModeChanged |
{mode, options} |
The SDK operational mode (standard/indoor) has changed. |
onMarkerDrag |
{marker, lngLat, options} |
A draggable marker is currently being moved. |
onMarkerDragEnd |
{marker, lngLat, options} |
A user releases a draggable marker. |
onRouteCalculated |
route |
A new route has been fetched and drawn. |
onGeometryExported |
{data} |
GeoJSON data has been exported from the editor. |
layerEnsured |
{layerId, group} |
A catalog layer has finished initializing sources. |
onError |
{source, message} |
Any internal error (Auth, Routing, Map) occurs. |
Event Subscription Example
map.onMapLoaded((e) => {
console.log("Ready!");
})
.onClick((e) => {
console.log(`Map clicked at: ${e.lngLat}`);
})
.onMarkerClick((data) => {
alert(`You clicked marker: ${data.options.popupContent}`);
})
.onMarkerDragEnd((data) => {
console.log(`New Coordinate: ${data.lngLat.lng}, ${data.lngLat.lat}`);
});
Generate high-precision routes with automatic visualization. The SDK automatically fits the map view to the resulting route.
addRoute(options) Options
| Option | Type | Default | Description |
|---|---|---|---|
start |
[lng, lat] |
Required | Start point coordinates. |
destination |
[lng, lat] |
Required | End point coordinates. |
waypoints |
Array<[lng, lat]> |
[] |
List of points to hit between start and end. |
optimize |
boolean |
false |
If true, reorders waypoints to find the shortest total trip (TSP). Requires at least 1 waypoint. |
profile |
string |
'driving' |
Routing engine profile: 'driving', 'walking', 'cycling'. |
language |
string |
'th' |
Return instructions/names in 'th' or 'en'. |
routeColor |
string |
'#3b82f6' |
Hex color code for the main route line. |
routeWidth |
number |
5 |
Thickness of the main route line. |
routeOpacity |
number |
1.0 |
Opacity of the route line (0.0 to 1.0). |
casingColor |
string |
'#1d4ed8' |
Hex color for the route's outer border (shadow). |
casingWidth |
number |
8 |
Thickness of the casing line. |
Route Methods
| Method | Arguments | Description |
|---|---|---|
addRoute(options) |
Object |
Calculate, draw route and fit view. Returns Promise. |
clearRoutes() |
- | Remove all route lines and markers from map. |
getRouteDetails() |
- | Returns distance, duration, and formatted strings. |
getRouteSummary() |
- | Returns top-level distance/duration strings. |
addRoute(options) Return Object
The addRoute method returns a Promise that resolves to a RouteResult object containing:
| Property | Type | Description |
|---|---|---|
distance |
number |
Total distance of the route in meters. |
duration |
number |
Estimated travel time in seconds. |
steps |
Array<Object> |
Turn-by-turn instructions (text, distance, duration, location). |
geometry |
Object |
GeoJSON LineString of the route. |
raw |
Object |
Raw response from the PowerMap routing backend. |
steps Object Structure
Each step in the steps array represents a turn or maneuver:
| Field | Type | Description |
|---|---|---|
text |
string |
The maneuver instruction (e.g., "เลี้ยวขวาที่ถนนสุขุมวิท"). |
distance |
number |
Distance to travel for this step (meters). |
duration |
number |
Estimated time for this step (seconds). |
location |
[lng, lat] |
Geographic coordinate where the maneuver starts. |
Routing Example
// Simple route from A to B
const route = await map.addRoute({
start: [100.5018, 13.7563],
destination: [100.5118, 13.7663],
language: "en"
});
// Complex optimized route with waypoints
const optimizedRoute = await map.addRoute({
start: [100.50, 13.75],
destination: [100.60, 13.85],
waypoints: [
[100.55, 13.80],
[100.52, 13.78]
],
optimize: true, // Will reorder the 2 waypoints for the best efficiency
language: "th"
});
console.log(`Total Distance: ${optimizedRoute.distance}m`);
console.log(`Total Duration: ${optimizedRoute.duration}s`);
// Custom red route for heavy traffic visualization
const redRoute = await map.addRoute({
start: [100.5, 13.7],
destination: [100.6, 13.8],
routeColor: "#ff0000",
routeWidth: 10,
casingColor: "#990000"
});
// Clear all routes from visibility
map.clearRoutes();
Route Summary Helpers
Quickly access formatted summaries (distance in km/m, time in hours/mins) for the total route and individual steps:
// Get enhanced details (Recommended)
const details = map.getRouteDetails();
if (details) {
console.log(`Total: ${details.distanceStr}, Time: ${details.durationStr}`);
// Each step also contains formatted strings
details.steps.forEach(step => {
console.log(`${step.text} (${step.distanceStr})`);
});
}
// Get just the top-level summary
const summary = map.getRouteSummary(); // { distanceStr, durationStr }
The Web SDK provides built-in search capabilities using the PowerMap Search API.
search(query, options)
Available options:
lang:'th'(default) or'en'.location:[latitude, longitude]to prioritize nearby results.
const results = await map.search("Central World", { lang: 'en' });
results.forEach(item => {
console.log(`${item.name}: ${item.address}`);
console.log(`Position: ${item.position}`); // [lat, lng]
});
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).
map.onPoiClick(({ feature, lngLat }) => {
const name = feature.properties.name_t;
console.log(`User clicked on: ${name}`);
// Highlighting the POI with a popup
map.addMarker({
id: 'poi-temp',
position: [lngLat.lng, lngLat.lat],
popupContent: { title: name, tag: 'POI' }
});
});
SDK provides handy formatters for displaying routing data.
| Method | Arguments | Description |
|---|---|---|
formatDistance(meters) |
number |
Converts meters to string (e.g., "1.25 km" or "500 m"). |
formatDuration(seconds) |
number |
Converts seconds to string (e.g., "1h 30m" or "5 min"). |
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.
16.1 Architecture Principle
Every geometry operation (create/update/delete) must pass through the SDK.
Whether it originates from the draw tool or programmatic API, it will always emit the same
geometryChanged event format.[Draw Tool] ─┐
[map.geometry.create()] ─┼──► GeometryManager ──► geometryChanged event
[map.geometry.update()] ─┤ │
[map.geometry.remove()] ─┘ ▼
GeometryStore
(log → placeholder API)
16.2 CRUD Methods
Create
map.geometry.create(kind, options) — Unified Create
Creates a feature programmatically with the exact same result as drawing it via the tool.
// ─── Standard Geometry ───────────────────────────────────────────────────────
map.geometry.create('point', { lngLat: [100.5, 13.7], properties: { name: 'Pin A' } });
map.geometry.create('line', { coordinates: [[100.5,13.7],[100.6,13.8]], properties: { lineColor: '#38bdf8' } });
map.geometry.create('polygon', { coordinates: [[[100.5,13.7],[100.6,13.7],[100.6,13.8],[100.5,13.7]]], properties: { fillColor: '#4f46e5' } });
map.geometry.create('rectangle', { bounds: [[100.5,13.7],[100.6,13.8]] });
map.geometry.create('circle', { center: [100.5, 13.7], radiusKm: 0.5 });
// ─── Indoor Geometry ─────────────────────────────────────────────────────────
map.geometry.create('floor', { coordinates: [[[...]]], properties: { ordinal: 1, name: 'Floor 1' } });
map.geometry.create('obstacle', { coordinates: [[[...]]], properties: { ordinal: 1, height: 2.5 } });
map.geometry.create('walkway', { coordinates: [[100.5,13.7],[100.51,13.71]], properties: { ordinal: 1 } });
map.geometry.create('beacon', { lngLat: [100.52, 13.72], properties: { ordinal: 1, name: 'BKK-01' } });
map.geometry.create('poi', { lngLat: [100.53, 13.73], properties: { ordinal: 1, name: 'Restroom', icon: 'restroom' } });
Parameters:
| Parameter | Type | Description |
|---|---|---|
kind |
string |
The type of feature (see supported kinds below) |
options.lngLat |
[number, number] |
Coordinates for Point features |
options.coordinates |
Array |
GeoJSON coordinates for Polygon/LineString |
options.bounds |
[[sw], [ne]] |
For rectangle features |
options.center + radiusKm |
- | For circle features |
options.properties |
Object |
Additional custom properties |
Supported Kinds:
| Kind | Format | Geometry Type | Required |
|---|---|---|---|
point | standard | Point | lngLat |
line | standard | LineString | coordinates |
polygon | standard | Polygon | coordinates |
rectangle | standard | Polygon | bounds |
circle | standard | Polygon | center, radiusKm |
floor | indoor | Polygon | coordinates |
obstacle | indoor | Polygon | coordinates |
walkway | indoor | LineString | coordinates |
beacon | indoor | Point | lngLat |
poi | indoor | Point | lngLat |
Read
Single / Filtered Read:
const feature = map.geometry.getById('abc123'); // GeoJSON Feature | null
const beacons = map.geometry.getByKind('beacon'); // Feature[]
const pois = map.geometry.getByKind('poi'); // Feature[]
Query Builder:
Chainable, lazy evaluation — executes the filter only when a terminal method is called:
// Standard mode
map.geometry.query().byType('Polygon').toArray();
map.geometry.query().where('height', 'gt', 0).toArray();
// Indoor mode
map.geometry.query().byKind('beacon').byFloor(1).toArray();
map.geometry.query().byKind('poi').where('name', 'contains', 'Exit').first();
// Mix — count all features on floor 2
map.geometry.query().byFloor(2).count();
| Query Method | Description |
|---|---|
.byKind(kind) | Filter by SDK kind |
.byFloor(ordinal) | Filter by floor (ordinal) |
.byType(geomType) | Filter by 'Point' | 'Polygon' | 'LineString' |
.where(prop, op, value) | Filter by property ('eq','neq','contains','gt','lt','exists') |
.toArray() | Execute → Feature[] |
.first() | Execute → Feature | null |
.count() | Execute → number |
.ids() | Execute → string[] |
Update
// Update properties
map.geometry.update('abc123', { properties: { name: 'New Name', color: '#ff0000' } });
// Update geometry
map.geometry.update('abc123', { geometry: { type: 'Point', coordinates: [100.6, 13.8] } });
// 3D Extrusion
map.geometry.setFeatureHeight('abc123', 10); // Extrude polygon by 10 meters
Delete
map.geometry.remove('abc123'); // Remove single feature
map.geometry.remove(['id1', 'id2']); // Remove multiple features
map.geometry.clear(); // Remove all features
Batch (Import / Export)
// Import receives data from API (defaults to 'imdf' format)
map.geometry.import(apiResponseJson, { format: 'imdf' });
// Export to save into the Database
const imdf = map.geometry.export({ format: 'imdf' });
16.3 GeometryStore — Placeholder API Bridge
map.geometry.store is a middleware event listener that intercepts geometry creations, modifications, deletions, or save actions. It converts the payload into a database-friendly format before sending it to developer-defined callbacks.
Use Cases:
- Auto-save / Real-time Sync: Use
.onCreate,.onUpdate,.onDeleteto instantly save changes. - Manual Save: Use
.onSavewhen users must click the Save icon manually (returns Bulk GeoJSON). - History & Audit Log: Every payload includes a timestamp.
// Pattern 1: Individual callbacks for live Auto-save operations
map.geometry.store.onCreate(async (payload) => {
// await fetch('/api/features', { method: 'POST', body: JSON.stringify(payload) })
});
map.geometry.store.onUpdate(async (payload) => {
// await fetch(`/api/features/${payload.featureId}`, { method: 'PUT', body: JSON.stringify(payload) })
});
map.geometry.store.onDelete(async (payload) => {
// await fetch(`/api/features/${payload.featureId}`, { method: 'DELETE' })
});
// Pattern 2: Bulk Save (Triggered via Toolbar Save button)
map.geometry.store.onSave(async (data) => {
// const success = await fetch('/api/buildings/save-all', { method: 'POST', body: JSON.stringify(data) })
// if (success) map.geometry.store.setDirty(false); // Clear unsaved changes flag
});
Payload Format:
Each callback receives a clean payload with user_ prefixes stripped.
{
operation: 'create',
source: 'programmatic', // or 'draw'
format: 'indoor', // or 'standard'
featureId: 'xyz789',
kind: 'beacon', // null for standard
floor: 1, // null for standard
timestamp: '2026-03-27T...',
geometry: { type: 'Point', coordinates: [100.5, 13.7] },
properties: { name: 'BKK-01', color: '#fbbf24', icon: 'broadcast-tower' }
}
Event System (Direct Listeners):
Every operation emits events for real-time tracking:
map.on('geometryChanged', (event) => {
// event.operation : 'create' | 'update' | 'delete'
// event.source : 'draw' | 'programmatic'
// event.feature : GeoJSON Feature (null if delete)
// event.featureId : string
});
map.on('geometryLiveUpdate', (event) => {
// Fired continuously during drag/edit for UI mapping
// event.type : 'update'
// event.features : GeoJSON Feature[] currently being edited
});
16.4 Database Storage Recommendations (For Backend Teams)
Since the data structure is Dynamic Schema GeoJSON, it is highly recommended to use Document/JSON storage rather than flattening properties into relational columns. This prevents SDK data loss.
1. PostgreSQL (with PostGIS)
Store the core searchable fields and dump the rest into a JSONB column.
CREATE TABLE indoor_features (
id VARCHAR(64) PRIMARY KEY,
feature_type VARCHAR(50) NOT NULL, -- For filtering (level, anchor)
ordinal INT NOT NULL, -- For filtering by floor
geometry GEOMETRY(Geometry, 4326), -- PostGIS Spatial Query
properties JSONB NOT NULL -- *** Store the entire SDK properties object here!
);
2. MongoDB
MongoDB natively aligns with GeoJSON.
const featureSchema = new Schema({
_id: { type: String, required: true },
type: { type: String, default: 'Feature' },
feature_type: { type: String, index: true },
geometry: {
type: { type: String, enum: ['Point', 'LineString', 'Polygon'] },
coordinates: Schema.Types.Mixed
},
// *** Store the properties object freely without defining a strict sub-schema
properties: { type: Schema.Types.Mixed }
});
featureSchema.index({ geometry: '2dsphere' });
featureSchema.index({ "properties.ordinal": 1 });
Standard API Pattern (Read):
When the SDK loads, the backend simply returns a FeatureCollection to pass directly into map.geometry.import().
app.get('/api/buildings/:id/features', async (req, res) => {
const docs = await db.collection('features').find({ building_id: req.params.id }).toArray();
const featureCollection = {
type: "FeatureCollection",
features: docs.map(doc => ({
id: doc._id || doc.id,
type: "Feature",
feature_type: doc.feature_type,
geometry: doc.geometry,
properties: doc.properties
}))
};
res.json(featureCollection);
});
The PowerMap SDK provides a robust API for managing indoor environments, 3D visualization, and indoor navigation.
17.1 Quick Start: Loading 3D Indoor Maps
To display a 3D indoor map, you need to import your IMDF/GeoJSON data into the SDK, and then activate the venue. The SDK will automatically manage the floors and render 3D extrusions (e.g., walls and rooms) for features with a height property.
Step 1: Prepare your GeoJSON Data
Your data must be a valid GeoJSON FeatureCollection. To work with the indoor system, ensure you have at least one feature with kind: "venue" and floors with kind: "floor" and an ordinal (level).
{
"type": "FeatureCollection",
"features": [
{
"id": "venue-01",
"type": "Feature",
"geometry": { "type": "Polygon", "coordinates": [[[100.5, 13.7], ...]]] },
"properties": { "kind": "venue", "name": { "en": "My Mall" } }
},
{
"id": "floor-1",
"type": "Feature",
"geometry": { "type": "Polygon", "coordinates": [[[100.5, 13.7], ...]]] },
"properties": { "kind": "floor", "ordinal": 1, "name": "1st Floor" }
},
{
"id": "room-101",
"type": "Feature",
"geometry": { "type": "Polygon", "coordinates": [[[100.5, 13.7], ...]]] },
"properties": { "kind": "unit", "ordinal": 1, "height": 3.0 } // 3D Extrusion
}
]
}
Step 2: Fetch and Import via API
Use map.geometry.import(geojson) to load the data into the SDK.
async function loadMyIndoorMap() {
// 1. Fetch GeoJSON from your database/API
// 1. Fetch GeoJSON from PowerMap Indoor API
const response = await fetch('https://maps.powermap.live/indoor-api/floors//geojson');
const geojsonData = await response.json();
// 2. Import the data into PowerMap
map.geometry.import(geojsonData);
// 3. Activate the indoor view by targeting the venue ID or name
const result = map.loadIndoorMap('venue-01', {
initialFloor: 1, // Show 1st Floor by default
fitBounds: true, // Auto-zoom to fit the building
animate: true // Smooth 3D transition
});
console.log("Available Floors:", result.floors); // [1, 2, 3]
}
Step 3: Switch Floors Programmatically
Once active, you can switch floors and the SDK will automatically update visibility.
// Switch to floor 2 with animation
map.setFloor(2, { animate: true });
17.2 Indoor Feature Types & Properties
The SDK categorizes indoor features using user_kind mapped to specific feature types:
| SDK Kind | Geometry | Description | |
|---|---|---|---|
venue | venue | Polygon | Main location (mall, airport) |
building | building | Polygon | Building within the venue |
level | floor | Polygon | Floor plan surface |
unit | unit | Polygon | Room, corridor, usable area |
opening | opening | LineString | Door, gate, passage |
amenity | amenity | Point | Facilities (ATM, restroom) |
anchor | anchor | Point | Reference coordinate point |
footprint | footprint | Polygon | Building shadow/outline |
fixture | fixture | Polygon | Permanent furniture/equipment |
section | section | Polygon | Specific zone or terminal |
geofence | geofence | Polygon | Virtual boundary |
kiosk | kiosk | Polygon | Service booth |
detail | detail | LineString | Crosswalk, railing, etc. |
pathway (ext) | walkway | LineString | Indoor navigation path (custom extension) |
anchor (ext) | beacon | Point | BLE beacon device |
anchor (ext) | poi | Point | Point of interest |
unit (ext) | obstacle | Polygon | Non-walkable restricted area |
Supported Properties:
category(string): Sub-category (e.g. room, door)name,short_name,alt_name({ en: string, th: string }): i18n namesordinal(number): Floor level index (0, 1, 2, -1)outdoor(boolean): Whether it's an outdoor arearestriction/accessibility: Unit access propertiesaddress_id,building_ids,level_id: Relational IDshours,phone,website: Venue metadata
properties object intact. The _internal object inside properties contains essential visual states (colors, icons, absolute XY metrics) required to perfectly restore the map state on reload.17.3 Indoor Venue Selection API
High-level API to initialize an indoor venue from loaded features:
// 1. Load data from API
const data = await fetch('/api/venues/siam-paragon/features').then(r => r.json());
map.geometry.import(data);
// 2. Select venue → auto enters indoor mode
const result = map.loadIndoorMap('siam_paragon', {
initialFloor: 0, // Start at G floor
fitBounds: true, // Zoom to venue automatically
animate: true // Smooth transition
});
console.log(result.venue); // GeoJSON Feature of the venue
console.log(result.floors); // [0, 1, 2, 3, 4]
console.log(result.currentFloor); // 0
// 3. Switch floors
map.setFloor(2); // Convenience alias
map.indoor.setFloor(3, { animate: true }); // Full API
// 4. Summarize Info
map.indoor.getVenueInfo(); // { id, name, category, floors, bounds, buildingCount }
map.indoor.getFloorInfo(2); // { id, name, shortName, ordinal, unitCount, poiCount... }
// 5. Query venues and buildings
map.indoor.getVenues(); // [{ id, name, category, feature }]
map.indoor.getBuildings(); // [{ id, name, category }]
// 6. Exit indoor mode
map.unloadIndoorMap();
| Method | Returns | Description |
|---|---|---|
loadIndoorMap(venueId, opts) | { venue, floors, currentFloor } | Select venue, enter indoor mode, fit bounds |
setFloor(level, opts) | PowerMap | Convenience alias for map.indoor.setFloor() |
unloadIndoorMap() | PowerMap | Exit indoor mode, clear selection |
getActiveVenue() | Feature | null | Returns the currently selected venue feature |
getVenues() | Array | Lists all venue features in loaded data |
getVenueInfo() | Object | null | Summary of active venue |
getFloorInfo(ordinal) | Object | null | Floor details |
getBuildings() | Array | Lists all building features in loaded data |
17.4 Geometry Query Filters
The GeometryQuery builder includes specific indoor filtering:
// Search by feature_type
map.geometry.query().byFeatureType('unit').toArray(); // All rooms
map.geometry.query().byFeatureType('opening').toArray(); // All doors
map.geometry.query().byFeatureType('amenity').toArray(); // All amenities
// Search by relationships
map.geometry.query().byVenueId('venue_001').toArray(); // Features within venue
map.geometry.query().byBuildingId('bldg_A').toArray(); // Features within building
map.geometry.query().byLevelId('level_2F').toArray(); // Features within level
// Mix with standard filters
map.geometry.query()
.byFeatureType('unit')
.byFloor(2)
.where('category', 'eq', 'restroom')
.toArray();
17.5 The IndoorAPI
The IndoorAPI automatically utilizes the SDK's AuthManager to handle authentication securely and allows you to fetch hierarchy data and perform searches within venues.
// Fetch all venues available for the user
const venues = await map.indoor.api.getVenues();
// Fetch buildings in a specific venue
const buildings = await map.indoor.api.getBuildings(venueId);
// Fetch floors in a specific building
const floors = await map.indoor.api.getFloors(buildingId);
// Fetch raw GeoJSON for a specific floor to render on the map
const geojson = await map.indoor.api.getFloorGeoJSON(floorId);
17.6 Indoor Search
You 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.
// Search within a specific building (buildingId is REQUIRED)
const results = await map.indoor.api.search('coffee', { buildingId: 'bld-1234' });
// Search with a limit
const limitedResults = await map.indoor.api.search('coffee', {
buildingId: 'bld-1234',
limit: 10
});
17.7 Indoor UI Controls
The 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.
import { IndoorFloorControl } from '@powermap/web-sdk';
// 1. Initialize the control
const floorControl = new IndoorFloorControl({
onChange: (floorId, floorInfo) => {
console.log("Selected floor:", floorInfo.name);
}
});
// 2. Add to the map (top-right corner)
map.addControl(floorControl, 'top-right');
// 3. Feed it data from the IndoorAPI
const floors = await map.indoor.api.getFloors('bld-1234');
const formattedFloors = floors.map(f => ({
id: f.id,
name: f.floorNumber,
score: parseInt(f.floorNumber) || 0
}));
// Sort top to bottom, then pass to control
formattedFloors.sort((a, b) => b.score - a.score);
floorControl.setFloors(formattedFloors);
// 4. Update the active floor programmatically
floorControl.setActiveFloor('fl-1');
17.8 Indoor Routing (Built-in UI)
The SDK provides a built-in IndoorRouter and a complete IndoorRoutingPanelControl UI for searching POIs and calculating indoor paths across different floors.
The router integrates directly with the Indoor API and automatically manages multi-floor rendering, step-by-step instructions, and origin/destination markers.
import { IndoorRoutingPanelControl } from '@powermap/web-sdk';
// 1. Initialize the Routing Panel Control
const routingPanel = new IndoorRoutingPanelControl({
buildingId: 'bld-1234',
router: map.indoor.router, // Provided automatically by the SDK
onFloorChange: (floorId) => {
console.log("Routing requested switch to floor:", floorId);
floorControl.setActiveFloor(floorId);
}
});
// 2. Add to the map (top-right corner, below the floor selector)
map.addControl(routingPanel, 'top-right');
The IndoorRoutingPanelControl features:
- Built-in POI Search: Users can type in the input fields to search for POIs via the backend.
- Auto-Switching Floors: When a route is calculated, the SDK automatically switches to the origin floor and pans the camera.
- Layer Management: The
IndoorRouterhandles drawing the route lines and rendering start/end markers on the correct floor.
The SDK provides built-in productivity shortcuts whenever the geometry engine is active:
- Ctrl + C: Copy selected geometry.
- Ctrl + V: Paste geometry at the current cursor position.
- Delete / Backspace: Remove selected features.
Enable the Debug HUD during development to monitor performance and coordinates.
// Enable debug panel
map.debug().enable({ position: 'top-right' });
// Toggle specific metrics
map.debug().showFPS(true);
map.debug().showCoordinate(false);
// Disable
map.debug().disable();
The SDK emits the following events through its central EventBus. You can subscribe to them via map.on(eventName, callback).
Core / Global
| Event | Payload | Description |
|---|---|---|
mapLoaded |
{ map } |
Fired when PowerMap finishes loading the style and is ready. |
mapStyleChanged |
{ styleKey } |
Fired after switching the base map style. |
modeChanged |
{ mode, options } |
Fired when SDK interactive mode changes (e.g. mode_free → mode_routing). |
error |
PowerMapError |
Fired globally for SDK internal errors. |
Geometry & Draw
| Event | Payload | Description |
|---|---|---|
geometryChanged |
{ operation/type, source, feature(s) } |
Fired upon completion of a CRUD operation or drawing action. |
geometryLiveUpdate |
{ type: 'update', features } |
Fired rapidly during property/coordinate editing for real-time UI mapping. |
geometrySelectionChanged |
{ features } |
Fired when the active selection changes in drawing mode. |
Markers
| Event | Payload | Description |
|---|---|---|
markerAdded |
{ marker, options } |
Fired when a new marker is added to the map. |
markerRemoved |
{ marker|id } |
Fired when a marker is explicitly removed. |
markerClick |
{ marker, originalEvent, options } |
Fired when a marker is clicked. |
markerDrag |
{ marker, lngLat, options } |
Fired continuously while a draggable marker is being dragged. |
markerDragEnd |
{ marker, lngLat, options } |
Fired when a marker is dropped after dragging. |
markersCleared |
- |
Fired when all markers on the map are cleared. |
Indoor
| Event | Payload | Description |
|---|---|---|
floorChanged |
ordinal (number) |
Fired when the active indoor floor changes. |
Routing & Navigation
| Event | Payload | Description |
|---|---|---|
routeRequestStart |
options |
Fired when a route calculation request is initiated. |
routeCalculated |
GeoJSON Feature |
Fired upon successful calculation of a MultiLineString route. |
routeCleared |
- |
Fired when the active route is removed from the map. |
navigationStart |
- |
Fired when Turn-by-Turn navigation is initiated. |
navigationEnd |
- |
Fired when Turn-by-Turn navigation stops. |
simulationStart |
- |
Fired when GPS Route Simulator starts running. |
simulationUpdate |
{ position, step } |
Fired at every tick of the GPS simulator with the interpolated position. |
simulationEnd |
- |
Fired when GPS simulation halts or reaches the destination. |
Layers
| Event | Payload | Description |
|---|---|---|
layerVisibilityChanged |
{ layerId, visible } |
Fired when a system layer's visibility is toggled. |
layerEnsured |
{ layerId, group } |
Fired when the SDK auto-creates a missing required layer group. |
© 2026 PowerMap Development Team. All rights reserved.