diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2022-02-10 19:44:05 +0100 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2022-12-12 12:21:00 +0100 |
commit | 39551b72a41b5ad00f8d200db2b49e4f3e02515f (patch) | |
tree | 10428f79f2c1f1f4e92ddaf3ba0f7d7efedb642c /ishtar_common/models_common.py | |
parent | f6563b7ddb44b180edd45c03bd4cf0fb6d48b331 (diff) | |
download | Ishtar-39551b72a41b5ad00f8d200db2b49e4f3e02515f.tar.bz2 Ishtar-39551b72a41b5ad00f8d200db2b49e4f3e02515f.zip |
Geodata redesign: new model
Diffstat (limited to 'ishtar_common/models_common.py')
-rw-r--r-- | ishtar_common/models_common.py | 222 |
1 files changed, 221 insertions, 1 deletions
diff --git a/ishtar_common/models_common.py b/ishtar_common/models_common.py index 921b5d59b..5cbb5da1e 100644 --- a/ishtar_common/models_common.py +++ b/ishtar_common/models_common.py @@ -22,6 +22,7 @@ from django.apps import apps from django.conf import settings from django.contrib.auth.models import User, Group from django.contrib.contenttypes.models import ContentType +from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.gis.db import models from django.contrib.gis.geos import Point from django.contrib.gis.gdal.error import GDALException @@ -65,6 +66,7 @@ from ishtar_common.utils import ( merge_tsvectors, cached_label_changed, post_save_geo, + post_save_geodata, task, duplicate_item, get_generated_id, @@ -1230,6 +1232,7 @@ class HistoricalRecords(BaseHistoricalRecords): item = q.all()[0] if attr in item.history_m2m: return item.history_m2m[attr] + return _get_history_m2m def get_serialize_call(attr): @@ -1237,6 +1240,7 @@ class HistoricalRecords(BaseHistoricalRecords): q = model.objects.filter(pk=getattr(self, model._meta.pk.attname)) if q.count(): return getattr(q.all()[0], attr)() + return _get_serialize_call def get_serialize_properties(attr): @@ -1244,6 +1248,7 @@ class HistoricalRecords(BaseHistoricalRecords): q = model.objects.filter(pk=getattr(self, model._meta.pk.attname)) if q.count(): return getattr(q.all()[0], attr) + return _get_serialize_properties extra_fields = super().get_extra_fields(model, fields) @@ -2889,7 +2894,221 @@ post_save.connect(post_save_cache, sender=SpatialReferenceSystem) post_delete.connect(post_save_cache, sender=SpatialReferenceSystem) +class GeoVectorData(models.Model): + name = models.CharField(_("Name"), default=_("Default"), max_length=200) + source_content_type = models.ForeignKey( + ContentType, related_name="content_type_geovectordata", + on_delete=models.CASCADE) + source_id = models.PositiveIntegerField() + source = GenericForeignKey('content_type', 'object_id') + + x = models.FloatField(_("X"), blank=True, null=True, help_text=_("User input")) + y = models.FloatField(_("Y"), blank=True, null=True, help_text=_("User input")) + z = models.FloatField(_("Z"), blank=True, null=True, help_text=_("User input")) + # x == cached_x if user input else get it from point_2d + # cached is converted to the display SRID + cached_x = models.FloatField(_("X (cached)"), blank=True, null=True) + cached_y = models.FloatField(_("Y (cached)"), blank=True, null=True) + cached_z = models.FloatField(_("Z (cached)"), blank=True, null=True) + estimated_error_x = models.FloatField( + _("Estimated error for X"), blank=True, null=True + ) + estimated_error_y = models.FloatField( + _("Estimated error for Y"), blank=True, null=True + ) + estimated_error_z = models.FloatField( + _("Estimated error for Z"), blank=True, null=True + ) + spatial_reference_system = models.ForeignKey( + SpatialReferenceSystem, + verbose_name=_("Spatial Reference System"), + blank=True, + null=True, + on_delete=models.PROTECT, + ) + point = models.PointField(_("Point"), blank=True, null=True, dim=3) + point_2d = models.PointField(_("Point (2D)"), blank=True, null=True) + multi_polygon = models.MultiPolygonField(_("Multi polygon"), blank=True, null=True) + need_update = models.BooleanField(_("Need update"), default=False) + + class Meta: + verbose_name = _("Geographic vector data") + verbose_name_plural = _("Geographic vector data") + + def display_coordinates(self, rounded=5, dim=2, cache=True): + srid = None + profile = get_current_profile() + if profile.display_srs and profile.display_srs.srid: + srid = profile.display_srs.srid + return self.get_coordinates(rounded=rounded, srid=srid, dim=dim, cache=cache) + + def get_coordinates(self, rounded=5, srid: int = None, dim=2, cache=False): + if dim not in (2, 3): + raise ValueError(_("Only 2 or 3 dimension")) + if cache: + coordinates = [self.cached_x, self.cached_y] + if dim == 3: + coordinates.append(self.cached_z) + else: + if self.x and self.y: # user input + if not srid or srid == self.spatial_reference_system.srid: + coordinates = [self.x, self.y] + if dim == 3: + coordinates.append(self.z) + else: + args = {"x": self.x, "y": self.y, + "srid": self.spatial_reference_system.srid} + if dim == 3: + args["z"] = self.z + point = Point(**args).transform(srid, clone=True) + coordinates = [point.x, point.y] + if dim == 3: + coordinates.append(point.z) + elif self.point_2d and dim == 2: + point = self.point_2d.transform(srid, clone=True) + coordinates = [point.x, point.y] + elif self.point and dim == 3: + point = self.point.transform(srid, clone=True) + coordinates = [point.x, point.y, point.z] + else: + return + if not rounded: + return coordinates + return [round(coord, rounded) for coord in coordinates] + + def get_coordinates_from_polygon(self, rounded=5, srid: int = None): + if self.multi_polygon: + return self.convert_coordinates(self.multi_polygon.centroid, + rounded=rounded, srid=srid) + + def get_x(self, srid: int = None) -> float: + coord = self.get_coordinates(srid) + if coord: + return coord[0] + + def get_y(self, srid: int = None) -> float: + coord = self.get_coordinates(srid) + if coord: + return coord[1] + + def get_z(self, srid: int = None) -> float: + coord = self.get_coordinates(srid, dim=3) + if coord: + return coord[2] + + @property + def display_spatial_reference_system(self): + profile = get_current_profile() + if not profile.display_srs or not profile.display_srs.srid: + return self.spatial_reference_system + return profile.display_srs + + def get_geo_items(self, get_polygons, rounded=5): + label = self.label if hasattr(self, "label") else self.short_label + dct = {"type": "Feature", "geometry": {}, "properties": {"label": label}} + if get_polygons: + list_coords = [] + if self.multi_polygon: + for polygon in self.multi_polygon: + list_coords.append([]) + for linear_ring in range(len(polygon)): + list_coords[-1].append([]) + for coords in polygon[linear_ring].coords: + point_2d = Point( + coords[0], coords[1], srid=self.multi_polygon.srid + ) + list_coords[-1][linear_ring].append( + self.convert_coordinates(point_2d, rounded) + ) + dct["geometry"]["type"] = "MultiPolygon" + dct["geometry"]["coordinates"] = list_coords + else: + dct["geometry"]["type"] = "Point" + coords = self.display_coordinates() + if coords: + dct["geometry"]["coordinates"] = coords + elif self.multi_polygon: + dct["geometry"]["coordinates"] = self.convert_coordinates( + self.multi_polygon.centroid, rounded + ) + else: + return {} + return dct + + def convert_coordinates(self, point_2d, rounded=5, srid=None): + if not srid: + profile = get_current_profile() + if profile.display_srs and profile.display_srs.srid: + srid = profile.display_srs.srid + if not srid: + x, y = point_2d.x, point_2d.y + else: + point = point_2d.transform(srid, clone=True) + x, y = point.x, point.y + if rounded: + return [round(x, rounded), round(y, rounded)] + return [x, y] + + def most_precise_geo(self): + if self.multi_polygon: + return "multi_polygon" + if self.point_2d: + return "point" + + def _geojson_serialize(self, geom_attr): + if not hasattr(self, geom_attr): + return "" + geojson = serialize( + "geojson", + self.__class__.objects.filter(pk=self.pk), + geometry_field=geom_attr, + fields=("name",), + ) + geojson_dct = json.loads(geojson) + profile = get_current_profile() + precision = profile.point_precision + + features = geojson_dct.pop("features") + for idx in range(len(features)): + feature = features[idx] + lbl = feature["properties"].pop("name") + feature["properties"]["name"] = lbl + feature["properties"]["id"] = self.pk + if precision is not None: + geom_type = feature["geometry"].get("type", None) + if geom_type == "Point": + feature["geometry"]["coordinates"] = [ + round(coord, precision) + for coord in feature["geometry"]["coordinates"] + ] + geojson_dct["features"] = features + geojson_dct["link_template"] = simple_link_to_window(self).replace( + "999999", "<pk>" + ) + geojson = json.dumps(geojson_dct) + return geojson + + @property + def point_2d_geojson(self): + return self._geojson_serialize("point_2d") + + @property + def multi_polygon_geojson(self): + return self._geojson_serialize("multi_polygon") + + +post_save.connect(post_save_geodata, sender=GeoVectorData) + + class GeoItem(models.Model): + main_geodata = models.ForeignKey( + GeoVectorData, on_delete=models.SET_NULL, blank=True, null=True, + related_name="main_related_items_%(app_label)s_%(class)s" + ) + geodata = models.ManyToManyField( + GeoVectorData, null=True, related_name="related_items_%(app_label)s_%(class)s" + ) + GEO_SOURCE = (("T", _("Town")), ("P", _("Precise")), ("M", _("Polygon"))) # gis @@ -3390,7 +3609,8 @@ class SerializeItem: ): # print(field.name, self.__class__, self) values = [ - v.full_serialize(search_model, recursion=True) for v in values.all() + v.full_serialize(search_model, recursion=True) + for v in values.all() ] else: if first_value in self.SERIALIZATION_FILES: |