Come realizzare una multi-join

A partire da 70 shapefile idendici, come unirli orizzontalmente e aggiungere lo stesso attributo.

A cura di Totò Fiandaca  | issue #220 | guida/e Andrea Borruso Giovanni Pirrotta


Caso d’uso

È un caso reale in cui si hanno molti shapefile identici, risultato di qualche analisi particolare, e dove un attributo vari al variare dell’analisi stessa, mantenendo la stessa geometria, e occorresse unire tutti gli shapefile in modo che la geometria rimanesse senza duplicati ma che gli attributi siano pari almeno al numero di shapefile da unire.

shapefile tipo:

shapefile unito, obiettivo della ricetta:

prima soluzione bash

Un probabile approccio potrebbe essere quello di convertire gli shapefile (dal shp002 al shp70) in semplice tabella CSV e fare un merge orizzontale tra tutti i file, infine, fare una sola JOIN tabellare tra il primo shp001 e il tabellone risultante dal merge precedente. [tratto da: quattrochiacchiereinquattro by Andrea Borruso]:

#!/bin/bash

set -x
set -e
set -u
set -o pipefail

# crea i CSV e ordina i CSV per ID
for i in *.shp; do
  name=$(basename "$i" .shp)
  ogr2ogr -f CSV -sql 'select id,value from '"$name"'' "$name".csv "$name".shp
  mlr -I --csv sort -n id then cut -f value then rename value,value_"$name" "$name".csv
done

# unisci i CSV in un unico CSV
paste -d "," shp*.csv > all.csv

# estrai il primo shp e convertilo in CSV
primoShape=$(find ./ -iname "*.shp" -type f | head -n 1)
tmp=$(basename "$primoShape" .shp)
ogr2ogr -f CSV  tmp.csv "$tmp".shp

# estrai da questo ultimo soltanto la colonna id
mlr -I --csv cut -f id then sort -n id tmp.csv

# crea il file finale
paste -d "," tmp.csv all.csv > finale.csv

ecco un esempio di output

id value_shp001 value_shp002 value_shp003 value_shp004
1 23 10 254 50
2 34 25 32 41
3 100 150 541 47

seconda soluzione bash

un altro approccio è quello di fare un normale merge verticale con tutti gli shapefile, successivamente convertirlo in file CSV ed infine trasformare il tabellone da wide a long:

#!/bin/bash

# unisci in verticale gli shape
ogrmerge.py -overwrite_ds -single -src_layer_field_name layer -o merged.shp shp*.shp

# converti lo shape in  CSV
ogr2ogr -f CSV merged.csv merged.shp

# converti il CSV da wide a long
mlr -I --csv reshape -s layer,value merged.csv

ecco un esempio di output

id value_shp001 value_shp002 value_shp003 value_shp004
1 23 10 254 50
2 34 25 32 41
3 100 150 541 47

terza soluzione python

altro approccio è quello tramite Python e in particolare Geopandas:

import glob
import geopandas as gpd

files = glob.glob("../data/andrea/*.shp")
files.sort()
gdf = gpd.read_file(files[0])
gdf.rename(columns={'value':f'value_shp001'}, inplace=True)

for f in files[1:]:
    name = f[f.rfind('/')+1:-4]
    gdf2 = gpd.read_file(f)
    new_column = f'value_{name}'
    gdf2.rename(columns={'value': new_column}, inplace=True)
    gdf = gdf.merge(gdf2[['id',new_column]], on='id')

dati

download

Riferimenti utili

Chi ha cucinato questa ricetta o ne ha tratto ispirazione

Ultima modifica 02/07/2022: update guide (5dd97bb)