Every map service your product needs,
from one container you own.
Routing, distance matrices, fares, geofencing, traffic from your own vehicles, geocoding, places, landmark addressing, Plus Codes, elevation, time zones, static maps, tiles and an embeddable map β over OpenStreetMap, on your own hardware. No accounts, no API quotas you did not set yourself, no per-request billing, and a drop-in compatibility surface so an existing integration moves by changing a base URL.
It knows the continent it runs on. Fifty-six countries with their currencies, clocks, driving sides and map extracts β and the local word for a motorcycle taxi, so a fare in Lagos comes back in naira and says okada, and one in Kigali comes back in francs and says moto.
What this deployment can answer
Read live from GET /v1/capabilities. Every service below is optional and
several need a dataset or a backend somebody has to choose to run β so rather than
documenting features this instance does not have, the portal asks it.
Asking the serviceβ¦
Thirty seconds to a road distance
Every endpoint is JSON over HTTP. There is no SDK you have to use.
curl -sS https://YOUR-HOST/v1/matrix \
-H 'Authorization: Bearer YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{
"origins": [{"latitude": 0.3476, "longitude": 32.5825}],
"destinations": [{"latitude": 0.0512, "longitude": 32.4637}],
"costing": "motor_scooter"
}'<script src="https://YOUR-HOST/sdk/duara-geo.js"></script>
<script>
const geo = new DuaraGeo({ baseUrl: 'https://YOUR-HOST', key: 'YOUR_KEY' });
const res = await geo.matrix({
origins: [{ latitude: 0.3476, longitude: 32.5825 }],
destinations: [{ latitude: 0.0512, longitude: 32.4637 }]
});
const leg = res.legs[0][0];
console.log(leg.distance_km, leg.seconds, leg.method);
// A map, with no other dependency:
const map = geo.map('map', { center: [32.58, 0.34], zoom: 12 });
map.addMarker([32.5825, 0.3476], { label: 'A' });
</script>npm install duara-geoimport DuaraGeo from 'duara-geo';
const geo = new DuaraGeo({ baseUrl: 'https://YOUR-HOST', key: 'YOUR_KEY' });
const res = await geo.matrix({
origins: [{ latitude: 0.3476, longitude: 32.5825 }],
destinations: [{ latitude: 0.0512, longitude: 32.4637 }]
});
const leg = res.legs[0][0];
console.log(leg.distance_km, leg.seconds, leg.method);No dependencies, TypeScript definitions included, and the same file in Node 18+, in a bundler and in a <script> tag. The map widget comes with it.
from duara_geo import Client
geo = Client("https://YOUR-HOST", key="YOUR_KEY")
res = geo.matrix(
origins=[(0.3476, 32.5825)],
destinations=[(0.0512, 32.4637)],
)
leg = res["legs"][0][0]
print(leg["distance_km"], leg["seconds"], leg["method"])client := geo.New("https://YOUR-HOST", geo.WithKey("YOUR_KEY"))
res, err := client.Matrix(ctx, geo.MatrixRequest{
Origins: []geo.Point{{Latitude: 0.3476, Longitude: 32.5825}},
Destinations: []geo.Point{{Latitude: 0.0512, Longitude: 32.4637}},
})
if err != nil { return err }
leg := res.Legs[0][0]
log.Println(leg.DistanceKM, leg.Seconds, leg.Method)$geo = new DuaraGeo\Client('https://YOUR-HOST', 'YOUR_KEY');
$res = $geo->matrix([
'origins' => [['latitude' => 0.3476, 'longitude' => 32.5825]],
'destinations' => [['latitude' => 0.0512, 'longitude' => 32.4637]],
]);
$leg = $res['legs'][0][0];
echo $leg['distance_km'], ' ', $leg['method'];method. Every distance says how it was arrived at β
road or great_circle. The matrix endpoint never fails: when
the routing engine is restarting or a pin has no road near it, you get a straight-line
answer labelled as one, because a delivery still has to be priced. Decide once, in one
place, what your product does with a degraded number.
Moving an existing integration
The compatibility surface speaks the request and response shapes of the dominant
commercial maps platform. Point your base URL here; the parameters, the JSON keys and
the status strings are the same.
| If you call | Point it at | Notes |
|---|---|---|
maps.googleapis.com/maps/api/geocode/json | /maps/api/geocode/json | Needs a geocoding backend |
β¦/maps/api/directions/json | /maps/api/directions/json | Polylines re-encoded to precision 5 |
β¦/maps/api/distancematrix/json | /maps/api/distancematrix/json | Degraded cells flagged in X-Geo-Degraded |
β¦/maps/api/elevation/json | /maps/api/elevation/json | Needs elevation tiles built |
β¦/maps/api/timezone/json | /maps/api/timezone/json | Needs a boundary dataset |
β¦/maps/api/staticmap | /maps/api/staticmap | Same marker and path grammar |
β¦/maps/api/place/* | /maps/api/place/* | No ratings, photos or reviews β those fields are omitted |
roads.googleapis.com/v1/snapToRoads | /v1/snapToRoads | Place ids are OpenStreetMap way ids |
routes.googleapis.com/directions/v2:computeRoutes | /directions/v2:computeRoutes | No traffic model; says so in fallbackInfo |
places.googleapis.com/v1/places:searchText | /v1/places:searchText | Field masks accepted, full response returned |
addressvalidation.googleapis.com/v1:validateAddress | /v1:validateAddress | Verdict from match granularity |
duration_in_traffic is returned.
Ratings, reviews and photos do not exist in OpenStreetMap, so those fields are absent
rather than zero.
Basemap layers
Every one is free and needs no key, and every one is proxied through this service rather
than handed to the browser β so a map pan's repeat traffic is a cache hit here rather
than load at the provider. Request one as
/v1/tiles/{layer}/{z}/{x}/{y}, or style it with
/v1/style/{layer}.json.
vector layer; for no third party at all, use a local
PMTiles archive.
The endpoints
Native surface. Everything is a POST with a JSON body except the three that have to work in an HTML attribute.
What it is built on
Valhalla
The routing engine: matrices, routes, isochrones, map matching, elevation. MIT licensed.
OpenStreetMap
The map itself, under the ODbL. Attribution is required wherever its data is shown and travels on every response.
Nominatim, Photon, Overpass
Optional geocoding, autocomplete and nearby-search backends. Each is independently choosable.
PMTiles
A whole basemap in one file, read directly by this process. No tile server to run.
Go standard library
This service has no third-party Go dependencies at all. The binary is a few megabytes in an empty image.
Your hardware
Two containers, one volume, one port. Nothing phones home; nothing needs an account.