Розпущення багатокутників на основі атрибутів з Python (витончено, фіона)?


15

Я намагався створити функцію, яка в основному робить те саме, що QGIS "розчиняє" функцію. Я подумав, що це буде дуже просто, але, мабуть, ні. Тож із того, що я зібрав навколо, найкращим варіантом тут має бути використання фіона з витонченою формою. Я тільки почав возитися з векторними файлами, так що цей світ для мене і пітона також досить новий.

Для цього прикладу я працюю із створеним тут графічним файлом округу http://tinyurl.com/odfbanu. Ось ось я зібрав якийсь фрагмент коду, але не можу знайти спосіб змусити їх працювати разом

На даний момент найкращим моїм методом є такий: https://sgillies.net/2009/01/27/a-more-perfect-union-continued.html . Це прекрасно працює, і я отримую список 52 станів як Shapely геометрії. Будь ласка, не соромтесь коментувати, якщо є більш прямий спосіб зробити цю частину.

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.

Тож написання насправді не просте вперед від того, що я бачив, я дуже просто хочу, щоб той самий шаблон файлу був тільки в країні, яка розчинилася на штати, мені навіть не потрібна велика частина атрибутичної таблиці, але мені цікаво побачити, як можна пройти він від джерела до нового створеного файлу форм.

Я знайшов багато фрагментів коду для написання з Fiona, але я ніколи не в змозі змусити його працювати зі своїми даними. Приклад з Як писати геометричні фігури у формі фігури? :

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

Проблема тут полягає в тому, як зробити те ж саме зі списком геометрії та як відтворити ті самі властивості, що й джерело.

Відповіді:


22

Питання стосується Фіони та Шаплі, а інша відповідь, яка використовує GeoPandas, також вимагає знати панди . Крім того, GeoPandas використовує Fiona для читання / запису форм.

Я не сумніваюся тут у вигідності GeoPandas, але ви можете це зробити безпосередньо з Fiona, використовуючи стандартний модуль itertools , спеціально за допомогою команди groupby ("У двох словах, groupby приймає ітератор і розбиває його на субітератори на основі змін в "ключі" головного ітератора. Це, звичайно, робиться без зчитування всього ітератора джерела в пам'ять ", itertools.groupby ).

Оригінальний кольоровий файл, пофарбований полем STATEFP

введіть тут опис зображення

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

Результат

введіть тут опис зображення


Хороший теж, важко вибрати між обома. Приємно бачити різні методи дякую!
Користувач18981898198119

11

Я настійно рекомендую GeoPandas працювати з великим асортиментом функцій та виконувати оптові операції.

Він розширює рамки даних Pandas і використовує їх під кришкою.

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")

Це набагато елегантніше, ніж моя дивна штука. Дякую ! Чи є спосіб передати просторову систему відліку?
Користувач18981898198119

Я відредагував свою публікацію, щоб показати, як перенести crs з вихідного файлу в новий файл. Це відбувається у рядку, де створено state_diss разрешено GeoDataFrame. Щодо схеми, я б запропонував просто створити один вручну (тобто, використовуючи властивості стовпців того самого рядка), які потім записуються у властивості нового файлу. тобто коли ви створюєте словник своїх станів, просто додайте будь-які інші властивості під час переходу та призначте їх стовпцю в новому кадрі даних.
сонгололо

0

В якості доповнення до @ гена відповідь , мені потрібно , щоб розчинити більш ніж одного поля так змінив свій код для обробки декількох полів. Код нижче використовується operator.itemgetterдля групування за кількома полями:

# 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"))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.