Arbres Monumentals de Catalunya (programació OSM)

De Wikijoan
Dreceres ràpides: navegació, cerca

Contingut

Arbres catalunya.png

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'arbrs 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

Arbres monumentals.png

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/

Annex: arbres singulars de Mallorca

Arbres monumentals mallorca.png

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

Eines de l'usuari
Espais de noms
Variants
Accions
Navegació
Institut Jaume Balmes
Màquines recreatives
CNC
Informàtica musical
joanillo.org Planet
Eines