powermap_sdk 0.10.0 copy "powermap_sdk: ^0.10.0" to clipboard
powermap_sdk: ^0.10.0 copied to clipboard

A comprehensive Flutter SDK for PowerMap, offering location plotting, geometry measurements, layer management, routing, search, and tracking.

PowerMap Flutter SDK πŸš€ #

Flutter Dart

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.8.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]
        Controller --> IndoorMgr[IndoorManager]
    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: PowerMapController provides 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).
initialTilt double? 0.0 Initial pitch of the camera (0-60).
mapStyle String 'th' Base tile style. Available: 'th', 'en', 'dark', 'gray'.
enable3DBuildings bool true Allows rendering of 3D fill-extrusion layers when the map is tilted.
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?, iconRotate?} 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: 'step_icon_straight',
  iconRotate: 90.0, // 0-360 degree bearing rotation
);

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 or registered image key 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.
iconRotate double? null Rotation angle in degrees (0Β°..360Β°) for directional symbols.

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:

  1. clearMarkers() β€” removes all old markers instantly
  2. addMarker(..., opacity: 0.0) β€” adds new markers invisibly
  3. setMarkersOpacity(value) driven by AnimationController β€” 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.
setCameraMode2D() - (Navigator) Smooth transition to 2D top-down view (0Β° tilt) during navigation.
setCameraMode3D() - (Navigator) Smooth transition to 3D perspective view (70Β° tilt) during navigation.

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 language script detection and search endpoint routing (v0.8.0+ defaults to 'auto'):

lang value Endpoint Strategy Behavior
'auto' (default) Dynamic Fallback Auto-detects Thai/English script, tries primary endpoint, and falls back/merges secondary results deduplicated by gid.
'th' /api/v2/map/address_th Target search for Thai location queries.
'en' /api/v2/map/address_en Target search for English/Latin location queries.
'mixed' /api/v2/map/address Queries combined endpoint for mixed Thai and English queries.

Tip

v0.8.0+: With lang: 'auto', the SDK automatically inspects the input query script (Thai vs English), executes smart multi-endpoint fallback querying when appropriate, and merges results while eliminating duplicate records.

PowerMapSearchResult Model #

Property Type Description
name String Primary localized title of the location.
nameTh String Explicit Thai name string.
nameEn String Explicit English/Latin name string.
address String Full formatted localized address string.
addressTh String Formatted Thai address string (Tambon, Amphoe, Province).
addressEn String Formatted English address string.
position LatLng Exact geographic coordinates.
province String Province / Changwat name.
amphoe String District / Amphoe name.
tambon String Sub-district / Tambon name.
postCode String Postal code string.
raw Map<String, dynamic> Raw API response payload.
// 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', 'motorcycling', 'foot', '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.
profile String Active transportation mode profile (e.g. 'driving', 'motorcycling', 'foot', 'cycling').
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}");

Note

v0.3.0+: Route lines are rendered as GeoJSON layers positioned below road labels for improved visibility.

v0.8.0+: Turn maneuver guidance arrows are dynamically drawn directly on the active navigation polyline via drawStepTurnArrows(). Featuring 3-layer zoom-level interpolation (Casing line, White core line, Arrowhead symbol layer), the arrows dynamically scale across zoom levels matching standard navigation aesthetics.


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. 🏒 Indoor Mapping (IMDF) #

The Mobile SDK natively supports the OGC Indoor Mapping Data Format (IMDF), allowing you to seamlessly render and navigate complex indoor environments like malls, airports, and hospitals.

The IndoorManager (map.indoor) provides the facade for managing venue states and floor switching logic.

πŸ—οΈ Loading an Indoor Map #

To initialize indoor mapping, first import the IMDF GeoJSON and tell the geometry manager to process it as imdf. Then, use the IndoorManager to switch to a specific venue and floor.

// 1. Load IMDF Data
final geoJsonString = await rootBundle.loadString('assets/sample_imdf.json');
final geoJson = jsonDecode(geoJsonString);

// 2. Import and set format to 'imdf'
await map?.importGeoJson(
  'indoor_source', 
  geoJson, 
  options: {'format': 'imdf'}
);

// 3. Load the venue and start at floor 1
await map?.indoor.loadIndoorMap('venue_001', initialFloor: 1);

πŸŽ›οΈ Floor Switching & State #

Method Description
loadIndoorMap(venueId, {initialFloor}) Sets the active venue and displays the requested floor.
setFloor(level) Changes the currently visible floor. Automatically hides other floors.
unloadIndoorMap() Clears the active indoor state and shows all geometry.
// Switch to floor 2
await map?.indoor.setFloor(2);

// Check current state
print("Current Venue: ${map?.indoor.currentVenue}");
print("Current Floor: ${map?.indoor.currentFloor}");

πŸ”„ Dynamic Floor Filtering (v0.3.0+) #

Instead of re-importing data on each floor change, use setLayerFilter() to toggle visibility:

// Show only floor 2
await map?.geometry.setLayerFilter('indoor_source', [
  '==', ['get', 'user_floor'], 2
]);

// Show all floors (clear filter)
await map?.geometry.setLayerFilter('indoor_source', null);

Note

v0.3.0+: IMDF conversion is now fully active. getGeoJSON() correctly converts between internal and IMDF formats. Venue and building features are now classified with their correct kind values instead of being misclassified as 'floor'.

πŸ” Querying Indoor Data #

The IndoorManager allows you to extract structural data from the loaded IMDF geometry, making it easy to build UI components like a Floor Switcher.

Method Returns Description
getVenues() Future<List<Map>> Get a list of all venues.
getBuildings() Future<List<Map>> Get a list of all buildings.
getVenueInfo([venueId]) Future<Map?> Returns venue metadata and a list of its available floors (floors).
getFloorInfo(ordinal) Future<Map?> Returns detailed features on a specific floor (units, anchors, POIs).
// Fetch the list of available floors for the current venue
final venueInfo = await map?.indoor.getVenueInfo();
if (venueInfo != null) {
  List<int> availableFloors = venueInfo['floors'];
  print("Available Floors: $availableFloors");
}

πŸ“Œ Native POI Icons (IconUtil) #

To display Points of Interest (POIs) such as restrooms, elevators, and stores smoothly at 60FPS without Flutter widget overhead, the SDK leverages MapLibre's native Symbol Layer combined with IconUtil.

Warning

Architectural Standard: Developers MUST use the SDK's IconUtil for rendering IMDF FontAwesome POIs. Do NOT create custom Flutter Widgets or separate icon parsers in your app, as it severely impacts 60FPS performance and violates the Indoor SDK standards.

IconUtil dynamically converts FontAwesome icons into native Uint8List bitmaps with a premium App-Icon style (rounded rectangle, drop shadow, white border). You can also dynamically colorize them by reading properties from your IMDF data (e.g., _internal.icon_color).

import 'package:powermap_sdk/utils/icon_util.dart';

// 1. Extract icon name and color from your IMDF feature properties
final String iconName = feature.properties['_internal']['user_icon'] ?? 'restroom';
final String hexColor = feature.properties['_internal']['icon_color'] ?? '#B47FF7';

// 2. Generate the bitmap from the string name & parsed color
final bytes = await IconUtil.createBitmapFromIcon(
  icon: IconUtil.getIconData(iconName),
  color: IconUtil.parseColor(hexColor, defaultColor: const Color(0xFFB47FF7)), 
  size: 64.0, // High-res for canvas rendering
);

// 3. Create a compound ID and register it with the map's native engine
final String imageId = '${iconName}_$hexColor';
await map?.mapController.addImage(imageId, bytes);

// 4. Update your GeoJSON feature to reference the new ID
// feature.properties['_internal']['user_icon'] = imageId;
// Now it will automatically render on the map with the correct color!

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


12. πŸš— 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 #

  • Real-Time Speed Limit API (v0.8.0+): Seamless integration with PowerMap Speed Limit API via SpeedLimitService featuring 4-layer request throttling (Road Segment ID ogc_fid cache, 30m distance filter, 4s time cooldown, in-flight lock).
  • Dynamic Step Turn Arrows (v0.8.0+): Real-time maneuver turn guidance arrows drawn on the navigation route line (drawStepTurnArrows) with 3-layer zoom-level interpolation.
  • Enhanced Camera Experience (2D/3D & Smart Offset): Support for 3D map tilting, speed-adaptive camera bearing, and smart camera offset (_offsetCameraTarget) to extend forward road visibility.
  • Smart Arrival Detection & Post-Navigation: Accurate destination arrival detection (available in both GPS and Simulation modes) with a seamless transition to a trip summary screen.
  • Voice Guidance & Audio Ducking: Full support for localized Text-to-Speech (TTS) with 3-stage distance-based pre-announcements (e.g., 500m, 200m, and maneuver point). Includes Audio Ducking to lower background music during announcements.
  • Advanced Navigation Engine:
    • Noise Reduction & Precision: Incorporates a custom GPS filtering system and speed-adaptive accuracy thresholds to ignore bad GPS signals gracefully.
    • Monotonic Progress Tracking: Prevents erratic vehicle snapping on complex road segments (e.g., parallel roads or U-turns).
  • Background Operations:
    • Designed to support continuous navigation even when the app is in the background.
    • Utilizes Android Foreground Services and iOS Background Audio/Location modes.
    • Built-in screen wake-lock to prevent the device from sleeping during active navigation.
  • High-Performance 2D Symbols: Renders the vehicle using native GL Symbol layers (iconSize: 1.0 with sharp Miter joins) to prevent flickering and guarantee hardware-accelerated drawing.

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. Pre-warm GPS during Route Preview (Recommended for instant Google Maps-style startup)
await navigator.warmUpGps();

// 4. 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);

Real GPS Navigation & Pre-Warming #

To achieve instant, zero-delay navigation startup similar to Google Maps, call navigator.warmUpGps() when entering the route selection or route preview screen. This pre-warms the hardware location stream so position and bearing are ready in memory before starting navigation:

// Pre-warm GPS during route preview
await navigator.warmUpGps();

// Start GPS Navigation instantly without location fix delay or position warp
await navigator.startNavigation(route, mode: PowerNavigationMode.gps);

Speed Limit API & Throttling & SpeedLimitResult #

The SDK integrates SpeedLimitService to query real-time road speed limits (/api/v2/map/speed-limit?lat={lat}&lon={lon}). PowerMapNavigator automatically updates NavigationState.speedLimit and NavigationState.speedLimitResult using a 4-layer throttling engine (Road Segment ID ogc_fid cache, 30m distance threshold, 4s time cooldown, and in-flight request lock).

// Standalone Service Query
final speedService = SpeedLimitService();
final SpeedLimitResult? result = await speedService.getSpeedLimit(LatLng(14.334397, 100.613304));
print('Speed Limit: ${result?.speed} km/h, Road Segment ID: ${result?.ogcFid}');

// Active Navigation State Listening
navigator.stateNotifier.addListener(() {
  final state = navigator.stateNotifier.value;
  if (state?.speedLimitResult != null) {
    final res = state!.speedLimitResult!;
    print('Speed limit: ${res.speed} km/h (Road ID: ${res.ogcFid})');
  }
});

SpeedLimitResult Properties

Property Type Description
speed double? Legal speed limit in km/h (e.g. 120, 90, 80, 50).
distM double? Distance in meters to matched road feature.
input LatLng? Original query coordinate input.
ogcFid int? GIS database feature ID (ogc_fid) representing the road segment.
debugCached bool? true if the result was served from local cache.
raw Map<String, dynamic>? Raw JSON payload from the Speed Limit API.

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.

Camera Mode (2D/3D) (v0.3.0+) #

Toggle between a top-down 2D view and an immersive 3D perspective during active navigation:

Method Description
setCameraMode2D() Smooth transition to 0Β° tilt. All subsequent camera updates maintain 2D.
setCameraMode3D() Smooth transition to 70Β° tilt. Default 3D perspective.
is2DMode Returns true if currently in 2D mode.
// Toggle example
if (navigator.is2DMode) {
  navigator.setCameraMode3D();
} else {
  navigator.setCameraMode2D();
}

Ensure all voice guidance and reroute instructions match your app's active language:

// Set before or during navigation
navigator.setNavigationLanguage('th'); // Thai
navigator.setNavigationLanguage('en'); // English

This updates both the routing API language parameter and the TTS engine locale in a single call.

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
        if (state.estimatedArrivalTime != null) 
          Text('ETA: ${state.estimatedArrivalTime}'),  // Estimated time of arrival
        if (state.hasArrived) 
          Text('You have reached your destination!'),  // Arrival status
      ],
    );
  },
);

Required Permissions Setup #

To fully utilize Navigation 2.0.1's background capabilities, ensure your app has the correct permissions configured:

Android

Add the following to your AndroidManifest.xml:

  • Foreground Service permission for background navigation.
  • Background Location permission for continuous tracking.

iOS

Add the following to your Info.plist:

  • Enable UIBackgroundModes with audio (for background TTS) and location (for background tracking).

13. 🎨 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(...);

14. πŸ›  Build & Development (For Contributors) #

To compile the SDK:

  1. Clone Repository.
  2. flutter pub get.
  3. Check lib/managers/ for logic separation.
  4. Run dart analyze to ensure code meets standards.

15. πŸ›‘οΈ 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.

1
likes
140
points
265
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A comprehensive Flutter SDK for PowerMap, offering location plotting, geometry measurements, layer management, routing, search, and tracking.

Homepage
Repository (GitLab)

License

MIT (license)

Dependencies

flutter, flutter_background_service, flutter_compass, flutter_tts, font_awesome_flutter, geolocator, http, maplibre_gl, path_provider

More

Packages that depend on powermap_sdk