Skip to content

Spatial Join

Enrich a set of points with attributes from a polygon dataset. For each point, find which polygon(s) match and attach the polygon’s properties to the point. Think of it as batch point-in-polygon with the results merged back into your input data.

POST /v1/spatial-join
ParameterTypeRequiredDefaultDescription
system_datasetstringOne requiredSystem dataset slug (e.g., us-counties)
datasetstringOne requiredYour dataset slug
dataset_idstringOne requiredDataset UUID
join_typestringNointersectsHow to test point-polygon matches. See Join types.
match_modestringNofirstHow many matches to return per point. See Match modes.
radiusnumberNoExpand the search area around each point (meters, up to 500,000). See Radius expansion.
formatstringNojsonResponse format: json or csv

Provide exactly one of system_dataset, dataset, or dataset_id.

An array of point objects. See Batch Requests for all supported input formats (JSON, GeoJSON, CSV, NDJSON).

Terminal window
curl -s -X POST "https://api.terranode.co/v1/spatial-join?system_dataset=us-counties" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '[
{"lat": 40.7128, "lng": -74.006, "id": "nyc"},
{"lat": 34.0522, "lng": -118.2437, "id": "la"}
]' | jq .
{
"summary": {
"total": 2,
"joined": 2,
"unjoined": 0
},
"dataset": "us-counties",
"version": 1,
"join_type": "intersects",
"match_mode": "first",
"results": [
{
"index": 0,
"input": {"lat": 40.7128, "lng": -74.006, "id": "nyc"},
"joined": true,
"match_count": 1,
"matches": [
{
"properties": {
"STATEFP": "36",
"COUNTYFP": "061",
"GEOID": "36061",
"NAME": "New York",
"NAMELSAD": "New York County"
},
"boundary": false,
"distance_m": 0,
"distance_mi": 0
}
]
},
{
"index": 1,
"input": {"lat": 34.0522, "lng": -118.2437, "id": "la"},
"joined": true,
"match_count": 1,
"matches": [
{
"properties": {
"STATEFP": "06",
"COUNTYFP": "037",
"GEOID": "06037",
"NAME": "Los Angeles",
"NAMELSAD": "Los Angeles County"
},
"boundary": false,
"distance_m": 0,
"distance_mi": 0
}
]
}
],
"latency_ms": 12,
"api_version": "v1",
"semantics_version": "0.1.0"
}
FieldDescription
summaryCounts of total, joined, and unjoined points
results[].indexPosition in the input array (for matching results back to your data)
results[].inputYour original input point with any passthrough properties
results[].joinedtrue if at least one polygon matched
results[].match_countNumber of polygons matched
results[].matches[].propertiesThe matched polygon’s attributes
results[].matches[].boundarytrue if the point falls exactly on the polygon boundary (intersects mode only)
results[].matches[].distance_mDistance to polygon in meters (0 if point is inside)
results[].matches[].distance_miDistance to polygon in miles

The join_type parameter controls how a point is tested against each polygon.

A point matches a polygon if it is inside the polygon or on its boundary. This is the standard point-in-polygon test. A point sitting exactly on a shared boundary between two polygons matches both (when match_mode=all).

The boundary field in each match tells you whether the point was on the boundary (true) or strictly inside (false).

Use this for most enrichment workflows — “which county is this address in?”, “what school district does this location belong to?”

A point matches a polygon only if it is strictly inside — points on the boundary are excluded. This is useful when boundary ambiguity matters, such as when a point on a shared border between two polygons should match neither rather than both.

The boundary field is always false in within mode (boundary points are excluded from results).


Returns only the first matching polygon for each point. Fastest. Use for the common case where you need one result per point.

Returns every matching polygon for each point. Use when polygons overlap or when you need complete coverage information (e.g., “what are ALL the school districts near this point?”). The match_count field tells you how many polygons matched.

Terminal window
curl -s -X POST "https://api.terranode.co/v1/spatial-join?system_dataset=us-counties&match_mode=all" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '[{"lat": 40.7128, "lng": -74.006}]' | jq .

The radius parameter expands the search area around each point to find additional polygons within the specified distance (in meters, up to 500,000). This is independent of join_type — it works with both intersects and within.

Without radius, the join only finds polygons that the point falls inside. With radius, it also finds polygons whose boundary is within the given distance of the point, even if the point isn’t inside them.

Each additional match found via radius expansion includes a non-zero distance_m showing how far the polygon boundary is from the point.

Terminal window
# Find the containing county AND any counties within 5 km
curl -s -X POST "https://api.terranode.co/v1/spatial-join?system_dataset=us-counties&match_mode=all&radius=5000" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '[{"lat": 40.7128, "lng": -74.006}]' | jq .

Add format=csv to get results as CSV:

Terminal window
curl -s -X POST "https://api.terranode.co/v1/spatial-join?system_dataset=us-counties&format=csv" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '[{"lat": 40.7128, "lng": -74.006}]'

Up to 100 points per request on the Free tier. Each point counts as one request against your monthly quota.