Dissolvere i poligoni in base agli attributi con Python (ben fatto, fiona)?


15

Ho cercato di creare una funzione che fa sostanzialmente la stessa cosa che la funzione "dissolve" di QGIS. Ho pensato che sarebbe stato semplicissimo ma apparentemente no. Quindi, da quello che ho raccolto in giro, l'uso di fiona con Shapely dovrebbe essere l'opzione migliore qui. Ho appena iniziato a scherzare con i file vettoriali, quindi questo mondo è abbastanza nuovo per me e anche per Python.

Per questo esempio, sto lavorando con uno shapefile di contea fondato qui http://tinyurl.com/odfbanu Quindi ecco alcuni frammenti di codice che ho raccolto ma non riesco a trovare un modo per farli lavorare insieme

Per ora il mio metodo migliore è il seguente basato su: https://sgillies.net/2009/01/27/a-more-perfect-union-continued.html . Funziona bene e ottengo un elenco dei 52 stati come geometria Shapely. Non esitate a commentare se esiste un modo più semplice per fare questa parte.

from osgeo import ogr
from shapely.wkb import loads
from numpy import asarray
from shapely.ops import cascaded_union

ds = ogr.Open('counties.shp')
layer = ds.GetLayer(0)

#create a list of unique states identifier to be able
#to loop through them later
STATEFP_list = []
for i in range(0 , layer.GetFeatureCount()) :
    feature = layer.GetFeature(i)
    statefp = feature.GetField('STATEFP')
    STATEFP_list.append(statefp)

STATEFP_list = set(STATEFP_list)

#Create a list of merged polygons = states 
#to be written to file
polygons = []

#do the actual dissolving based on STATEFP
#and append polygons
for i in STATEFP_list : 
    county_to_merge = []
    layer.SetAttributeFilter("STATEFP = '%s'" %i ) 
    #I am not too sure why "while 1" but it works 
    while 1:
        f = layer.GetNextFeature()
        if f is None: break
        g = f.geometry()
        county_to_merge.append(loads(g.ExportToWkb()))

    u = cascaded_union(county_to_merge)
    polygons.append(u)

#And now I am totally stuck, I have no idea how to write 
#this list of shapely geometry into a shapefile using the
#same properties that my source.

Quindi la scrittura non è davvero semplice da quello che ho visto, voglio solo lo stesso shapefile solo con il paese che si dissolve in stati, non ho nemmeno bisogno di gran parte della tabella degli attributi ma sono curioso di vedere come puoi passare dalla sorgente al nuovo shapefile creato.

Ho trovato molti pezzi di codice per scrivere con Fiona ma non sono mai riuscito a farlo funzionare con i miei dati. Esempio da Come scrivere geometrie Shapely su shapefile? :

from shapely.geometry import mapping, Polygon
import fiona

# Here's an example Shapely geometry
poly = Polygon([(0, 0), (0, 1), (1, 1), (0, 0)])

# Define a polygon feature geometry with one attribute
schema = {
    'geometry': 'Polygon',
    'properties': {'id': 'int'},
}

# Write a new Shapefile
with fiona.open('my_shp2.shp', 'w', 'ESRI Shapefile', schema) as c:
    ## If there are multiple geometries, put the "for" loop here
    c.write({
        'geometry': mapping(poly),
        'properties': {'id': 123},
    })

Il problema qui è come fare lo stesso con un elenco di geometrie e come ricreare le stesse proprietà rispetto all'origine.

Risposte:


22

La domanda riguarda Fiona e Shapely e l'altra risposta usando GeoPandas richiede di conoscere anche i panda . Inoltre GeoPandas utilizza Fiona per leggere / scrivere shapefile.

Non metto in dubbio qui l'utilità di GeoPandas, ma puoi farlo direttamente con Fiona usando il modulo standard itertools , specialmente con il comando groupby ("In poche parole, groupby prende un iteratore e lo scompone in sub-iteratori in base alle modifiche nella "chiave" dell'iteratore principale. Ciò avviene ovviamente senza leggere l'intero iteratore di sorgente in memoria ", itertools.groupby ).

Shapefile originale colorato dal campo STATEFP

inserisci qui la descrizione dell'immagine

from shapely.geometry import shape, mapping
from shapely.ops import unary_union
import fiona
import itertools
with fiona.open('cb_2013_us_county_20m.shp') as input:
    # preserve the schema of the original shapefile, including the crs
    meta = input.meta
    with fiona.open('dissolve.shp', 'w', **meta) as output:
        # groupby clusters consecutive elements of an iterable which have the same key so you must first sort the features by the 'STATEFP' field
        e = sorted(input, key=lambda k: k['properties']['STATEFP'])
        # group by the 'STATEFP' field 
        for key, group in itertools.groupby(e, key=lambda x:x['properties']['STATEFP']):
            properties, geom = zip(*[(feature['properties'],shape(feature['geometry'])) for feature in group])
            # write the feature, computing the unary_union of the elements in the group with the properties of the first element in the group
            output.write({'geometry': mapping(unary_union(geom)), 'properties': properties[0]})

Risultato

inserisci qui la descrizione dell'immagine


Bello anche quello, difficile da scegliere tra entrambi. Bello vedere diversi metodi grazie!
Utente 18981898198119

11

Consiglio vivamente GeoPandas per gestire vasti assortimenti di funzionalità ed eseguire operazioni in blocco.

Estende i frame di dati di Pandas e li usa formalmente sotto il cofano.

from geopandas import GeoSeries, GeoDataFrame

# define your directories and file names
dir_input = '/path/to/your/file/'
name_in = 'cb_2013_us_county_20m.shp'
dir_output = '/path/to/your/file/'
name_out = 'states.shp'

# create a dictionary
states = {}
# open your file with geopandas
counties = GeoDataFrame.from_file(dir_input + name_in)

for i in range(len(counties)):
    state_id = counties.at[i, 'STATEFP']
    county_geometry = counties.at[i, 'geometry']
    # if the feature's state doesn't yet exist, create it and assign a list
    if state_id not in states:
        states[state_id] = []
    # append the feature to the list of features
    states[state_id].append(county_geometry)

# create a geopandas geodataframe, with columns for state and geometry
states_dissolved = GeoDataFrame(columns=['state', 'geometry'], crs=counties.crs)

# iterate your dictionary
for state, county_list in states.items():
    # create a geoseries from the list of features
    geometry = GeoSeries(county_list)
    # use unary_union to join them, thus returning polygon or multi-polygon
    geometry = geometry.unary_union
    # set your state and geometry values
    states_dissolved.set_value(state, 'state', state)
    states_dissolved.set_value(state, 'geometry', geometry)

# save to file
states_dissolved.to_file(dir_output + name_out, driver="ESRI Shapefile")

È molto più elegante della mia strana cosa strana. Grazie ! Esiste un modo per trasmettere il sistema di riferimento spaziale?
Utente 18981898198119

Ho modificato il mio post per mostrare come trasferire i CR dal file sorgente al nuovo file. Ciò accade nella riga in cui viene creato il GeoDataFrame States_dissolved. Per quanto riguarda lo schema, suggerirei di crearne uno manualmente (ovvero utilizzando le proprietà delle colonne della stessa riga) che vengono quindi scritte nelle proprietà del nuovo file. cioè quando crei il dizionario degli stati, aggiungi semplicemente altre proprietà mentre procedi e assegnale a una colonna nel nuovo frame di dati.
Songololo,

0

Come addendum alla risposta di @ gene , avevo bisogno di dissolversi da più di un campo, quindi ho modificato il suo codice per gestire più campi. Il codice seguente utilizza operator.itemgetterper raggruppare per più campi:

# Modified from /gis//a/150001/2856
from shapely.geometry import shape, mapping
from shapely.ops import unary_union
import fiona
import itertools
from operator import itemgetter


def dissolve(input, output, fields):
    with fiona.open(input) as input:
        with fiona.open(output, 'w', **input.meta) as output:
            grouper = itemgetter(*fields)
            key = lambda k: grouper(k['properties'])
            for k, group in itertools.groupby(sorted(input, key=key), key):
                properties, geom = zip(*[(feature['properties'], shape(feature['geometry'])) for feature in group])
                output.write({'geometry': mapping(unary_union(geom)), 'properties': properties[0]})


if __name__ == '__main__':
    dissolve('input.shp', 'input_dissolved.shp', ["FIELD1", "FIELD2", "FIELDN"))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.