GeoInsight API Docs Log in Get an API key

Grid API

A rectangular grid of elevation, light pollution or terrain over a bounding box.

GET /v1/grid

Base URL https://geoinsight.dev. Every request needs the X-API-Key header — see authentication, what this call costs and error codes.

GET /v1/grid

A rectangular grid of terrain data over a bounding box. Resample elevation (Copernicus GLO-30) or light pollution (VIIRS) across a grid with fixed spatial resolution. Cells may be null where data is unavailable. The grid's row 0 is the northernmost edge.

Request

FieldTypeRequiredDefaultDescription
datasetstringyeselevation, light-pollution, or terrain.
minLatfloatyesMinimum latitude of the bounding box, −90..90.
minLngfloatyesMinimum longitude of the bounding box, −180..180.
maxLatfloatyesMaximum latitude of the bounding box, −90..90.
maxLngfloatyesMaximum longitude of the bounding box, −180..180.
colsintyesNumber of grid columns (West–East), 1–256.
rowsintyesNumber of grid rows (South–North), 1–256. Constraint: cols × rows ≤ 4096.
interpolationstringnobilinearbilinear or nearest.
lightAzimuthDegfloatnoLight source azimuth. 0 = N, clockwise, [0, 360). Optional; supply together with lightAltitudeDeg or neither. Only affects dataset=terrain (adds the illumination band); ignored by other datasets.
lightAltitudeDegfloatnoLight source altitude above the horizon, −90..90. Optional; supply together with lightAzimuthDeg or neither. Only affects dataset=terrain (adds the illumination band); ignored by other datasets.

Providing only one of lightAzimuthDeg / lightAltitudeDeg returns 400 with incomplete_light_params, regardless of dataset. The pair only affects the response for dataset=terrain (it is accepted but has no effect for elevation / light-pollution); omit both to get aspect_deg / slope_deg without illumination.

Examples

curl -H "X-API-Key: gi_live_..." \
  "https://geoinsight.dev/v1/grid?dataset=elevation&minLat=52.0&minLng=14.0&maxLat=52.5&maxLng=14.5&cols=32&rows=32"
import requests

r = requests.get(
"https://geoinsight.dev/v1/grid",
params={
    "dataset": "elevation",
    "minLat": 52.0, "minLng": 14.0,
    "maxLat": 52.5, "maxLng": 14.5,
    "cols": 32, "rows": 32,
},
headers={"X-API-Key": "gi_live_..."},
)
data = r.json()
print(data["bands"]["elevation"]["values"][0])
const res = await fetch(
  "https://geoinsight.dev/v1/grid?dataset=elevation&minLat=52.0&minLng=14.0&maxLat=52.5&maxLng=14.5&cols=32&rows=32",
  { headers: { "X-API-Key": "gi_live_..." } },
);
const data = await res.json();
console.log(data.bands.elevation.values[0]);

Response

{
  "dataset": "elevation",
  "bbox": {"minLat": 52.0, "minLng": 14.0, "maxLat": 52.5, "maxLng": 14.5},
  "rows": 32,
  "cols": 32,
  "cell_deg": {"lat": 0.0156, "lng": 0.0156},
  "resolution_m": 30,
  "bands": {
"elevation": {
  "units": "m",
  "values": [
    [52.1, 51.8, 52.3, null, 51.9],
    [53.2, 52.7, 52.1, 52.4, 52.0]
  ]
}
  }
}
Field Type Unit Description
dataset string Which dataset was sampled: elevation, light-pollution or terrain. Determines the shape of bands.
bbox.minLat number deg Southern edge of the sampled box, echoed from the request.
bbox.minLng number deg Western edge of the sampled box.
bbox.maxLat number deg Northern edge of the sampled box.
bbox.maxLng number deg Eastern edge of the sampled box.
rows integer count Number of grid rows returned; equals the length of every values matrix.
cols integer count Number of grid columns; equals the length of every row inside values.
cell_deg.lat number deg Latitude span of a single cell. Multiply by ~111 320 m for an approximate cell height in metres.
cell_deg.lng number deg Longitude span of a single cell. Metric width shrinks with cos(latitude), so cells are not square away from the equator.
resolution_m integer m Ground sample distance of the underlying raster (30 for elevation/terrain, 463 for light-pollution). If cell_deg is finer than this, neighbouring cells repeat the same source pixel.
bands object Sampled data, keyed by band name. Shape differs by dataset — see the two rows below.
bands.<name>.units string elevation and light-pollution only: unit label for the band (m, nW/cm2/sr, bortle 1-9).
bands.<name>.values array per band elevation and light-pollution only: 2D matrix [rows][cols]. Row 0 is the northernmost. Cells are null where the source has no data.
bands.<name> array per band terrain only: the 2D matrix directly, with NO units/values wrapper. Bands: aspect_deg, slope_deg and — only when light params are supplied — illumination.

Datasets

Available datasets:

  • elevation — Copernicus GLO-30 (global coverage, 30 m resolution).
  • light-pollution — VIIRS VNL Annual V2 (see GET /v1/light-pollution for attribution and caveats).
  • terrain — per-cell aspect/slope (and optional illumination) derived from Copernicus GLO-30, 30 m resolution. See "Terrain dataset" below.

Light-pollution dataset: radiance & Bortle

With dataset=light-pollution the grid returns two bands with the same units/values shape as elevation: radiance_nw_cm2_sr (the raw VIIRS night-lights radiance) and estimated_bortle (a 1–9 Bortle class derived from it). Source resolution is 463 m, so a cell_deg finer than that repeats the same source pixel across neighbouring cells.

Examples (light-pollution)
curl -H "X-API-Key: gi_live_..." \
  "https://geoinsight.dev/v1/grid?dataset=light-pollution&minLat=52.20&minLng=20.90&maxLat=52.30&maxLng=21.05&cols=2&rows=2"
import requests

r = requests.get(
    "https://geoinsight.dev/v1/grid",
    params={"dataset": "light-pollution", "minLat": 52.20, "minLng": 20.90,
            "maxLat": 52.30, "maxLng": 21.05, "cols": 2, "rows": 2},
    headers={"X-API-Key": "gi_live_..."},
)
print(r.json()["bands"]["estimated_bortle"]["values"])
const res = await fetch(
  "https://geoinsight.dev/v1/grid?dataset=light-pollution&minLat=52.20&minLng=20.90&maxLat=52.30&maxLng=21.05&cols=2&rows=2",
  { headers: { "X-API-Key": "gi_live_..." } },
);
const { bands } = await res.json();
console.log(bands.estimated_bortle.values);
Response (light-pollution)
{
  "dataset": "light-pollution",
  "bbox": {"minLat": 52.20, "minLng": 20.90, "maxLat": 52.30, "maxLng": 21.05},
  "rows": 2,
  "cols": 2,
  "cell_deg": {"lat": 0.05, "lng": 0.075},
  "resolution_m": 463,
  "bands": {
    "radiance_nw_cm2_sr": {
      "units": "nW/cm2/sr",
      "values": [
        [0.25, null],
        [2.5,  48.0]
      ]
    },
    "estimated_bortle": {
      "units": "bortle 1-9",
      "values": [
        [4, null],
        [5, 9]
      ]
    }
  }
}

Each cell of estimated_bortle is derived from the matching radiance_nw_cm2_sr cell — here 0.25 → rural Bortle 4, 2.5 → suburban 5, 48 → inner-city 9; a null radiance (no VIIRS data) gives a null Bortle. The Bortle mapping is a heuristic — see the light-pollution endpoint for the estimate's caveats and attribution.

Terrain dataset: aspect, slope & illumination

With dataset=terrain, the grid returns aspect_deg and slope_deg bands and, when lightAzimuthDeg/lightAltitudeDeg are also given, an illumination band. Unlike the elevation / light-pollution bands above, terrain bands are 2D value matrices directly — there is no units/values wrapper object.

Examples (terrain)

curl -H "X-API-Key: gi_live_..." \
  "https://geoinsight.dev/v1/grid?dataset=terrain&minLat=45.80&minLng=6.80&maxLat=45.82&maxLng=6.82&cols=2&rows=2&lightAzimuthDeg=100&lightAltitudeDeg=8.5"
import requests

r = requests.get(
"https://geoinsight.dev/v1/grid",
params={
    "dataset": "terrain",
    "minLat": 45.80, "minLng": 6.80,
    "maxLat": 45.82, "maxLng": 6.82,
    "cols": 2, "rows": 2,
    "lightAzimuthDeg": 100, "lightAltitudeDeg": 8.5,
},
headers={"X-API-Key": "gi_live_..."},
)
data = r.json()
print(data["bands"]["illumination"][0])
const res = await fetch(
  "https://geoinsight.dev/v1/grid?dataset=terrain&minLat=45.80&minLng=6.80&maxLat=45.82&maxLng=6.82&cols=2&rows=2&lightAzimuthDeg=100&lightAltitudeDeg=8.5",
  { headers: { "X-API-Key": "gi_live_..." } },
);
const data = await res.json();
console.log(data.bands.illumination[0]);

Response (terrain)

{
  "dataset": "terrain",
  "bbox": {"minLat": 45.8, "minLng": 6.8, "maxLat": 45.82, "maxLng": 6.82},
  "rows": 2,
  "cols": 2,
  "cell_deg": {"lat": 0.01, "lng": 0.01},
  "resolution_m": 30,
  "bands": {
"aspect_deg": [
  [92.4, null],
  [100.0, 5.0]
],
"slope_deg": [
  [31.7, null],
  [20.0, 2.0]
],
"illumination": [
  [0.6409, null],
  [0.0, 0.1447]
]
  }
}

Terrain band fields:

  • aspect_deg — the compass direction the slope faces (0 = N, clockwise). null on flat terrain (no defined downhill direction) or where elevation data is missing (nodata) — in the example above, row 0 / col 1 is a nodata cell (both aspect_deg and slope_deg are null).
  • slope_deg — steepness of the terrain, 0 = flat. null where elevation data is missing (nodata).
  • illumination — present only when lightAzimuthDeg/lightAltitudeDeg are given; how directly each cell faces the given light source, from 0 (facing away / grazing / in shadow) to 1 (directly facing), null on nodata cells. illumination accounts for terrain cast shadows (a ridge blocking the sun), unlike the local-only /v1/sun-exposure; a shadowed cell → 0. In the example above, row 1 / col 0 (aspect_deg: 100.0, nearly facing the light directly) would compute to roughly 0.48 from slope/aspect geometry alone, but the cell sits behind a taller ridge, so the actual illumination is forced to 0.0 — a cast shadow.

Constraints

  • cols × rows ≤ 4096 — maximum 4,096 cells per call.
  • interpolation may be bilinear (smooth, default) or nearest (sharp).