Polygone basierend auf Attributen mit Python auflösen (shapely, fiona)?

15

Ich habe versucht, eine Funktion zu erstellen, die im Grunde dasselbe tut, was die QGIS-Funktion "auflöst". Ich dachte es wäre super einfach aber naja anscheinend nicht. Nach allem, was ich gesammelt habe, sollte der Einsatz von Fiona mit Shapely hier die beste Option sein. Ich habe gerade angefangen, mit Vektordateien herumzuspielen, also ist diese Welt für mich und auch für Python ziemlich neu.

Für dieses Beispiel arbeite ich mit einem Kreis Shape - Datei hier gegründet http://tinyurl.com/odfbanu so bin hier einiges Stück Code , den ich gesammelt, kann aber nicht einen Weg finden , um sie zusammen zu machen arbeiten

Im Moment ist meine beste Methode die folgende, basierend auf: https://sgillies.net/2009/01/27/a-more-perfect-union-continued.html . Es funktioniert gut und ich bekomme eine Liste der 52 Zustände als Shapely-Geometrie. Bitte zögern Sie nicht zu kommentieren, wenn es einen einfacheren Weg gibt, diesen Teil zu tun.

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.

Das Schreiben ist also wirklich nicht direkt von dem, was ich gesehen habe, ich möchte wirklich nur dasselbe Shapefile mit dem Land in Staaten auflösen, ich brauche nicht einmal viel von der Attributtabelle, aber ich bin gespannt, wie Sie weitergeben können Von der Quelle bis zum neu erstellten Shapefile.

Ich habe viele Codeteile zum Schreiben mit fiona gefunden, kann sie aber mit meinen Daten nie zum Laufen bringen. Beispiel aus Wie schreibe ich Shapely-Geometrien in Shapefiles? :

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},
    })

Das Problem hierbei ist, wie Sie dasselbe mit einer Geometrieliste tun und dieselben Eigenschaften wie mit der Quelle neu erstellen.

User18981898198119
quelle

Antworten:

22

Die Frage ist über Fiona und Shapely und die andere Antwort mit GeoPandas erfordert, dass Sie auch Pandas kennen . Darüber hinaus verwendet GeoPandas Fiona zum Lesen / Schreiben von Shapefiles.

Ich frage hier nicht nach dem Nutzen von GeoPandas, aber Sie können es direkt mit Fiona tun, indem Sie das Standardmodul itertools verwenden , insbesondere mit dem Befehl groupby ("Kurz gesagt, groupby nimmt einen Iterator und zerlegt ihn auf der Grundlage von Änderungen in Unteriteratoren Dies geschieht natürlich, ohne den gesamten Quell-Iterator in den Speicher einzulesen ", itertools.groupby ).

Ursprüngliches Shapefile, das durch das STATEFP-Feld gefärbt wurde

Bildbeschreibung hier eingeben

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]})

Ergebnis

Bildbeschreibung hier eingeben

Gen
quelle
Auch schön, es ist schwer, sich zwischen beidem zu entscheiden. Schön, verschiedene Methoden zu sehen, danke!
User18981898198119
11

Ich kann GeoPandas nur empfehlen, wenn Sie mit einer großen Auswahl an Funktionen arbeiten und Massenoperationen ausführen möchten.

Es erweitert Pandas Datenrahmen und wird formschön unter der Haube verwendet.

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")
Songololo
quelle
Das ist viel eleganter als mein komisches Ding. Vielen Dank ! Gibt es eine Möglichkeit, das räumliche Bezugssystem weiterzugeben?
User18981898198119
Ich habe meinen Beitrag bearbeitet, um zu zeigen, wie die crs aus der Quelldatei in die neue Datei übertragen werden. Dies geschieht in der Zeile, in der der GeoDataFrame states_dissolved erstellt wird. In Bezug auf das Schema würde ich vorschlagen, nur eine manuell zu erstellen (dh Spalteneigenschaft derselben Zeile zu verwenden), die dann in die Eigenschaften der neuen Datei geschrieben werden. Wenn Sie also Ihr Statuswörterbuch erstellen, fügen Sie nach und nach weitere Eigenschaften hinzu und weisen Sie sie einer Spalte im neuen Datenrahmen zu.
Songololo
0

Als Ergänzung zu @ genes Antwort musste ich mich um mehr als ein Feld auflösen , also änderte ich seinen Code, um mehrere Felder zu verarbeiten. Der folgende Code wird operator.itemgetterzum Gruppieren nach mehreren Feldern verwendet:

# 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"))
user2856
quelle