Sample: rider pickup
Find the pickup from what the customer typed, route the rider there by okada around the ban areas, and show what the trip pays.
On this page
A rider gets a new pickup: the customer typed “Shoprite Ikeja”, a NIPOST postcode, or a GhanaPost code. This screen finds the place, picks the building’s entrance when N-Ways knows it, plans the okada trip there around the okada ban areas, and shows how long it takes and what the fare should be.
What it uses
- Maps SDK for the web: the map and the search box (autocomplete).
- Postcode lookup: a NIPOST postcode’s building and its entrances, verified ones first.
- Routes in
okadamode withfares: true: the route keeps off roads and areas where okadas are banned, and the answer carries the fare.
Use a key restricted to your website (or your app, in a web view) and to the Maps, Autocomplete, Geocoding and Routes APIs.
The page
pickup.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Pickup</title>
<link rel="stylesheet" href="https://nways.ng/sdk/v1/maplibre-gl.css" />
<style>
body { margin: 0; font: 16px/1.4 system-ui, sans-serif }
#map { height: 60vh }
.panel { padding: 12px 16px }
input { width: 100%; box-sizing: border-box; padding: 12px; font-size: 16px; border: 1px solid #b8c2bc; border-radius: 10px }
#trip { margin-top: 12px }
#trip b { font-size: 1.4em }
</style>
</head>
<body>
<div id="map"></div>
<div class="panel">
<label for="pickup">Pickup</label>
<input id="pickup" placeholder="Address, NIPOST postcode or GhanaPost code" />
<div id="trip" aria-live="polite"></div>
</div>
<script type="module">
import { attachAutocomplete, createClient, createMap, maplibregl } from 'https://nways.ng/sdk/v1/nways-maps.standalone.js'
const key = 'YOUR_API_KEY'
const nways = createClient({ key })
const map = createMap({ container: 'map', key, center: [3.3515, 6.6018], zoom: 13 })
const trip = document.getElementById('trip')
let pin = null
// Where the rider is now (Allen Avenue, Ikeja, if the phone won't say).
const here = () =>
new Promise((done) =>
navigator.geolocation.getCurrentPosition(
(p) => done({ lat: p.coords.latitude, lng: p.coords.longitude }),
() => done({ lat: 6.6018, lng: 3.3515 }),
{ enableHighAccuracy: true, timeout: 8000 },
),
)
// A NIPOST postcode's best entrance: a verified one if there is one.
async function entranceOf(place) {
if (!place.postcode) return null
const building = await nways.postcode(place.postcode)
const door = building.entrances?.find((e) => e.verified) ?? building.entrances?.[0]
return door ? door.location : null
}
async function plan(place) {
trip.textContent = 'Planning…'
const door = await entranceOf(place).catch(() => null)
const target = door ?? place.location
const [lng, lat] = target.coordinates
pin?.remove()
pin = new maplibregl.Marker({ color: '#0b7a3e' }).setLngLat([lng, lat]).addTo(map)
try {
const { routes } = await nways.route({ origin: await here(), destination: { lat, lng }, mode: 'okada', fares: true })
const r = routes[0]
const line = { type: 'Feature', properties: {}, geometry: r.geometry }
if (map.getSource('trip')) map.getSource('trip').setData(line)
else {
map.addSource('trip', { type: 'geojson', data: line })
map.addLayer({ id: 'trip', type: 'line', source: 'trip', layout: { 'line-cap': 'round', 'line-join': 'round' }, paint: { 'line-color': '#1a73e8', 'line-width': 6 } })
}
const box = r.geometry.coordinates.reduce((b, c) => b.extend(c), new maplibregl.LngLatBounds(r.geometry.coordinates[0], r.geometry.coordinates[0]))
map.fitBounds(box, { padding: 48 })
const fare = r.fares?.find((f) => f.mode === 'okada')
const bans = r.vehicleRules?.avoided?.length ?? 0
trip.innerHTML = ''
trip.append(
Object.assign(document.createElement('b'), { textContent: `${Math.round(r.durationS / 60)} min` }),
` · ${(r.distanceM / 1000).toFixed(1)} km`,
fare ? ` · fare ${fare.text}` : '',
bans ? ` · keeps out of ${bans} okada ban area${bans > 1 ? 's' : ''}` : '',
door ? ' · to the entrance' : '',
)
} catch (e) {
// No okada route (for example, the pickup is cut off by ban areas), or a key or quota error.
trip.textContent = /^(OKADA_BANNED|NO_ROUTE|NO_ROAD_ROUTE)/.test(e.reason ?? '') ? 'No okada route there. Try a car.' : `Couldn’t plan the trip (${e.reason ?? e.message}).`
}
}
attachAutocomplete(document.getElementById('pickup'), {
client: nways,
near: { lat: 6.6018, lng: 3.3515 },
onSelect: plan,
})
</script>
</body>
</html>How it works
- Search.
attachAutocompletesuggests streets, businesses, landmarks and codes as the rider types, nearest first. The whole search is one autocomplete session for billing. - The door, not the middle of the building. A suggestion with a
postcodeis a NIPOST-coded building; itsentrancessay where the gate is, andverifiedones have been confirmed by people there. - An okada route.
mode: 'okada'keeps to the okada bans N-Ways knows;vehicleRules.avoidedlists the ones the route went around. If the pickup is inside a ban area, the route ends at its edge and says so invehicleRules.endBan. - The fare. With
fares: true, each route carriesfares: a range and atextsuch as ₦1,500–2,200, from what riders paid on similar trips.
Make it yours
- Kekes and cars. Use
mode: 'keke'or'drive'; the fare follows the mode. - Postcodes straight away. If your customers give a NIPOST postcode, skip the search:
destination: { postcode: 'LA08A09DB09' }routes to the building’s best entrance. - In a native app. The same calls are in the Android and iOS SDKs.