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