Overpass API > Blog >
Published: 2017-04-10, updated 2019-10-31
With this blog post, we complete the presentation of new features of version 0.7.54. As you have seen in the various remarks of the last weeks, there is still room to improve the software. Hence, I will put priority on developing Overpass API further now and for some weeks .
There are a lot of excellent examples on the Examples Page in the Wiki. Unfortunately, these can easily get lost in that rather large page. For this reason, I will present in the upcoming weeks some examples from that page here.
Have you ever panned the map to China? For me, it is hard to use it there because I cannot make sense even of the letters. This would not apply to a Chinese. Hence, there is a reason to build the name tag according to the user's capabilities.
On the other hand, a lot of cities simply do not have names in foreign languages. Therefore, we want a set of rules that we can easily configure and that could make sense of all combinations of name tags we find.
We work with a European multi-language country, Switzerland, because it is easier to debug the results there. A straightforward approach would be to first collect all towns and then do some magic on their names:
area["name:en"="Switzerland"]; node(area)[place~"^(city|town)$"]; convert node name=???(t["name"], t["name:de"], t["name:fr"]); out;
But this would not enable us to disentangle the various cases:
Do not forget that there are even Italian speaking regions in Switzerland.
We therefore make the rules explicit in the query:
area["name:en"="Switzerland"]; node(area)[place~"^(city|town)$"]; node._["name:fr"]["name:de"]->.de_fr; node._["name"]["name:de"](if:t["name"]!=t["name:de"])->.de_name; ( node._["name"]; - (.de_fr; .de_name;); )->.name; node._["name:fr"][!"name"]->.fr; ( .de_fr convert extra_node ::id=id(),name=t["name:de"] + " (" + t["name:fr"] + ")"; .de_name convert extra_node ::id=id(),name=t["name:de"] + " (" + t["name"] + ")"; .name convert extra_node ::id=id(),name=t["name"]; .fr convert extra_node ::id=id(),name=t["name:fr"]; ); out;
We produce one named set per case:
Please note that we explicitly exclude the former cases from the latter cases to avoid having duplicate entries. Once we have separated each relevant combination into its proper named set, we can apply convert on each set with a suitable set of rules. The embracing union query ensures that we have in the end all derived elements in a single result.
A task for you: Could you produce a French-first version. What about a trilingual version including Italian?
Unfortunately, the effect is not visible on the map view. To see the results you should open the data tab in Overpass Turbo. You then have the produced name in the extra_node element.
This is also the reason for using ::id=id(): ::id= allows to explicitly set the id of the derived element. You could use any numerical expression here, but just reusing the id of the underlying element allows to reassign them as easy as possible afterwards.
This inconvenience stems from that the produced tags do not have coordinates. You can partly overcome this by just adding the coordinate information in an extra outstatement:
area["name:en"="Switzerland"]; node(area)[place~"^(city|town)$"]; node._["name:fr"]["name:de"]->.de_fr; node._["name"]["name:de"](if:t["name"]!=t["name:de"])->.de_name; ( node._["name"]; - (.de_fr; .de_name;); )->.name; node._["name:fr"][!"name"]->.fr; ( .de_fr convert extra_node ::id=id(),name=t["name:de"] + " (" + t["name:fr"] + ")"; .de_name convert extra_node ::id=id(),name=t["name:de"] + " (" + t["name"] + ")"; .name convert extra_node ::id=id(),name=t["name"]; .fr convert extra_node ::id=id(),name=t["name:fr"]; ); out; ( .de_fr; .de_name; .name; .fr; ); out skel;
Using out skel means that you get just the coordinates and not also all the other tags. You can then match the information based on the ids of the elements. This is admittedly only practical if you have few results or if you post-process the data with software. Because of this, the long-term solution will be to support geometry in the next version of Overpass API.
Occasionally one can find in the ref tag of a bus stop the list of services calling there. This is not what the tag is intended for. The best way to convince people that this is not an information worth manual maintenance is to create this content on the fly.
We start with a simple case, a traffic hub in Dusseldorf, Germany. We want to add for each node that represents a bus stop an extra_node that lists the services calling there.
We can combine the principle of collecting extra data first seen here with the set operator on a tag
:( node({{bbox}})[highway=bus_stop]; node({{bbox}})[public_transport=platform]; ); foreach( out; rel[type=route](bn)->.r; convert extra_node ::id=id(), rel_ref=r.set(t["ref"]); out; );
The heart of the request is to collect the relations relative to each node in a named set r. This enables us to make a set of their ref tags as an expression in the convertstatement.
This works slow, but does in the data tab its job. However, in different locations, the request does not work because the stops or the relations are modeled different.
We adapt the query to figure out what is going on:
( node({{bbox}})[highway=bus_stop]; node({{bbox}})[public_transport=platform]; ); foreach( rel(bn)->.r; make marker; out; .r out tags; );
We still recurse inside the foreach loop. What we have changed is that we dump all the tags of the relations found to get a better idea what relations are there. To allow to easily tell apart the individual groups per stop, we print a marker in front of each group of results per node. It turns out that the service information is missing.
I would like to conclude with an example from practice. We were asked which stations under administration of the traffic agency Verkehrsverbund Rhein-Ruhr have platforms equal or below level -2. For the traffic agency, this breaks down to the question which platforms are mapped in OSM at or below level -2.
Because some station names exist in multiple cities, it is necessary to amend the station names with the names of the cities they are in.
The query has two parts:
area[name="Verkehrsverbund Rhein-Ruhr"]->.a; ( ( way(area.a)[public_transport=platform] (if:is_tag(level)&&number(t["level"])<=-2); way(area.a)[railway=platform] (if:is_tag(level)&&number(t["level"])<=-2); ); rel[type=public_transport](bw); ( rel(area.a)[public_transport=platform] (if:is_tag(level)&&number(t["level"])<=-2); rel(area.a)[railway=platform] (if:is_tag(level)&&number(t["level"])<=-2); ); rel[type=public_transport](br); ); foreach( > ->.n; .n is_in->.c; area.c[admin_level~"^[68]$"]->.c; make foo Name=set(t["name"]),Stadt=c.set(t["name"]); out tags; );
The first paragraph employs the filtering for numbers as first presented here. The new thing happens in the foreach loop: We make our own is_in tag. This is done with the same technique as in the previous section. The most important thing to observe is that is_in currently only works on nodes. This requires us to go down to the nodes in a first step with > ->.n.