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.

← Documentation