diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2022-02-14 19:42:49 +0100 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2022-12-12 12:21:00 +0100 |
commit | b93a677cc30d92a0efa0ca8be52614ed1eb67329 (patch) | |
tree | e896479281059a23d5ceaa109ea8eba2e78ae2f0 /ishtar_common | |
parent | 5ecc278a4beded9d64b02744c8b80f39c8c04e19 (diff) | |
download | Ishtar-b93a677cc30d92a0efa0ca8be52614ed1eb67329.tar.bz2 Ishtar-b93a677cc30d92a0efa0ca8be52614ed1eb67329.zip |
Geodata redesign: town migrations
Diffstat (limited to 'ishtar_common')
-rw-r--r-- | ishtar_common/admin.py | 5 | ||||
-rw-r--r-- | ishtar_common/management/commands/migrate_to_geo_v4.py | 98 | ||||
-rw-r--r-- | ishtar_common/migrations/0220_auto_20220214_1920.py (renamed from ishtar_common/migrations/0220_auto_20220211_1630.py) | 12 | ||||
-rw-r--r-- | ishtar_common/models_common.py | 671 |
4 files changed, 459 insertions, 327 deletions
diff --git a/ishtar_common/admin.py b/ishtar_common/admin.py index 0ef72991f..b7cc5adf6 100644 --- a/ishtar_common/admin.py +++ b/ishtar_common/admin.py @@ -1022,7 +1022,7 @@ class TownAdmin(ImportGEOJSONActionAdmin, ImportActionAdmin): search_fields += ["numero_insee"] list_filter = ("areas",) form = AdminTownForm - autocomplete_fields = ["children"] + autocomplete_fields = ["children", "main_geodata", "geodata"] inlines = [TownParentInline] actions = [ export_as_csv_action(exclude=["center", "limit"]), @@ -2445,6 +2445,9 @@ class GeoVectorDataForm(forms.ModelForm): class GeoVectorDataAdmin(admin.ModelAdmin): model = models_common.GeoVectorData + search_fields = ["name"] + list_display = ["name", "origin", "data_type", "provider", "source_content_type"] + list_filter = ["origin", "data_type", "provider"] admin_site.register(models_common.GeoVectorData, GeoVectorDataAdmin)
\ No newline at end of file diff --git a/ishtar_common/management/commands/migrate_to_geo_v4.py b/ishtar_common/management/commands/migrate_to_geo_v4.py new file mode 100644 index 000000000..f747cb72d --- /dev/null +++ b/ishtar_common/management/commands/migrate_to_geo_v4.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from django.contrib.contenttypes.models import ContentType +import csv +import datetime +import os +import sys + +from django.conf import settings +from django.core.management.base import BaseCommand + +from ishtar_common import models_common + + +log_path = os.sep.join([settings.ROOT_PATH, "logs"]) +if not os.path.exists(log_path): + os.mkdir(log_path, mode=0o770) + + +def migrate(quiet=False, log=True): + changed = [] + # create towns + q = models_common.Town.objects.exclude( + center__isnull=True, limit__isnull=True).exclude(main_geodata__isnull=False) + nb = q.count() + town_content_type = ContentType.objects.get(app_label='ishtar_common', model='town') + data_type, __ = models_common.GeoDataType.objects.get_or_create( + txt_idx="town-limit", defaults={"label": "Limites commune"}) + provider, __ = models_common.GeoProviderType.objects.get_or_create( + txt_idx="france-ign", defaults={"label": "IGN"}) + for idx, town in enumerate(q.all()): + if not quiet: + sys.stdout.write(f"\r[{percent(idx, nb)}] Migrate towns {idx + 1}/{nb}") + sys.stdout.flush() + attrs = { + "name": town._generate_cached_label(), + "source_content_type": town_content_type, + "source_id": town.pk, + "data_type": data_type, + "provider": provider, + } + if town.limit: + attrs["multi_polygon"] = town.limit + else: + attrs["point_2d"] = town.center + data, created = models_common.GeoVectorData.objects.get_or_create(**attrs) + if created: + changed.append(["geovectordata", data.name, data.pk]) + town.main_geodata = data + town.save() + + if log and changed: + filename = f"geo_migration-created-{get_time().replace(':', '')}.txt" + path = os.sep.join([log_path, filename]) + with open(path, 'w+') as fle: + writer = csv.writer(fle) + writer.writerow(["model", "name", "id"]) + for change in changed: + writer.writerow(change) + if not quiet: + sys.stdout.write(f"log: {path} written.") + if not quiet: + sys.stdout.write("\n") + + +def percent(current, total): + return f"{(current + 1) / total * 100:.1f}".rjust(4, "0") + "%" + + +def get_time(): + return datetime.datetime.now().isoformat().split(".")[0] + + +class Command(BaseCommand): + help = "Migrate to new geo data management" + + def add_arguments(self, parser): + parser.add_argument( + "--quiet", dest="quiet", action="store_true", help="Quiet output" + ) + parser.add_argument( + "--log", dest="log", action="store_false", help="Log into a file" + ) + + def handle(self, *args, **options): + log = options["log"] + quiet = options["quiet"] + if not quiet: + sys.stdout.write(f"[{get_time()}] Processing migration\n") + errors = migrate(quiet=quiet, log=log) + if not errors: + if not quiet: + sys.stdout.write(f"[{get_time()}] Migration finished\n") + sys.exit() + if not quiet: + sys.stdout.write("\n".join(errors)) + sys.exit(1) diff --git a/ishtar_common/migrations/0220_auto_20220211_1630.py b/ishtar_common/migrations/0220_auto_20220214_1920.py index 159b65fcd..2f40eab00 100644 --- a/ishtar_common/migrations/0220_auto_20220211_1630.py +++ b/ishtar_common/migrations/0220_auto_20220214_1920.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.24 on 2022-02-11 16:30 +# Generated by Django 2.2.24 on 2022-02-14 19:20 import django.contrib.gis.db.models.fields import django.core.validators @@ -111,4 +111,14 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Geographic - Vector data', }, ), + migrations.AddField( + model_name='town', + name='geodata', + field=models.ManyToManyField(blank=True, related_name='related_items_ishtar_common_town', to='ishtar_common.GeoVectorData'), + ), + migrations.AddField( + model_name='town', + name='main_geodata', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='main_related_items_ishtar_common_town', to='ishtar_common.GeoVectorData'), + ), ] diff --git a/ishtar_common/models_common.py b/ishtar_common/models_common.py index ebf480ff0..cc3c61b05 100644 --- a/ishtar_common/models_common.py +++ b/ishtar_common/models_common.py @@ -1985,12 +1985,356 @@ class Canton(models.Model): return settings.JOINT.join((self.name, str(self.arrondissement))) +class SpatialReferenceSystem(GeneralType): + order = models.IntegerField(_("Order"), default=10) + auth_name = models.CharField(_("Authority name"), default="EPSG", max_length=256) + srid = models.IntegerField(_("Authority SRID")) + + class Meta: + verbose_name = _("Spatial reference system") + verbose_name_plural = _("Spatial reference systems") + ordering = ( + "order", + "label", + ) + + @classmethod + def get_documentation_string(cls): + """ + Used for automatic documentation generation + """ + doc = super(SpatialReferenceSystem, cls).get_documentation_string() + doc += ", **srid** {}, **auth_name** {}".format( + _("Authority SRID"), _("Authority name") + ) + return doc + + +post_save.connect(post_save_cache, sender=SpatialReferenceSystem) +post_delete.connect(post_save_cache, sender=SpatialReferenceSystem) + + +class GeoOriginType(HierarchicalType): + """ + ex: topographical surveys, georeferencing, ... + """ + + order = models.IntegerField(_("Order"), default=10) + + class Meta: + verbose_name = _("Geographic - Origin type") + verbose_name_plural = _("Geographic - Origin types") + ordering = ( + "order", + "label", + ) + + +class GeoDataType(HierarchicalType): + """ + ex: outline, z-sup, ... + """ + + order = models.IntegerField(_("Order"), default=10) + + class Meta: + verbose_name = _("Geographic - Data type") + verbose_name_plural = _("Geographic - Data types") + ordering = ( + "order", + "label", + ) + + +class GeoProviderType(HierarchicalType): + """ + ex: GeoNames, IGN, ... + """ + + order = models.IntegerField(_("Order"), default=10) + + class Meta: + verbose_name = _("Geographic - Provider type") + verbose_name_plural = _("Geographic - Provider types") + ordering = ( + "order", + "label", + ) + + +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("source_content_type", "source_id") + origin = models.ForeignKey( + GeoOriginType, + blank=True, + null=True, + on_delete=models.PROTECT, + verbose_name=_("Origin"), + ) + data_type = models.ForeignKey( + GeoDataType, + blank=True, + null=True, + on_delete=models.PROTECT, + verbose_name=_("Data type"), + ) + provider = models.ForeignKey( + GeoProviderType, + blank=True, + null=True, + on_delete=models.PROTECT, + verbose_name=_("Provider"), + ) + comment = models.TextField(_("Comment"), default="", blank=True) + 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 other sources + # 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_2d = models.PointField(_("Point (2D)"), blank=True, null=True) + point_3d = models.PointField(_("Point (3D)"), blank=True, null=True, dim=3) + multi_points = models.MultiPointField(_("Multi points"), blank=True, null=True) + multi_line = models.MultiLineStringField(_("Multi line"), 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 __str__(self): + name = self.name + if self.data_type: + name += f" ({str(self.data_type).lower()})" + return name + + 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_3d and dim == 3: + point = self.point_3d.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 GeographicItem(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, blank=True, related_name="related_items_%(app_label)s_%(class)s" + ) + + class Meta: + abstract = True + + def save( + self, force_insert=False, force_update=False, using=None, update_fields=None + ): + super(GeographicItem, self).save( + force_insert=force_insert, + force_update=force_update, + using=using, + update_fields=update_fields, + ) + if self.main_geodata and not self.geodata.filter(pk=self.main_geodata.pk): + self.geodata.add(self.main_geodata) + + class TownManager(models.Manager): def get_by_natural_key(self, numero_insee, year): return self.get(numero_insee=numero_insee, year=year) -class Town(Imported, models.Model): +class Town(GeographicItem, Imported, models.Model): name = models.CharField(_("Name"), max_length=100) surface = models.IntegerField(_("Surface (m2)"), blank=True, null=True) center = models.PointField( @@ -2868,330 +3212,7 @@ class DynamicRequest: return alt_names -class SpatialReferenceSystem(GeneralType): - order = models.IntegerField(_("Order"), default=10) - auth_name = models.CharField(_("Authority name"), default="EPSG", max_length=256) - srid = models.IntegerField(_("Authority SRID")) - - class Meta: - verbose_name = _("Spatial reference system") - verbose_name_plural = _("Spatial reference systems") - ordering = ( - "order", - "label", - ) - - @classmethod - def get_documentation_string(cls): - """ - Used for automatic documentation generation - """ - doc = super(SpatialReferenceSystem, cls).get_documentation_string() - doc += ", **srid** {}, **auth_name** {}".format( - _("Authority SRID"), _("Authority name") - ) - return doc - - -post_save.connect(post_save_cache, sender=SpatialReferenceSystem) -post_delete.connect(post_save_cache, sender=SpatialReferenceSystem) - - -class GeoOriginType(HierarchicalType): - """ - ex: topographical surveys, georeferencing, ... - """ - - order = models.IntegerField(_("Order"), default=10) - - class Meta: - verbose_name = _("Geographic - Origin type") - verbose_name_plural = _("Geographic - Origin types") - ordering = ( - "order", - "label", - ) - - -class GeoDataType(HierarchicalType): - """ - ex: outline, z-sup, ... - """ - - order = models.IntegerField(_("Order"), default=10) - - class Meta: - verbose_name = _("Geographic - Data type") - verbose_name_plural = _("Geographic - Data types") - ordering = ( - "order", - "label", - ) - - -class GeoProviderType(HierarchicalType): - """ - ex: GeoNames, IGN, ... - """ - - order = models.IntegerField(_("Order"), default=10) - - class Meta: - verbose_name = _("Geographic - Provider type") - verbose_name_plural = _("Geographic - Provider types") - ordering = ( - "order", - "label", - ) - - -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("source_content_type", "source_id") - origin = models.ForeignKey( - GeoOriginType, - blank=True, - null=True, - on_delete=models.PROTECT, - verbose_name=_("Origin"), - ) - data_type = models.ForeignKey( - GeoDataType, - blank=True, - null=True, - on_delete=models.PROTECT, - verbose_name=_("Data type"), - ) - provider = models.ForeignKey( - GeoProviderType, - blank=True, - null=True, - on_delete=models.PROTECT, - verbose_name=_("Provider"), - ) - comment = models.TextField(_("Comment"), default="", blank=True) - 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 other sources - # 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_2d = models.PointField(_("Point (2D)"), blank=True, null=True) - point_3d = models.PointField(_("Point (3D)"), blank=True, null=True, dim=3) - multi_points = models.MultiPointField(_("Multi points"), blank=True, null=True) - multi_line = models.MultiLineStringField(_("Multi line"), 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_3d and dim == 3: - point = self.point_3d.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, blank=True, related_name="related_items_%(app_label)s_%(class)s" - ) - +class GeoItem(GeographicItem): GEO_SOURCE = (("T", _("Town")), ("P", _("Precise")), ("M", _("Polygon"))) # gis |