@geoapify/route-waypoint-selector
v1.0.0
Published
TypeScript waypoint selector UI component for route planning using Geoapify Autocomplete.
Maintainers
Readme
Route Waypoint Selector UI Library
@geoapify/route-waypoint-selector is a TypeScript UI library for building route waypoint pickers in web applications. Use it when you need to add, edit, reorder, and remove route stops in delivery planning, trip builders, ride booking, logistics dashboards, and map-based route editors. It uses Geoapify Address Autocomplete to help users search for and select addresses for each waypoint.
Each waypoint represents one stop in a route. The library gives you a simple UI for building, updating, and rearranging those stops.
- Create waypoints for routing: start with one or more stops and build a route step by step.
- Search addresses with autocomplete: type an address and choose from Geoapify suggestions.
- Add and remove waypoints: insert new stops when needed and remove stops you no longer want.
- Reorder waypoints with drag and drop: change the stop order by dragging items up or down.
- Use callbacks for changes: listen for updates when the route changes and keep your app state in sync.
Install
Install via npm for framework or bundler-based apps. Use the CDN example if you want to try the library directly in a browser without a build step.
To get a Geoapify API key with a free tier, sign up at https://www.geoapify.com/.
npm
Installing @geoapify/route-waypoint-selector also installs @geoapify/geocoder-autocomplete as a dependency:
npm i @geoapify/route-waypoint-selectorIn your app, import the CSS for both packages so the waypoint selector and geocoder are styled correctly:
import "@geoapify/geocoder-autocomplete/styles/minimal.css";
import "@geoapify/route-waypoint-selector/styles/core.css";
import "@geoapify/route-waypoint-selector/styles/theme-minimal.css";CDN
Use versioned URLs with <link> tags for CSS and an import map for browser-safe ESM loading:
<link rel="stylesheet" href="https://unpkg.com/@geoapify/[email protected]/styles/minimal.css" />
<link rel="stylesheet" href="https://unpkg.com/@geoapify/route-waypoint-selector@1/styles/core.css" />
<link rel="stylesheet" href="https://unpkg.com/@geoapify/route-waypoint-selector@1/styles/theme-minimal.css" />
<script type="importmap">
{
"imports": {
"@geoapify/geocoder-autocomplete": "https://unpkg.com/@geoapify/[email protected]/dist/index.js"
}
}
</script>
<script type="module">
import { WaypointSelector } from "https://unpkg.com/@geoapify/route-waypoint-selector@1/dist/index.js";
</script>This example has three parts:
- The
<link>tags load the CSS needed for the geocoder and the waypoint selector. - The import map tells the browser how to resolve
@geoapify/geocoder-autocomplete, which the library uses internally. - The
type="module"script loadsWaypointSelectorfrom unpkg and creates the selector instance in your page.
Basic usage
Use this setup when your app already has a bundler or build step. The JavaScript import gives you the selector class, and the CSS imports add the default widget and geocoder styles.
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 selector = new WaypointSelector("#waypoint-selector", "YOUR_GEOAPIFY_API_KEY", {
onChange: (waypoints, context) => {
console.log("Waypoints changed", { waypoints, context });
}
});Notes:
WaypointSelectorrenders the route waypoint UI into the container you pass in.#waypoint-selectorshould point to an element that already exists in your page.YOUR_GEOAPIFY_API_KEYshould be replaced with your actual Geoapify API key.onChangelets your app react whenever the waypoint list changes.
Styling
The library styles are split into two parts:
styles/core.csscontains shared layout and component base styles.- A theme file contains the visual skin (colors, borders, spacing details).
For now, there are 4 themes, and they correspond to the @geoapify/geocoder-autocomplete themes with the same names. Use matching theme pairs for consistent UI.
| Route Waypoint Selector | Geocoder Autocomplete |
| --- | --- |
| styles/theme-minimal.css | styles/minimal.css |
| styles/theme-minimal-dark.css | styles/minimal-dark.css |
| styles/theme-round-borders.css | styles/round-borders.css |
| styles/theme-round-borders-dark.css | styles/round-borders-dark.css |
API Reference
The API is small and focused: create the selector, read or update the waypoint list, and listen for changes.
| Method | Description |
| --- | --- |
| new WaypointSelector(container: HTMLElement \| string, apiKey: string, options?: WaypointSelectorOptions) | Creates and mounts the waypoint selector. See WaypointSelectorOptions. |
| getWaypoints(): Waypoint[] | Returns the current list of waypoints. See Waypoint. |
| setWaypoints(waypoints: Waypoint[]): void | Allows you to set the waypoint list from outside the component. See Waypoint. |
| addWaypoint(waypoint: Waypoint, index?: number): void | Adds a new waypoint at the end or at a chosen position. See Waypoint. |
| removeWaypoint(waypointIndexOrId: number \| string): void | Removes a waypoint by index or id. See Waypoint. |
| setWaypoint(waypointIndexOrId: number \| string, label: string, lat?: number, lon?: number): void | Updates one waypoint by index or id. See Waypoint. |
| isWaypointResolved(waypoint: Waypoint): boolean | Checks whether a waypoint has label, latitude, and longitude. See Waypoint. |
| destroy(): void | Removes the UI and cleans up events. |
Methods
new WaypointSelector(container: HTMLElement \| string, apiKey: string, options?: WaypointSelectorOptions)
Creates and mounts the selector in the chosen container.
const selector = new WaypointSelector("#waypoint-selector", "YOUR_GEOAPIFY_API_KEY", {
onChange: (waypoints, context) => {
console.log("Waypoints changed", waypoints, context);
}
});getWaypoints(): Waypoint[]
Returns the current waypoint list.
const waypoints = selector.getWaypoints();
console.log(waypoints);setWaypoints(waypoints: Waypoint[]): void
Replaces the current waypoint list with a new one.
selector.setWaypoints([
{
id: "wp-1",
label: "Times Square, New York, NY, USA",
lat: 40.758,
lon: -73.9855
},
{
id: "wp-2",
label: "Central Park, New York, NY, USA",
lat: 40.785091,
lon: -73.968285
}
]);addWaypoint(waypoint: Waypoint, index?: number): void
Adds a new waypoint at the end or at a specific position.
selector.addWaypoint({
id: "wp-3",
label: "Brooklyn Bridge, New York, NY, USA",
lat: 40.706086,
lon: -73.996864
});removeWaypoint(waypointIndexOrId: number | string): void
Removes a waypoint by index or by id.
selector.removeWaypoint("wp-3");
selector.removeWaypoint(0);setWaypoint(waypointIndexOrId: number | string, label: string, lat?: number, lon?: number): void
Updates a single waypoint’s label and coordinates.
selector.setWaypoint("wp-1", "Times Square, Manhattan, NY, USA", 40.758, -73.9855);reorderWaypoints(fromIndex: number, toIndex: number): void
Moves a waypoint to a different position in the list.
selector.reorderWaypoints(0, 1);isWaypointResolved(waypoint: Waypoint): boolean
Checks whether a waypoint already has label, latitude, and longitude.
const resolved = selector.getWaypoints().map((waypoint) => ({
waypoint,
resolved: selector.isWaypointResolved(waypoint)
}));
console.log(resolved);destroy(): void
Removes the selector from the page and cleans up event listeners.
selector.destroy();Waypoint
Waypoints are the data objects returned by the library and passed into callbacks. Each waypoint can contain:
| Field | Description |
| --- | --- |
| id | A custom waypoint identifier used by your app to connect related data. The library does not require it, but it helps you keep things like map markers in sync when waypoints are reordered. |
| label | The selected address or place name. |
| lat | Latitude. |
| lon | Longitude. |
Use the returned waypoint data to store route stops in your app state, send them to your backend, or build routing logic.
Localization / customization
You can customize the visible text in the UI and the behavior of address suggestions.
labelslets you change button text, headings, and accessibility labels.geocoderOptionsis forwarded to@geoapify/geocoder-autocompleteso you can customize the suggestion widget.
Example:
const selector = new WaypointSelector("#waypoint-selector", "YOUR_GEOAPIFY_API_KEY", {
labels: {
route_waypoint_selector: "Planificador de rutas",
choose_destination: "Elige una parada",
add_destination: "Añadir parada",
remove_destination: "Eliminar parada",
switch_waypoints: "Intercambiar paradas",
drag_to_reorder: "Arrastra para reordenar"
},
geocoderOptions: {
lang: "es"
}
});Callbacks
Callbacks are provided through WaypointSelectorOptions. Use them to react to changes in the waypoint list and keep your app state in sync.
| Callback | When it runs | Parameters |
| --- | --- | --- |
| onWaypointChange | When a waypoint is set or cleared. | (waypoint, context) |
| onAdd | When a waypoint is added. | (waypoint, index) |
| onRemove | When a waypoint is removed. | (waypoint, index) |
| onReorder | When the waypoint order changes. | (waypoints, context) |
| onChange | On any list change, including init, add, remove, reorder, set, or waypoint updates. | (waypoints, context) |
| onError | When the component reports an internal error. | (error) |
Example:
const selector = new WaypointSelector("#waypoint-selector", "YOUR_GEOAPIFY_API_KEY", {
onWaypointChange: (waypoint, context) => {
console.log("Waypoint changed", waypoint, context);
},
onAdd: (waypoint, index) => {
console.log("Waypoint added", waypoint, index);
},
onRemove: (waypoint, index) => {
console.log("Waypoint removed", waypoint, index);
},
onReorder: (waypoints, context) => {
console.log("Waypoints reordered", waypoints, context);
},
onChange: (waypoints, context) => {
console.log("Waypoints changed", waypoints, context);
},
onError: (error) => {
console.error("Waypoint selector error", error);
}
});WaypointSelectorOptions
WaypointSelectorOptions is the overview of the main configuration settings you can pass to WaypointSelector.
It includes the initial route data, UI labels, geocoder settings, and event callbacks.
import { WaypointSelector } from "@geoapify/route-waypoint-selector";
const selector = new WaypointSelector("#waypoint-selector", "YOUR_GEOAPIFY_API_KEY", {
initialWaypoints: [
// Optional initial list rendered on mount
{
label: "Times Square, New York, NY, USA",
lat: 40.758,
lon: -73.9855
}
],
labels: {
// Optional custom UI texts
route_waypoint_selector: "Route waypoint selector",
choose_destination: "Choose destination",
add_destination: "Add destination",
remove_destination: "Remove destination",
switch_waypoints: "Switch waypoints",
drag_to_reorder: "Drag waypoint to reorder"
},
geocoderOptions: {
// Forwarded to @geoapify/geocoder-autocomplete
// Example: force English results in suggestions
lang: "en"
},
onWaypointChange: (waypoint, context) => {
// Fired when a waypoint is set or cleared
console.log("onWaypointChange", { waypoint, context });
},
onAdd: (waypoint, index) => {
// Fired when a waypoint is added
console.log("onAdd", { waypoint, index });
},
onRemove: (waypoint, index) => {
// Fired when a waypoint is removed
console.log("onRemove", { waypoint, index });
},
onReorder: (waypoints, context) => {
// Fired when waypoint order changes
console.log("onReorder", { waypoints, context });
},
onChange: (waypoints, context) => {
// Fired on any list change (init/add/remove/reorder/waypoint/set)
console.log("onChange", { waypoints, context });
},
onError: (error) => {
// Fired when the component reports an internal error
console.error("onError", error);
}
});FAQ
How do I connect selected waypoints to a routing API request?
Read waypoints from the selector, keep only resolved points, and send them to a routing API when it fits your UX (for example on onChange or on a "Build route" button click).
With Geoapify Routing API, see Routing API docs.
const selector = new WaypointSelector("#waypoint-selector", "YOUR_GEOAPIFY_API_KEY", {
onChange: async (waypoints) => {
// Build a route only when all waypoints are resolved (have label, lat, and lon)
const allResolved = waypoints.length >= 2 && waypoints.every((waypoint) => selector.isWaypointResolved(waypoint));
if (!allResolved) return;
const waypointsParam = waypoints
.map((point) => `${point.lon},${point.lat}`)
.join("|");
const url = `https://api.geoapify.com/v1/routing?waypoints=${encodeURIComponent(waypointsParam)}&mode=drive&apiKey=YOUR_GEOAPIFY_API_KEY`;
const response = await fetch(url);
const routeData = await response.json(); // GeoJSON FeatureCollection with route geometry and properties
console.log("Routing response", routeData);
}
});Is this library free to use?
Yes. @geoapify/route-waypoint-selector itself is open-source and free to use.
Geoapify API calls (for example Address Autocomplete) may be billed when your usage is over the free tier. At the time of writing, the free tier includes up to 3,000 requests/day.
How can I get a Geoapify API key?
Geoapify is a commercial service, but it has a generous free tier that can also be used for commercial projects. Sign up at https://www.geoapify.com/, then create an API key in your dashboard and pass it as apiKey to new WaypointSelector(...). No credit card is required to get started.
Can I use a third-party routing API?
Yes. You can use any routing backend. The selector gives you waypoint data (label, lat, lon, optional id), and you can map that data to any routing provider request format.
How do I add waypoints to Leaflet?
Use onChange and maintain a Map<string, L.Marker> keyed by waypoint.id.
import L from "leaflet";
const leafletMap = L.map("map");
const markersById = new Map<string, L.Marker>();
const selector = new WaypointSelector("#waypoint-selector", "YOUR_GEOAPIFY_API_KEY", {
onChange: (waypoints) => {
const nextIds = new Set<string>();
waypoints.forEach((waypoint, index) => {
if (!waypoint.id || !selector.isWaypointResolved(waypoint)) return;
nextIds.add(waypoint.id);
const latLng: L.LatLngExpression = [waypoint.lat!, waypoint.lon!];
const markerTitle = `${index + 1}. ${waypoint.label ?? "Waypoint"}`;
const existing = markersById.get(waypoint.id);
if (existing) {
existing.setLatLng(latLng).bindTooltip(markerTitle);
} else {
const marker = L.marker(latLng).addTo(leafletMap).bindTooltip(markerTitle);
markersById.set(waypoint.id, marker);
}
});
for (const [id, marker] of markersById) {
if (!nextIds.has(id)) {
marker.remove();
markersById.delete(id);
}
}
}
});How do I add waypoints to MapLibre?
Same idea: keep Map<string, maplibregl.Marker> keyed by waypoint.id and sync it on onChange.
import maplibregl from "maplibre-gl";
const map = new maplibregl.Map({
container: "map",
style: `https://maps.geoapify.com/v1/styles/osm-bright/style.json?apiKey=YOUR_GEOAPIFY_API_KEY`,
center: [0, 0],
zoom: 2
});
const markersById = new Map<string, maplibregl.Marker>();
const selector = new WaypointSelector("#waypoint-selector", "YOUR_GEOAPIFY_API_KEY", {
onChange: (waypoints) => {
const nextIds = new Set<string>();
waypoints.forEach((waypoint, index) => {
if (!waypoint.id || !selector.isWaypointResolved(waypoint)) return;
nextIds.add(waypoint.id);
const lngLat: [number, number] = [waypoint.lon!, waypoint.lat!];
const existing = markersById.get(waypoint.id);
if (existing) {
existing.setLngLat(lngLat);
} else {
const marker = new maplibregl.Marker()
.setLngLat(lngLat)
.setPopup(new maplibregl.Popup().setText(waypoint.label ?? `Waypoint ${index + 1}`))
.addTo(map);
markersById.set(waypoint.id, marker);
}
});
for (const [id, marker] of markersById) {
if (!nextIds.has(id)) {
marker.remove();
markersById.delete(id);
}
}
}
});License
MIT
Resources
- Geoapify Address Autocomplete
- Geoapify Routing API
- Geoapify Map Tiles
- @geoapify/geocoder-autocomplete on npm
Attribution
This library uses icons from Font Awesome 7 Free licensed under the Creative Commons Attribution 4.0 International License.
