Arbres Monumentals de Catalunya (programació OSM)
Contingut
- 1 Introducció
- 2 Cercar els arbres que hi ha a Catalunya (OSM)
- 3 wikipedia, llista d'arbres catalogats
- 4 2 possibilitats: s'haurà de crear un node, s'haurà d'editar un node
- 5 Python interactiu: vull fer els canvis (yes/no)?
- 6 Programació Osmapi: crear, editar, esborrar nodes
- 7 arbres.json: carregar la llista d'arbres
- 8 Posar-ho tot junt: importar_arbres_monumentals_v1.py
- 9 Referències
- 10 Annex: arbres singulars de Barcelona
- 11 Annex: arbres singulars de Mallorca
Introducció
Volem actualitzar els arbres monumentals de Catalunya a partir de:
Es tracta de fer un script python per actualitzar la informació ja existent d'un arbre/node. I si no existeix, inserir l'arbre/node.
En aquest article es documenta tot el procés fins arribar al script funcional. Es desitja que el script sigui semi-automàtic per tal de poder comparar sempre la informació existent i la nova, i poder abortar el script en cas de dubte.
Cercar els arbres que hi ha a Catalunya (OSM)
node ["tree"] ({{bbox}}); (._;>;); out;
resultat:
<node id="2315628015" lat="41.3950882" lon="2.0110520"> <tag k="natural" v="tree"/> <tag k="tree" v="pinus"/> </node>
node ["natural"="tree"] ({{bbox}}); (._;>;); out;
resultat:
<node id="409741485" lat="41.6286762" lon="2.1700571"> <tag k="genus" v="Olea"/> <tag k="natural" v="tree"/> <tag k="species" v="Olea europaea"/> <tag k="species:ca" v="Olivera"/> </node> <node id="5613734131" lat="41.3460831" lon="1.3627114"> <tag k="name" v="Xiprers del Palau de l'Abat de Santes Creus"/> <tag k="natural" v="tree"/> <tag k="note" v="Arbre Monumental MA-01.001.01 https://ca.wikipedia.org/wiki/Llista_d%27arbres_monumentals_de_Catalunya"/> </node>
és a dir, els arbres els puc trobar buscant per tag=tree (de fet, només n'hi ha 6), o bé tag=natural v=tree (que és la manera correcta).
i ara faig el mateix, però cercant directament per 'Catalunya' en comptes de la bbox:
area["name"="Catalunya"]->.boundaryarea; ( nwr(area.boundaryarea)[natural=tree]; nwr(area.boundaryarea)[tree]; ); out meta;
i ara genero la llista d'arbres en format JSON (o XML) amb un script python que fa aquesta consulta (arbres.py):
import requests import json overpass_url = "http://overpass-api.de/api/interpreter" overpass_query = """ [out:json]; area["name"="Catalunya"]->.boundaryarea; ( nwr(area.boundaryarea)[natural=tree]; nwr(area.boundaryarea)[tree]; ); out meta; """ response = requests.get(overpass_url, params={'data': overpass_query}) #JSON: #data = response.json() #print data #XML: print response.content;
$ python arbres.py > arbres.json $ python arbres.py > arbres.xml
També podem comptar els arbres (arbres_count.py):
import requests import json overpass_url = "http://overpass-api.de/api/interpreter" overpass_query = """ [out:csv(::count, ::"count:nodes", ::"count:ways", ::"count:relations")][timeout:25]; area["name"="Catalunya"]->.boundaryarea; ( nwr(area.boundaryarea)[natural=tree]; nwr(area.boundaryarea)[tree]; ); out count; """ response = requests.get(overpass_url, params={'data': overpass_query}) #JSON: #data = response.json() #print data #XML: print response.content;
$ python arbres_count.py @count @count:nodes @count:ways @count:relations 115685 115683 2 0
Esten parlant de 115000 arbres a Catalunya ingressats a OSM
wikipedia, llista d'arbres catalogats
Hi ha uns 250 arbres catalogats
Donada una posició geogràfica, vull saber si al voltant d'aquest punt (diguem 50m) hi ha un arbre. És a dir, vull buscar si l'arbre ja existeix a la base de dades (i aleshores només s'hauria d'actualitzar la informació).
La manera de localitzar arbres al voltant d'un punt (50 m) és:
node ["natural"="tree"] (around:50,41.3461,1.3627); out;
i ja puc recórrer tots les posicions on hi ha d'haver arbres catalogats, i cercar les coincidències (recorrer_arbres_catalogats_v2.py).
import requests import json overpass_url = "http://overpass-api.de/api/interpreter" locations=[(41.3461,1.3627),(41.3936,1.4625)] for loc in locations: print loc[0], loc[1] overpass_query = "node[natural=tree](around:50,%f,%f); out;" % (loc[0], loc[1]) #print overpass_query; response = requests.get(overpass_url, params={'data': overpass_query}) print response.content;
Per ex, en la posicio (41.3936,1.4625) hi ha d'haver un arbre catalogat i només es fa referència a una alzina:
<?xml version="1.0" encoding="UTF-8"?> <osm version="0.6" generator="Overpass API 0.7.55.1009 5e627b63"> <note>The data included in this document is from www.openstreetmap.org. The data is made available under ODbL.</note> <meta osm_base="2019-10-24T11:28:02Z"/> <node id="5613735415" lat="41.3936696" lon="1.4624844"> <tag k="name" v="alzina"/> <tag k="natural" v="tree"/> </node> </osm>
2 possibilitats: s'haurà de crear un node, s'haurà d'editar un node
He de distingir entre quan he trobat un node (per fer un update), i quan no he trobat el node (per fer un create)
script recorrer_arbres_catalogats_v3.py
He d'instal·lar jxmlease per tal de poder parsejar el xml
M'ha donat problemes de permisos en la instal·lació de jxmlease, i he necessitat l'opció --user:
$ pip install --user jxmlease
import requests import json import jxmlease overpass_url = "http://overpass-api.de/api/interpreter" #locations=[(41.3461,1.3627),(41.3936,1.4625),(41.3900,1.4600)] locations=[(41.3461,1.3627)] for loc in locations: print loc[0], loc[1] overpass_query = "node[natural=tree](around:50,%f,%f); out;" % (loc[0], loc[1]) #print overpass_query; response = requests.get(overpass_url, params={'data': overpass_query}) print response.content; #volem recuperar el id del node que hem trobat root = jxmlease.parse(response.content) #1a manera node = root['osm']['node'] print(node.get_xml_attr("id")) #2a manera for i in root['osm'].find_nodes_with_tag('node', recursive=False): print i.get_xml_attr("id")
Hauré de fer un if per distingir entre els dos casos: recorrer_arbres_catalogats_v4.py, recorrer_arbres_catalogats_v5.py
... root = jxmlease.parse(response.content) #2a manera num = 0 for i in root['osm'].find_nodes_with_tag('node', recursive=False): print "node: " + i.get_xml_attr("id") + " en les immediacions" num+=1; if (num==0): print("no hem trobat cap arbre-node en les immediacions")
41.3461 1.3627 node: 5613734131 en les immediacions 41.3936 1.4625 node: 5613735415 en les immediacions 41.39 1.46 no hem trobat cap arbre-node en les immediacions
Python interactiu: vull fer els canvis (yes/no)?
Recorro tots els nodes d'un array. Per cada node, el script m'ha de preguntar si vull fer els canvis, si o no?
La solució està a arbres_monumentals/proves/prova_presskey2.py a partir de la solució que es proposa en l'enllaç:
import termios, fcntl, sys, os fd = sys.stdin.fileno() oldterm = termios.tcgetattr(fd) newattr = termios.tcgetattr(fd) newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO termios.tcsetattr(fd, termios.TCSANOW, newattr) oldflags = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK) try: while 1: try: c = sys.stdin.read(1) print "Got character", repr(c) except IOError: pass finally: termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm) fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)
Programació Osmapi: crear, editar, esborrar nodes
El següent pas és poder actualitzar la informació d'un node, des de Python. Per exemple, el node id="5613735415"
$ pip install --user osmapi
ja podem crear un node a OSM, seguint l'exemple bàsic de la documentació. script osmpai_v2.py:
# encoding: utf-8 from osmapi import OsmApi #MyApi = OsmApi(username = u"joanqc@gmail.com", password = u"jq********") MyApi = OsmApi(passwordfile="/home/joan/projectes/arbres_monumentals/.password") MyApi.ChangesetCreate({u"comment": u"Modificació de prova en el Camp Llarg"}) print(MyApi.NodeCreate({u"lat":42.25543, u"lon":1.85804, u"tag": {}})) MyApi.ChangesetClose() #{u'changeset': 76186141, u'version': 1, u'lon': 1.85743, u'tag': {}, u'lat': #42.25532, u'id': 6914599486}
El format del passwordfile és joanqc@gmail.com:jq******** (o bé el nom d'usuari, joanillo)
Ara ja podem crear un arbre: osmapi_v3.py
# encoding: utf-8 from osmapi import OsmApi MyApi = OsmApi(passwordfile="/home/joan/projectes/arbres_monumentals/.password") MyApi.ChangesetCreate({u"comment": u"Modificació de prova en el Camp Llarg"}) data = {u"lat":42.25543, u"lon":1.85804, u"tag": { u"natural": u"tree", u"name": u"Pi del Camp Llarg" }} print(MyApi.NodeCreate(data)) MyApi.ChangesetClose()
I ara ja puc afegir totes les dades de l'arbre, i gravar automàticament les dades (amb flush()): osmapi_v4.py
# encoding: utf-8 from osmapi import OsmApi MyApi = OsmApi(passwordfile="/home/joan/projectes/arbres_monumentals/.password") MyApi.ChangesetCreate({u"comment": u"Modificació de prova en el Camp Llarg"}) data = {u"lat":42.25543, u"lon":1.85804, u"tag": { u"natural": u"tree", u"name": u"Pi del Camp Llarg", u"species": u"Olea europea", u"species:ca": u"Olivera", u"note": u"Arbre Monumental (MA-01.001.01) https://ca.wikipedia.org/wiki/Llista_d%27arbres_monumentals_de_Catalunya" }} print(MyApi.NodeCreate(data)) MyApi.ChangesetClose() MyApi.flush()
Donat un node que ja existeix, el vull actualitzar. Aquesta és la manera de procedir: (osmapi_v5.py):
# encoding: utf-8 from osmapi import OsmApi MyApi = OsmApi(passwordfile="/home/joan/projectes/arbres_monumentals/.password") MyApi.ChangesetCreate({u"comment": u"Modificació de prova4 en el Camp Llarg"}) node = MyApi.NodeGet(6914950285) tags = node["tag"] tags[u"natural"] = u"tree" tags[u"name"] = u"Pi del Camp Llarg" tags[u"species"] = u"Pinus sylvestris" tags[u"species:ca"] = u"pi roig" tags[u"note"] = u"Arbre Monumental (MA-01.001.01) https://ca.wikipedia.org/wiki/Llista_d%27arbres_monumentals_de_Catalunya" print(MyApi.NodeUpdate(node)) MyApi.ChangesetClose() MyApi.flush()
I ara vull esborrar el node programàticament (osmpai_v6.py):
# encoding: utf-8 from osmapi import OsmApi MyApi = OsmApi(passwordfile="/home/joan/projectes/arbres_monumentals/.password") MyApi.ChangesetCreate({u"comment": u"Eliminació del pi en el Camp Llarg"}) node = MyApi.NodeGet(6914920685) MyApi.NodeDelete(node) MyApi.ChangesetClose() MyApi.flush()
arbres.json: carregar la llista d'arbres
Una petita mostra de les dades
[ {"name":"Xiprers del Palau de l'Abat de Santes Creus","lat":"41.3461","lon":"1.3628","species":"Cupressus sempervirens","species:ca":"xiprer","note":"Arbre Monumental (MA-01.001.01) https://ca.wikipedia.org/wiki/Llista_d%27arbres_monumentals_de_Catalunya"}, {"name":"Alzina de la Casa Nova de Bonany","lat":"41.3936","lon":"1.4625","species":"Quercus ilex subsp. ilex","species:ca":"alzina","note":"Arbre Monumental (MA-01.120.01) https://ca.wikipedia.org/wiki/Llista_d%27arbres_monumentals_de_Catalunya"}, {"name":"Suro del Mas Perxés","lat":"42.3964","lon":"2.8353","species":"Quercus suber","species:ca":"alizina surera","note":"Arbre Monumental (MA-02.001.01) https://ca.wikipedia.org/wiki/Llista_d%27arbres_monumentals_de_Catalunya"} ]
Per carregar les dades amb Python:
import json with open('arbres.json', 'r') as f: arbres_dict = json.load(f) for arbre in arbres_dict: print(arbre['name'])
Posar-ho tot junt: importar_arbres_monumentals_v1.py
El script recorre els arbres monumentals per actualitzar o inserir. Sempre es té en compte la informació que ja existeix, per tal de respectar-la al màxim. Quan detectem un arbre que ja existeix es compara els valors antics amb els valors nous, i es pot procedir a actualitzar, o bé s'aborta per actualitzar la nostra informació amb la que ja existeix.
Aquest script s'ha realitzat amb la idea de què es pugui reutilitzar en altres exemples que no tinguin res a veure amb els arbres monumentals. Allà on s'hagi d'inserir/actualitzar informació de manera semi-automàtica.
script importar_arbres_monumentals_v1.py:
# encoding: utf-8 import json #parsejar JSON import termios, fcntl, sys, os #script interactiu import requests #cercar node import jxmlease from osmapi import OsmApi #create, update node overpass_url = "http://overpass-api.de/api/interpreter" #--- def press_key(): fd = sys.stdin.fileno() oldterm = termios.tcgetattr(fd) newattr = termios.tcgetattr(fd) newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO termios.tcsetattr(fd, termios.TCSANOW, newattr) oldflags = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK) try: while 1: try: c = sys.stdin.read(1) #print "Got character", repr(c) return c; except IOError: pass finally: termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm) fcntl.fcntl(fd, fcntl.F_SETFL, oldflags) #--- with open('arbres.json', 'r') as f: arbres_dict = json.load(f) num_changeset = 1 for arbre in arbres_dict: name = arbre['name']; lat = float(arbre['lat']); lon = float(arbre['lon']); species = arbre['species']; species_ca = arbre['species:ca']; note = arbre['note']; print("=================================") print(num_changeset) print("=================================") print name print "(",lat,",",lon,")"; #cerquem els arbres que tenim, per actualitzar o per inserir overpass_query = "node[natural=tree](around:50,%f,%f); out;" % (lat, lon) #print overpass_query; response = requests.get(overpass_url, params={'data': overpass_query}) print response.content; #volem recuperar el id del node que hem trobat root = jxmlease.parse(response.content) num = 0 for i in root['osm'].find_nodes_with_tag('node', recursive=False): print "node: " + i.get_xml_attr("id") + " en les immediacions" num+=1; print("ACTUALITZACIÓ\n============="); print(name) print(species) print(species_ca) print(note) print('Vols actualitzar les dades (y/n)'); tecla = press_key(); #print(tecla) if (tecla=='y'): print('Anem a actualitzar') MyApi = OsmApi(passwordfile="/home/joan/projectes/arbres_monumentals/.password") changeset_comment = '{"comment": "Modificació arbre monumental #%s"}' % (str(num_changeset)) changeset_comment_json = json.loads(changeset_comment) MyApi.ChangesetCreate(changeset_comment_json) node = MyApi.NodeGet(i.get_xml_attr("id")) tags = node["tag"] tags[u"natural"] = u"tree" tags[u"name"] = name tags[u"species"] = species tags[u"species:ca"] = species_ca tags[u"note"] = note print(MyApi.NodeUpdate(node)) MyApi.ChangesetClose() MyApi.flush() print ("arbre actualitzat"); num_changeset = num_changeset + 1; else: print('No volem actualitzar') if (num==0): print("\n\nINSERCIÓ\n========") MyApi = OsmApi(passwordfile="/home/joan/projectes/arbres_monumentals/.password") changeset_comment = '{"comment": "Inserció arbre monumental #%s"}' % (str(num_changeset)) changeset_comment_json = json.loads(changeset_comment) MyApi.ChangesetCreate(changeset_comment_json) data = '{"lat":%f, "lon":%f, "tag": { "natural": "tree", "name": "%s", "species": "%s", "species:ca": "%s", "note": "%s" }}' % (lat, lon, name, species, species_ca, note) data_json = json.loads(data) print(data_json) print(MyApi.NodeCreate(data_json)) MyApi.ChangesetClose() MyApi.flush() print ("arbre inserit"); num_changeset = num_changeset + 1;
Podem veure el resum gràfic dels canvis realitzats
area["name"="Catalunya"]->.boundaryarea; ( nwr(area.boundaryarea)[note~'https://ca.wikipedia.org/wiki/Llista_d%27arbres_monumentals_de_Catalunya']; nwr(area.boundaryarea)[tree]; ); out meta;
Fixar-se que podem utilitzar una expressió regular:
Referències
script al github:
OpenStreetMap al bloc
Annex: arbres singulars de Barcelona
projecte: /home/joan/projectes/OSM/arbres_monumentals/arbres_interes_local_bcn/
{{geocodeArea:"Barcelona"}}->.boundaryarea; ( node["name"~"arbre singular"](area.boundaryarea); ); out body; {{style: node[natural=tree] { icon-image: url('https://img.icons8.com/cotton/2x/tree.png'); icon-width: 25; icon-height: 25; } }}
Annex: arbres singulars de Mallorca
projecte: /home/joan/projectes/OSM/arbres_monumentals/arbres_singulars_mallorca/
area["name"="Illes Balears"]->.boundaryarea; ( nwr(area.boundaryarea)[website~'arbres_singulars_de_les_Illes_Balears']; ); out meta; {{style: node[natural=tree] { icon-image: url('https://img.icons8.com/cotton/2x/tree.png'); icon-width: 25; icon-height: 25; } }}
creat per Joan Quintana Compte, octubre 2019