Jak pisać kształty geometryczne w plikach kształtów?

26

Czy ktoś może zademonstrować prosty sposób zapisywania struktur danych geometrii z kształtnych do plików kształtów? Szczególnie interesują mnie wielokąty z dziurami i obramowaniami. Byłoby również korzystne, aby trzymać się z daleka od arcpy (więc osgeo, pyshp itp. Byłoby lepiej).

terra_matics
źródło

Odpowiedzi:

44

Dobrze znany plik binarny jest dobrym formatem wymiany binarnej, który można wymieniać dużą ilością oprogramowania GIS, w tym Shapely i GDAL / OGR.

To mały przykład przepływu pracy z 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

Aktualizacja : Mimo że plakat zaakceptował odpowiedź GDAL / OGR, oto odpowiednik Fiony :

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

(Uwaga użytkownicy systemu Windows: nie masz wymówki )

Mike T.
źródło
Ciekawe, dlaczego wybrałeś tę metodę, a nie bibliotekę Fiona.
Nathan W
1
Plakat szukał przykładu osgeo.ogr, a porównanie jest interesujące.
sgillies
Dodano wyraźne porównanie @sgillies
Mike T
3
Szczerze mówiąc, była to głównie pragmatyka. Doceniałem wysiłek w zademonstrowaniu kodu w odpowiedzi na moje pytanie i już rozmyślałem o osgeo. Od tego czasu wypróbowałem obie metody i obie są wystarczającymi odpowiedziami. Doceniam wysiłek osób odpowiadających za to, że były zarówno dokładne, jak i szybkie.
terra_matics
@ Mike T Jeśli chodzi o podejście osgeo.ogr, używam go we wtyczce Python dla QGIS. Rozważany plik kształtu do zapisania to Linia (LineString in Shapely). Tam, gdzie zdefiniowałeś zmienną „poli”, zdefiniowałem zmienną „linii” o współrzędnych z Qgs.Rectangle. Użyłem dokładnego kodu, żadnych błędów, ale nie dodaje on żadnej funkcji i daje mi plik kształtu bez żadnych funkcji.
Achil
28

Zaprojektowałem Fionę, aby dobrze współpracowała z Shapely. Oto bardzo prosty przykład używania ich razem w celu „czyszczenia” funkcji pliku kształtu:

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

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

sgillies
źródło
6

Możesz także pisać geometrie kształtów za pomocą PyShp (ponieważ w oryginalnym plakacie też pytano o PyShp).

Jednym ze sposobów byłoby przekonwertowanie kształtowej geometrii na geojson (za pomocą metody shapely.geometry.mapping), a następnie użycie mojego zmodyfikowanego rozwidlenia PyShp, który zapewnia metodę Writer, która akceptuje słowniki geometrii geojson podczas pisania do pliku kształtu.

Jeśli wolisz polegać na głównej wersji PyShp, poniżej zapewniłem również funkcję konwersji:

# 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

Po prostu skopiuj i wklej funkcję do własnego skryptu i wywołaj go, aby przekonwertować dowolną kształtną geometrię na kształt zgodny z pyshp. Aby je zapisać, wystarczy dołączyć każdy wynikowy kształt pyshp do listy ._shapes instancji shapefile.Writer (na przykład patrz skrypt testowy na dole tego postu).

Uwaga: funkcja NIE obsłuży żadnych wewnętrznych otworów wielokąta, jeśli takie istnieją, po prostu je ignoruje. Z pewnością można dodać tę funkcję do funkcji, ale po prostu jeszcze się tym nie przejmowałem. Mile widziane sugestie lub zmiany mające na celu poprawę funkcji :)

Oto pełny samodzielny skrypt testowy:

### 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
źródło
5

Odpowiedź Karima jest dość stara, ale użyłem jego kodu i chciałem mu za to podziękować. Jedną drobną rzecz, którą wymyśliłem, korzystając z jego kodu: jeśli typ kształtu to Wielokąt lub Wielokąt, nadal może mieć wiele części (otwory w środku). Dlatego część jego kodu należy zmienić na

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
źródło