Installation

Install the package via npm or yarn:

npm install @aapicoits/powermap-web-sdk

Architecture Overview

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))

Quick Start

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 });
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.

Method Chaining

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 (like addMarker or getRouteDetails) 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);

Configuration Reference

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.

Marker Management

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 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.

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.

CSS Customization

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);

Map Layers Management

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:

  1. CITY PLAN (city): City plans for various provinces.
  2. TRANSPORT (transport): Roads, Current/Future Railways, Current/Future BTS/MRT.
  3. 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.


Camera & Viewport Control

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 });

Event System

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}`);
});

Routing & Optimization

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 }

Search & Geocoding

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]
});

Interactive Metadata

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' }
  });
});

Utility Functions

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").

Geometry Engine & Editor

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

GeometryManager = Single Source of Truth
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
pointstandardPointlngLat
linestandardLineStringcoordinates
polygonstandardPolygoncoordinates
rectanglestandardPolygonbounds
circlestandardPolygoncenter, radiusKm
floorindoorPolygoncoordinates
obstacleindoorPolygoncoordinates
walkwayindoorLineStringcoordinates
beaconindoorPointlngLat
poiindoorPointlngLat

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 MethodDescription
.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:

  1. Auto-save / Real-time Sync: Use .onCreate, .onUpdate, .onDelete to instantly save changes.
  2. Manual Save: Use .onSave when users must click the Save icon manually (returns Bulk GeoJSON).
  3. 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);
});

Indoor Mapping

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
venuevenuePolygonMain location (mall, airport)
buildingbuildingPolygonBuilding within the venue
levelfloorPolygonFloor plan surface
unitunitPolygonRoom, corridor, usable area
openingopeningLineStringDoor, gate, passage
amenityamenityPointFacilities (ATM, restroom)
anchoranchorPointReference coordinate point
footprintfootprintPolygonBuilding shadow/outline
fixturefixturePolygonPermanent furniture/equipment
sectionsectionPolygonSpecific zone or terminal
geofencegeofencePolygonVirtual boundary
kioskkioskPolygonService booth
detaildetailLineStringCrosswalk, railing, etc.
pathway (ext)walkwayLineStringIndoor navigation path (custom extension)
anchor (ext)beaconPointBLE beacon device
anchor (ext)poiPointPoint of interest
unit (ext)obstaclePolygonNon-walkable restricted area

Supported Properties:

  • category (string): Sub-category (e.g. room, door)
  • name, short_name, alt_name ({ en: string, th: string }): i18n names
  • ordinal (number): Floor level index (0, 1, 2, -1)
  • outdoor (boolean): Whether it's an outdoor area
  • restriction / accessibility: Unit access properties
  • address_id, building_ids, level_id: Relational IDs
  • hours, phone, website: Venue metadata
Important for API Developers: When exporting data to the backend, keep the 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)PowerMapConvenience alias for map.indoor.setFloor()
unloadIndoorMap()PowerMapExit indoor mode, clear selection
getActiveVenue()Feature | nullReturns the currently selected venue feature
getVenues()ArrayLists all venue features in loaded data
getVenueInfo()Object | nullSummary of active venue
getFloorInfo(ordinal)Object | nullFloor details
getBuildings()ArrayLists 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();

Indoor API & Mapping

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 IndoorRouter handles drawing the route lines and rendering start/end markers on the correct floor.

Keyboard Shortcuts

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.

Debug & Telemetry

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();

Complete SDK Event List

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.