Maps
Search, then route
Wire a search box to a map, let the user pick a destination, and draw the route. Includes the coordinate flip that catches everyone.
Search-then-route is the core Maimaps flow: the user types a place, picks a result, and sees the way there. This guide builds it, and along the way covers the single most common integration bug in the whole API.
1. Wire up search
useSearch gives you execute, plus data, loading, and error. It is safe
to call on every keystroke: the hook drops stale responses using a sequence
token, so a slow request for "gar" can never overwrite the results for "garki".
import { useSearch } from "@maimaps/react";
function SearchBox({ onPick }) {
const { execute, data, loading } = useSearch();
return (
<>
<input
placeholder="Search places…"
onChange={(e) =>
execute({
q: e.target.value,
latitude: 9.0579, // bias toward the user
longitude: 7.4951,
})
}
/>
{loading && <p>Searching…</p>}
<ul>
{data?.map((result) => (
<li key={result.id}>
<button onClick={() => onPick(result)}>
{result.name} — {result.distanceKm} km
</button>
</li>
))}
</ul>
</>
);
}
Passing latitude and longitude does two things: it ranks nearby results
higher, and it populates distanceKm on every hit. Omit them and you get
neither.
2. Route to the result
The user has picked a destination. Feed its coordinates to useRoute.
import { useRoute } from "@maimaps/react";
const { execute: getRoute, data: route } = useRoute();
const onPick = (result) =>
getRoute({
originLat: 9.0579,
originLng: 7.4951,
destLat: result.latitude,
destLng: result.longitude,
mode: "drive",
});
The response is OSRM-shaped. route.routes[0] carries distance in meters,
duration in seconds, and geometry as an encoded polyline.
const best = route?.routes[0];
const km = best && (best.distance / 1000).toFixed(1);
const mins = best && Math.round(best.duration / 60);
3. Draw it, and mind the coordinate order
Here is the bug. Read this section even if you skim the rest.
decodePolyline returns [latitude, longitude] pairs, because that is the
convention the polyline format uses. But RoutePolyline, RouteShape, and
MapLibre generally all want [longitude, latitude], because that is the GeoJSON
convention. You have to flip.
import { decodePolyline, RoutePolyline } from "@maimaps/react";
const line = route
? decodePolyline(route.routes[0].geometry).map(
([lat, lng]) => [lng, lat] as const
)
: [];
return (
<MaimapsMap>
{line.length > 0 && <RoutePolyline coordinates={line} color="#2563eb" />}
</MaimapsMap>
);
The rule, stated once: requests take latitude first, response geometry is
longitude first. That applies to maneuver.location, waypoints[].location,
and every GeoJSON coordinates array in the API.
Flutter sidesteps this entirely. RouteInfo decodes the polyline into
List<LatLng> for you:
final route = await Maimaps.instance.routing.getRoute(
origin: const LatLng(9.0579, 7.4951),
destination: const LatLng(9.0765, 7.3986),
);
final points = route.polylinePoints; // ready to draw
4. Add stops
For waypoints, or to route from a loccode instead of a coordinate, drop to the
client and call routeAdvanced. There is no dedicated hook for it.
import { useMaimaps } from "@maimaps/react";
const client = useMaimaps();
const trip = await client.routeAdvanced({
originLoccode: "FC2F 3KN",
destLat: 9.0765,
destLng: 7.3986,
waypoints: [
{ order: 1, latitude: 9.0611, longitude: 7.4702 },
{ order: 2, latitude: 9.0688, longitude: 7.4310 },
],
mode: "drive",
});
Waypoints are visited in ascending order. Give each endpoint either a
coordinate pair or a loccode, never neither, or you get a 400 INVALID_PARAMS.
What's next
- Working with loccodes explains what
FC2F 3KNactually is, and why it is a street rather than a point. - Handling errors and quotas covers what to do when routing fails, which it will.