Overpass QL
By the end of this guide, you'll be able to write Overpass QL queries that run on Plaza and get the data you actually want back.
If you've used the public Overpass API before, the syntax is the same. If you haven't, Overpass QL is a purpose-built query language for OpenStreetMap data. It's more expressive than REST parameters -- you can do spatial joins, set operations, and recursive traversals that would be painful to express as URL query strings.
Your first query
Find all cafes in a bounding box:
[out:json];
node[amenity=cafe](48.8,2.3,48.9,2.4);
out;
Three parts: output settings ([out:json]), a statement (select nodes with amenity=cafe in this bbox), and an output command (out). The bounding box format is (south, west, north, east).
Run it:
curl -X POST "https://plaza.fyi/api/v1/overpass" \
-H "x-api-key: pk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"data": "[out:json];node[amenity=cafe](48.8,2.3,48.9,2.4);out;"}'
You get back a GeoJSON FeatureCollection.
Element types
OSM has three element types, and you use them directly in queries:
node-- points. Shops, benches, trees, traffic lights.way-- lines and polygons. Roads, buildings, park boundaries.relation-- groups of elements. Bus routes, multipolygon buildings, administrative boundaries.
node[shop=bicycle](48.8,2.3,48.9,2.4);
way[highway=cycleway](48.8,2.3,48.9,2.4);
relation[route=bicycle](48.8,2.3,48.9,2.4);
Tag filters
This is where queries get interesting. You filter elements by their OSM tags:
node[amenity=cafe] // exact match
node[amenity] // tag exists, any value
node[amenity!=restaurant] // not equal
node[name~"^Starbucks"] // regex match on value
node[~"^addr:.*"~"."] // regex match on key (any addr:* tag)
node[amenity=cafe][cuisine=coffee] // multiple filters = AND
The regex filter is powerful. Want every element with any address tag? [~"^addr:.*"~"."]. Want places with "park" anywhere in the name? [name~"[Pp]ark"].
Spatial filters
Bounding box
The most common spatial filter. Four coordinates: south, west, north, east.
node[amenity=pub](51.5,-0.12,51.52,-0.08);
Around a point
Find everything within a radius (in meters) of a coordinate:
node[amenity=drinking_water](around:500,48.8584,2.2945);
Around another result set
This is where Overpass gets powerful. Find all bike parking within 200m of any cycleway:
way[highway=cycleway](48.8,2.3,48.9,2.4)->.cycleways;
node[amenity=bicycle_parking](around.cycleways:200);
out;
The ->.cycleways stores the result in a named set. The around.cycleways:200 uses that set as the center for a radius search.
Output modes
Control what comes back in the header block:
[out:json]; // GeoJSON (default)
[out:xml]; // OSM XML
[out:csv(::id,::type,name,amenity)]; // CSV with specific columns
Control how much detail per element:
out; // everything -- geometry and tags
out skel; // geometry only, no tags
out ids; // just OSM IDs
out count; // count of matches (no individual elements)
out center; // centroid instead of full geometry
out count is useful for testing whether your filters are too broad before downloading all the data.
Set operations
Union
Combine results from multiple statements:
[out:json];
(
node[amenity=cafe](48.8,2.3,48.9,2.4);
node[amenity=bar](48.8,2.3,48.9,2.4);
);
out;
Parentheses around multiple statements merge their results. Cafes and bars, one query.
Difference
Subtract one set from another. All amenities except parking:
[out:json];
(
node[amenity](48.8,2.3,48.9,2.4);
- node[amenity=parking](48.8,2.3,48.9,2.4);
);
out;
Named sets
Store intermediate results and reference them later:
[out:json];
node[amenity=school](48.8,2.3,48.9,2.4)->.schools;
node[amenity=library](around.schools:1000);
out;
Every library within 1km of a school. Named sets make multi-step queries readable.
Recurse operators
Navigate up and down OSM's element hierarchy:
way[building](48.85,2.33,48.86,2.34); // get buildings (ways)
>; // expand to their child nodes
out;
| Operator | Direction | What it does |
|---|---|---|
> |
down | Members/nodes of the current set |
>> |
down (recursive) | All descendants |
< |
up | Parent ways/relations |
<< |
up (recursive) | All ancestors |
Five complete examples
1. Cafes within 500m of a point
[out:json];
node[amenity=cafe](around:500,48.8584,2.2945);
out;
2. All bike lanes in east London
[out:json];
way[highway=cycleway](51.48,-0.05,51.55,0.05);
out;
3. Tall buildings in central Paris
[out:json];
way[building]["height"~"^[5-9][0-9]|[1-9][0-9]{2}"](48.85,2.33,48.87,2.36);
out;
The regex matches height values of 50+. It's not elegant, but OSM stores height as a string, so regex is what you've got.
4. Pubs and bars in one query
[out:json];
(
node[amenity=pub](51.5,-0.12,51.52,-0.08);
node[amenity=bar](51.5,-0.12,51.52,-0.08);
);
out;
5. Schools with a library nearby
[out:json];
node[amenity=school](48.8,2.3,48.9,2.4)->.schools;
node[amenity=library](around.schools:500);
out;
Differences from the public Overpass API
Plaza's implementation covers the most common query patterns. A few things work differently:
- Timeout is 30 seconds per query. The
[timeout:N]setting is ignored. [bbox:...]global bounding box works, but per-statement(bbox)is preferred.out metaandout tagsare treated asout(full output).is_inandtimelineare not supported.- Area queries (
area[name="..."]) are not supported yet. Use bounding boxes oraroundinstead.
If you're porting a query from overpass-turbo.eu and it doesn't work, check the limitations above first. Most of the time it's an area query that needs to be replaced with a bounding box.