{"_id":"@afrimap/sdk","_rev":"2-155679f10858d05ff87574dbf9451a96","name":"@afrimap/sdk","dist-tags":{"latest":"1.0.2"},"versions":{"1.0.1":{"name":"@afrimap/sdk","version":"1.0.1","keywords":["maps","africa","côte-d-ivoire","abidjan","routing","geocoding","places","poi","maplibre","valhalla","afrimap"],"author":{"name":"Rellinxe"},"license":"MIT","_id":"@afrimap/sdk@1.0.1","maintainers":[{"name":"rellinxe","email":"rellinxe@gmail.com"}],"homepage":"https://portal.afrimap.ci/docs/sdk/javascript","bugs":{"url":"https://github.com/Rellinxe27/afri-map/issues"},"dist":{"shasum":"65f3562dc5a75acf050adcba49dc140d1bb2b76b","tarball":"https://registry.npmjs.org/@afrimap/sdk/-/sdk-1.0.1.tgz","fileCount":8,"integrity":"sha512-h7SYch7sYtrFpEK1lTtFjbVTGJhBqbkXhWsoG2tuT89GI4GanHPv3jSkOT1Bx+lOidnHNrQ2edCBoe3mSJv5lg==","signatures":[{"sig":"MEYCIQCMVcv4Ylq55KSGi3tIzSzkH/FkGnwG/uuCZ5hlG/b7pQIhAK390aAuwS8R/9aK+CACNSvNSWc+bOjSzynHYtRog5hR","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@afrimap%2fsdk@1.0.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":485234},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.mjs","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"6f60023b99cc78f867afb20d19d8d1aea0a29ef8","scripts":{"test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","build:watch":"tsup --watch","test:coverage":"vitest run --coverage","prepublishOnly":"npm run build && npm run typecheck"},"_npmUser":{"name":"rellinxe","email":"rellinxe@gmail.com"},"repository":{"url":"git+https://github.com/Rellinxe27/afri-map.git","type":"git","directory":"sdks/javascript"},"_npmVersion":"10.9.7","description":"AfriMap Engine SDK — West African mapping API for routing, geocoding, and POI search","directories":{},"sideEffects":false,"_nodeVersion":"22.22.2","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","vitest":"^4.1.4","typescript":"^5.9.3","@types/geojson":"^7946.0.16","@vitest/coverage-v8":"^4.1.4"},"peerDependencies":{"maplibre-gl":">=4.5.0"},"peerDependenciesMeta":{"maplibre-gl":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_1.0.1_1776540107585_0.348193939644712","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@afrimap/sdk","version":"1.0.2","description":"AfriMap Engine SDK — West African mapping API for routing, geocoding, and POI search","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"main":"dist/index.js","module":"dist/index.mjs","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"sideEffects":false,"engines":{"node":">=18.0.0"},"scripts":{"build":"tsup","build:watch":"tsup --watch","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","typecheck":"tsc --noEmit","prepublishOnly":"npm run build && npm run typecheck"},"keywords":["maps","africa","côte-d-ivoire","abidjan","routing","geocoding","places","poi","maplibre","valhalla","afrimap"],"author":{"name":"Rellinxe"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/Rellinxe27/afri-map.git","directory":"sdks/javascript"},"bugs":{"url":"https://github.com/Rellinxe27/afri-map/issues"},"homepage":"https://portal.afrimap.ci/docs/sdk/javascript","peerDependencies":{"maplibre-gl":">=4.5.0"},"peerDependenciesMeta":{"maplibre-gl":{"optional":true}},"devDependencies":{"@types/geojson":"^7946.0.16","@vitest/coverage-v8":"^4.1.4","tsup":"^8.5.1","typescript":"^5.9.3","vitest":"^4.1.4"},"_id":"@afrimap/sdk@1.0.2","gitHead":"ae943507ce51ec88c37067364b45f769f11b5623","_nodeVersion":"22.22.2","_npmVersion":"10.9.7","dist":{"integrity":"sha512-weL3+gYWV+qdCipVCCU3ihYbdAQG6W57r/mUZMp8Azg5mmmGM8vKqxCB0ydT6uTkbZGk83w66Edi18I8wQ9uPQ==","shasum":"df4235b7d106eb81636e4c84512f39a4e3e3ae22","tarball":"https://registry.npmjs.org/@afrimap/sdk/-/sdk-1.0.2.tgz","fileCount":8,"unpackedSize":486658,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@afrimap%2fsdk@1.0.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDDM69rNPzARYK1TWlWANFLNPBoH8QIC82VqpOCPcZwZAIgGQIVO8G1lokWYjRo9pW+sX/OMgRydgZCRfej4yqBLYs="}]},"_npmUser":{"name":"rellinxe","email":"rellinxe@gmail.com"},"directories":{},"maintainers":[{"name":"rellinxe","email":"rellinxe@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sdk_1.0.2_1776542869876_0.8774538534534693"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-18T19:21:47.479Z","modified":"2026-04-18T20:07:50.640Z","1.0.1":"2026-04-18T19:21:47.720Z","1.0.2":"2026-04-18T20:07:50.023Z"},"bugs":{"url":"https://github.com/Rellinxe27/afri-map/issues"},"author":{"name":"Rellinxe"},"license":"MIT","homepage":"https://portal.afrimap.ci/docs/sdk/javascript","keywords":["maps","africa","côte-d-ivoire","abidjan","routing","geocoding","places","poi","maplibre","valhalla","afrimap"],"repository":{"type":"git","url":"git+https://github.com/Rellinxe27/afri-map.git","directory":"sdks/javascript"},"description":"AfriMap Engine SDK — West African mapping API for routing, geocoding, and POI search","maintainers":[{"name":"rellinxe","email":"rellinxe@gmail.com"}],"readme":"# AfriMap JavaScript / TypeScript SDK\n\nTypeScript-first SDK for the AfriMap Engine API. Provides routing, geocoding, places search, and MapLibre GL JS map integration helpers for web applications.\n\n## Requirements\n\n| Dependency | Minimum version |\n|---|---|\n| Node.js | 18+ |\n| MapLibre GL JS | 4.5+ (optional, for map rendering and 3D car markers) |\n| TypeScript | 5.x (optional, types included) |\n\n## Installation\n\n```bash\nnpm install @afrimap/sdk\n# or\nyarn add @afrimap/sdk\n```\n\n## Quick start\n\n```typescript\nimport { AfriMapClient } from '@afrimap/sdk';\n\nconst client = new AfriMapClient({\n  apiKey: 'afm_live_your_key_here',   // get one at portal.afrimap.ci\n  baseUrl: 'https://api.afrimap.ci',  // default\n});\n```\n\n## Routing\n\n```typescript\n// Point-to-point directions (Plateau → Cocody, Abidjan)\nconst response = await client.routing.route({\n  origin: { lat: 5.3197, lng: -4.0167 },\n  destination: { lat: 5.3480, lng: -3.9904 },\n  mode: 'auto',\n  language: 'fr',\n});\n\nconst route = response.routes[0];\nconsole.log(`${route.distance} km, ${route.duration / 60} min`);\n\n// eta_enhanced: ML-adjusted ETA in seconds (undefined when no traffic data)\nif (route.eta_enhanced !== undefined) {\n  console.log(`Traffic-adjusted ETA: ${route.eta_enhanced} s`);\n}\n\n// Turn-by-turn instructions\nfor (const leg of route.legs) {\n  for (const maneuver of leg.maneuvers) {\n    console.log(`${maneuver.type}: ${maneuver.instruction}`);\n  }\n}\n```\n\n## Geocoding\n\n```typescript\n// Forward geocode\nconst result = await client.geocoding.forward('Cocody Abidjan');\nconsole.log(result.features[0].geometry.coordinates); // [-3.9904, 5.3480]\n\n// Reverse geocode (returns neighbourhood name when inside a known quartier)\nconst reverse = await client.geocoding.reverse({ lat: 5.3480, lng: -3.9904 });\nconsole.log(reverse.features[0].properties.neighbourhood); // \"Riviera 2\"\n\n// Autocomplete (merges Elasticsearch + local landmark DB)\nconst suggestions = await client.geocoding.autocomplete('rond-point', {\n  focus: { lat: 5.3614, lng: -3.9689 },\n});\n```\n\n### landmark_nearby\n\nWhen a result has a well-known landmark within ~200 m, the `landmark_nearby` property is set:\n\n```typescript\nfor (const feature of suggestions.features) {\n  const lm = feature.properties.landmark_nearby;\n  if (lm) {\n    console.log(`Near ${lm.name} (${lm.category}, ${lm.distance_m} m away)`);\n  }\n}\n```\n\n## Places\n\n```typescript\n// Nearby search with market hours\nconst places = await client.places.nearby({\n  lat: 5.36, lng: -4.03,\n  radius: 500,\n  category: 'market',\n});\n\nfor (const place of places.places) {\n  if (place.market_hours?.open_today) {\n    console.log(`${place.name} open until ${place.market_hours.closes_at}`);\n  }\n}\n```\n\n## Map Styles\n\nAfriMap provides three production-ready MapLibre GL JS styles. All styles are:\n\n- **Mobile-optimised** — larger labels at low zoom, `symbol-spacing` to prevent crowding, `text-optional` so icons show even when text doesn't fit, minor road labels deferred until z15\n- **Traffic overlay included** — toggleable `traffic` line layer with data-driven color (green/orange/red). Toggle with `map.setLayoutProperty('traffic', 'visibility', 'none')`\n\n| Style name | Description | Best for |\n|---|---|---|\n| `day` | \"Sahel Clair\" — warm sand/terracotta palette | Daytime navigation |\n| `night` | \"Lagune Nuit\" — dark blue/charcoal | Night driving, OLED screens |\n| `satellite` | Esri World Imagery + vector overlay | Aerial view, landmark identification |\n\n### Loading a style\n\n```typescript\nimport maplibregl from 'maplibre-gl';\n\n// via the AfriMap SDK (automatically injects API key)\nconst styleUrl = client.maps.styleUrl('day');  // or 'night', 'satellite'\n\nconst map = new maplibregl.Map({\n  container: 'map',\n  style: styleUrl,\n  center: [-4.0167, 5.3197],  // Plateau, Abidjan\n  zoom: 12,\n});\n```\n\n### Direct style URLs (without SDK)\n\n```javascript\nconst API_KEY = 'afm_live_your_key_here';\nconst GATEWAY = 'https://api.afrimap.ci';\n\nconst map = new maplibregl.Map({\n  container: 'map',\n  style: `${GATEWAY}/v1/tiles/style/day?key=${API_KEY}`,\n  // Alternatively fetch and inject key:\n  // style: await fetch(`${GATEWAY}/v1/tiles/style/day`, {\n  //   headers: { 'X-AfriMap-Key': API_KEY }\n  // }).then(r => r.json()),\n  center: [-4.0167, 5.3197],\n  zoom: 12,\n});\n```\n\n### 3D Buildings\n\nThe `day` and `night` styles include a `buildings-3d` fill-extrusion layer that renders Overture building height data as 3D geometry at zoom ≥ 15. It is toggleable:\n\n```javascript\n// Toggle 3D buildings\nmap.setLayoutProperty('buildings-3d', 'visibility', 'none');    // flat\nmap.setLayoutProperty('buildings-3d', 'visibility', 'visible'); // 3D\n```\n\n### Traffic layer toggle\n\n```javascript\n// The traffic layer is visible by default — hide it:\nmap.setLayoutProperty('traffic', 'visibility', 'none');\n\n// Show it again:\nmap.setLayoutProperty('traffic', 'visibility', 'visible');\n\n// Build a toggle button using the layer metadata:\nconst trafficLayer = map.getStyle().layers.find(l => l.id === 'traffic');\nconst legend = trafficLayer?.metadata?.['afrimap:legend'];\n// { green: '> 40 km/h', orange: '15–40 km/h', red: '< 15 km/h' }\n```\n\n### Real-time traffic via WebSocket\n\n```typescript\nconst ws = new WebSocket(\n  `wss://api.afrimap.ci/v1/traffic/ws?bbox=-4.1,5.25,-3.9,5.40`,\n  [],\n  { headers: { 'X-AfriMap-Key': API_KEY } },\n);\n\nws.onmessage = (event) => {\n  const frame = JSON.parse(event.data);\n  // frame.type === 'traffic.update'\n  // frame.data  → GeoJSON FeatureCollection\n  // frame.ts    → ISO-8601 timestamp\n  // frame.bbox  → [minLng, minLat, maxLng, maxLat]\n\n  if (frame.type === 'traffic.update') {\n    (map.getSource('traffic-live') as maplibregl.GeoJSONSource)\n      .setData(frame.data);\n  }\n};\n```\n\n## 3D Driver Markers\n\n`DriverMarker3D` renders a glTF binary car model as a MapLibre `model` layer for navigation views. It smoothly interpolates 1 Hz GPS fixes into 60 fps motion via `requestAnimationFrame`, and falls back to a flat brand-coloured circle below zoom 14 where 3D models become unreadable.\n\n**Requires `maplibre-gl >= 4.5`** for native `model` layer support — the constructor throws if the supplied map doesn't expose `addModel`.\n\n```typescript\nimport maplibregl from 'maplibre-gl';\nimport { DriverMarker3D } from '@afrimap/sdk';\n\nconst map = new maplibregl.Map({\n  container: 'map',\n  style: client.maps.styleUrl('day'),\n  center: [-4.0167, 5.3197],\n  zoom: 16,\n  pitch: 60,  // tilt the camera for a navigation feel\n});\n\nmap.on('load', () => {\n  const car = new DriverMarker3D(map, {\n    id: 'driver-1',\n    position: { lat: 5.3197, lng: -4.0167 },\n    heading: 0,                  // degrees clockwise from north\n    variant: 'orange',           // 'orange' | 'gray' | 'blue'\n    modelBaseUrl: 'https://api.afrimap.ci/v1/assets/models',  // default\n    fallbackZoomThreshold: 14,   // default\n  });\n\n  // Push GPS fixes — easing handles the 60 fps in-between frames\n  setInterval(() => car.update(nextFix, nextHeading), 1000);\n\n  // Swap colour to indicate state change (e.g. driver becomes available)\n  car.setVariant('gray');\n\n  // Tear down when the trip ends\n  // car.remove();\n});\n```\n\nThe three colour variants map to:\n- `orange` — `#F2731A` — HopRide brand car\n- `gray` — `#8C8C95` — available driver\n- `blue` — `#3373D9` — premium tier\n\n`heading` is **degrees clockwise from north**, matching the GPS bearing convention and Valhalla maneuver `bearing_after` values.\n\n## ETA (ML-Enhanced)\n\n```typescript\nconst eta = await client.eta.get({\n  origin: { lat: 5.3197, lng: -4.0167 },\n  destination: { lat: 5.3480, lng: -3.9904 },\n});\n\nconsole.log(`ETA: ${eta.duration_min} min (${eta.confidence}% confidence)`);\nconsole.log(`Distance: ${eta.distance_km} km`);\nconsole.log(`Speed factor applied: ${eta.speed_factor}`);\n```\n\n## Turn-by-turn navigation (Phase 3)\n\n`NavigationSession` wraps the full GPS tracking loop described in\n`NAVIGATION.md §4`:\n\n```typescript\nimport { NavigationSession, NavigationVoice } from '@afrimap/sdk';\n\nconst voice = new NavigationVoice({ apiConfig: client.apiConfig, language: 'fr' });\n\nconst session = new NavigationSession({\n  http: client.http,\n  destination: { lat: 5.3480, lng: -3.9904 },\n  language: 'fr',\n  listeners: {\n    onStateChange:     (from, to) => console.log(from, '→', to),\n    onProgress:        (p) => hud.updateETA(p.remainingDurationS),\n    onVoicePrompt:     (p) => voice.speak(p),\n    onManeuverAdvance: (_from, _to, m) => hud.showManeuver(m),\n    onArrive:          () => hud.showArrival(),\n  },\n});\n\nsession.start(route);\n\nnavigator.geolocation.watchPosition((pos) => {\n  session.onLocationUpdate({\n    lat: pos.coords.latitude,\n    lng: pos.coords.longitude,\n    heading: pos.coords.heading ?? undefined,\n    speedMps: pos.coords.speed ?? undefined,\n  });\n});\n```\n\nState machine: `IDLE → NAVIGATING → OFF_ROUTE → REROUTING → NAVIGATING → ARRIVED`.\nOff-route threshold defaults to **50 m**; arrival to **30 m**; reroute retries\nup to **3** times before parking the session in `OFF_ROUTE`.\n\n### Voice guidance\n\n- **French / English** → Web Speech API at `rate = 0.9` for in-car clarity.\n- **Dioula / Baoulé / etc.** → pre-fetched from the Edge TTS microservice\n  via `POST /v1/navigate/voice/batch`, cached in `IndexedDB`. Call\n  `voice.precacheRoute(route)` once after `session.start(...)` to warm the\n  cache before entering no-signal areas.\n\n## Offline maps (Phase 3)\n\n```typescript\nconst zones = await client.offline.listZones();\nconst manifest = await client.offline.getZoneVersion('cocody');\n\n// Download every file of the bundle (tiles.mbtiles, routing.tar, landmarks.sqlite, audio.tar)\nconst outDir = '/path/in/your/OPFS/cocody';\nawait client.offline.downloadZone({ zoneId: 'cocody', baseDir: outDir,\n    onProgress: (p) => setBar(p.ratio) });\n```\n\nSee `MOBILE_VALHALLA.md` at the repo root for details on linking the\nValhalla native library for fully-offline routing (the SDK ships a stub\n`OfflineRouter` that throws until the binary is wired in).\n\n## Driver feedback (Phase 3)\n\n```typescript\nawait client.feedback.submit({\n  driverId: 'drv-42',\n  type: 'road_closed',\n  location: { lat: 5.3197, lng: -4.0167 },\n  description: 'Accident bloquant',\n  metadata: { closure_radius_m: 80, expires_in_h: 2 },\n});\n```\n\nSupported types: `road_closed`, `new_shortcut`, `name_correction`, `landmark_new`.\n\n## Error handling\n\nAll methods throw an `AfriMapError` on API errors:\n\n```typescript\nimport { AfriMapError } from '@afrimap/sdk';\n\ntry {\n  const result = await client.routing.route({ ... });\n} catch (err) {\n  if (err instanceof AfriMapError) {\n    console.error(err.code, err.message);\n    // err.code: 400 (bad request), 401 (invalid key), 429 (rate limit), 503 (upstream)\n  }\n}\n```\n\n## Bundle size\n\nThe SDK ships as ESM + CJS with full tree-shaking support. Only the modules you import are included:\n\n```typescript\nimport { routing } from '@afrimap/sdk/routing';    // ~4 KB gzipped\nimport { geocoding } from '@afrimap/sdk/geocoding'; // ~3 KB gzipped\nimport { maps } from '@afrimap/sdk/maps';           // ~2 KB gzipped\n```\n\n## Abidjan coordinates reference\n\n| Location | lat | lng |\n|---|---|---|\n| Plateau (Centre) | 5.3197 | -4.0167 |\n| Cocody | 5.3480 | -3.9904 |\n| Yopougon | 5.3364 | -4.0694 |\n| Abobo | 5.4200 | -4.0100 |\n| Adjamé | 5.3640 | -4.0270 |\n| Port-Bouët (Aéroport) | 5.2544 | -3.9262 |\n","readmeFilename":"README.md"}