Як написати фігурну геометрію до форм-файлів?


26

Чи може хтось продемонструвати простий спосіб записати структури даних про геометрію з формально у форматні форми? Мене особливо цікавлять багатокутники з отворами та рядками. Було б також корисно триматися подалі від архпі (так що osgeo, pyshp тощо було б краще).

Відповіді:


44

Добре відомий бінарний формат - хороший формат бінарного обміну, який можна обміняти великою кількістю програм ГІС, включаючи Shapely та GDAL / OGR.

Це крихітний приклад робочого процесу з osgeo.ogr:

from osgeo import ogr
from shapely.geometry import Polygon

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

# Now convert it to a shapefile with OGR    
driver = ogr.GetDriverByName('Esri Shapefile')
ds = driver.CreateDataSource('my.shp')
layer = ds.CreateLayer('', None, ogr.wkbPolygon)
# Add one attribute
layer.CreateField(ogr.FieldDefn('id', ogr.OFTInteger))
defn = layer.GetLayerDefn()

## If there are multiple geometries, put the "for" loop here

# Create a new feature (attribute and geometry)
feat = ogr.Feature(defn)
feat.SetField('id', 123)

# Make a geometry, from Shapely object
geom = ogr.CreateGeometryFromWkb(poly.wkb)
feat.SetGeometry(geom)

layer.CreateFeature(feat)
feat = geom = None  # destroy these

# Save and close everything
ds = layer = feat = geom = None

Оновлення : Хоча плакат прийняв відповідь GDAL / OGR, ось еквівалент 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},
    })

(Зверніть увагу на користувачів Windows: у вас немає виправдання )


Цікавить, чому ви вибрали цей метод, а не бібліотеку Fiona.
Nathan W

1
Ну, плакат шукав приклад osgeo.ogr, і порівняння цікаве.
sgillies

Додано чітке порівняння @sgillies
Майк Т

3
Ну, чесно кажучи, це були переважно прагматики. Я оцінив зусилля продемонструвати код у відповідь на моє запитання, і я вже говорив про osgeo. З тих пір я пробував обидва методи, і вони є достатньою відповіді. Я ціную зусилля з боку респондентів як точні, так і швидкі.
terra_matics

@Mike T Щодо підходу osgeo.ogr, я використовую його в плагіні Python для QGIS. Розглянуте файл форми, який потрібно записати, - це рядок (LineString у Shapely). Там, де ви визначили змінну "poly", я визначив змінну "line" з координатами з Qgs.Rectangle. Я використав точний код, помилок немає, але він не додає функції, і надає мені форму файлів без особливостей.
Ахіл

28

Я сконструював Fiona добре співпрацювати з Shapely. Ось дуже простий приклад використання їх разом для "очищення" функцій формфайлу:

import logging
import sys

from shapely.geometry import mapping, shape

import fiona

logging.basicConfig(stream=sys.stderr, level=logging.INFO)

with fiona.open('docs/data/test_uk.shp', 'r') as source:

    # **source.meta is a shortcut to get the crs, driver, and schema
    # keyword arguments from the source Collection.
    with fiona.open(
            'with-shapely.shp', 'w',
            **source.meta) as sink:

        for f in source:

            try:
                geom = shape(f['geometry'])
                if not geom.is_valid:
                    clean = geom.buffer(0.0)
                    assert clean.is_valid
                    assert clean.geom_type == 'Polygon'
                    geom = clean
                f['geometry'] = mapping(geom)
                sink.write(f)

            except Exception, e:
                # Writing uncleanable features to a different shapefile
                # is another option.
                logging.exception("Error cleaning feature %s:", f['id'])

З https://github.com/Toblerity/Fiona/blob/master/examples/with-shapely.py .


6

Ви також можете писати Shapely геометрії, використовуючи PyShp (оскільки оригінальний плакат також запитував про PyShp).

Одним із способів було б перетворити вашу витончену геометрію в geojson (за допомогою методу shapely.geometry.mapping), а потім використати мою модифіковану вилку PyShp, яка забезпечує метод Writer, який приймає словники геометрії geojson під час запису у filefile.

Якщо ви віддаєте перевагу основній версії PyShp, я також надав функцію перетворення нижче:

# THIS FUNCTION CONVERTS A GEOJSON GEOMETRY DICTIONARY TO A PYSHP SHAPE OBJECT
def shapely_to_pyshp(shapelygeom):
    # first convert shapely to geojson
    try:
        shapelytogeojson = shapely.geometry.mapping
    except:
        import shapely.geometry
        shapelytogeojson = shapely.geometry.mapping
    geoj = shapelytogeojson(shapelygeom)
    # create empty pyshp shape
    record = shapefile._Shape()
    # set shapetype
    if geoj["type"] == "Null":
        pyshptype = 0
    elif geoj["type"] == "Point":
        pyshptype = 1
    elif geoj["type"] == "LineString":
        pyshptype = 3
    elif geoj["type"] == "Polygon":
        pyshptype = 5
    elif geoj["type"] == "MultiPoint":
        pyshptype = 8
    elif geoj["type"] == "MultiLineString":
        pyshptype = 3
    elif geoj["type"] == "MultiPolygon":
        pyshptype = 5
    record.shapeType = pyshptype
    # set points and parts
    if geoj["type"] == "Point":
        record.points = geoj["coordinates"]
        record.parts = [0]
    elif geoj["type"] in ("MultiPoint","Linestring"):
        record.points = geoj["coordinates"]
        record.parts = [0]
    elif geoj["type"] in ("Polygon"):
        record.points = geoj["coordinates"][0]
        record.parts = [0]
    elif geoj["type"] in ("MultiPolygon","MultiLineString"):
        index = 0
        points = []
        parts = []
        for eachmulti in geoj["coordinates"]:
            points.extend(eachmulti[0])
            parts.append(index)
            index += len(eachmulti[0])
        record.points = points
        record.parts = parts
    return record

Просто скопіюйте та вставте функцію у свій власний скрипт та зателефонуйте на неї, щоб перетворити будь-яку з ваших витончених геометрій у форму, сумісну з pyshp. Щоб зберегти їх, ви просто додаєте кожну отриману форму pyshp до списку ._shapes екземпляра shapefile.Writer (наприклад, дивіться тестовий скрипт внизу цієї публікації).

Однак зауважте: функція НЕ буде обробляти жодні внутрішні отвори в багатокутнику, якщо такі є, вона просто ігнорує їх. Безумовно, можна додати цю функціональність до функції, але я просто ще не турбувався. Пропозиції чи зміни щодо покращення функції вітаються :)

Ось повний автономний тестовий сценарій:

### HOW TO SAVE SHAPEFILE FROM SHAPELY GEOMETRY USING PYSHP

# IMPORT STUFF
import shapefile
import shapely, shapely.geometry

# CREATE YOUR SHAPELY TEST INPUT
TEST_SHAPELYSHAPE = shapely.geometry.Polygon([(133,822),(422,644),(223,445),(921,154)])

#########################################################
################## END OF USER INPUT ####################
#########################################################

# DEFINE/COPY-PASTE THE SHAPELY-PYSHP CONVERSION FUNCTION
def shapely_to_pyshp(shapelygeom):
    # first convert shapely to geojson
    try:
        shapelytogeojson = shapely.geometry.mapping
    except:
        import shapely.geometry
        shapelytogeojson = shapely.geometry.mapping
    geoj = shapelytogeojson(shapelygeom)
    # create empty pyshp shape
    record = shapefile._Shape()
    # set shapetype
    if geoj["type"] == "Null":
        pyshptype = 0
    elif geoj["type"] == "Point":
        pyshptype = 1
    elif geoj["type"] == "LineString":
        pyshptype = 3
    elif geoj["type"] == "Polygon":
        pyshptype = 5
    elif geoj["type"] == "MultiPoint":
        pyshptype = 8
    elif geoj["type"] == "MultiLineString":
        pyshptype = 3
    elif geoj["type"] == "MultiPolygon":
        pyshptype = 5
    record.shapeType = pyshptype
    # set points and parts
    if geoj["type"] == "Point":
        record.points = geoj["coordinates"]
        record.parts = [0]
    elif geoj["type"] in ("MultiPoint","Linestring"):
        record.points = geoj["coordinates"]
        record.parts = [0]
    elif geoj["type"] in ("Polygon"):
        record.points = geoj["coordinates"][0]
        record.parts = [0]
    elif geoj["type"] in ("MultiPolygon","MultiLineString"):
        index = 0
        points = []
        parts = []
        for eachmulti in geoj["coordinates"]:
            points.extend(eachmulti[0])
            parts.append(index)
            index += len(eachmulti[0])
        record.points = points
        record.parts = parts
    return record

# WRITE TO SHAPEFILE USING PYSHP
shapewriter = shapefile.Writer()
shapewriter.field("field1")
# step1: convert shapely to pyshp using the function above
converted_shape = shapely_to_pyshp(TEST_SHAPELYSHAPE)
# step2: tell the writer to add the converted shape
shapewriter._shapes.append(converted_shape)
# add a list of attributes to go along with the shape
shapewriter.record(["empty record"])
# save it
shapewriter.save("test_shapelytopyshp.shp")

5

Відповідь Каріма досить стара, але я використав його код і хотів подякувати йому за це. Одне незначне, що я з'ясував, використовуючи його код: Якщо тип фігури - це багатокутник або мультиполігон, він може все ще мати кілька деталей (отвори всередині). Тому частина його коду має бути змінена на

elif geoj["type"] == "Polygon":
    index = 0
    points = []
    parts = []
    for eachmulti in geoj["coordinates"]:
        points.extend(eachmulti)
        parts.append(index)
        index += len(eachmulti)
    record.points = points
    record.parts = parts
elif geoj["type"] in ("MultiPolygon", "MultiLineString"):
    index = 0
    points = []
    parts = []
    for polygon in geoj["coordinates"]:
        for part in polygon:
            points.extend(part)
            parts.append(index)
            index += len(part)
    record.points = points
    record.parts = parts
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.