Qartas Maps for Flutter
Standalone native MapLibre presentation for CHAF Maps tiles. Android/iOS use native MapLibre views; web uses MapLibre GL JS. The SDK never loads the raster experiment URL or embeds a WebView. This package is currently an unpublished integration preview.
Integrate
Download and extract the SDK source archive, then depend on sdk/flutter by path. Keep sdk/dart and
third_party alongside it; the vendored MapLibre fixes are required. Use Flutter
3.35+ / Dart 3.7+, Android compile SDK 36 and JDK 21, or iOS 13+. See example
for a complete Android/iOS/web host. iOS device validation is still outstanding.
import 'package:qartas_maps/qartas_maps.dart';
final client = ChafMapsClient(
baseUrl: 'https://YOUR-QARTAS-API',
apiKey: scopedCustomerKey,
);
final map = QartasMapController(position: const LatLng(32.5225, 35.8745));
// Place in a bounded layout, such as Expanded.
QartasNavigationMap(
client: client,
controller: map,
attribution: requiredProviderAttribution,
onError: (error) => showMapUnavailableState(),
);
// Host owns GPS permissions, location filtering, routing and trip lifecycle.
map.updateLocation(location, heading: courseDegrees);
map.setView(QartasMapView.forward2d);
map.setDirections(directions); // rejects degraded straight-line responses
map.recenter();
The route defaults match the accepted orange driver-app line: 7 logical pixels
with an 11-pixel dark orange casing. Supply QartasRouteStyle to customize it.
setRouteGeometry takes a validated GeoJSON LineString and optional traveled
LineString. User gestures pause following; recenter() resumes it.
Use a scoped customer credential with appropriate quotas. Never put the proxy's
internal token, signing secret, or administrative key in an app. Short-lived
access can use the existing tokenProvider integration when the issuing server
and standalone API share its documented authentication contract. Keys are kept
in memory by this SDK. The widget refreshes signed tile access during long
sessions and on resume.
Upright labels and coverage
The API must serve aligned original and label-free 512px tiles through
/maps/v1/tiles and /maps/v1/clean-tiles. The widget fetches a moving 3×3 window,
joins neighbors, extracts label pixels in an isolate on native platforms, and
registers anchored MapLibre symbol images. Road labels follow the map surface; place labels stay upright at their geographic anchors. Image slots, paired-tile memory and processing concurrency are
bounded; superseded requests are canceled. Web label processing runs in a worker. Dense-city performance still needs workload-specific measurement.
Only sharp, correctly sized, successful tile pairs are used. If conversion fails, the widget switches to original imagery and reports the error. An error callback does not necessarily mean that all imagery is unavailable.
The code handles world XYZ coordinates and zooms 0–20; availability depends on CHAF Maps. The current visual fixtures cover a limited test area, not every region/script. Clean tile colors are traced into flat vector polygons beneath the route and labels, restoring the original experiment’s graphic treatment. Tracing shares the label worker/native isolate and is capped at 60,000 vertices per window. Original mode and failed tile windows hide this overlay. The 3D preset is a tilted navigation view. Labels remain raster sprites; this is not an OCR POI database, measured building model, vector road graph, or universal raster-to-vector conversion algorithm.
For a web host on another origin, configure its API edge's CORS allowlist and
expose X-Tile-Source; also allow the SDK's authentication headers. No CORS
policy or production edge configuration is changed by installing this package.
Voice
final player = QartasVoicePlayer(voice: client.voice);
final guidance = QartasVoiceGuidance(language: QartasVoiceLanguage.arabic);
guidance.setRoute(stableRouteId);
final prompt = guidance.update(
maneuverId: nextManeuver.id,
instruction: nextManeuver.instruction, // same text as your navigation banner
distanceMeters: alongRoadDistanceToManeuver,
speedKmh: speedKmh,
);
if (prompt != null) await player.speak(prompt.text, language: 'ar-JO');
Timing follows the WSLT active-trip reference's speed-based far/near/now announcements. Qartas owns its implementation and does not import WSLT services. Piper uses Amy (English) and Kareem (Arabic), with device TTS fallback. New prompts cancel old network requests; playback commands are serialized. Muting and disposal suppress stale playback and fallback. The host should mute while calls or recordings own its audio session, and stop voice before installing a reroute. Missing maneuver data must never produce guessed spoken turns.
Android hosts need INTERNET permission and an android.intent.action.TTS_SERVICE
query on recent Android releases (included in the example). iOS navigation
speech uses playback/voicePrompt with ducking. Browser speech/audio may require
a user gesture. Actual audio focus, Bluetooth and Arabic voice availability
still need device checks.
Dispose the map controller, voice player and API client when their owning session ends. The widget owns only its map-specific requests and timers.
Browser worker asset
After building a Flutter web host, compile tool/label_worker.dart with
dart compile js -O2 and publish the output as qartas_label_worker.js next to
that host's index.html. It uses the same extraction code as the native isolate.
The worker keeps tile processing off the browser UI thread. Native apps need no
worker asset. See docs/native-navigation-sdk.md for preview diagnostics and the
example build command.