Comment écrire des géométries galbées dans des fichiers de formes?

26

Quelqu'un peut-il démontrer un moyen simple d'écrire des structures de données géométriques de galbées dans des fichiers de formes? Je m'intéresse particulièrement aux polygones avec trous et chaînes linéaires. Il serait également avantageux de rester à l'écart de l'arcpy (donc osgeo, pyshp, etc. serait mieux).

terra_matics
la source

Réponses:

44

Le binaire bien connu est un bon format d'échange binaire qui peut être échangé avec de nombreux logiciels SIG, notamment Shapely et GDAL / OGR.

Ceci est un petit exemple du flux de travail avec 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

Mise à jour : Bien que l'affiche ait accepté la réponse GDAL / OGR, voici un équivalent 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},
    })

(Remarquez les utilisateurs de Windows: vous n'avez aucune excuse )

Mike T
la source
Intéressé pourquoi vous avez choisi cette méthode plutôt que la bibliothèque Fiona.
Nathan W
1
Eh bien, l'affiche cherchait un exemple osgeo.ogr, et la comparaison est intéressante.
sgillies
@sgillies comparaison explicite ajoutée
Mike T
3
Eh bien, pour être honnête, c'était surtout de la pragmatique. J'ai apprécié l'effort de démonstration du code en réponse à ma question et j'étais déjà en train de déblayer avec osgeo. J'ai depuis essayé les deux méthodes et ce sont toutes deux des réponses suffisantes. J'apprécie l'effort des intervenants d'être à la fois précis et rapide.
terra_matics
@Mike T Concernant l'approche osgeo.ogr, je l'utilise dans un plugin Python pour QGIS. Le fichier de formes considéré à écrire est une ligne (LineString in Shapely). Lorsque vous avez défini la variable "poly", j'ai défini une variable "ligne" avec les coordonnées d'un Qgs.Rectangle. J'ai utilisé le code exact, pas d'erreurs, mais il n'ajoute pas de fonctionnalité et me donne un fichier de formes sans fonctionnalités.
Akhil
28

J'ai conçu Fiona pour bien fonctionner avec Shapely. Voici un exemple très simple de les utiliser ensemble pour "nettoyer" les fonctionnalités du fichier de formes:

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

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

sgillies
la source
6

Vous pouvez également écrire des géométries Shapely en utilisant PyShp (puisque l'affiche originale a également posé des questions sur PyShp).

Une façon serait de convertir votre géométrie galbée en geojson (avec la méthode shapely.geometry.mapping) puis d'utiliser ma fourche modifiée de PyShp qui fournit une méthode Writer qui accepte les dictionnaires de géométrie geojson lors de l'écriture dans un fichier de formes.

Si vous préférez vous fier à la version principale de PyShp, j'ai également fourni une fonction de conversion ci-dessous:

# 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

Copiez et collez simplement la fonction dans votre propre script et appelez-la pour convertir n'importe laquelle de vos géométries galbées en une forme compatible pyshp. Pour les enregistrer, vous ajoutez simplement chaque forme pyshp résultante à la liste ._shapes de l'instance shapefile.Writer (pour un exemple, voir le script de test au bas de cet article).

Remarque cependant: la fonction ne gérera PAS les trous de polygone intérieurs s'il y en a, elle les ignore simplement. Il est certainement possible d'ajouter cette fonctionnalité à la fonction mais je n'ai tout simplement pas encore pris la peine. Les suggestions ou modifications pour améliorer la fonction sont les bienvenues :)

Voici un script de test autonome complet:

### 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
la source
5

La réponse de Karim est assez ancienne mais j'ai utilisé son code et je voulais l'en remercier. Une chose mineure que j'ai trouvée en utilisant son code: si le type de forme est Polygone ou Multipolygon, il peut toujours avoir plusieurs parties (trous à l'intérieur). Par conséquent, une partie de son code devrait être remplacée par

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
la source