diff options
Diffstat (limited to 'ishtar_common/models_common.py')
-rw-r--r-- | ishtar_common/models_common.py | 671 |
1 files changed, 346 insertions, 325 deletions
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 |