Script per scaricare/ricostruire i dati DTM 5 m della Lombardia
Il presente script in linguaggio Python consente di interrogare automaticamente il servizion WMS relativo DTM 5x5m della regione Lombardia,
estrarre la quota e ricostruire il modello digitale di terreno come
file ASCII (praticamente un file di testo). Lo script scarica e crea il
DTM con sistema di riferimento UTM 32 - WGS 84 (EPSG:32632).
Il file di testo creato può essere caricato in QGIS che lo vede come un normale raster.
L'utente deve cambiare le prime righe dello script per specificare:
- percorso e nome del file di testo e del relativo file prj che verranno creati,
- coordinate dell'angolo in basso a sinistra dell'area di proprio interesse (inserire multipli di 10),
- estensione in pixel (righe e colonne) dell'area di proprio interesse (1 pixel = 5x5 m);
poi è sufficiente copiare lo script, incollarlo nella console python di QGIS e premere due volte invio. (Alla
prima esecuzione dello script è meglio fare una prova con estensioni
limitate, es. 10x10 pixel, per valutare la velocità.)
Questo è lo script:
# percorso e nome del file di testo da creare:
filepathname = "D:/Dati/Italia/prova/dtm1.txt"
fileprj = "D:/Dati/Italia/prova/dtm1.prj"
# coordinate dell'angolo in basso a sinistra (inserire multipli di 10):
x1 = 536500
y1 = 5078000
# numero di righe (height) e colonne (width) dell'area in pixel:
hgt = 10
wdt = 10
# risoluzione
res = 5
# valore da assegnare in assenza di dato
valNoData = 0
# parti fisse della richiesta GetFeatureInfo request1
=
'http://www.cartografia.servizirl.it/arcgis/services/wms/DTM5_RL_wms/MapServer/WmsServer?service=wms&version=1.3.0&request=GetFeatureInfo&query_layers=DTM%205X5&info_format=text/xml&crs=EPSG:32632&bbox='
# importazione librerie
import xml.etree.ElementTree as ET
import urllib2
import time
# calcolo bbox
x2 = x1 + (wdt * res)
y2 = y1 + (hgt * res)
bbox = str(x1) + ',' + str(y1) + ',' + str(x2) + ',' + str(y2)
request2 = request1 + bbox + '&width=' + str(wdt) + '&height=' + str(hgt)
# verifica che l'area indicata sia interna all'estensione del DTM
if x1 > 692666 or x2 < 460001 or y1 > 5166901 or y2 <4944901:
print 'Area esterna alla copertura del DTM'
else:
#
print 'Ok, script avviato'
start = time.time()
#
# crea e inizia il file di output
out_file = open(filepathname ,"w")
out_file.write( 'ncols '+ str(wdt) + '\n' )
out_file.write( 'nrows '+ str(hgt) + '\n' )
xll = x1 - 3.1306
yll = y1 + 1.2481
out_file.write( 'xllcorner ' + str(xll) + '\n' )
out_file.write( 'yllcorner ' + str(yll) + '\n' )
out_file.write( 'cellsize ' + str(res) + '\n' )
out_file.write( 'NODATA_value ' + str( valNoData ) + '\n' )
#
# azzeramento contatori valori nulli
NoDataCount = 0
NoDataCountTemp = 0
NoRisp = 0
NoRispTemp = 0
NoPixelValue = 0
NoPixelValueTemp = 0
#
# interrogazione del servizio
#
iy = 0
while iy < hgt:
ix = 0
row = ''
while ix < wdt:
value = "NoData"
iNoData = 0
while value == "NoData" and iNoData < 10:
iNoData = iNoData + 1
#
# interrogazione GetFeatureInfo del servizio wms
request = request2 + '&x=' +str(ix) + '&y=' + str(iy)
risposta = urllib2.urlopen(request)
rispostaxml = risposta.read()
risposta.close()
#
# estrazione degli attributi dalla risposta xml
FeatureInfoResponse = ET.fromstring(rispostaxml)
#
# se la risposta è vuota viene incrementato il contatore relativo
if len( FeatureInfoResponse ) == 0:
NoPixelValueTemp = 0
NoRispTemp = NoRispTemp + 1
# altrimenti viene cercato l'attributo PixelValue
else:
NoRispTemp = 0
fieldxml = FeatureInfoResponse[0]
if 'PixelValue' in fieldxml.attrib:
NoPixelValueTemp = 0
value = fieldxml.attrib['PixelValue']
#
# se la risposta è NoData viene incrementato il contatore relativo
if value == "NoData":
NoDataCountTemp = NoDataCountTemp + 1
else:
# se non è presente l'attributo PixelValue viene incrementato il
contatore relativo
NoPixelValueTemp = NoPixelValueTemp +1
#
# conteggio dei NoData, risposte assenti, risposte senza PixelValue
if value == "NoData":
NoDataCount = NoDataCount + 1
value = str( valNoData )
if NoRispTemp > 0:
NoRisp = NoRisp + 1
# Togliere il cancelletto dalle righe sottostanti per visualizzare un
avviso ad ogni risposta vuota definitiva
#print 'Attenzione! risposta vuota al punto x=' + str(ix) + 'y=' +
str(iy)
#print rispostaxml
NoRispTemp = 0
if NoPixelValueTemp > 0:
NoPixelValue = NoPixelValue + 1
# Togliere il cancelletto dalle righe sottostanti per visualizzare un
avviso ad ogni risposta definitiva senza PixelValue
#print 'Attenzione! Pixel Value assente al punto x=' + str(ix) + 'y=' +
str(iy)
#print rispostaxml
NoPixelValueTemp = 0
#
row = row + ' ' + value
ix = ix +1
#
# scrittura della riga nel file di output
out_file.write( row + '\n' )
iy = iy + 1
end = time.time()
print 'riga ' + str(iy) + '; tempo impiegato: ' + str( end - start ) + ' secondi'
#
# chiusura del file di output
out_file.close()
#
# creazione e scrittura del file prj per il sistema di riferimento UTM32-WGS84
prj_file = open(fileprj ,"w")
prj_file.write(
'PROJCS["WGS_1984_UTM_Zone_32N",GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",9],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["Meter",1]]'
)
prj_file.close()
#
print ''
print 'Creato file ' + filepathname
print 'Valori NoData temporanei: ' + str(NoDataCountTemp)
print 'Valori NoData definitivi: ' + str(NoDataCount)
print 'Risposte vuote (probabilmente al di fuori della copertura geografica): ' + str(NoRisp)
print 'Risposte senza PixelValue: ' + str(NoPixelValue)
#
|
DESCRIZIONE
Parametri di base:
# percorso e nome del file di testo da creare:
filepathname = "D:/Dati/Italia/prova/dtm1.txt"
fileprj = "D:/Dati/Italia/prova/dtm1.prj"
# coordinate dell'angolo in basso a sinistra (inserire multipli di 10):
x1 = 536500
y1 = 5078000
# numero di righe (height) e colonne (width) dell'area in pixel:
hgt = 10
wdt = 10
# risoluzione
res = 5
# valore da assegnare in assenza di dato
valNoData = 0
|
questi parametri possono essere cambiati dall'utente.
Le coordinate sono riferite al sistema UTM 32 - WGS 84 ed indicano l'angolo in basso a sinistra dell'area da scaricare/ricostruire. Lo script allinea automaticamente il DTM ricreato a quello effettivo presente sul server aggiungendo 1,2481 m in latitudine e sottraendo 3,1306 m in longitudine ma perché sia corretto le coordinate inserite devono essere multipli di 10 (ovvero numeri interi terminanti con 0).
Come numeri di righe e colonne è consigliabile iniziare con valori bassi (es. 10) per valutare il tempo necessario.
La risoluzione è impostata di default su 5 m per avere il massimo dettaglio.
valNoData indica il valore che viene assegnato alle celle se
l'interrogazione non h arisposta o se la risposta contiene un valore
nullo; poichè le quote in Lombardia sono tutte al di sopra del livello
del mare si può usare un valore pari a 0.
Indirizzo internet del servizio wms e funzione GetFeatureInfo:
# parti fisse della richiesta GetFeatureInfo request1
=
'http://www.cartografia.servizirl.it/arcgis/services/wms/DTM5_RL_wms/MapServer/WmsServer?service=wms&version=1.3.0&request=GetFeatureInfo&query_layers=DTM%205X5&info_format=text/xml&crs=EPSG:32632&bbox='
|
Il DTM viene interrogato grazie alla funzione GetFeatureInfo del servizio WMS (> DTM 5m Lombardia - interrogazione).
L'indirizzo di base è
http://www.cartografia.servizirl.it/arcgis/services/wms/DTM5_RL_wms/MapServer/WmsServer
al quale sono aggiunti i seguenti parametri:
- service=wms
- version=1.3.0
- request=GetFeatureInfo
- query_layers=DTM%205X5 per interrogare il livello DTM 5X5 (lo spazio è sostituito da %20)
- info_format=text/xml per avere la risposta in formato xml da cui si possono estrarre i valori chiave
- crs=EPSG:32632
i parametri bbox, width, height, x, y vengono aggiunti automaticamente durante l'esecuzione dello script.
Librerie:
# importazione librerie
import xml.etree.ElementTree as ET
import urllib2
import time |
Per il funzionamento dello script sono necessarie queste librerie:
- xml.etree.ElementTree per leggere la risposta in formato xml ed estrarre i valori
- urllib2 per accedere agli indirizi internet
- time per misurare il tempo di esecuzione dello script
Area:
# calcolo bbox
x2 = x1 + (wdt * res)
y2 = y1 + (hgt * res)
bbox = str(x1) + ',' + str(y1) + ',' + str(x2) + ',' + str(y2)
request2 = request1 + bbox + '&width=' + str(wdt) + '&height=' + str(hgt)
|
Vengono calcolate le coordinate dei limiti destro e superiore dell'area
di interesse (bbox) e all'indirizzo di interrogazione sono aggiunti i
parametri bbox, width e heigth.
Verifica che l'area indicata (bbox) sia coperta dal DTM:
...
# verifica che l'area indicata sia interna all'estensione del DTM
if x1 > 692666 or x2 < 460001 or y1 > 5166901 or y2 <4944901:
print 'Area esterna alla copertura del DTM'
else:
#
print 'Ok, script avviato'
start = time.time()
...
|
Lo script procede solo se l'area indicata ricade almeno parzialmente
nell'estensione del DTM. In tal caso compare il messaggio 'Ok, script
avviato' e viene registrato l'orario di inizio (start) con time.time().
Creazione e scrittura dei file di testo:
# percorso e nome del file di testo da creare:
filepathname = "D:/Dati/Italia/prova/dtm1.txt"
fileprj = "D:/Dati/Italia/prova/dtm1.prj"
...
...
# crea e inizia il file di output
out_file = open(filepathname ,"w")
out_file.write( 'ncols '+ str(wdt) + '\n' )
out_file.write( 'nrows '+ str(hgt) + '\n' )
xll = x1 - 3.1306
yll = y1 + 1.2481
out_file.write( 'xllcorner ' + str(x1) + '\n' )
out_file.write( 'yllcorner ' + str(y1) + '\n' )
out_file.write( 'cellsize ' + str(res) + '\n' )
out_file.write( 'NODATA_value ' + str( valNoData ) + '\n' )
...
...
# scrittura della riga nel file di output
out_file.write( row + '\n' )
...
...
# chiusura del file di output
out_file.close()
#
# creazione e scrittura del file prj per il sistema di riferimento UTM32-WGS84
prj_file = open(fileprj ,"w")
prj_file.write(
'PROJCS["WGS_1984_UTM_Zone_32N",GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",9],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["Meter",1]]'
)
prj_file.close()
#
|
I file di testo sono creati con il comando variabile_file = open( percorso/nome, "w") dove "w" indica la possibilità di scrittura.
Il testo viene inserito con variabile_file.write( testo ). \n indica il passaggio alla riga sottostante (a capo). Alla fine il file deve essere chiuso con .close().
In out_file vengono prima inseriti i parametri di base del file:
ncols
nrows
xllcorner
yllcorner
cellsize
NODATA_value
(Per allineare il DTM ricreato con quello effettivo lo script aggiunge 1,2481 m in latitudine e sottrae 3,1306 m in longitudine ma ciò risulta solo corretto solo se le coordinate inserite sono multipli di 10, ovvero numeri interi terminanti con 0).
Poi vengono aggiunti i valori delle celle di ogni riga una volta che
tutta la riga è stata calcolata. I valori vanno da sinistra verso
dastra e le righe dall'alto verso il basso.
Quindi risulta un file di testo tipo:
ncols 10
nrows 10
xllcorner 536496.8694
yllcorner 5078001.2481
cellsize 5
NODATA_value 0
1500.668579 1504.863159 1509.191895 1512.725342 1516.002197 ...
1502.546143 1506.083008 1509.546387 1513.095703 1516.451904 ...
1505.094482 1508.475220 1511.342407 1514.268188 1517.391846 ...
1507.908691 1510.892212 1513.254639 1515.883057 1518.879883 ...
...
|
Contatori:
contano le volte in cui non si riceve un valore di quota valido dal servizio WMS.
...
# azzeramento contatori valori nulli
NoDataCount = 0
NoDataCountTemp = 0
NoRisp = 0
NoRispTemp = 0
NoPixelValue = 0
NoPixelValueTemp = 0
...
...
value = "NoData"
iNoData = 0
while value == "NoData" and iNoData < 10:
iNoData = iNoData + 1
...
# se la risposta è vuota viene incrementato il contatore relativo
if len( FeatureInfoResponse ) == 0:
NoPixelValueTemp = 0
NoRispTemp = NoRispTemp + 1
# altrimenti viene cercato l'attributo PixelValue
else:
NoRispTemp = 0
fieldxml = FeatureInfoResponse[0]
if 'PixelValue' in fieldxml.attrib:
NoPixelValueTemp = 0
value = fieldxml.attrib['PixelValue']
#
# se la risposta è NoData viene incrementato il contatore relativo
if value == "NoData":
NoDataCountTemp = NoDataCountTemp + 1
else:
# se non è presente l'attributo PixelValue viene incrementato il
contatore relativo
NoPixelValueTemp = NoPixelValueTemp +1
#
# conteggio dei NoData, risposte assenti, risposte senza PixelValue
if value == "NoData":
NoDataCount = NoDataCount + 1
value = str( valNoData )
if NoRispTemp > 0:
NoRisp = NoRisp + 1
# Togliere il cancelletto dalle righe sottostanti per visualizzare un
avviso ad ogni risposta vuota definitiva
#print 'Attenzione! risposta vuota al punto x=' + str(ix) + 'y=' +
str(iy)
#print rispostaxml
NoRispTemp = 0
if NoPixelValueTemp > 0:
NoPixelValue = NoPixelValue + 1
# Togliere il cancelletto dalle righe sottostanti per visualizzare un
avviso ad ogni risposta definitiva senza PixelValue
#print 'Attenzione! Pixel Value assente al punto x=' + str(ix) + 'y=' +
str(iy)
#print rispostaxml
NoPixelValueTemp = 0
...
...
print 'Valori NoData temporanei: ' + str(NoDataCountTemp)
print 'Valori NoData definitivi: ' + str(NoDataCount)
print 'Risposte vuote (probabilmente al di fuori della copertura geografica): ' + str(NoRisp)
print 'Risposte senza PixelValue: ' + str(NoPixelValue)
|
NoDataCount: numero di volte in cui il parametro PixelValue è uguale a
NoData (succede quando viene interrogato un punto all'interno dell'area
rettangolare del DTM ma fuori dai confini regionali).
NoRisp: numero di risposte vuote (succede in genere quando viene
interrogoto un punto al di fuori della copertura rettangolare del DTM).
NoPixelValue: numero di risposte con schema xml ma senza PixelValue (non dovrebbe succedere).
A volte il servizio WMS sembra distrarsi e restituisce risposte non
valide o valori nulli anche quando il punto interrogato ha un valore di
quota valido. Per questo lo script insiste nel chiedere la quota finchè
non ottiene un valore valido o finchè non si raggiunge il numero
massimo di ripetizioni della domanda impostato a 10 (while value
== "NoData" and iNoData < 10:).
NoRispTemp e NoPixelValueTemp contano le risposte non valide temporanee
ricevute in questi momenti di "distrazione". Se dopo 10 ripetizioni
della damanda uno di questi contatori temporanei è maggiore di 0 allora
viene incrementato il rispettivo contatore definitivo. NoDataCountTemp
conte le risposte nulle temporanee a volte molto frequenti; non viene
mai azzerato e il suo valore finale viene visualizzato al termine
dell'esecuzione per dare un'idea di quanto affidabile è stato il
servizio WMS.
Iterazione su righe e colonne:
iy = 0
while iy < hgt:
ix = 0
row = ''
while ix < wdt:
...
row = row + ' ' + value
ix = ix +1
#
# scrittura della riga nel file di output
out_file.write( row + '\n' )
iy = iy + 1
end = time.time()
print 'riga ' + str(iy) + '; tempo impiegato: ' + str( end - start ) + ' secondi'
...
|
L'iterazione viene eseguita con cicli while prima sulle righe poi, al
loro interno, sui pixel di ogni riga. Determinato il valore per un
pixel esso viene aggiunto alla variabile row (una stringa di testo in
cui i valori sono separati da uno spazio). Al termine di ogni riga la
stringa row viene scritta nel file di output.
Interrogazione via internet:
# interrogazione GetFeatureInfo del servizio wms
request = request2 + '&x=' +str(ix) + '&y=' + str(iy)
risposta = urllib2.urlopen(request)
rispostaxml = risposta.read()
risposta.close()
|
Una volta ricostruito l'indirizzo per la richiesta GetFeatureInfo
(http://www.cartografia.servizirl.it/arcgis/services/wms/DTM5_RL_wms/MapServer/WmsServer?service=wms&version=1.3.0&request=GetFeatureInfo&query_layers=DTM%205X5&info_format=text/xml&crs=EPSG:32632&bbox=xmin,ymin,xmax,ymax&width=width&height=height&x=ix&y=iy)
viene assimilata la risposta via internet grazie alla libreria urllib2 ed in particolare al comando urllib2.urlopen(url). La risposta deve poi essere letta con .read() e salvata in una variabile che in questo caso è rispostaxml; infine è buona norma chiuderla con .close().
urllib vs urllib2:
l'interrogazione via internet funziona anche con la libreria urllib
tuttavia la versione 2 ha prestazioni in velocità notevolmente
migliori; personalmente con urllib per scaricare un'area di 100x100
pixel ho impiagato quasi 2 ore mentre con urllib2 sono bastati 15
minuti.
Estrazione degli attributi dalla risposta xml:
Con la funzione GetFeatureInfo è stato chiesta la risposta in formato
xml (info_format=text/xml) da cui ora possiamo estrarre gli attributi
grazie alla libreria xml.etree.ElementTree.
# estrazione degli attributi dalla risposta xml
FeatureInfoResponse = ET.fromstring(rispostaxml)
#
# se la risposta è vuota viene incrementato il contatore relativo
if len( FeatureInfoResponse ) == 0:
NoPixelValueTemp = 0
NoRispTemp = NoRispTemp + 1
# altrimenti viene cercato l'attributo PixelValue
else:
NoRispTemp = 0
fieldxml = FeatureInfoResponse[0]
if 'PixelValue' in fieldxml.attrib:
NoPixelValueTemp = 0
value = fieldxml.attrib['PixelValue'] |
import xml.etree.ElementTree as ET (vedi sopra: librerie) permette di abbreviare i riferimenti alla libreria con la sigla ET.
ET.fromstring(rispostaxml)
converte la rispostaxml in un formato da cui è possibile estrarre i
componenti (_children). Visto che la nosrta risposta ha un solo
componente possiamo indicarlo con il suo numero d'ordine che è [0] ed
estrarne gli attributi con .attrib['attributo'].
Se abbiamo una risposta xml tipo:
<FeatureInfoResponse xmlns:esri_wms="http://www.esri.com/wms" xmlns="http://www.esri.com/wms">
<FIELDS Stretchedvalue="187" PixelValue="1568.901855"></FIELDS>
</FeatureInfoResponse>
ecco alcuni possibili comandi e i relativi risultati:
rispostaxml
'<?xml version="1.0"
encoding="UTF-8"?>\n\r\n<FeatureInfoResponse
xmlns:esri_wms="http://www.esri.com/wms"
xmlns="http://www.esri.com/wms">\r\n<FIELDS Stretchedvalue="187"
PixelValue="1568.901855"></FIELDS>\r\n</FeatureInfoResponse>\r\n'
ET.fromstring(rispostaxml)
<Element '{http://www.esri.com/wms}FeatureInfoResponse' at 0x20da7a20>
ET.fromstring(rispostaxml).__dict__
{'text': '\n', 'attrib': {}, 'tag': '{http://www.esri.com/wms}FeatureInfoResponse', '_children': [<Element '{http://www.esri.com/wms}FIELDS' at 0x20da7e48>]}
ET.fromstring(rispostaxml)._children
[<Element '{http://www.esri.com/wms}FIELDS' at 0x20da7e48>]
ET.fromstring(rispostaxml).attrib
{}
ET.fromstring(rispostaxml)[0]
<Element '{http://www.esri.com/wms}FIELDS' at 0x20da7e48>
ET.fromstring(rispostaxml)[0].__dict__
{'attrib': {'Stretchedvalue': '187', 'PixelValue': '1568.901855'}, 'tag': '{http://www.esri.com/wms}FIELDS', 'tail': '\n', '_children': []}
ET.fromstring(rispostaxml)[0].attrib
{'Stretchedvalue': '187', 'PixelValue': '1568.901855'}
ET.fromstring(rispostaxml)[0].attrib['PixelValue']
'1568.901855'
Pagina visitata volte
|
|