Overpass API > Blog >

Version 0.7.56 Examples

Published: 2020-04-14

Table of content

Measuring Angles
Only the First Node of a Way
Type Shortcuts

I'm back with providing updates. After more than a year I'm proud to present a new release. This release provides some smaller new features.

This is not exactly in line with the strategy of: release early, release often The rationale is that all the substantial planned changes will change the underlying database model. This ia a lot of work and bandwith load for all people running their our instances. Thus I hope I can bundle those changes in one large later version.

A first step are the versions 0.7.56.100x (as opposed to the 0.7.56.x line presented here). I do not recommend them yet for private installations because there are no clone files for it. They feature more flexible block sizes. That way they can accomodate the area description for Antartica which had been too large due to a projection artifact. At the same time they are faster because on average less data needs to be decompressed per request. The code currently runs on the z.overpass-api.de instance.

Back to this version, there are three new features: You can measure and work with the angles of ways now. You can recurse on only selected nodes of a way. And some extra shortcuts make it easier to get some but not all types of objects.

Measuring Angles

It is now possible to measure angles in ways. In the example we query for all ways that have a very acute angle:

way({{bbox}})
  (if:lrs_in(1,per_vertex(angle() < -170 || angle() > 170)));
out geom;

Why does this look so involved? First of all, we search for all ways in a given bouding box, contained in the Overpass Turbo link. The angle related part happens in the filter (if:...). This filter is evaluated once for every object, i.e. for each way here, but angles are different per vertex of the way.

For this reason, the angle related condition is stated within per_vertex(...). That evaluator evalutes its expression once for every vertex of the way and returns the results as a semicolon separated list. For illustrative purposes, an example for an (almost) retangular building and for an almost straight street:

way(id:266149365,194926851);
convert info desc=is_tag("highway")?"highway":"building",
    angles=per_vertex(angle());
out geom;

You can see the built list of angles in tag angle of each of the two created objects. It is almost zero for all angles for the street, and it mixes almost zero with almost 90 degrees for the building.

A remark to values: I decided to use turning angle as the mental point of view. I.e. a straight road is understood as having an angle close to zero at the respective vertex, a sharply turning road has a higher angle, and a U-turn is having an angle close to 180 degrees. Consequently, acute angles in buildings are also close to 180 or -180 degrees respectively.

The approach is to simplify these numbers to boolean zero or one already in the expression of per_vertex. We put a boolean expression like angle() < -2 in the argument of per_vertex (refined to catch both negative and positive large angles). This way we get zero for almost zero angles and one for all other angles:

way(id:266149365,194926851);
convert info desc=is_tag("highway")?"highway":"building",
    angles=per_vertex(angle() < -2 || angle() > 2);
out geom;

We can now use the here documented evaluator lrs_in(1, ...) to check for each way whether there is at least one vertex in a way that fulfills the spike condition. This way, we keep only those ways that have a very acute angle.

For your convenience, a complete example to query for non-rectangular buildings:

way({{bbox}})[building]
  (if:lrs_in(1,per_vertex(
     angle() < -92 || (angle() > -88 && angle() < -2)
     || (angle() > 2 && angle() < 88) || angle() > 92)));
out geom;

In this case, it could be interesting to figure out where the non-rectangular vertices are. We do this for a single way, because where houses are built wall-to-wall, it is probable that one looks on the wrong building otherwise:

way(60538085);
out geom;

Now we select all nodes for which the way has a non-rectangular and non-zero angle in that node:

way(60538085);
node(w)(if:lrs_in(id(),set(per_vertex(
    (angle()<-92 || (angle() >-88 && angle() < -2)
    || (angle() > 2 && angle() < 88) || angle() > 92)
    ? ref() : 0))));
out geom;

Only the First Node of a Way

There are more evaluators that can be applied per vertex of a way: With the evalutators for pos and ref of an element, one can get out of a recursion only elements on specific positions. We can ask for only the first and the last member of the way just mentioned before:

way(60538085);
node(w)(if:lrs_in(id(),set(
    per_member(lrs_in(pos(),"1;"+(count_members()-1))?ref():0))));
out geom;

We use the filter to accept only the ids of those objects that are a member of the way on the desired positions. They are taken from a list constructed for this purpose with per_member.

Why do we not use per_vertex again? The evaluator per_vertex is designed for angles: It skips the first position always and the last position for open ways, because there is no angle defined there. By contrast, per_member is always executed once for every member.

Inside per_member, a list evaulator is used to select the pos from the expected positions 1 or count_members()-1. If it matches, then the id is returned, and that adds the id to the list of accepted ids. Otherwise zero is returned, and this is never a valid id.

For your convenience, there is a shortcut integrated in the recurse filter, as shown in this example. Negative values are counted from the tail of the way, i.e. -2 is the last but one node of the way:

way(60538085);
node(w:1,-2);
out geom;

The shortcut is not available for relations yet, because I have not figured out so far a compelling syntax for that.

Type Shortcuts

The introduction of the nwr shortcut has improved the readability of queries. Thus the language goes further down that road: there are now shortcuts nw, nr, and wr available. Please feel free to experiment in examples like this: Exchange one shortcut for another and watch how the result changes!

nwr({{bbox}})[amenity=place_of_worship];
out center;

To make the language more consistent, the same set of shortcuts can be used as argument for the count evaluator:

[out:csv(cnt,amenity)];
nwr({{bbox}})[amenity];
for (t["amenity"])
{
  make stat cnt=count(nwr),amenity=_.val;
  out;
}