Come scrivere geometrie Shapely su shapefile?


26

Qualcuno può dimostrare un modo semplice per scrivere strutture di dati geometrici da sagomati in shapefile? Sono particolarmente interessato ai poligoni con buchi e lacci. Sarebbe anche utile stare lontano da arcpy (quindi osgeo, pyshp, ecc. Sarebbero tutti meglio).

Risposte:


44

Il noto binario è un buon formato di scambio binario che può essere scambiato con molti software GIS, inclusi Shapely e GDAL / OGR.

Questo è un piccolo esempio del flusso di lavoro con 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

Aggiornamento : sebbene il poster abbia accettato la risposta GDAL / OGR, ecco un equivalente di 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},
    })

(Nota per gli utenti Windows: non hai scuse )


Interessato perché hai scelto questo metodo piuttosto che la libreria Fiona.
Nathan W,

1
Bene, il poster stava cercando un esempio di osgeo.ogr e il confronto è interessante.
sgillies,

@sgillies esplicito confronto aggiunto
Mike T

3
Beh, a dire il vero era principalmente pragmatica. Ho apprezzato lo sforzo di dimostrare il codice in risposta alla mia domanda e stavo già scherzando con osgeo. Da allora ho provato entrambi i metodi e sono entrambe risposte sufficienti. Apprezzo lo sforzo dei rispondenti di essere accurati e veloci.
terra_matics,

@Mike T Per quanto riguarda l'approccio osgeo.ogr, lo sto usando in un plugin Python per QGIS. Lo shapefile considerato da scrivere è una linea (LineString in Shapely). Dove hai definito la variabile "poli", ho definito una variabile "linea" con coordinate da un rettangolo Qgs. Ho usato il codice esatto, nessun errore, ma non aggiunge una funzione e mi dà uno shapefile senza funzionalità.
Akhil,

28

Ho progettato Fiona per funzionare bene con Shapely. Ecco un esempio molto semplice di usarli insieme per "pulire" le funzionalità del file di forma:

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

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


6

Puoi anche scrivere geometrie Shapely usando PyShp (poiché il poster originale chiedeva anche di PyShp).

Un modo sarebbe quello di convertire la tua geometria formosa in geojson (con il metodo shapely.geometry.mapping) e quindi utilizzare il mio fork modificato di PyShp che fornisce un metodo Writer che accetta i dizionari di geometria geojson quando si scrive in un file di forma.

Se preferisci affidarti alla versione principale di PyShp, ho anche fornito una funzione di conversione di seguito:

# 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

Basta copiare e incollare la funzione nel proprio script e chiamarla per convertire qualsiasi geometria formosa in una forma compatibile con pyshp. Per salvarli, aggiungi semplicemente ogni forma pyshp risultante all'elenco ._shapes dell'istanza di Shapefile.Writer (per un esempio vedi lo script di test in fondo a questo post).

Nota comunque: la funzione NON gestirà alcun foro poligonale interno se presente, semplicemente li ignora. È certamente possibile aggiungere quella funzionalità alla funzione ma semplicemente non mi sono ancora preoccupato. Suggerimenti o modifiche per migliorare la funzione sono i benvenuti :)

Ecco uno script di test autonomo completo:

### 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

La risposta di Karim è piuttosto vecchia ma ho usato il suo codice e volevo ringraziarlo per questo. Una cosa minore che ho capito usando il suo codice: se il tipo di forma è Poligono o Poligono, potrebbe avere ancora più parti (buchi all'interno). Pertanto parte del suo codice dovrebbe essere modificato in

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
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.