You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
205 lines
5.8 KiB
205 lines
5.8 KiB
import argparse |
|
import sys |
|
import os |
|
from osgeo import ogr |
|
from osgeo import osr |
|
import anyjson |
|
import shapely.geometry |
|
import shapely.ops |
|
import codecs |
|
import time |
|
|
|
|
|
format = '%.8f %.8f' |
|
tolerance = 0.01 |
|
infile = '/Users/kirilllebedev/Maps/50m-admin-0-countries/ne_50m_admin_0_countries.shp' |
|
outfile = 'map.shp' |
|
|
|
# Open the datasource to operate on. |
|
in_ds = ogr.Open( infile, update = 0 ) |
|
in_layer = in_ds.GetLayer( 0 ) |
|
in_defn = in_layer.GetLayerDefn() |
|
|
|
|
|
# Create output file with similar information. |
|
shp_driver = ogr.GetDriverByName( 'ESRI Shapefile' ) |
|
if os.path.exists('map.shp'): |
|
shp_driver.DeleteDataSource( outfile ) |
|
shp_ds = shp_driver.CreateDataSource( outfile ) |
|
shp_layer = shp_ds.CreateLayer( in_defn.GetName(), |
|
geom_type = in_defn.GetGeomType(), |
|
srs = in_layer.GetSpatialRef() ) |
|
|
|
in_field_count = in_defn.GetFieldCount() |
|
for fld_index in range(in_field_count): |
|
src_fd = in_defn.GetFieldDefn( fld_index ) |
|
fd = ogr.FieldDefn( src_fd.GetName(), src_fd.GetType() ) |
|
fd.SetWidth( src_fd.GetWidth() ) |
|
fd.SetPrecision( src_fd.GetPrecision() ) |
|
shp_layer.CreateField( fd ) |
|
|
|
|
|
# Load geometries |
|
geometries = [] |
|
for feature in in_layer: |
|
geometry = feature.GetGeometryRef() |
|
geometryType = geometry.GetGeometryType() |
|
|
|
if geometryType == ogr.wkbPolygon or geometryType == ogr.wkbMultiPolygon: |
|
shapelyGeometry = shapely.wkb.loads( geometry.ExportToWkb() ) |
|
#if not shapelyGeometry.is_valid: |
|
#buffer to fix selfcrosses |
|
#shapelyGeometry = shapelyGeometry.buffer(0) |
|
if shapelyGeometry: |
|
geometries.append(shapelyGeometry) |
|
in_layer.ResetReading() |
|
|
|
start = int(round(time.time() * 1000)) |
|
# Simplification |
|
points = [] |
|
connections = {} |
|
counter = 0 |
|
for geom in geometries: |
|
counter += 1 |
|
polygons = [] |
|
|
|
if isinstance(geom, shapely.geometry.Polygon): |
|
polygons.append(geom) |
|
else: |
|
for polygon in geom: |
|
polygons.append(polygon) |
|
|
|
for polygon in polygons: |
|
if polygon.area > 0: |
|
lines = [] |
|
lines.append(polygon.exterior) |
|
for line in polygon.interiors: |
|
lines.append(line) |
|
|
|
for line in lines: |
|
for i in range(len(line.coords)-1): |
|
indexFrom = i |
|
indexTo = i+1 |
|
pointFrom = format % line.coords[indexFrom] |
|
pointTo = format % line.coords[indexTo] |
|
if pointFrom == pointTo: |
|
continue |
|
if not (pointFrom in connections): |
|
connections[pointFrom] = {} |
|
connections[pointFrom][pointTo] = 1 |
|
if not (pointTo in connections): |
|
connections[pointTo] = {} |
|
connections[pointTo][pointFrom] = 1 |
|
print int(round(time.time() * 1000)) - start |
|
|
|
simplifiedLines = {} |
|
pivotPoints = {} |
|
def simplifyRing(ring): |
|
coords = list(ring.coords)[0:-1] |
|
simpleCoords = [] |
|
|
|
isPivot = False |
|
pointIndex = 0 |
|
while not isPivot and pointIndex < len(coords): |
|
pointStr = format % coords[pointIndex] |
|
pointIndex += 1 |
|
isPivot = ((len(connections[pointStr]) > 2) or (pointStr in pivotPoints)) |
|
pointIndex = pointIndex - 1 |
|
|
|
if not isPivot: |
|
simpleRing = shapely.geometry.LineString(coords).simplify(tolerance) |
|
if len(simpleRing.coords) <= 2: |
|
return None |
|
else: |
|
pivotPoints[format % coords[0]] = True |
|
pivotPoints[format % coords[-1]] = True |
|
simpleLineKey = format % coords[0]+':'+format % coords[1]+':'+format % coords[-1] |
|
simplifiedLines[simpleLineKey] = simpleRing.coords |
|
return simpleRing |
|
else: |
|
points = coords[pointIndex:len(coords)] |
|
points.extend(coords[0:pointIndex+1]) |
|
iFrom = 0 |
|
for i in range(1, len(points)): |
|
pointStr = format % points[i] |
|
if ((len(connections[pointStr]) > 2) or (pointStr in pivotPoints)): |
|
line = points[iFrom:i+1] |
|
lineKey = format % line[-1]+':'+format % line[-2]+':'+format % line[0] |
|
if lineKey in simplifiedLines: |
|
simpleLine = simplifiedLines[lineKey] |
|
simpleLine = list(reversed(simpleLine)) |
|
else: |
|
simpleLine = shapely.geometry.LineString(line).simplify(tolerance).coords |
|
lineKey = format % line[0]+':'+format % line[1]+':'+format % line[-1] |
|
simplifiedLines[lineKey] = simpleLine |
|
simpleCoords.extend( simpleLine[0:-1] ) |
|
iFrom = i |
|
if len(simpleCoords) <= 2: |
|
return None |
|
else: |
|
return shapely.geometry.LineString(simpleCoords) |
|
|
|
|
|
def simplifyPolygon(polygon): |
|
simpleExtRing = simplifyRing(polygon.exterior) |
|
if simpleExtRing is None: |
|
return None |
|
simpleIntRings = [] |
|
for ring in polygon.interiors: |
|
simpleIntRing = simplifyRing(ring) |
|
if simpleIntRing is not None: |
|
simpleIntRings.append(simpleIntRing) |
|
return shapely.geometry.Polygon(simpleExtRing, simpleIntRings) |
|
|
|
|
|
results = [] |
|
for geom in geometries: |
|
polygons = [] |
|
simplePolygons = [] |
|
|
|
if isinstance(geom, shapely.geometry.Polygon): |
|
polygons.append(geom) |
|
else: |
|
for polygon in geom: |
|
polygons.append(polygon) |
|
|
|
for polygon in polygons: |
|
simplePolygon = simplifyPolygon(polygon) |
|
if not (simplePolygon is None or simplePolygon._geom is None): |
|
simplePolygons.append(simplePolygon) |
|
|
|
if len(simplePolygons) > 0: |
|
results.append(shapely.geometry.MultiPolygon(simplePolygons)) |
|
else: |
|
results.append(None) |
|
|
|
|
|
# Process all features in input layer. |
|
in_feat = in_layer.GetNextFeature() |
|
counter = 0 |
|
while in_feat is not None: |
|
if results[counter] is not None: |
|
out_feat = ogr.Feature( feature_def = shp_layer.GetLayerDefn() ) |
|
out_feat.SetFrom( in_feat ) |
|
out_feat.SetGeometryDirectly( |
|
ogr.CreateGeometryFromWkb( |
|
shapely.wkb.dumps( |
|
results[counter] |
|
) |
|
) |
|
) |
|
shp_layer.CreateFeature( out_feat ) |
|
out_feat.Destroy() |
|
else: |
|
print 'geometry is too small: '+in_feat.GetField(16) |
|
|
|
in_feat.Destroy() |
|
in_feat = in_layer.GetNextFeature() |
|
counter += 1 |
|
|
|
|
|
# Cleanup |
|
shp_ds.Destroy() |
|
in_ds.Destroy() |
|
|
|
print int(round(time.time() * 1000)) - start |