cleangeometries & round_coordinates shouldn't return duplicate coordinates
This commit is contained in:
parent
484f725aa6
commit
4d113da653
4 changed files with 76 additions and 14 deletions
|
@ -100,7 +100,7 @@ class GeometryField(models.TextField):
|
||||||
else:
|
else:
|
||||||
logging.debug('Fixing rounded geometry failed, saving it to the database without rounding.')
|
logging.debug('Fixing rounded geometry failed, saving it to the database without rounding.')
|
||||||
|
|
||||||
return format_geojson(mapping(value), round=False) if as_json else value
|
return format_geojson(mapping(value), rounded=False) if as_json else value
|
||||||
|
|
||||||
def get_prep_value(self, value):
|
def get_prep_value(self, value):
|
||||||
if value is None:
|
if value is None:
|
||||||
|
|
19
src/c3nav/mapdata/management/commands/cleangeometries.py
Normal file
19
src/c3nav/mapdata/management/commands/cleangeometries.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from c3nav.mapdata.models.geometry.base import GeometryMixin
|
||||||
|
from c3nav.mapdata.utils.models import get_submodels
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'clean-up/fix all geometries in the database'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
with transaction.atomic():
|
||||||
|
for model in get_submodels(GeometryMixin):
|
||||||
|
for instance in model.objects.all():
|
||||||
|
old_geom = instance.geometry.wrapped_geojson
|
||||||
|
instance.save()
|
||||||
|
instance.refresh_from_db()
|
||||||
|
if instance.geometry.wrapped_geojson != old_geom:
|
||||||
|
print('Fixed %s' % instance)
|
|
@ -59,11 +59,11 @@ class GeometryMixin(SerializableMixin):
|
||||||
result = {
|
result = {
|
||||||
'type': 'Feature',
|
'type': 'Feature',
|
||||||
'properties': self.get_geojson_properties(instance=instance),
|
'properties': self.get_geojson_properties(instance=instance),
|
||||||
'geometry': format_geojson(smart_mapping(self.geometry), round=False),
|
'geometry': format_geojson(smart_mapping(self.geometry), rounded=False),
|
||||||
}
|
}
|
||||||
original_geometry = getattr(self, 'original_geometry', None)
|
original_geometry = getattr(self, 'original_geometry', None)
|
||||||
if original_geometry:
|
if original_geometry:
|
||||||
result['original_geometry'] = format_geojson(smart_mapping(original_geometry), round=False)
|
result['original_geometry'] = format_geojson(smart_mapping(original_geometry), rounded=False)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -80,7 +80,7 @@ class GeometryMixin(SerializableMixin):
|
||||||
def _serialize(self, geometry=True, simple_geometry=False, **kwargs):
|
def _serialize(self, geometry=True, simple_geometry=False, **kwargs):
|
||||||
result = super()._serialize(simple_geometry=simple_geometry, **kwargs)
|
result = super()._serialize(simple_geometry=simple_geometry, **kwargs)
|
||||||
if geometry:
|
if geometry:
|
||||||
result['geometry'] = format_geojson(smart_mapping(self.geometry), round=False)
|
result['geometry'] = format_geojson(smart_mapping(self.geometry), rounded=False)
|
||||||
if simple_geometry:
|
if simple_geometry:
|
||||||
result['point'] = (self.level_id, ) + tuple(round(i, 2) for i in self.point.coords[0])
|
result['point'] = (self.level_id, ) + tuple(round(i, 2) for i in self.point.coords[0])
|
||||||
if not isinstance(self.geometry, Point):
|
if not isinstance(self.geometry, Point):
|
||||||
|
@ -96,8 +96,8 @@ class GeometryMixin(SerializableMixin):
|
||||||
|
|
||||||
def get_geometry(self, detailed_geometry=True):
|
def get_geometry(self, detailed_geometry=True):
|
||||||
if detailed_geometry:
|
if detailed_geometry:
|
||||||
return format_geojson(smart_mapping(self.geometry), round=False)
|
return format_geojson(smart_mapping(self.geometry), rounded=False)
|
||||||
return format_geojson(smart_mapping(box(*self.geometry.bounds)), round=False)
|
return format_geojson(smart_mapping(box(*self.geometry.bounds)), rounded=False)
|
||||||
|
|
||||||
def get_shadow_geojson(self):
|
def get_shadow_geojson(self):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -35,21 +35,64 @@ def json_encoder_reindent(method, data, *args, **kwargs):
|
||||||
return result.replace(b'"'+magic_marker, b'').replace(magic_marker+b'"', b'')
|
return result.replace(b'"'+magic_marker, b'').replace(magic_marker+b'"', b'')
|
||||||
|
|
||||||
|
|
||||||
def format_geojson(data, round=True):
|
def format_geojson(data, rounded=True):
|
||||||
coordinates = data.get('coordinates', None)
|
coordinates = data.get('coordinates', None)
|
||||||
if coordinates is not None:
|
if coordinates is not None:
|
||||||
|
if data['type'] == 'Point':
|
||||||
|
coordinates = tuple(round(i, 2) for i in coordinates)
|
||||||
|
elif data['type'] == 'LineString':
|
||||||
|
coordinates = round_coordinates(coordinates)
|
||||||
|
elif data['type'] == 'MultiLineString':
|
||||||
|
coordinates = tuple(round_coordinates(linestring) for linestring in coordinates)
|
||||||
|
elif data['type'] == 'Polygon':
|
||||||
|
coordinates = round_polygon(coordinates)
|
||||||
|
if not coordinates:
|
||||||
|
data['type'] = 'MultiPolygon'
|
||||||
|
elif data['type'] == 'MultiPolygon':
|
||||||
|
coordinates = round_multipolygon(coordinates)
|
||||||
|
else:
|
||||||
|
raise ValueError('Unknown geojson type: %s' % data['type'])
|
||||||
return {
|
return {
|
||||||
'type': data['type'],
|
'type': data['type'],
|
||||||
'coordinates': round_coordinates(data['coordinates']) if round else data['coordinates'],
|
'coordinates': coordinates,
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
'type': data['type'],
|
'type': data['type'],
|
||||||
'geometries': [format_geojson(geometry, round=round) for geometry in data['geometries']],
|
'geometries': [format_geojson(geometry, rounded=rounded) for geometry in data['geometries']],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def round_coordinates(data):
|
def round_multipolygon(coordinates):
|
||||||
if isinstance(data, (list, tuple)):
|
# round every polygon on its own, then remove empty polygons
|
||||||
return tuple(round_coordinates(item) for item in data)
|
coordinates = tuple(round_polygon(polygon) for polygon in coordinates)
|
||||||
else:
|
return tuple(polygon for polygon in coordinates if polygon)
|
||||||
return round(data, 2)
|
|
||||||
|
|
||||||
|
def check_ring(coordinates):
|
||||||
|
# check if this is a valid ring
|
||||||
|
# that measn it has at least 3 points (or 4 if the first and last one are identical)
|
||||||
|
return len(coordinates) >= (4 if coordinates[0] == coordinates[-1] else 3)
|
||||||
|
|
||||||
|
|
||||||
|
def round_polygon(coordinates):
|
||||||
|
# round each ring on it's own and remove rings that are invalid
|
||||||
|
# if the exterior ring is invalid, return and empty polygon
|
||||||
|
coordinates = tuple(round_coordinates(ring) for ring in coordinates)
|
||||||
|
exterior, *interiors = coordinates
|
||||||
|
if not check_ring(exterior):
|
||||||
|
return ()
|
||||||
|
return (exterior, *(interior for interior in interiors if check_ring(interior)))
|
||||||
|
|
||||||
|
|
||||||
|
def round_coordinates(coordinates):
|
||||||
|
# round coordinates, as in a list of x,y tuples
|
||||||
|
# filter out consecutive identical points
|
||||||
|
result = []
|
||||||
|
last_point = None
|
||||||
|
for x, y in coordinates:
|
||||||
|
point = (round(x, 2), round(y, 2))
|
||||||
|
if point == last_point:
|
||||||
|
continue
|
||||||
|
result.append(point)
|
||||||
|
last_point = point
|
||||||
|
return result
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue