N-Ways
Developer docsRider pickup

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 okada mode with fares: 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

  1. Search. attachAutocomplete suggests streets, businesses, landmarks and codes as the rider types, nearest first. The whole search is one autocomplete session for billing.
  2. The door, not the middle of the building. A suggestion with a postcode is a NIPOST-coded building; its entrances say where the gate is, and verified ones have been confirmed by people there.
  3. An okada route. mode: 'okada' keeps to the okada bans N-Ways knows; vehicleRules.avoided lists the ones the route went around. If the pickup is inside a ban area, the route ends at its edge and says so in vehicleRules.endBan.
  4. The fare. With fares: true, each route carries fares: a range and a text such 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.