# Show a place's boundary polygon on a map

> Search a place with geolith, fetch it by gid with geometry=true, and draw its outline on a MapLibre map, falling back to the bounding box when the place has no outline.

---
title: Show a place's boundary polygon on a map
description: Search a place with geolith, fetch it by gid with geometry=true, and draw its outline on a MapLibre map, falling back to the bounding box when the place has no outline.
date: '2026-10-22'
tags: [maplibre, polygons, javascript]
minutes: 12
steps:
  - name: Search the place
    text: Call /v1/search with layers=locality or layers=region and keep the gid of the first result.
  - name: Fetch it with geometry=true
    text: Call /v1/place?ids=<gid>&geometry=true. Places with an outline return a Polygon or MultiPolygon.
  - name: Draw it
    text: Add the feature as a GeoJSON source with a fill and a line layer, then fit the map to its bbox.
  - name: Fall back to the bbox
    text: When the geometry is a Point, draw the bbox as a rectangle and fit the map to it.
faqs:
  - q: Which value does geometry take?
    a: true or false. Other values such as 1 are rejected with HTTP 400 ("geometry must be true or false").
  - q: Which places have outlines?
    a: Countries, regions, counties, localities and venues that are mapped as areas.
  - q: Can I store the polygon?
    a: Yes. geolith results may be stored permanently; keep the attribution where you display them.
---

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

```bash
# 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.

```html
<!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

- `MultiPolygon` outlines (islands, enclaves) draw with the same two layers; nothing changes.
- With `geometry=true`, an area keeps its point in `properties.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 `France` with *Country*, or `Bavaria` with *State / region*.


Get a free API key: https://console.geolith.app/signup
