To show a place's boundary on a map with geolith, search it with /v1/search, fetch it by its gid with GET /v1/place?ids=<gid>&geometry=true, and add the returned GeoJSON feature to a MapLibre map as a fill layer. With geometry=true, the API returns a Polygon or MultiPolygon for countries, regions, counties and venues mapped as areas, with the original point in properties.center. Some places, such as cities mapped only as a point, have no outline; the code below draws the outline when there is one and the bounding box when there is not.
The two requests
# 1. Find the place, keep its gid
curl "https://api.geolith.app/v1/search?text=maharashtra&layers=region&size=1" \
-H "Authorization: Bearer YOUR_API_KEY"
# -> features[0].properties.gid = "geolith:region:13395044029"
# 2. Fetch it with its outline
curl "https://api.geolith.app/v1/place?ids=geolith:region:13395044029&geometry=true" \
-H "Authorization: Bearer YOUR_API_KEY"
The page
boundary.html. Use a publishable key (gc_pk_...) that allows the page's origin.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Place boundary</title>
<link rel="stylesheet" href="https://unpkg.com/maplibre-gl@6.15.0/dist/maplibre-gl.css" />
<style>
body { margin: 0; font: 15px system-ui, sans-serif; }
form { position: absolute; z-index: 1; top: 12px; left: 12px; display: flex; gap: 6px; background: #fff; padding: 8px; }
#map { height: 100vh; }
#note { position: absolute; z-index: 1; bottom: 28px; left: 12px; background: #fff; padding: 4px 8px; font-size: 13px; }
</style>
</head>
<body>
<form id="f">
<input id="q" value="Maharashtra" required />
<select id="layer">
<option value="region">State / region</option>
<option value="county">District / county</option>
<option value="locality">City / town</option>
<option value="country">Country</option>
</select>
<button>Show</button>
</form>
<div id="note"></div>
<div id="map"></div>
<script type="module">
import * as maplibregl from 'https://unpkg.com/maplibre-gl@6.15.0/dist/maplibre-gl.mjs';
const API = 'https://api.geolith.app';
const API_KEY = 'YOUR_API_KEY'; // publishable key, gc_pk_...
const map = new maplibregl.Map({
container: 'map',
style: 'https://tiles.openfreemap.org/styles/liberty',
center: [78.9, 21.1],
zoom: 4,
attributionControl: {
customAttribution: 'Places © OpenStreetMap contributors (ODbL), Overture Maps Foundation',
},
});
await map.once('load');
map.addSource('place', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } });
map.addLayer({ id: 'place-fill', type: 'fill', source: 'place', paint: { 'fill-color': '#2a7f78', 'fill-opacity': 0.2 } });
map.addLayer({ id: 'place-line', type: 'line', source: 'place', paint: { 'line-color': '#2a7f78', 'line-width': 2 } });
async function get(path, params) {
const url = new URL(path, API);
for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
url.searchParams.set('api_key', API_KEY);
const res = await fetch(url);
if (!res.ok) throw new Error(`geolith ${res.status}: ${await res.text()}`);
return res.json();
}
function bboxPolygon([w, s, e, n]) {
return { type: 'Polygon', coordinates: [[[w, s], [e, s], [e, n], [w, n], [w, s]]] };
}
async function show(text, layer) {
const note = document.getElementById('note');
const found = await get('/v1/search', { text, layers: layer, size: '1' });
const hit = found.features[0];
if (!hit) {
note.textContent = 'No match.';
return;
}
const { features } = await get('/v1/place', { ids: hit.properties.gid, geometry: 'true' });
const place = features[0];
const isArea = place.geometry.type === 'Polygon' || place.geometry.type === 'MultiPolygon';
const bbox = place.bbox ?? hit.bbox;
let shape;
if (isArea) {
shape = place;
note.textContent = `${place.properties.label}: outline`;
} else if (bbox) {
shape = { type: 'Feature', properties: place.properties, geometry: bboxPolygon(bbox) };
note.textContent = `${place.properties.label}: bounding box (no outline returned)`;
} else {
shape = place;
note.textContent = `${place.properties.label}: point only`;
}
map.getSource('place').setData({ type: 'FeatureCollection', features: [shape] });
if (bbox) map.fitBounds([[bbox[0], bbox[1]], [bbox[2], bbox[3]]], { padding: 40 });
else map.flyTo({ center: place.geometry.coordinates, zoom: 12 });
}
document.getElementById('f').addEventListener('submit', (e) => {
e.preventDefault();
show(document.getElementById('q').value, document.getElementById('layer').value).catch((err) => {
document.getElementById('note').textContent = err.message;
});
});
show('Maharashtra', 'region');
</script>
</body>
</html>
Serve it over HTTP (for example npx serve .) so the browser sends an Origin header your key allows.
Notes
MultiPolygonoutlines (islands, enclaves) draw with the same two layers; nothing changes.- With
geometry=true, an area keeps its point inproperties.center, so you can still drop a label there. - To show many areas at once, pass up to 40 gids in
ids, comma-separated. - Coverage is worldwide: try
Francewith Country, orBavariawith State / region.