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 meta and out tags are treated as out (full output).
  • is_in and timeline are not supported.
  • Area queries (area[name="..."]) are not supported yet. Use bounding boxes or around instead.

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.