Modificare la struttura di un file JSON e creare un GeoJSON

Il caso delle API dei dati EFFIS

Da poco su TANTO abbiamo pubblicato un post sulle API per accedere ai dati dello European Forest Fire Information System (EFFIS).

L’output di queste API è in JSON, formato di grande comodità per sviluppare siti web, applicazioni, bot, ecc., basati su questi dati. Tra i dati esposti anche dei dati geografici (come i poligoni che delimitano le aree bruciate) e nasce spontanea la voglia di averli in un formato file nativamente spaziale, con il quale sarà immediata la visualizzazione, l’editing e l’analisi anche in un’applicazione GIS.

Visto il formato di output originario e vista la struttura interna di questi dati è naturale pensare di convertirlo in GeoJSON.

A seguire verrà mostrato come eseguire questa conversione a partire da uno degli endpoint disponibili delle API di EFFIS, ovvero quello delle areee bruciate: /rest/2/burntareas/current/.

Da JSON a GeoJSON con jq

jq è una straordinaria utility — open-source e disponibile per qualsiasi sistema operativo — per manipolare sorgenti JSON.

Un esempio “tipo” di output delle API di EFFIS per l’endpoint /rest/2/burntareas/current/è quello di sotto.

I dati “importanti” sono dentro .results[]:

  • .bbox, il bounding box dell'area bruciata;
  • .shape, la geometria poligonale dell'area bruciata;
  • e tutti gli attributi correlati all’area bruciata (id, province,comune,firedate, ecc.).
{
"count": 360,
"next": "http://effis.jrc.ec.europa.eu/rest/2/burntareas/current/?country=IT&format=json&limit=1&offset=1&ordering=-firedate",
"previous": null,
"results": [
{
"objectid": 319401,
"centroid": {
"type": "Point",
"coordinates": [
15.91900524881104,
39.59244913204589
]
},
"bbox": [
15.911960259,
39.588792363,
15.927565351,
39.595131864
],
"id": 168877,
"countryful": "Italy",
"province": "Cosenza",
"commune": "Bonifati",
"firedate": "2017-07-23",
"area_ha": 60,
"broadlea": "98.31000000",
"conifer": "0.00000000",
"mixed": "0.00000000",
"scleroph": "0.00000000",
"transit": "0.00000000",
"othernatlc": "0.00000000",
"agriareas": "1.69000000",
"artifsurf": "0.00000000",
"otherlc": "0.00000000",
"percna2k": "0.00000000",
"lastupdate": "2017-07-23",
"ba_class": "07DAYS",
"mic": "",
"se_anno_cad_data": "",
"shape": {
"type": "Polygon",
"coordinates": [[[15.914439261,39.595029202],[15.914311703,39.59499103],[15.914181904,39.594946449],[15.914050446,39.594895658],[15.913917991,39.594838911],[15.913785154,39.594776469],[15.913652798,39.594708729],[15.914439261,39.595029202]]]
},
"critech": "",
"country": "IT"
}
]
}

La struttura di base di un file GeoJSON, per un layer poligonale, è invece quella di sotto.

I dati “importanti” sono dentro .features[]:

  • .geometry, le coordinate del poligono;
  • .properties, gli attributi di ciascun poligono (in questo esempio è solo il campo "tipo").
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"tipo": "A"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[13.1396484375,38.238180119798635],[12.83203125,37.82280243352756],[13.623046875,37.84015683604136],[13.1396484375,38.238180119798635]
]
]
}
}
]
}

La struttura della parte geometrica dell’output di EFFIS (contenuta in results[].shape) è in partenza conforme a quella delle specifiche GeoJSON. Quindi basta copiare i dati e rinominare il contenitore da .shape.geometry.

Mentre nel file JSON di EFFIS non c’è il contenitore .properties, che sarà quindi da creare, per poi inserirgli all'interno i vari campi informativi contenuti nei dati di input.

Il download e la conversione possono essere eseguiti da riga di comando in questa modalità:

curl "URL endpoint EFFIS" | jq 'filtro da applicare all'input' > nomeFileOutput.geojson

Per convertire in GeoJSON le ultime 25 “aree bruciate” in Italia, il comando sarà:

curl "http://effis.jrc.ec.europa.eu/rest/2/burntareas/current/?limit=25&country=IT&ordering=-firedate&format=json" | jq '[.results[] | del(.centroid) | del(.bbox) | .geometry=.shape | del(.shape )] | {"type": "FeatureCollection", features:(map ( {"type": "Feature"}+{"properties": .}+{geometry:.geometry}))} | del(.features[].properties.geometry)' > output.geojson

Il filtro jq (che si può guardare live qui su jqplay) fa per grandi linee questo:

  • entra nei risultati con .results[];
  • cancella alcuni elementi che non verranno riportati nel GeoJSON con la funzione del() (ad esempio .centroid, .bbox, ecc.);
  • rinomina .shape in .geometry;
  • crea l’elemento type nella testa del GeoJSON;
  • crea l’oggetto features che è il cuore del GeoJSON (contiene tutti i poligoni e le proprietà relative), e al suo interno si inserisce tutte le proprietà (dentro il contenitore properties) e l’oggetto geometry;
  • cancella l’oggetto geometry che era duplicato all'interno dell’oggetto properties.

Quanto filtrato viene infine salvato in output nel file output.geojson; ne ho cristallizzato una copia qui (sotto in anteprima).

È un piccolo esempio delle feature di jq, che fra l’altro ha vita più facile in casi come questo, in cui l’input è in partenza ben strutturato.

mappa dinamica con, in celeste. alcuni poligoni GeoJSON di esempio

Per scrivere questo testo mi sono tornati utili tre strumenti che uso spesso: il già citato jqplay (con cui si possono testare i filtri di jq), geojson.io (con cui si ha contezza della struttura di un file GeoJSON) e Gist di GitHub.