powermap_sdk 0.2.0
powermap_sdk: ^0.2.0 copied to clipboard
A comprehensive Flutter SDK for PowerMap, offering location plotting, geometry measurements, layer management, routing, search, and tracking.
PowerMap Flutter SDK π #
A professional-grade, high-performance Map SDK for Flutter, built with a Powerful Native Engine. Optimized for logistics, real-time tracking, indoor mapping, and buttery-smooth 60FPS mobile map interactions with deep architectural parity to the PowerMap Web SDK.
1. π¦ Installation #
Add the package to your pubspec.yaml:
dependencies:
powermap_sdk: ^0.2.0
Then, run:
flutter pub get
2. ποΈ Architecture Overview #
The PowerMap Flutter SDK is built with a Layered Facade Architecture, ensuring that high-performance native rendering is wrapped in an easy-to-use Flutter API.
graph TD
App[Flutter App] --> PowerMap[PowerMap Widget]
PowerMap --> Controller[PowerMapController]
subgraph "Facade Layer"
Controller
end
subgraph "Manager Layer"
Controller --> MarkerMgr[MarkerManager]
Controller --> SearchMgr[SearchManager]
Controller --> RouteMgr[RoutingManager]
Controller --> GeomMgr[GeometryManager]
Controller --> TrackingMgr[TrackingManager]
Controller --> LayerMgr[LayerManager]
end
subgraph "Service Layer"
MarkerMgr --> TileSvc[TileService]
SearchMgr --> GeoSvc[GeocodingProvider]
TileSvc --> Cache[3-Layer Cache]
end
subgraph "Native Engine"
PowerMap --> NativeEngine[High-Performance Native Rendering]
end
Cache -.-> Disk[(Local Storage)]
Cache -.-> CDN((PowerMap CDN))
- Unified Facade:
PowerMapControllerprovides a single entry point for all features, matching the Web SDK's DX. - Native Rendering: Symbols, Lines, and Polygons are rendered in C++/OpenGL via our custom native engine for 60FPS performance.
- 3-Layer Caching: Automated Memory/Disk/Network caching ensures offline resilience and fast style loading.
- Hybrid Overlay: Arbitrary Flutter Widgets can be used as markers, perfectly synced with the map camera.
3. π Quick Start #
Get a map up and running in less than 2 minutes. The SDK provides a powerful PowerMapController Facade.
Step A: Initialize Dual-Auth #
Setup the SDK with your API keys globally in your main.dart.
import 'package:flutter/material.dart';
import 'package:powermap_sdk/powermap_sdk.dart';
void main() {
PowerMapSDK.initialize(
mapApiKey: "YOUR_MAP_API_KEY", // Public key for tiles/base layers
clientId: "YOUR_CLIENT_ID", // Core Service Credentials
clientSecret: "YOUR_CLIENT_SECRET",
);
runApp(const MyApp());
}
Step B: The Map Widget #
class MapPage extends StatefulWidget {
@override
_MapPageState createState() => _MapPageState();
}
class _MapPageState extends State<MapPage> {
PowerMapController? map;
@override
Widget build(BuildContext context) {
return Scaffold(
body: PowerMap(
initialCenter: const LatLng(13.7563, 100.5018), // Bangkok
initialZoom: 14.0,
mapStyle: 'dark', // 'th', 'en', 'dark', 'gray'
onMapCreated: (controller) => setState(() => map = controller),
onMapClick: (coords) => print("Tapped at: $coords"),
),
);
}
}
4. π οΈ Configuration Reference #
PowerMap (Widget Properties) #
| Parameter | Type | Default | Description |
|---|---|---|---|
initialCenter |
LatLng |
LatLng(13.7563, 100.5018) |
The initial center point of the map. |
initialZoom |
double |
14.0 |
Initial viewport zoom level (0-22). |
mapStyle |
String |
'th' |
Base tile style. Available: 'th', 'en', 'dark', 'gray'. |
onMapCreated |
Function(PowerMapController) |
null |
Triggered when the GL context is fully initialized. |
onMapClick |
Function(LatLng) |
null |
Coordinate where the user tapped. |
onMapLongClick |
Function(LatLng) |
null |
Coordinate where user long-pressed. |
onPoiTapped |
Function(Map<String, dynamic>) |
null |
Fired when a base map POI or custom Geometry is tapped. Returns GeoJSON feature. |
onError |
Function(PowerMapException) |
null |
Global unified callback to catch map loading, network, or auth errors natively. |
5. π Marker Management (map.markers, map.clusters) #
The SDK renders native Symbol Layers for highest drawing performance. Markers support full customization matching the Web SDK's capabilities.
Core Marker Methods #
| Method | Arguments | Description |
|---|---|---|
addMarker(position, ...) |
LatLng, {color?, iconImage?, size?, data?, opacity?} |
Adds a GL-rendered map symbol marker. Best for performance. |
addWidgetMarker(position, widget, ...) |
LatLng, widget, {id?, alignment?} |
Overlays a Flutter Widget as a marker. Best for complex UI. |
setMarkersOpacity(opacity) |
double (0.0β1.0) |
Bulk-sets the opacity of all native markers. Use with AnimationController for smooth fade effects. |
clearMarkers() |
- | Removes all GL symbols and circles. |
clearWidgetMarkers() |
- | Removes all Flutter widget markers. |
1. GL Symbol Markers (High Performance) #
Best for large datasets (100+ points). Rendered directly by the GPU.
await map?.addMarker(
LatLng(13.76, 100.50),
color: '#f97316',
iconImage: 'packages/powermap_sdk/assets/markers/0101.png',
);
addMarker() Options
| Option | Type | Default | Description |
|---|---|---|---|
position |
LatLng |
Required | Coordinates for the marker. |
color |
String? |
null |
Hex color code. If set without iconImage, renders a GL Circle pinpoint. |
iconImage |
String? |
marker-o.png |
Asset path for the icon. Uses bundled default if omitted. |
size |
double |
1.0 |
Scale factor for the icon or circle radius. |
data |
Map<String, dynamic>? |
null |
Metadata attached to the marker (accessible on tap). |
opacity |
double |
1.0 |
Initial visibility of the marker (0.0 = invisible, 1.0 = fully visible). Use with setMarkersOpacity() to animate a fade-in. |
2. Flutter Widget Markers (High Interactivity) #
Best for complex UIs, buttons, or animations inside a marker. These markers are overlaid in a Stack and synchronized with the map's camera using pixel projection.
map?.addWidgetMarker(
LatLng(13.75, 100.48),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [BoxShadow(blurRadius: 4, color: Colors.black26)],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.restaurant, color: Colors.orange, size: 16),
SizedBox(width: 4),
Text("Delicious Cafe", style: TextStyle(fontSize: 12)),
],
),
),
alignment: Alignment.bottomCenter,
);
addWidgetMarker() Options #
| Option | Type | Default | Description |
|---|---|---|---|
position |
LatLng |
Required | Geographic location to pin the widget. |
widget |
Widget |
Required | The Flutter widget to display. |
id |
String? |
null |
Optional unique identifier. |
alignment |
Alignment |
Alignment.center |
Anchor point of the widget relative to the position. |
Bundled Icon Assets #
The SDK ships with 290+ marker icons copied directly from the Web SDK's asset library (assets/markers/). These include category icons, route waypoint markers, and the default pinpoint.
// 1. Default marker (uses bundled marker-o.png automatically)
await map?.addMarker(LatLng(13.7563, 100.5018));
// 2. Custom colored GL pinpoint (no image needed, like Web SDK)
await map?.addMarker(
LatLng(13.76, 100.50),
color: '#f97316', // Vibrant Orange
size: 1.5,
data: {'name': 'My Store'},
);
// 3. Custom icon from bundled assets
await map?.addMarker(
LatLng(13.77, 100.51),
iconImage: 'packages/powermap_sdk/assets/markers/0101.png',
);
// 4. High-Performance Clustering
final List<LatLng> stores = [ ... ];
await map?.setClusters(stores);
3. Marker Opacity & Fade Animation (v0.2.0+) #
Eliminate the visual "flash" that occurs when clearing and re-adding markers by using the opacity parameter together with setMarkersOpacity() and a Flutter AnimationController.
How it works:
clearMarkers()β removes all old markers instantlyaddMarker(..., opacity: 0.0)β adds new markers invisiblysetMarkersOpacity(value)driven byAnimationControllerβ fades all markers in smoothly
// In your StatefulWidget, declare:
late final AnimationController _fadeCtrl;
@override
void initState() {
super.initState();
_fadeCtrl = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 350),
);
}
// Render markers with smooth fade-in:
Future<void> renderMarkersWithFade(List<LatLng> positions) async {
// Step 1: Clear old markers
await map?.clearMarkers();
// Step 2: Add all new markers invisibly
for (final pos in positions) {
await map?.addMarker(
pos,
color: '#E42320',
opacity: 0.0, // Start invisible
);
}
// Step 3: Fade in all markers together
_fadeCtrl.addListener(() {
map?.setMarkersOpacity(_fadeCtrl.value);
});
_fadeCtrl.forward(from: 0); // Animates 0.0 β 1.0 over 350ms
}
@override
void dispose() {
_fadeCtrl.dispose();
super.dispose();
}
Tip
setMarkersOpacity() updates all GL Circles and Symbols in a single batch β there is no per-marker loop required from the application side.
6. π₯ Camera & Viewport Control #
Directly manipulate the map viewport matching the fluidity of the Web SDK.
Movement Methods #
| Method | Arguments | Description |
|---|---|---|
moveCamera(target, ...) |
LatLng, zoom?, tilt?, bearing? |
Smooth cinematic fly-to animation with optional rotations. |
zoomIn() |
- | Increments zoom level by 1.0 with smooth animation. |
zoomOut() |
- | Decrements zoom level by 1.0 with smooth animation. |
setTilt(deg) |
double |
Set tilt angle (0 = 2D, 60+ = 3D). |
setBearing(deg) |
double |
Set direction (0 = North). |
tiltUp() |
- | Shortcut to animate the camera to a 3D perspective view. |
tiltDown() |
- | Shortcut to animate back to a top-down view. |
State Functions #
| Method | Returns | Description |
|---|---|---|
getCenter() |
LatLng? |
Returns current view center. |
getZoom() |
double? |
Returns current map zoom level. |
getBearing() |
double? |
Returns current map rotation. |
getTilt() |
double? |
Returns current camera pitch. |
await map?.moveCamera(
LatLng(13.7649, 100.5383),
zoom: 17.0,
tilt: 60.0,
bearing: 90.0,
);
7. π Search & Geocoding #
The Mobile SDK incorporates a fully custom GeocodingProvider interface. It mirrors the exact capabilities of the Web SDK while adding safety boundaries like Debouncing and LRU Caching.
Search Methods #
| Method | Arguments | Description |
|---|---|---|
search() |
query, location?, lang? |
Standard one-off search. Checks local LRU Cache first. |
searchByCategory() |
categoryId, location, {dist?, lang?} |
Searches for places nearby by category ID (e.g. '2100' = Restaurant). |
autocompleteDebounced() |
query, delay, onResult, location? |
Safe to bind to TextField.onChanged. Custom delay prevents quota burn. |
reverseGeocode() |
location, lang? |
Converts coordinates into a structured address (PowerMapSearchResult). |
clearSearchCache() |
- | Clears the internal LRU cache manually. |
Language Detection (lang Parameter) #
The lang parameter controls which search endpoint is used. The demo app automatically detects the language from the query text and passes the appropriate value:
lang value |
Endpoint | When to use |
|---|---|---|
'th' (default) |
/api/v2/map/address_th |
Query contains Thai characters only |
'en' |
/api/v2/map/address_en |
Query contains Latin/English characters only |
'mixed' |
/api/v2/map/address |
Query contains both Thai and English |
Tip
The detection logic in the demo uses Unicode ranges: Thai (U+0E00βU+0E7F) and Latin (U+0041βU+007A). If a query contains both, 'mixed' is passed to use the unified endpoint.
PowerMapSearchResult Model #
| Property | Type | Description |
|---|---|---|
name |
String |
Primary title of the location. |
address |
String |
Full formatted address string. |
position |
LatLng |
Exact geographic coordinates. |
raw |
Map<String, dynamic> |
Raw API response if backend schemas change. |
// Language is detected automatically in the demo:
String detectLang(String query) {
final hasLatin = query.runes.any((c) => (c >= 0x41 && c <= 0x5A) || (c >= 0x61 && c <= 0x7A));
final hasThai = query.runes.any((c) => c >= 0x0E00 && c <= 0x0E7F);
if (hasLatin && !hasThai) return 'en';
if (hasLatin && hasThai) return 'mixed';
return 'th';
}
// 1. Forward search with auto-detected lang
final results = await map?.search(query, lang: detectLang(query));
// 2. Debounced Autocomplete (safe for TextField)
map?.autocompleteDebounced(
query,
Duration(milliseconds: 500),
(results) {
if (results.isNotEmpty) {
print("Found: ${results.first.name} at ${results.first.position}");
}
}
);
// 3. Reverse Geocode (Lat/Lng to Address)
final address = await map?.reverseGeocode(LatLng(14.2558, 100.9810));
if (address != null) {
print("Address: ${address.name}, ${address.province}");
}
// 4. Category Search (Nearby POIs by type)
final myLocation = await map?.getCurrentLocation();
if (myLocation != null) {
final restaurants = await map?.searchByCategory('2100', myLocation, dist: 5);
print("Found ${restaurants?.length} restaurants nearby!");
}
8. π£οΈ Routing & Optimization (map.routing) #
Calculate logistics-grade paths containing step-by-step metadata.
addRoute() Options #
| Option | Type | Default | Description |
|---|---|---|---|
start |
LatLng |
Required | Origin coordinate. |
destination |
LatLng |
Required | Final destination coordinate. |
waypoints |
List<LatLng> |
[] |
Intended stops between origin and destination. |
optimize |
bool |
false |
If true, applies TSP logic to reorder waypoints for efficiency. Minimum 1 waypoint required. |
profile |
String |
'driving' |
Transportation mode: 'driving', 'walking', 'cycling'. |
language |
String |
'th' |
Return instructions in 'th' or 'en'. |
PowerMapRouteResult Object #
| Property | Type | Description |
|---|---|---|
distance |
double |
Route length in meters. |
duration |
double |
Estimated seconds en route. |
distanceStr |
String |
Human-readable (e.g. "5.2 km"). |
durationStr |
String |
Human-readable (e.g. "12 mins"). |
steps |
List<PowerRouteStep> |
Step-by-step turn objects (contains text, distanceStr, location). |
geometry |
List<LatLng> |
Raw polyline coordinates array. |
final route = await map?.routing.addRoute(
start: LatLng(13.7468, 100.5346),
destination: LatLng(13.7649, 100.5383),
optimize: true
);
print("Route calculated: ${route?.distanceStr}");
9. π Geometry & Vector Data #
The Mobile SDK provides a robust API for handling GeoJSON data. You can import, render, and retrieve your map features with ease.
π Geometry API Reference #
| Method | Arguments | Description |
|---|---|---|
getGeoJSON() |
String? id, Map? opts |
The primary way to retrieve your data. Returns feature(s) as a Map object. |
exportGeometry() |
String? id, Map? opts |
Alias for getGeoJSON() for parity with the Web SDK. |
addGeoJson() |
sourceId, geoJsonMap |
Injects raw GeoJSON into the map source for later styling. |
importGeoJson() |
sourceId, geoJsonMap, ... |
Injects raw GeoJSON into the map and optionally renders it. |
addIndoorExtrusionLayer() |
sourceId, {layerId} |
Automatically renders 3D walls using the height property. |
addPolygonLayer() |
sourceId, {layerId} |
Draws standard 2D shapes (Fills and Outlines). Perfect for Geofences. |
addPolylineLayer() |
sourceId, {layerId} |
Renders paths and lines with scalable widths (LineString). |
π Code Example: Data Retrieval #
// 1. Get ALL geometry current in the session
final allFeatures = await map?.getGeoJSON();
// 2. Get a SPECIFIC feature by ID with format options
final specificFeature = await map?.getGeoJSON(
'poi-123',
options: { 'format': 'standard' }
);
π₯ Code Example: Data Ingestion & Layer Creation #
// 1. Prepare your Geometry Data (GeoJSON format: Lng, Lat)
final geofenceData = {
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [[
[100.534, 13.746], [100.538, 13.746],
[100.538, 13.749], [100.534, 13.749],
[100.534, 13.746] // Must close the loop
]]
}
};
// 2. Inject Data into the Map Source
await map?.geometry.addGeoJson("my-geofence", geofenceData);
// 3. Apply Visual Styles to that Source ID
await map?.geometry.addPolygonLayer(
"my-geofence",
fillColor: "#4264fb",
fillOpacity: 0.5,
outlineColor: "#000000"
);
10. π§ Real-Time Tracking #
Powered by Geolocator for battery-efficient, high-accuracy GPS positioning. Designed for delivery apps and navigation use cases without burning battery on heavy Dart timers.
Tracking Modes #
| Mode | Description |
|---|---|
TrackingMode.none |
Camera is unlocked β user can pan freely. |
TrackingMode.follow |
Camera follows device location smoothly. |
TrackingMode.followWithCompass |
Camera follows + rotates to match device heading (3D navigation view). |
Core Methods #
| Method | Returns | Description |
|---|---|---|
enableFollowMode() |
Future<void> |
Locks camera smoothly on device GPS location. Auto-requests permission. |
enableFollowModeWithCompass() |
Future<void> |
Locks camera + rotates map to match device heading with 3D tilt. |
disableFollowMode() |
Future<void> |
Unlocks camera, stops GPS stream to conserve battery. |
getCurrentLocation() |
Future<LatLng?> |
One-shot GPS request. Useful for "My Location" button. |
requestPermission() |
Future<bool> |
Checks & requests location permission. Returns true if granted. |
Usage Examples #
// 1. Follow device location smoothly
await map?.enableFollowMode();
// 2. Navigation mode with compass heading (3D view)
await map?.enableFollowModeWithCompass();
// 3. Stop tracking to save battery
await map?.disableFollowMode();
// 4. One-shot: Get current coordinates
final myLocation = await map?.getCurrentLocation();
if (myLocation != null) {
print("I'm at: $myLocation");
}
// 5. Listen to position updates reactively
map?.tracking.positionNotifier.addListener(() {
final pos = map?.tracking.positionNotifier.value;
if (pos != null) {
print("Lat: ${pos.latitude}, Lng: ${pos.longitude}");
}
});
Important
Battery Efficiency: The GPS stream uses distanceFilter: 5 meters β updates only fire when the device actually moves β₯5m. This prevents unnecessary CPU/battery drain compared to timer-based polling.
Note
Permissions: enableFollowMode() and enableFollowModeWithCompass() automatically request location permissions. You can also call requestPermission() manually during onboarding to pre-ask the user.
11. π Turn-by-Turn Navigation & Simulation (PowerMapNavigator) #
The SDK includes a fully-featured, 60FPS native-feeling Navigation Engine that maintains parity with the PowerMap Android/Kotlin SDK. It supports real-time GPS driving, dynamic route simulation, smooth 3D camera tracking, and Voice Guidance (TTS).
Core Features #
- Smooth 3D Tracking: Cinema-grade camera movement (60FPS interpolation) maintaining a
70.0degree tilt and19.5zoom for maximum 3D depth and immersion. - High-Performance 2D Symbols: Renders the vehicle using native GL Symbol layers (
iconSize: 1.0with sharp Miter joins) to prevent flickering and guarantee hardware-accelerated drawing. - Voice Guidance (TTS): Built-in integration with
flutter_ttsusing the Google Android TTS engine (com.google.android.tts). It intelligently parsesVoiceInstructioncues and triggers voice announcements exactly when needed (+20m buffer before maneuvers). - HUD Synchronization: Exposes a
ValueNotifier<NavigationState>so you can bind any custom Flutter UI (Top Banners, Bottom Boards, Speed Limits) directly to the simulation loop.
Getting Started #
// 1. Initialize the Navigator
final navigator = PowerMapNavigator(controller: mapController);
// 2. Fetch a Route
final route = await mapController.routing.addRoute(
start: LatLng(13.7468, 100.5346),
destination: LatLng(13.7649, 100.5383),
profile: 'driving',
);
// 3. Start Navigation (GPS Mode with Map Matching)
await navigator.startNavigation(route, mode: PowerNavigationMode.gps);
// OR Start Simulation Mode for testing
// await navigator.startNavigation(route, mode: PowerNavigationMode.simulation);
GPS Map Matching (Snap-to-Route) #
When running in PowerNavigationMode.gps, the PowerMap SDK utilizes robust Map Matching (snapToRoute) mathematics. As location updates are received from the physical hardware, the SDK automatically orthogonally projects the coordinates directly onto the geometry of the active route. This perfectly mitigates GPS jitter inside buildings or urban canyons and ensures accurate distance remaining, maneuver detection, and correct triggering of Voice Instructions.
Simulation Controls #
The simulation mode allows you to control the playback of the route for testing and demo purposes:
| Method | Description |
|---|---|
pauseSimulation() |
Halts the vehicle and camera movement instantly. |
resumeSimulation() |
Resumes movement from the paused position. |
setSimulationSpeed(multiplier) |
Adjusts the playback speed (e.g., 1X, 2X, 3X). |
setTracking(bool) |
Toggles camera tracking to follow the vehicle. |
stopNavigation() |
Kills the ticker, resets camera to Top-Down North (tilt 0), and clears markers. |
Vehicle Customization & Interaction #
You can change the color of the navigation arrow on the fly, and even detect when the user taps on it!
// 1. Change vehicle color dynamically (e.g., via a Color Picker)
await navigator.setVehicleColor(Colors.blue);
// 2. Detect when the user taps the moving vehicle icon
navigator.onVehicleTapped = () {
print("Vehicle icon tapped! Show a menu!");
};
Binding to the UI #
React to real-time navigation changes using the NavigationState object:
ValueListenableBuilder<NavigationState?>(
valueListenable: navigator.stateNotifier,
builder: (context, state, child) {
if (state == null) return const SizedBox.shrink();
return Column(
children: [
Text(state.instructionText), // 'Turn left onto Sukhumvit Rd'
Text('${state.distanceToNextManeuver} m'), // '500.0 m'
Text('Speed: ${state.speedMps * 3.6} km/h'), // Current driven speed
],
);
},
);
12. π¨ UI & UX Best Practices #
The Facade Pattern #
For maximum DX, you do not need to dive into individual managers. All primary actions (search, addMarker, addRoute, moveCamera, setLayers, getGeoJSON) are available directly on the PowerMapController instance:
map?.search("Mall");
map?.getGeoJSON("poi-123");
map?.addRoute(...);
13. π Build & Development (For Contributors) #
To compile the SDK:
- Clone Repository.
flutter pub get.- Check
lib/managers/for logic separation. - Run
dart analyzeto ensure code meets standards.
14. π‘οΈ Error Handling System #
The SDK provides a unified PowerMapException class for handling all types of failures. Instead of generic errors, you get specific codes and descriptive messages.
PowerMapException Properties #
| Property | Type | Description |
|---|---|---|
code |
String |
Programmatic error code (e.g., NETWORK_ERROR). |
message |
String |
Human-readable explanation in English. |
details |
dynamic |
The underlying raw error (Stacktrace or Response body). |
Common Error Codes #
| Code | Triggered By | Reason |
|---|---|---|
AUTH_FAILED |
Any API | Missing/Incorrect mapApiKey or clientSecret. |
NETWORK_ERROR |
Any API | No internet connection or server unreachable. |
STYLE_LOAD_FAILED |
Map View | Failed to load the optimized style JSON. |
ROUTE_NOT_FOUND |
Routing | No path exists between the selected points. |
SERVER_ERROR |
Any API | Backend returned a 500 error. |
Handling Errors in Code #
try {
final results = await map.search("Siam Paragon");
} on PowerMapException catch (e) {
if (e.code == PowerMapException.network) {
print("Please check your connection!");
} else {
print("PowerMap Error: ${e.message}");
}
}
Β© 2026 PowerMap Development Team. All rights reserved.