duara-geo
v2.0.0
Published
Client and map widget for a self-hosted duara-geo deployment: routing, distance matrices, isochrones, multi-drop, geocoding, places, road attributes, elevation, time zones, tiles and static maps over OpenStreetMap.
Maintainers
Readme
duara-geo
The JavaScript client and map widget for a duara-geo deployment: routing, distance matrices, isochrones, multi-drop sequencing, geocoding, places, road attributes, map matching, elevation, time zones, tiles and static maps — over OpenStreetMap, from a server you run.
It talks to your deployment. There is no hosted service behind this package and no account
to create; baseUrl is where you put the container.
npm install duara-geoNo dependencies. About 20 KB. Works in Node 18+, in any bundler, and from a plain <script>
tag with no build step at all.
Using it
import DuaraGeo from 'duara-geo';
const geo = new DuaraGeo({ baseUrl: 'https://geo.example.com', key: 'dgk_live_…' });
const trip = await geo.route({
locations: [
{ latitude: 0.3476, longitude: 32.5825 },
{ latitude: 0.3556, longitude: 32.6136 },
],
});
console.log(trip.distance_km, 'km,', Math.round(trip.seconds / 60), 'min');CommonJS works the same way:
const DuaraGeo = require('duara-geo');baseUrl is optional in a browser, where it defaults to the page's own origin — right when the
service sits behind the same proxy. Off a page there is no origin to inherit, so the constructor
throws rather than letting the first request fail somewhere inside fetch.
A map, without a mapping library
The package ships a small raster map widget: tiles, markers, lines, polygons, circles and popups, in about twenty kilobytes with no dependencies. It is browser-only and is not loaded until you construct it.
const map = geo.map('map', { center: [32.5825, 0.3476], zoom: 13 });
map.addMarker([32.5825, 0.3476], { label: 'A', draggable: true });
map.addPolyline(trip.shape, { precision: trip.shape_precision });
map.on('click', (e) => console.log(e.lngLat));Coordinates in the widget are [longitude, latitude], the order GeoJSON uses. Coordinates in
the API are { latitude, longitude } objects. That is deliberate: the two conventions exist,
and a single silent [a, b] at the boundary is how a delivery ends up in the wrong hemisphere.
For a vector basemap, 3D or clustering, point MapLibre GL at your deployment's
/v1/style.json instead. The two coexist and the rest of this client keeps working next to it.
From a <script> tag
Your own deployment serves the same file, so an air-gapped page needs no CDN:
<script src="https://geo.example.com/sdk/duara-geo.js"></script>Or from a CDN, if you would rather:
<script src="https://cdn.jsdelivr.net/npm/duara-geo@2/duara-geo.js"></script>Both define window.DuaraGeo and window.DuaraMap.
What is actually in the box
const caps = await geo.capabilities(); // cached after the first call
if (geo.can('places_nearby')) { /* show the "what's near me" button */ }Every service except routing is optional, because every one of them needs a backend or a
dataset somebody chose to run. capabilities() reports what this deployment can answer and,
for the rest, why it is off in terms of the configuration that would turn it on. Building a UI
from it is the difference between hiding a feature and shipping one that 501s.
| | |
|---|---|
| routing | route matrix isochrone tour |
| roads | mapMatch nearestRoads speedLimits |
| places | geocode reverseGeocode autocomplete searchPlaces nearbyPlaces placeDetails validateAddress |
| environment | elevation timezone |
| URLs | tileUrl styleUrl staticMapUrl embedUrl |
| browser | map |
Three things worth knowing before you build on it
method is not decoration. A matrix cell comes back as road or great_circle. The second
is a real answer you can price from, but it is not the answer you asked for, and rendering the
two identically is how a straight-line estimate reaches a customer as a road distance.
const leg = res.legs[0][0];
if (leg.method !== 'road') showEstimateBadge();Decode shapes with the precision the response gave you. shape_precision is 6 on /v1
and 5 on the compatibility surface. Assuming 5 for a precision-6 shape puts a Kampala route in
the Gulf of Guinea. A route through three or more points returns several legs joined with ;
— decodePolyline splits them; a decoder that does not will draw a rectangle across a
continent.
import { decodePolyline } from 'duara-geo';
const coords = decodePolyline(trip.shape, trip.shape_precision);Absent is not zero. metres on an elevation sample and speed_limit_kph on a road edge are
missing rather than 0 when unknown, because 0 m and 0 km/h are both plausible readings and
both would be false. The TypeScript definitions mark them optional for exactly this reason.
Errors
import { GeoError } from 'duara-geo';
try {
await geo.route({ locations });
} catch (err) {
if (err instanceof GeoError) {
err.code; // 'unroutable', 'invalid_coordinate', 'rate_limited', …
err.status; // HTTP status, or 0 for a transport failure
err.retryable; // whether asking again could plausibly help
err.requestId; // quote this in a support conversation
}
}Branch on code, not on the status: unroutable and invalid_coordinate are both 422 and need
opposite handling.
TypeScript
Types ship with the package; nothing to install. They are checked against every module resolution mode TypeScript has, so both import styles are real:
// ESM
import DuaraGeo, { type RouteResponse, type Place, GeoError } from 'duara-geo';
// CommonJS -- the export is `module.exports = DuaraGeo`, so the types come through the class
import DuaraGeo = require('duara-geo');
const trip: DuaraGeo.RouteResponse = await geo.route({ locations });Other languages
Python · Go · PHP · or generate a client for anything else from
your deployment's /openapi.json.
Releasing
OTP=123456 make sdk-js-publish # from the repository rootThe target refreshes the copy from web/sdk, runs the package tests and the drift check, and
refuses to publish an UNLICENSED package or an account without a second factor. npm requires
2FA to publish; OTP is the six digits from your authenticator.
Licence
MIT. Use it, ship it, vendor it into your build — a client library for a paid service is worthless if you cannot.
The service it talks to is proprietary and separate: this package grants you nothing in respect of running your own duara-geo deployment. Talk to us about that.
