Table of Contents

Ricette Mappe OpenStreetMap

In questa pagina alcune ricette utili per cucinare mappe OpenStreetMap da caricare su navigatore GPS. In particolare l'obiettivo è creare delle versioni ridotte compatibili con il Garmin eTrex 10, che ha capacità di memoria davvero ridotte (circa 7 Mb).

Tutti gli script qui descritti sono contenuti nell'archivio garmin-etrex10-map.tgz.

Spazio limitato: è necessario un compromesso

Per motivi di capienza sarà quindi necessario restringere l'estensione della mappa ad una sola regione (in questo esempio la Toscana), inoltre si dovrà optare per una mappa di tipo on-road (ad esempio per uso cicloturistico) oppure una off-road (per uso trekking o MTB). Nel primo caso si includerà solo la rete stradale ordinaria (da motorway a unclassified nella classificazione OSM), nel secondo caso quella off-road (footway, track e path nella classificazione OSM).

ATTENZIONE: Dopo un po' di prove sul campo ho realizzato che per fare trekking è necessario avere contemporaneamente sia le strade asfaltate che i sentieri. A queste condizioni una regione intera come la Toscana non ci sta. Il mio suggerimento è quindi di estrarre i dati solo di alcuni comuni limitrofi.

Cosa includere

Galleria

Confini regioni italiane da Istat

L'Istituto nazionale di statistica fornisce gratuitamente il file dei confini comunali e regionali, in formato Shapefile. Per effettuare le opportune manipolazioni abbiamo caricato il file in un database geografico PostgreSQL/PostGIS. Inoltre abbiamo utilizzato i tool GDAL/OGR e gpsbabel.

I pacchetti Debian da installare sono:

apt-get install postgis gdal-bin gpsbabel

Dalla pagina Archivio dei confini delle unità amministrative si è scaricato l'archivio più dettagliato nel formato UTM32N.

Queste sono le trasformazioni applicate al dataset originale:

  1. Rimozione dei “buchi”.
  2. Semplificazione, con errore massimo di 150 metri, per ridurre il numero dei vertici.
  3. Eliminazione dei poligoni più piccoli di una certa area.
  4. Riproiezione dal sistema UTM zone 32N (EPSG:32632) al WGS84 (EPSG:4326).
  5. Conversione del file GPX in file OSM.

Per la creazione di un database geografico consultare la pagina PostGIS su Debian. La conversione dello Shapefile in SLQ è ottenuta con:

shp2pgsql -s 32632 Reg_2016_WGS84.shp reg2016 > Reg_2016_WGS84.sql

Il primo passaggio è la rimozione dei “buchi” nei multipoligoni. La funzione da usare è ST_ExteriorRing(), che però deve essere applicata dopo aver scomposto i MULTIPOLYGON componenti con la funzione ST_Dump(). La geometria risultante è una LINESTRING che deve essere ricomposta in poligoni con ST_MakePolygon(). Il risultato viene salvato in una tabella temporanea reg2016_no_holes:

SELECT regione, ST_MakePolygon(ST_ExteriorRing((ST_Dump(geom)).geom)) AS geom
    INTO TABLE reg2016_no_holes FROM reg2016;

I passaggi successivi sono realizzati da una sola istruzione SQL, che viene eseguita dal tool ogr2ogr, in modo da salvare il risultato direttamente in un file GPX:

#!/bin/sh
SQL="SELECT regione AS name, ST_Transform(ST_Simplify(geom, 150), 4326) AS geom
     FROM reg2016_no_holes WHERE ST_Area(geom) > 11000000"
export GPX_USE_EXTENSIONS
ogr2ogr \
    -f "GPX" regioni.gpx \
    PG:"host=localhost user=username dbname=dbname password=MySecret" \
    -sql "$SQL" \
    -lco "FORCE_GPX_TRACK=YES" -nlt "MULTILINESTRING" -mapFieldType "Integer64=String"

Nell'istruzione SQL si utilizza ST_Simplfy() indicando un errore massimo di 150 metri (la proiezione dei dati Istat è in metri), la ST_Transform() per riproiettare le geometrie nel sistema WGS84, infine si impone una clausola con ST_Area() per togliere i poligoni (isole) che hanno una superficie inferiore agli 11 km quadrati.

Il passaggio finale viene fatto con il tool gpsbabel, consiste nel convertire il file GPX estratto dal database in un file OSM, utilizzabile in seguito da mkgmap. Durante la conversione vengono aggiunti ad ogni way i tag che indicano un confine regionale. Il file prodotto da gpsbabel ha purtroppo ha gli ID negativi ordinati in modo decrescente, questo impedisce a osmconvert (che verrà usato successivamente) di funzionare correttamente. Si usa un comando brutale sed per togliere il segno meno a tutti gli id e ref:

#!/bin/sh
gpsbabel -i gpx -f regioni.gpx \
    -o osm,created_by=,tag="boundary:administrative;admin_level:4" \
    -F regioni_bad_id.osm
cat regioni_bad_id.osm  | sed "s/id='-/id='/" | sed "s/ref='-/ref='/" > regioni.osm
rm regioni_bad_id.osm

FIXME Questa ricetta ha un problema teorico: gli ID trasformati in numeri positivi potrebbero andare in conflitto con gli ID degli oggetti scaricati dal database OpenStreetMap e quindi creare problemi al programma osmconvert. Esiste anche la funzione renumber del programma osmium (dal pacchetto osmium-tool), ma occorre la versione 1.8.0 da Debian stretch-backports e comunque non risolve il problema del potenziale conflitto.

Confine per l'estrazione dei dati

|Disegno del confine con JOSM Utilizzeremo il linguaggio Overpass API per estrarre i dati da OpenStreetMap. Per ogni richiesta forniremo il confine per l'estrazione sotto forma di vertici di un poligono grossolano (una trentina di vertici) che racchiude la regione desiderata.

Per disegnare i confini abbiamo utilizzato JOSM con questa procedura.

  1. Scaricato una porzione della mappa che includa un piccolo tratto del confine regionale.
  2. Dopo aver selezionato tale tratto di confine, dal tab Tags / Memberships si evidenzia la relazione boundary[4] Toscana, click destro, Download members. Vengono scaricate tutte le way che formano il confine reginale.
  3. Menu File, New Layer.
  4. Nel nuovo layer si disegna il poligono approssimato che racchiuda la regione.
  5. Click destro sul layer disegnato, Export to GPX.

Dal file GPX creato è possibile estrarre le coordinate con un semplice script di shell (lo abbiamo chiamato bounds-gpx2txt):

#!/bin/sh
cat "bounds-toscana.gpx" \
    | grep '<trkpt' \
    | sed 's/.*lat="\([0-9\.]\+\)" lon="\([0-9\.]\+\)".*/\1 \2/' \
    | tr '\n' ' ' \
    | awk '{$1=$1};1'

Il risultato desiderato è semplicemente la sequenza delle coordinate lat lon di tutti i vertici separate da spazi:

44.381756 9.629437 44.488539 9.699057 44.569342 9.951234 ...

Grafo stradale da OSM

Per questa ricetta è necessario osmfilter, contenuto nel pacchetto Debian osmctools.

In un file che chiameremo query_way.xml si prepara la query Overpass per estrarre le way dalla zona desiderata:

<osm-script>
  <query type="way">
    <has-kv k="highway" regv="^motorway$|^trunk$|^primary$|^secondary$|^tertiary$|^unclassified$"/>
    <polygon-query bounds="44.381756 9.629437 44.488539 9.699057 44.569342 ..."/>
  </query>
  <union>
    <item />
    <recurse type="way-node"/>
  </union>
  <print mode="body"/>
</osm-script>

NOTA: all'espressione regolare regv è opportuno aggiungere anche tutte le highway di tipo *_link, ad esempio motorway_link, ecc. Vedere la classificazione delle highway.

Nell'opportuno linguaggio è scritto che devono essere estratte non solo le way con i relativi attributi, ma anche i node che costituiscono effettivamente la forma della strada, questo è il senso della ricorsione di tipo way-node. Il polygon-query è quello definito nel paragrafo precedente.

Per prelevare i grafo stradale è quindi sufficiente l'uso di wget:

wget --post-file="query_way.xml" -O "op_way.osm" "http://overpass-api.de/api/interpreter"

Infine usiamo osmfilter per ripulire il file, togliendo eventuali tag associati ai singoli nodi e mantenendo solo alcuni tag delle strade (i tag highway, name e ref):

osmfilter "op_way.osm" \
    --drop-node-tags="*=" --keep-way-tags="all highway= name= ref=" \
    --drop-author --fake-version --out-osm -o="f_way.osm"

Per ottenere il grafo delle strade off-road si utilizza una ricetta del tutto analoga, salvo che le highway da estrarre sono solo quelle di tipo footway, track e path, mentre i tag tag preservare sono highway, name, ref e tracktype.

Waypoint da OSM

L'estrazione di waypoint dalla mappa OSM presenta una difficoltà. Prendiamo ad esempio le farmacie: in alcuni casi saranno indicate sulla mappa OSM come node con il tag:amenity=pharmacy, in altri casi saranno invece disegnate come way (ad esempio per rappresentare l'edificio) e il tag amenity sarà applicato alla way invece che a un node. Per questo motivo abbiamo fatto l'estrazione degli waypoint con due query Overpass diverse.

Questa è la query amenity_node.xml per l'estrazione dei semplici nodi:

<osm-script>
  <query type="node">
    <has-kv k="amenity" regv="^restaurant$|^cafe$|^drinking_water$|^pharmacy$|^hospital$"/>
    <polygon-query bounds="44.3817 9.6294 44.4885 ..."/>
  </query>
  <print mode="body"/>
</osm-script>

Per estrarre le way si usa una query amenity_way.xml leggermente diversa:

<osm-script>
  <query type="way">
    <has-kv k="amenity" regv="^restaurant$|^cafe$|^pharmacy$|^hospital$"/>
    <polygon-query bounds="44.3817 9.6294 44.4885 ..."/>
  </query>
  <print mode="body" geometry="center"/>
</osm-script>

La differenza sta nell'aver aggiunto il modificatore geometry="center" nel print, in tal modo viene aggiunto all'elemento <way> un child <center>, mentre i nodi che compongono la way non saranno estratti. Bisognerà manipolare opportunamente il file per avere <node> al posto di <way>.

Notare che secondo le indicazioni del wiki il tag:amenity=drinking_water si applica solo ai nodi e non alle way, perciò non è stato ripetuto.

Con wget si scarica i due estratti da OpenStreetMap:

wget --post-file=amenity_node.xml -O "op_amenity_n.osm" "http://overpass-api.de/api/interpreter"
wget --post-file=amenity_way.xml  -O "op_amenity_w.osm" "http://overpass-api.de/api/interpreter"

Con lo script name_fix si assegna un tag name a tutti i nodi che non ce l'hanno, in modo che siano etichettati opportunamente nella lista che compare sul GPS. Ad esempio per una farmacia si usa name=pharmacy (forse si potrebbe fare anche con uno stile di mkgmap). Con questo passaggio dal file op_amenity_n.osm si ottiene il file op_amenity_n_fix.osm.

Per diminuire le dimensioni del file destinazione si rimuovono tutti i tag ad eccezione di amenity e name:

osmfilter "op_amenity_n_fix.osm" --keep-node-tags="all amenity= name=" \
    --drop-author --fake-version --out-osm -o="f_amenity_n.osm"

Per trasformare ogni way del file op_amenity_w.osm in un node si usa lo script way2node. Il risultato è il file f_amenity_w.osm.

Come si sono estratti gli opportuni oggetti amenity, si estraggono anche gli oggetti tourism. In particolare si sono estratti: hotel, motel, guest_house, chalet, hostel, alpine_hut, wilderness_hut, caravan_site e camp_site.

Centri abitati da OSM

L'estrazione dei centri abitati è del tutto simile a quella degli altri waypoint. La query Overpass deve selezionare i node con tag place e valore city, town oppure village. Anche in questo caso si aggiunge il tag nome nel caso che sia assente ed infine si rimuovono tutti i tag ad eccezione di name, place e population:

osmfilter "tmp/op_place_n_fix.osm" \
    --keep-node-tags="all name= place= population=" \
    --drop-author --fake-version --out-osm -o="tmp/f_place_n.osm"

Assemblare tutti i pezzi

Per combinare tutte le parti prodotte in precedenza si utilizza osmconvert contenuto nel pacchetto Debian osmctools:

osmconvert \
    "admin_boundaries.osm" \
    "tmp/f_track.osm" \
    "tmp/f_way.osm" \
    "tmp/f_place_n.osm" \
    "tmp/f_amenity_n.osm" "tmp/f_amenity_w.osm" \
    "tmp/f_tourism_n.osm" "tmp/f_tourism_w.osm" \
    -o="gmapsupp.osm"

Il risultato è un file gmapsupp.osm ancora nel formato XML di OpenStreetMap.

mkgmap-splitter

Il file risultante gmapsupp.osm deve essere suddiviso in quadranti più piccoli (tile, mattonelle) per adeguarsi al formato Garmin, si utilizza il programma mkgmap-splitter installato dal pacchetto Debian omonimo.

mkgmap-splitter gmapsupp.osm

Il risultato è una serie di file la cui simensione è di circa 10 Mb ciascuno:

Personalizzare lo stile della mappa

Prima dell'esecuzione finale del programma mkgmap è opportuno definire lo stile della mappa; a causa delle limitate capacità grafiche del Garmin eTrex 10 lo stile predefinito non è ottimale. Tre sono i concetti propedeutici da comprendere:

Fondamentale lettura è il Conversion Style manual di mkgmap (qui una copia locale). Si deve anzitutto familiarizzare con alcuni concetti:

In genere una mappa Garmin contiene diversi livelli di rappresentazione: dal più dettagliato al più generico. Sullo schermo verrà visualizzato il livello opportuno in base alla scala (zoom), o più propriamente alla risoluzione. Quindi la scelta di quali elementi visualizzare a quali livelli di zoom viene effettuata al momento della creazione della mappa, utilizzando l'opportuno stile. In pratica un file mappa Garmin contiene al suo interno mappe diverse della stessa regione e l'apparecchio GPS sceglie automaticamente quale mostrare in base allo zoom. Ciò differisce sostanzialmente dall'applicare al volo uno stile di rendering basato sullo zoom.

Style Definisce gli elementi della mappa OpenStreetMap che costituiranno la mappa Garmin e quali simboli Garmin verranno usati; non definisce la simbologia grafica della mappa. Esistono alcuni stili predefiniti: per ottenere il risultato desiderato sarà sufficiente in generale fare alcune modifiche allo stile default per definire quali oggetti saranno visualizzati e a quale scala
TYP files Definiscono la simbologia grafica della mappa: colori, spessori delle linee, ecc.
Resolution È un numero compreso fra 1 e 24. La mappa più dettagliata è alla risoluzione di 24. Al diminuire di uno step la risoluzione si dimezza; cioè se un oggetto occupa 10 unità alla risoluzione 24, occuperà 5 unità alla risoluzione di 23. Un determinato GPS utilizza una determinata risoluzione per ogni scala di visualizzazione. Ad esempio un Garmin Legend Cx usa la risoluzione 22 quando la scala di visualizzazione è su 500m-200m.
Level È un numero compreso fra 0 e 16 (in pratica non si usa mai oltre il 10). Il livello 0 è quello maggiormente dettagliato e viene usato a risoluzioni maggiori. Uno stile in genere definisce quanti livelli sono presenti nella mappa e a quali risoluzioni vanno utilizzati. Ad esempio lo stile default definisce 4 livelli (da 0 a 3) da usare alle risoluzioni 24, 22, 20 e 18.
Livelli diversi sono a tutti gli effetti mappe diverse: uno stesso oggetto fisico può essere rappresentato in modo del tutto diverso da un livello ad un altro, ad esempio una città può essere rappresentata da un poligono a livello 1 e con un singolo punto a livello 2.
Risoluzione mappa su eTrex 10
Resolution Scala
18 8 km
19 5 km
20 2 km
21 1.2 km
22 500 m
23 300 m
24 120 - 80 m

Lo stile "default"

Lo stile default è inglobato nell'eseguibile di mkgmap, quindi è necessario ispezionare i sorgenti. In particolare osserviamo i file options e lines.

In options si vede che la mappa contiene 4 livelli di dettaglio:

levels = 0:24, 1:22, 2:20, 3:18

In lines viene definito a quali risoluzioni compaiono le strade di tipo highway=footway, highway=path, highway=track e i confini amministrativi:

highway=footway|highway=path|highway=steps [0x16 road_class=0 road_speed=0 resolution 23]
highway=track [0x0a road_class=0 road_speed=1 resolution 22]
boundary=administrative & admin_level<3 [0x1e resolution 12]
boundary=administrative & admin_level<5 [0x1d resolution 19]
boundary=administrative & admin_level<7 [0x1c resolution 21]
boundary=administrative & admin_level<9 [0x1c resolution 22]
boundary=administrative [0x1c resolution 22]

Secondo le indicazioni OpenStreetMap l'admin_level per i confini amministrativi dell'Italia assume i valori: 2=Nazione, 4=Regione, 6=Provincia e 8=Comune. Questo significa che il confine regionale appare alla risoluzione 19 o superiore. Nello stile default il primo livello che visualizza tali confini (partendo da quello meno dettagliato, cioè con indice maggiore) è quindi il livello 2 che ha risoluzione 20. L'effetto pratico è che i confini regionali si vedono sul GPS con la mappa alla scala dei 2 km o inferiore (più ingrandita).

Personalizzazione dello stile "default"

Rispetto allo stile default si sono rese necessarie alcune modifiche per realizzare una mappa destinata all'escursionismo off-road:

Schematicamente:

Oggetto Visibile da Risoluzione Livello
Confini regionali 800 km 18 3
Centri abitati minori 5 km 19 2
Track e path 2 km 20 1
Waypoint 120-80 m 24 0

Dai sorgenti di mkgmap si è estratta tutta la directory mkgmap-r3741/resources/styles/default/, quindi si sono modificati due file:

Nel primo si definiscono i livelli di dettaglio e la relativa risoluzione:

levels = 0:24, 1:20, 2:19, 3:18

Nel secondo si definisce a quale risoluzione far comparire i vari oggetti:

highway=footway|highway=path|highway=steps [0x16 road_class=0 road_speed=0 resolution 20]
highway=track [0x0a road_class=0 road_speed=1 resolution 20]
...
boundary=administrative & admin_level<3 [0x1e resolution 12] # admin_level:2 Nazione
boundary=administrative & admin_level<5 [0x1d resolution 18] # admin_level:4 Regione
boundary=administrative & admin_level<7 [0x1c resolution 21] # admin_level:6 Provincia
boundary=administrative & admin_level<9 [0x1c resolution 22] # admin_level:8 Comune

Personalizzazione del simbolismo (file TYP)

Ogni GPS Garmin ha un simbolismo predefinito che definisce l'aspetto grafico per ogni oggetto mappa. Ad esempio una highway=track della mappa OSM viene convertita da mkgmap in un oggetto Garmin line Type=0x00a, che viene mostrata sul display dell'eTrex 10 con un certo spessore e colore. Il simbolismo è codificato all'interno del firmware del GPS, nell'eTrex provvede anche ad adattare la rappresentazione grafica rispetto alla scala; ad esempio una strada di tipo secondary verrà rappresentata con una linea più spessa quando si aumenta lo zoom.

Nella mappa compilata con mkgmap è possibile tuttavia definire delle personalizzazioni rispetto al simbolismo predefinito. Abbiamo sfruttato questa possibilità per modificare l'aspetto delle linee Type=0x00a e Type=0x016 (track e path rispettivamente) in modo che fossero più sottili e non confondessero il display alla scala dei 2 km.

Per ottenere il risultato è sufficiente creare un file my_etrex10.txt con questo contenuto:

[_id]
ProductCode=0x1
FID=9999
[end]

[_line]
Type=0x00a
;GRMN_TYPE: Roads/UNPAVED_ROAD/Gravel or dirt road/Non NT, NT
UseOrientation=Y
Xpm="32 1 2  1"
"- c #000000"
"  c none"
"------  ------  ------  ------  "
;12345678901234567890123456789012
String1=0x04,Track
ExtendedLabels=N
[end]

[_line]
Type=0x016
;GRMN_TYPE: Roads/TRAIL/Walkway or trail/Non NT, NT
UseOrientation=Y
Xpm="32 1 2  1"
"- c #808080"
"  c none"
"----  -----  ----  -----  ----  "
;12345678901234567890123456789012
String1=0x04,Path
ExtendedLabels=N
[end]

ATTENZIONE: Il file qui sopra produce un simbolismo che verrà applicato per ogni level. Cioè le linee Type=0x00a e Type=0x016 saranno rappresentate sempre allo stesso modo ad ogni livello di zoom. Questo è molto differente dagli stili applicati internamente dall'eTrex, dove ad esempio il Type=0x04 applicato alle highway=secondary produce linee via via più spesse quando si aumenta lo zoom. In alternativa si dovrebbero definire diverse righe nel file style/default/lines (vedi sopra), ciascuna associata ad una resolution diversa e con un codice Type diverso, qualcosa del tipo:

highway=track [0x0a road_class=0 road_speed=1 resolution 24 continue]
highway=track [0x0a01 road_class=0 road_speed=1 resolution 23-20 continue]
highway=track [0x0a02 road_class=0 road_speed=1 resolution 19]

La riga Xpm definisce il bitmap in termini di larghezza, altezza, colori e caratteri per pixel. Le righe successive definiscono il colore rappresentato da ciascun carattere (il punto esclamativo è nero o grigio, lo spazio è trasparente), infine il bitmap vero e proprio che nel nostro caso è una matrice di 32×1 caratteri.

NOTA BENE: Con mkgmap versione svn3741 non è più necessario compilare il file TYP, è possibile utilizzare direttamente il testo sorgente.

No Results found: bug POI non elencati per categoria

Sembra che ci sia un bug nel firmware dell'eTrex 10: in alcune circostanze gli waypoint inclusi nella mappa non vengono elencati nelle opportune categorie. Ad esempio uno waypoint a cui sia assegnato il tipo 0x2c0d viene visualizzato sulla mappa con l'icona Place of worship e dovrebbe comparire nel menu Where to?Community.

Dovrebbe essere sufficiente che la mappa OSM contenga un nodo di questo tipo:

<node id="170860041" lat="43.91410710" lon="11.22389060" version="1">
  <tag k="amenity" v="place_of_worship"/>
  <tag k="name" v="Pieve di San Severo"/>
</node>

e lo stile styles/default/points contenga una riga del tipo:

amenity=place_of_worship [0x2c0d resolution 24]

Si è verificato invece che in alcune condizioni ciò non accade, la categoria Community riporta No Results found - Try Adjusting Search Parameters. Dalle prove fatte è necessario rispettare le seguenti condizioni per evitare il bug:

  1. Il file mappa OSM deve contenere dei nodi - eventualmente fasulli - che verranno assegnati agli stili da 0x2c0a a 0x2c0c. Tali nodi devono essere in prossimità degli altri, cioè più o meno nel range della mappa; posizionandoli in prossimità delle coordinate lat=0 lon=0 non funziona.
  2. Il file dello stile deve definire degli stili per i nodi di cui sopra, assegando le icone da 0x2c0a a 0x2c0c.

In pratica sembra che l'eTrex abbia bisogno che nella mappa esista la serie completa di sottotipi (in questo caso a partire da 0x2c0a fino a quello che interessa a noi, cioè il 0x2c0d), altrimenti la categoria non viene creata. Non è chiaro come mai la serie completa in questo caso può iniziare da 0x2c0a, quando intuitivamente si potrebbe pensare che la serie inizia con 0x2c00.

mkgmap

L'ultimo passaggio è quindi la compilazione della mappa con stile e simbolismo personalizzato.

Durante l'invocazione di mkgmap è sufficiente passare il riferimento alla directory con lo stile modificato e al file con le modifiche al simbolismo per ottenere la mappa desiderata (il file TYP viene compilato al volo):

mkgmap \
    --reduce-point-density=4 --unicode \
    --description="Trekking map for eTrex-10" \
    --style-file='./styles/default/' \
    --country-name='Italy' --country-abbr='I' \
    --region-name='Toscana' \
    --family-id=9999 \
    --gmapsupp 63240*.osm.pbf \
    ./typ/my_etrex10.txt

L'opzione --region-name viene usata come stringa da visualizzare accanto al nome del centro abitato nella visualizzazione dell'elenco, altrimenti compare la stringa generica ABC. FIXME Forse esiste la possibilità di usare i confini amministrativi e l'indicizzazione degli indirizzi per associare il centro abitato al nome del comune che lo contiene?

Riferimenti Web