FME en de Overpass API van Openstreetmap

Stel je voor: je wilt op vakantie. Fietsen tussen Salt Lake City en Denver in de USA. En je wilt alle horeca, onderkomens en winkels om eten te kopen in beeld hebben. Hoe kom je aan die gegevens? Een van de mogelijkheden is via de Overpass API van Openstreetmap.

De Overpass API van Openstreetmap (OSM) stelt je in staat om elementen van Openstreetmap te selecteren en te downloaden. De data is actueel, in de zin dat je de meest recente OSM-data tot je beschikking hebt. Hoe accuraat en actueel de data zelf is, is echter onmogelijk te achterhalen.

Wat te doen
  • Je definieert een bounding box van het gebied waar je doorheen wilt fietsen
  • Je bepaalt de OSM tags die de horeca, de accommodaties en de relevante winkels opleveren
  • Je combineert deze twee datasets tot een query die je aan de Overpass API website ‘voert’
  • Je haalt met FME het resultaat binnen
  • Je verwerkt de binnengekomen data
  • Je schrijft het eindresultaat weg als een GPX-bestand
Bounding Box

De bounding box is een rechthoek die je helpt in het beperken van de data. Je wilt immers niet alle OSM-data, maar alleen de data van het voor jou relevante gebied. Deze definieer je door de lat-lon waardes op te geven van de noordoostelijke en de zuidwestelijke hoek van de rechthoek. In mijn geval is dat

<bbox-query e="-104.20530" n="39.41819" s="35.11077" w="-114.05950"/>

Tags

De key-value pairs die je nodig heb, moet je zelf bepalen. Een goed startpunt is http://wiki.openstreetmap.org/wiki/Tags.

Ik kwam tot de volgende selectie:

 shop="bicycle"
 shop="supermarket"
 shop="grocery"
 shop="bakery"
 amenity="restaurant"
 amenity="pub"
 amenity="fastfood"
 amenity="cafe"
 tourism=hotel"
 tourism=hostel"
 tourism="motel"
 tourism="guest_house"
 tourism="chalet"
 tourism="camp_site"
 tourism="caravan_site" AND  tents="yes"

Omdat er geen dwang staat op het gebruik van wanneer welke tags en welke values, blijft het de vraag hoe compleet de geretourneerde data is. Die vraag valt buiten het bereik van deze blogpost.

Query

De Overpass XML queries die we moeten opvoeren, zijn:

     <osm-script timeout="1500">
         <union>
             <query type="node">
                 <has-kv k="shop" v="bicycle"/>
                 <bbox-query e="-104.20530" n="39.41819" s="35.11077" w="-114.05950"/>
             </query>
             <query type="node">
                 <has-kv k="shop" v="convenience"/>
                 <bbox-query e="-104.20530" n="39.41819" s="35.11077" w="-114.05950"/>
             </query>
             <query type="node">
                 <has-kv k="shop" v="supermarket"/>
                 <bbox-query e="-104.20530" n="39.41819" s="35.11077" w="-114.05950"/>
             </query>
             <query type="node">
                 <has-kv k="shop" v="grocery"/>
                 <bbox-query e="-104.20530" n="39.41819" s="35.11077" w="-114.05950"/>
             </query>
             <query type="node">
                 <has-kv k="shop" v="bakery"/>
                 <bbox-query e="-104.20530" n="39.41819" s="35.11077" w="-114.05950"/>
             </query>
             <query type="node">
                 <has-kv k="amenity" v="restaurant"/>
                 <bbox-query e="-104.20530" n="39.41819" s="35.11077" w="-114.05950"/>
             </query>
             <query type="node">
                 <has-kv k="amenity" v="pub"/>
                 <bbox-query e="-104.20530" n="39.41819" s="35.11077" w="-114.05950"/>
             </query>
             <query type="node">
                 <has-kv k="amenity" v="fastfood"/>
                 <bbox-query e="-104.20530" n="39.41819" s="35.11077" w="-114.05950"/>
             </query>
             <query type="node">
                 <has-kv k="takeaway" v="yes"/>
                 <bbox-query e="-104.20530" n="39.41819" s="35.11077" w="-114.05950"/>
             </query>
             <query type="node">
                 <has-kv k="amenity" v="cafe"/>
                 <bbox-query e="-104.20530" n="39.41819" s="35.11077" w="-114.05950"/>
             </query>
         </union>
     </osm-script>
     <osm-script timeout="1500">
         <union>
             <query type="node">
                 <has-kv k="tourism" v="hotel"/>
                 <bbox-query e="-104.20530" n="39.41819" s="35.11077" w="-114.05950"/>
             </query>
             <query type="node">
                 <has-kv k="tourism" v="hostel"/>
                 <bbox-query e="-104.20530" n="39.41819" s="35.11077" w="-114.05950"/>
             </query>
             <query type="node">
                 <has-kv k="tourism" v="motel"/>
                 <bbox-query e="-104.20530" n="39.41819" s="35.11077" w="-114.05950"/>
             </query>
             <query type="node">
                 <has-kv k="tourism" v="guest_house"/>
                 <bbox-query e="-104.20530" n="39.41819" s="35.11077" w="-114.05950"/>
             </query>
             <query type="node">
                 <has-kv k="tourism" v="chalet"/>
                 <bbox-query e="-104.20530" n="39.41819" s="35.11077" w="-114.05950"/>
             </query>
             <query type="node">
                 <has-kv k="tourism" v="camp_site"/>
                 <bbox-query e="-104.20530" n="39.41819" s="35.11077" w="-114.05950"/>
             </query>
             <query type="node">
                 <has-kv k="guest_house" v="bed_and_breakfast"/>
                 <bbox-query e="-104.20530" n="39.41819" s="35.11077" w="-114.05950"/>
             </query>
             <query type="node">
                 <has-kv k="tourism" v="caravan_site"/>
                 <has-kv k="tents" v="yes"/>
                 <bbox-query e="-104.20530" n="39.41819" s="35.11077" w="-114.05950"/>
             </query>
         </union>
     </osm-script>

Het zijn er twee, want in de praktijk leidt een request met teveel parameters tot een timeout.

De query in deze vorm moet nog worden geconverteerd naar Overpass QL format. Dat kan via het convert form van http://overpass-api.de/query_form.html.

Resultaat:

http://overpass-api.de/api/interpreter?data=[timeout:1500];(node["shop"="bicycle"](35.11077,-114.05950,39.41819,-104.20530);node["shop"="convenience"](35.11077,-114.05950,39.41819,-104.20530);node["shop"="supermarket"](35.11077,-114.05950,39.41819,-104.20530);node["shop"="grocery"](35.11077,-114.05950,39.41819,-104.20530);node["shop"="bakery"](35.11077,-114.05950,39.41819,-104.20530);node["amenity"="restaurant"](35.11077,-114.05950,39.41819,-104.20530);node["amenity"="pub"](35.11077,-114.05950,39.41819,-104.20530);node["amenity"="fastfood"](35.11077,-114.05950,39.41819,-104.20530);node["takeaway"="yes"](35.11077,-114.05950,39.41819,-104.20530);node["amenity"="cafe"](35.11077,-114.05950,39.41819,-104.20530););out;

http://overpass-api.de/api/interpreter?data=[timeout:1500];(node["tourism"="hotel"](35.11077,-114.05950,39.41819,-104.20530);node["tourism"="hostel"](35.11077,-114.05950,39.41819,-104.20530);node["tourism"="motel"](35.11077,-114.05950,39.41819,-104.20530);node["tourism"="guest_house"](35.11077,-114.05950,39.41819,-104.20530);node["tourism"="chalet"](35.11077,-114.05950,39.41819,-104.20530);node["tourism"="camp_site"](35.11077,-114.05950,39.41819,-104.20530);node["guest_house"="bed_and_breakfast"](35.11077,-114.05950,39.41819,-104.20530);node["tourism"="caravan_site"]["tents"="yes"](35.11077,-114.05950,39.41819,-104.20530););out;

FME

En deze HTTP-requests kan ik nu opvoeren met behulp van een HTTPCaller transformer van FME

Zie de workflow hieronder

  • De HTTPCaller verwerkt de request

P.S. De querystring value parameter kan je ook instellen als choice user parameter met shortcuts voor de lange strings, maar dat stuitte op een bug in FME (Deze is in de nieuwe beta opgelost – 24 juni 2016, maar een eenvoudige oplossing wordt beschreven in versie 2). Het resultaat wordt default als ML teruggegeven.

  • XSLTProcessor maakt er eenvoudig XML van. Input XML heeft bijv. k=”amenity”, v=”restaurant”. De XSLT file transformeert dat naar amenity=”restaurant”, dat maakt het werk van de Flattener in de XMLFragmenter gemakkelijker.

Zie het script hieronder:

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XS/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
     <xsl:strip-space elements="*"/>
     <xsl:output method="xml" indent="yes"/>
     <xsl:template match="@* | node()">
         <xsl:copy>
             <xsl:apply-templates select="@* | node()"/>
         </xsl:copy>
     </xsl:template>
     <xsl:template match="node">
         <xsl:copy>
             <xsl:element name="lat">
                 <xsl:value-of select="@lat"/>
             </xsl:element>
             <xsl:element name="lon">
                 <xsl:value-of select="@lon"/>
             </xsl:element>
             <xsl:for-each select="tag">
                 <xsl:element name="{translate(@k, \':\', \'\')}">
                     <xsl:value-of select="@v"/>
                 </xsl:element>
             </xsl:for-each>
         </xsl:copy>
     </xsl:template>
 </xsl:stylesheet>
  • XMLFragmenter converteert de gewenste XML elementen naar attributes
Hierin wordt de rootnode aangegeven, m.a.w. het begin-element van een feature, en wordt via de Flattener parameter aangeven dat alle subelementen van de rootnode vertaald moeten worden naar attributes.
P.S. Het is belangrijk expliciet aan te geven welke attributes je wilt. Een heleboel elementen in de OSM xml zijn overbodig. In het Expose Attributes veld geef je de namen op van de attributes die je zichtbaar wilt maken voor vervolgacties. Mooi, want zo kan je de overbodige weglaten.
  • VertexCreator maakt de gewenste punt geometrieën aan, inclusief hoogtes waar vermeld.
GPX
  • Tenslotte doet de Attributemanager veel werk, door verschillende attributes te combineren naar één attribute, ofwel door samenvoeging, ofwel door conditional values. Bijv. de description attribute vul ik hier met attribute waarden zoals telefoon, email, openingstijden en bijzonderheden: velden die ik niet kwijt kan in de gangbare GPX structuur. Een voorbeeld van conditional values is de opbouw van het attribute Symbol, waar in het GPX bestand de waarde wordt gezet voor het icoon. Zie hieronder.

Alle relevante attributen worden gekoppeld aan de standaard attributen van een GPX Writer. Waypoint feature type is voldoende.

Het resultaat is wellicht ook op andere manieren te bereiken. Ik denk aan een selectie via JOSM een bekende OSM-editor, of voor Garmin via http://garmin.openstreetmap.nl/. De laatste is kant en klare data, maar dan wel alles, en natuurlijk ook alleen voor gebruik binnen Garmin Basecamp. De hier beschreven benadering is meer generiek.

P.S. Bij het opslaan van scripts en codebocks worden rechte aanhalingstekens veranderd in gekrulde. Dat kan problemen opleveren bij het gebruik in software. Verander dit door zoek-en-vervang in een teksteditor voordat je de scripts/codeblocks gebruik..