2017-10-28 21:36:52 +02:00
|
|
|
import math
|
2017-05-06 17:24:09 +02:00
|
|
|
from collections import OrderedDict
|
2017-05-08 21:55:45 +02:00
|
|
|
|
2017-10-10 13:26:46 +02:00
|
|
|
from django.db import models
|
2017-10-28 21:36:52 +02:00
|
|
|
from django.utils.functional import cached_property
|
2017-10-10 13:26:46 +02:00
|
|
|
from django.utils.translation import ugettext_lazy as _
|
2017-05-07 12:06:13 +02:00
|
|
|
from shapely.geometry import Point, mapping
|
2017-05-06 17:24:09 +02:00
|
|
|
|
2017-06-21 12:47:28 +02:00
|
|
|
from c3nav.mapdata.models.base import SerializableMixin
|
2017-05-06 17:24:09 +02:00
|
|
|
from c3nav.mapdata.utils.json import format_geojson
|
|
|
|
|
|
|
|
|
2017-10-10 17:49:30 +02:00
|
|
|
class GeometryManager(models.Manager):
|
|
|
|
def within(self, minx, miny, maxx, maxy):
|
|
|
|
return self.get_queryset().filter(minx__lte=maxx, maxx__gte=minx, miny__lte=maxy, maxy__gte=miny)
|
|
|
|
|
|
|
|
|
2017-06-21 12:47:28 +02:00
|
|
|
class GeometryMixin(SerializableMixin):
|
2017-05-06 17:24:09 +02:00
|
|
|
"""
|
|
|
|
A map feature with a geometry
|
|
|
|
"""
|
2017-05-09 12:50:32 +02:00
|
|
|
geometry = None
|
2017-10-10 13:26:46 +02:00
|
|
|
minx = models.DecimalField(_('min x coordinate'), max_digits=6, decimal_places=2, db_index=True)
|
|
|
|
miny = models.DecimalField(_('min y coordinate'), max_digits=6, decimal_places=2, db_index=True)
|
|
|
|
maxx = models.DecimalField(_('max x coordinate'), max_digits=6, decimal_places=2, db_index=True)
|
|
|
|
maxy = models.DecimalField(_('max y coordinate'), max_digits=6, decimal_places=2, db_index=True)
|
2017-10-10 17:49:30 +02:00
|
|
|
objects = GeometryManager()
|
2017-05-09 12:50:32 +02:00
|
|
|
|
2017-05-06 17:24:09 +02:00
|
|
|
class Meta:
|
|
|
|
abstract = True
|
2017-10-10 17:49:30 +02:00
|
|
|
base_manager_name = 'objects'
|
2017-05-06 17:24:09 +02:00
|
|
|
|
2017-10-23 19:25:15 +02:00
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.orig_geometry = None if 'geometry' in self.get_deferred_fields() else self.geometry
|
|
|
|
|
2017-06-16 18:38:41 +02:00
|
|
|
def get_geojson_properties(self, *args, **kwargs) -> dict:
|
2017-05-27 16:19:49 +02:00
|
|
|
result = OrderedDict((
|
2017-05-06 17:24:09 +02:00
|
|
|
('type', self.__class__.__name__.lower()),
|
2017-06-16 18:19:52 +02:00
|
|
|
('id', self.pk),
|
2017-05-06 17:24:09 +02:00
|
|
|
))
|
2017-05-27 16:19:49 +02:00
|
|
|
if getattr(self, 'bounds', False):
|
|
|
|
result['bounds'] = True
|
|
|
|
return result
|
2017-05-06 17:24:09 +02:00
|
|
|
|
2017-06-16 18:38:41 +02:00
|
|
|
def to_geojson(self, instance=None) -> dict:
|
2017-05-29 10:14:39 +02:00
|
|
|
result = OrderedDict((
|
2017-05-06 17:24:09 +02:00
|
|
|
('type', 'Feature'),
|
2017-06-16 18:38:41 +02:00
|
|
|
('properties', self.get_geojson_properties(instance=instance)),
|
2017-05-06 17:24:09 +02:00
|
|
|
('geometry', format_geojson(mapping(self.geometry), round=False)),
|
|
|
|
))
|
2017-05-29 10:14:39 +02:00
|
|
|
original_geometry = getattr(self, 'original_geometry', None)
|
|
|
|
if original_geometry:
|
|
|
|
result['original_geometry'] = format_geojson(mapping(original_geometry), round=False)
|
|
|
|
return result
|
2017-05-06 17:24:09 +02:00
|
|
|
|
2017-05-11 21:30:29 +02:00
|
|
|
@classmethod
|
|
|
|
def serialize_type(cls, geomtype=True, **kwargs):
|
|
|
|
result = super().serialize_type()
|
|
|
|
if geomtype:
|
|
|
|
result['geomtype'] = cls._meta.get_field('geometry').geomtype
|
|
|
|
return result
|
|
|
|
|
2017-10-28 21:36:52 +02:00
|
|
|
@cached_property
|
|
|
|
def centroid(self):
|
|
|
|
return self.geometry.centroid
|
|
|
|
|
2017-10-31 15:22:12 +01:00
|
|
|
def serialize(self, **kwargs):
|
|
|
|
result = super().serialize(**kwargs)
|
|
|
|
if 'geometry' in result:
|
2017-05-11 19:36:49 +02:00
|
|
|
result.move_to_end('geometry')
|
|
|
|
return result
|
|
|
|
|
2017-10-28 21:36:52 +02:00
|
|
|
def _serialize(self, geometry=True, simple_geometry=False, **kwargs):
|
|
|
|
result = super()._serialize(simple_geometry=simple_geometry, **kwargs)
|
2017-05-11 19:36:49 +02:00
|
|
|
if geometry:
|
|
|
|
result['geometry'] = format_geojson(mapping(self.geometry), round=False)
|
2017-10-28 21:36:52 +02:00
|
|
|
if simple_geometry:
|
|
|
|
result['point'] = (self.level_id, ) + tuple(round(i, 2) for i in self.centroid.coords[0])
|
|
|
|
if not isinstance(self.geometry, Point):
|
2017-10-29 11:34:23 +01:00
|
|
|
result['bounds'] = ((int(math.floor(self.minx)), int(math.floor(self.miny))),
|
|
|
|
(int(math.ceil(self.maxx)), int(math.ceil(self.maxy))))
|
2017-05-11 19:36:49 +02:00
|
|
|
return result
|
|
|
|
|
2017-05-06 17:24:09 +02:00
|
|
|
def get_shadow_geojson(self):
|
2017-05-09 09:36:08 +02:00
|
|
|
pass
|
2017-05-06 17:24:09 +02:00
|
|
|
|
2017-05-09 13:16:36 +02:00
|
|
|
def contains(self, x, y) -> bool:
|
2017-05-06 17:24:09 +02:00
|
|
|
return self.geometry.contains(Point(x, y))
|
2017-10-10 13:26:46 +02:00
|
|
|
|
|
|
|
def recalculate_bounds(self):
|
|
|
|
self.minx, self.miny, self.maxx, self.maxy = self.geometry.bounds
|
|
|
|
|
2017-10-23 19:25:15 +02:00
|
|
|
@property
|
|
|
|
def geometry_changed(self):
|
2017-10-24 00:11:02 +02:00
|
|
|
if self.orig_geometry is None:
|
|
|
|
return True
|
|
|
|
if self.geometry is self.orig_geometry:
|
|
|
|
return False
|
|
|
|
if not self.geometry.almost_equals(self.orig_geometry, 1):
|
|
|
|
return True
|
|
|
|
field = self._meta.get_field('geometry')
|
|
|
|
rounded = field.to_python(field.get_prep_value(self.geometry))
|
|
|
|
if not rounded.almost_equals(self.orig_geometry, 2):
|
|
|
|
return True
|
|
|
|
return False
|
2017-10-23 19:25:15 +02:00
|
|
|
|
|
|
|
def get_changed_geometry(self):
|
|
|
|
return self.geometry if self.orig_geometry is None else self.geometry.symmetric_difference(self.orig_geometry)
|
|
|
|
|
2017-10-10 13:26:46 +02:00
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
self.recalculate_bounds()
|
|
|
|
super().save(*args, **kwargs)
|