How to Collect and Reorder Route Waypoints in a Web App
- API or service
- Route Waypoint Selector + Address Autocomplete API
- Task
- Addresses and stop order → resolved route waypoints
- Examples
- Difficulty
- Beginner
- Time
- 8 min
Collect and reorder route waypoints in a web app
A user needs to enter an origin, a destination, and optional intermediate stops before your application can calculate a route. Each typed address must be resolved to latitude and longitude, and the application must preserve the user's chosen stop order.
Use @geoapify/route-waypoint-selector to provide this interface. The library uses Geoapify Address Autocomplete and lets users search for, add, remove, edit, and drag waypoints into order. It returns ordered waypoint objects containing a label, latitude, longitude, and an optional application ID.
The selector collects route inputs; it does not calculate or display the route. Your application decides when to pass the resolved coordinates to the Routing API or another routing service.
Task flow: typed addresses → autocomplete selections → ordered { label, lat, lon } waypoints → routing request.
Plan the waypoint collection experience
- Require selected suggestions: Typed text alone is not a resolved waypoint. Enable route calculation only when at least two waypoints have a label, latitude, and longitude.
- Keep stable IDs: Assign an application ID when waypoints correspond to deliveries, appointments, or other records. Use the ID—not the current array position—to keep markers and business data connected when stops move.
- Define the stop order: The array order is the route order. Let users drag intermediate stops, but make it clear whether the first and last items represent a fixed origin and destination.
- Choose when to calculate: A “Build route” button avoids a new routing request after every edit. Live recalculation can work when it is debounced and stale requests are cancelled or ignored.
- Separate collection from optimization: Drag-and-drop records the order selected by the user. To calculate a better intermediate-stop order, send the resolved waypoints to the Routing API with
optimize_stops=true. - Show incomplete state: Keep unresolved or cleared waypoints visibly different from selected addresses, and do not silently send their text as coordinates.
- Support recovery: Preserve the current waypoint list when a routing request fails so the user can retry without entering the stops again.
- Clean up the component: Call
destroy()when the view is permanently removed so owned DOM listeners and autocomplete resources are released.
Add the route waypoint selector with JavaScript
Use @geoapify/route-waypoint-selector when the application needs a ready-made waypoint form with address autocomplete and drag-and-drop reordering. Install the package; its autocomplete dependency is installed automatically.
npm install @geoapify/route-waypoint-selector
Add a container for the selector and a separate action for calculating the route:
<div id="waypoint-selector"></div>
<button id="build-route" type="button" disabled>Build route</button>
Import matching styles for the waypoint selector and its autocomplete fields, then enable the button only when every waypoint is resolved:
import { WaypointSelector } from "@geoapify/route-waypoint-selector";
import "@geoapify/geocoder-autocomplete/styles/minimal.css";
import "@geoapify/route-waypoint-selector/styles/core.css";
import "@geoapify/route-waypoint-selector/styles/theme-minimal.css";
const apiKey = "YOUR_API_KEY";
const buildRouteButton = document.querySelector("#build-route");
const isResolved = ({ label, lat, lon }) =>
Boolean(label) && Number.isFinite(lat) && Number.isFinite(lon);
const selector = new WaypointSelector("#waypoint-selector", apiKey, {
onChange: (waypoints) => {
// A route needs at least two fully resolved waypoints.
buildRouteButton.disabled =
waypoints.length < 2 || !waypoints.every(isResolved);
}
});
buildRouteButton.addEventListener("click", async () => {
const waypoints = selector.getWaypoints();
// Routing API query coordinates use latitude,longitude order.
const waypointValue = waypoints
.map(({ lat, lon }) => `${lat},${lon}`)
.join("|");
const query = new URLSearchParams({
waypoints: waypointValue,
mode: "drive",
format: "geojson",
apiKey
});
const response = await fetch(
`https://api.geoapify.com/v1/routing?${query}`
);
if (!response.ok) {
throw new Error(`Routing failed with status ${response.status}`);
}
const route = await response.json();
console.log(route);
});
// Call selector.destroy() when this view is permanently removed.
WaypointSelector returns waypoint objects as { label, lat, lon, id? }. The Routing API expects each query waypoint as latitude,longitude, separated by |; URLSearchParams safely encodes the complete value.
The package provides the input and autocomplete behavior. The Routing API produces the route geometry, distance, travel time, and directions. Rendering that route on a map remains the responsibility of your application and map library.
Handle waypoint state in production
- Keep the full waypoint objects in application state instead of storing only their display labels.
- Use
onChangeto update the canonical ordered list. Use the more specificonAdd,onRemove,onReorder, oronWaypointChangecallbacks only when the application needs event-specific behavior. - Treat manual edits after address selection as unresolved until the user selects another autocomplete suggestion.
- Key map markers and domain records by stable waypoint IDs so drag-and-drop reordering does not attach data to the wrong stop.
- Prevent duplicate submissions while a route request is pending. Handle authorization errors, quota exhaustion, network failures, and empty route results without clearing the waypoint form.
- Restrict browser API keys to the application's allowed origins. Never ship an unrestricted key in client code.
- Keep a visible label for the selector, expose clear add/remove controls, preserve keyboard reordering, and announce changes for assistive technology.
- Call
selector.destroy()during the framework's unmount or destroy lifecycle.
If the user changes the order manually, send that order to the Routing API without optimize_stops. If the application should reorder only the intermediate stops, set optimize_stops=true; the origin and destination remain fixed.