duara-geo …

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-geo
import 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'];
Read 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 callPoint it atNotes
maps.googleapis.com/maps/api/geocode/json/maps/api/geocode/jsonNeeds a geocoding backend
…/maps/api/directions/json/maps/api/directions/jsonPolylines re-encoded to precision 5
…/maps/api/distancematrix/json/maps/api/distancematrix/jsonDegraded cells flagged in X-Geo-Degraded
…/maps/api/elevation/json/maps/api/elevation/jsonNeeds elevation tiles built
…/maps/api/timezone/json/maps/api/timezone/jsonNeeds a boundary dataset
…/maps/api/staticmap/maps/api/staticmapSame 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/snapToRoadsPlace ids are OpenStreetMap way ids
routes.googleapis.com/directions/v2:computeRoutes/directions/v2:computeRoutesNo traffic model; says so in fallbackInfo
places.googleapis.com/v1/places:searchText/v1/places:searchTextField masks accepted, full response returned
addressvalidation.googleapis.com/v1:validateAddress/v1:validateAddressVerdict from match granularity
Three things are genuinely different. Place ids are this service's own and are not interchangeable with another platform's β€” anything that stored them has to re-resolve once. There is no traffic model, so durations are free-flow and no fabricated duration_in_traffic is returned. Ratings, reviews and photos do not exist in OpenStreetMap, so those fields are absent rather than zero.

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.