Wie schreibe ich Shapely-Geometrien in Shapefiles?

26

Kann jemand einen einfachen Weg demonstrieren, um Geometriedatenstrukturen von Shapely in Shapefiles zu schreiben? Ich interessiere mich besonders für Polygone mit Löchern und Linien. Es wäre auch vorteilhaft, sich von Arcpy fernzuhalten (also wären Osgeo, Pyschp etc. besser).

terra_matics
quelle

Antworten:

44

Bekannte Binärdatei ist ein gutes binäres Austauschformat, das mit zahlreichen GIS-Programmen, einschließlich Shapely und GDAL / OGR, ausgetauscht werden kann.

Dies ist ein kleines Beispiel für den Workflow mit 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

Update : Obwohl das Poster die GDAL / OGR-Antwort akzeptiert hat, ist hier ein Fiona- Äquivalent:

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

(Hinweis Windows-Benutzer: Sie haben keine Entschuldigung )

Mike T
quelle
Interessiert, warum Sie sich für diese Methode entschieden haben und nicht für die Fiona-Bibliothek.
Nathan W
1
Nun, das Plakat hat nach einem osgeo.ogr-Beispiel gesucht, und der Vergleich ist interessant.
sgillies
@sgillies expliziter Vergleich hinzugefügt
Mike T
3
Ehrlich gesagt handelte es sich hauptsächlich um Pragmatiker. Ich habe die Mühe, den Code als Antwort auf meine Frage zu demonstrieren, sehr geschätzt und mich bereits mit osgeo beschäftigt. Ich habe seitdem beide Methoden ausprobiert und sie sind beide ausreichende Antworten. Ich schätze die Bemühungen der Antwortenden, sowohl präzise als auch schnell zu sein.
terra_matics
@Mike T In Bezug auf den osgeo.ogr-Ansatz verwende ich ihn in einem Python-Plugin für QGIS. Das zu schreibende Shapefile ist eine Linie (LineString in Shapely). Wo Sie die Variable "poly" definiert haben, habe ich eine Variable "line" mit Koordinaten aus einem Qgs.Rectangle definiert. Ich habe den genauen Code verwendet, keine Fehler, aber es wird kein Feature hinzugefügt, und ich erhalte ein Shapefile ohne Features.
Akhil
28

Ich habe Fiona so entworfen , dass sie gut mit Shapely zusammenarbeitet. Hier ist ein sehr einfaches Beispiel, wie sie zusammen verwendet werden, um Shapefile-Features zu "bereinigen":

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

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

sgillies
quelle
6

Sie können auch Shapely-Geometrien mit PyShp schreiben (da im Original-Poster auch nach PyShp gefragt wurde).

Eine Möglichkeit wäre, Ihre formschöne Geometrie in Geojson umzuwandeln (mit der shapely.geometry.mapping-Methode) und dann meine modifizierte Fork von PyShp zu verwenden, die eine Writer-Methode bereitstellt, die Geojson-Geometriewörterbücher beim Schreiben in ein Shapefile akzeptiert.

Wenn Sie sich lieber auf die Hauptversion von PyShp verlassen möchten, habe ich nachfolgend eine Konvertierungsfunktion bereitgestellt:

# 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

Kopieren Sie die Funktion einfach, fügen Sie sie in Ihr eigenes Skript ein und rufen Sie es auf, um eine Ihrer formschönen Geometrien in eine pyshp-kompatible Form zu konvertieren. Um sie zu speichern, hängen Sie einfach jede resultierende pyshp-Form an die ._shapes-Liste der shapefile.Writer-Instanz an (ein Beispiel finden Sie im Testskript am Ende dieses Beitrags).

Beachten Sie jedoch, dass die Funktion KEINE inneren Polygonlöcher verarbeitet, sofern vorhanden, sondern diese einfach ignoriert. Es ist sicherlich möglich, diese Funktionalität zu der Funktion hinzuzufügen, aber ich habe mich einfach noch nicht darum gekümmert. Vorschläge oder Änderungen zur Verbesserung der Funktion sind willkommen :)

Hier ist ein vollständiges eigenständiges Testskript:

### 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")
Karim Bahgat
quelle
5

Karims Antwort ist ziemlich alt, aber ich habe seinen Code verwendet und wollte ihm dafür danken. Eine Kleinigkeit, die ich mithilfe seines Codes herausgefunden habe: Wenn der Formtyp Polygon oder Multipolygon ist, kann er immer noch mehrere Teile (Löcher im Inneren) enthalten. Daher sollte ein Teil seines Codes in geändert werden

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
lebenlechzer
quelle