From 6ad2c51cd799001c8176afab39e8b3a905712ae3 Mon Sep 17 00:00:00 2001 From: LePetitTim Date: Tue, 7 Mar 2023 10:15:43 +0100 Subject: [PATCH 01/19] =?UTF-8?q?=E2=9C=A8=20Add=20streams=20for=20descrip?= =?UTF-8?q?tions/=20administrative=20files=20/=20Interventions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- georiviere/description/models.py | 2 ++ georiviere/river/models.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/georiviere/description/models.py b/georiviere/description/models.py index aaf5f159..a6f67b91 100644 --- a/georiviere/description/models.py +++ b/georiviere/description/models.py @@ -397,6 +397,7 @@ def get_create_label(cls): Land.add_property('proceedings', Proceeding.within_buffer, _("Proceeding")) Land.add_property('knowledges', Knowledge.within_buffer, _("Knowledge")) +Morphology.add_property('streams', lambda self: [self.topology.stream], _("Stream")) Morphology.add_property('status', lambda self: self.get_topology('status'), _("Status")) Morphology.add_property('lands', Land.within_buffer, _("Lands")) Morphology.add_property('usages', Usage.within_buffer, _("Usages")) @@ -405,6 +406,7 @@ def get_create_label(cls): Morphology.add_property('proceedings', Proceeding.within_buffer, _("Proceeding")) Morphology.add_property('knowledges', Knowledge.within_buffer, _("Knowledge")) +Status.add_property('streams', lambda self: [self.topology.stream], _("Stream")) Status.add_property('morphologies', lambda self: self.get_topology('morphology'), _("Morphologies")) Status.add_property('lands', Land.within_buffer, _("Lands")) Status.add_property('usages', Usage.within_buffer, _("Usages")) diff --git a/georiviere/river/models.py b/georiviere/river/models.py index 7f25da31..3afe376f 100644 --- a/georiviere/river/models.py +++ b/georiviere/river/models.py @@ -198,3 +198,6 @@ class Meta: Stream.add_property('studies', Study.within_buffer, _("Studies")) Stream.add_property('knowledges', Knowledge.within_buffer, _("Knowledges")) Stream.add_property('proceedings', Proceeding.within_buffer, _("Proceedings")) + +Intervention.add_property('streams', Stream.within_buffer, _("Stream")) +AdministrativeFile.add_property('streams', Stream.within_buffer, _("Stream")) \ No newline at end of file From eda48f94bd7ce5729464d68ce135a3abf95e443a Mon Sep 17 00:00:00 2001 From: LePetitTim Date: Tue, 7 Mar 2023 10:37:12 +0100 Subject: [PATCH 02/19] =?UTF-8?q?=E2=9C=85=20Add=20factories=20with=20stre?= =?UTF-8?q?am?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- georiviere/description/tests/factories.py | 23 ++++++-- georiviere/description/tests/test_views.py | 59 ++++++++++++++++++- .../tests/factories.py | 3 +- georiviere/knowledge/tests/factories.py | 3 +- georiviere/maintenance/tests/factories.py | 26 ++++++++ georiviere/maintenance/tests/test_views.py | 3 +- georiviere/river/tests/factories.py | 11 ++++ georiviere/tests/factories.py | 15 +++++ 8 files changed, 132 insertions(+), 11 deletions(-) diff --git a/georiviere/description/tests/factories.py b/georiviere/description/tests/factories.py index c22854de..8bc88007 100644 --- a/georiviere/description/tests/factories.py +++ b/georiviere/description/tests/factories.py @@ -4,11 +4,11 @@ from georiviere.tests.factories import BaseLineStringFactory from georiviere.river.models import Stream -from georiviere.river.tests.factories import TopologyFactory +from georiviere.river.tests.factories import TopologyFactory, WithStreamFactory from .. import models -class UsageFactory(BaseLineStringFactory): +class UsageFactory(WithStreamFactory, BaseLineStringFactory): class Meta: model = models.Usage @@ -25,7 +25,7 @@ class Meta: label = Sequence(lambda n: "Usage type %s" % n) -class MorphologyFactory(BaseLineStringFactory): +class MorphologyFactory(WithStreamFactory, BaseLineStringFactory): topology = SubFactory(TopologyFactory) description = fuzzy.FuzzyText(length=200) @@ -33,6 +33,17 @@ class Meta: model = models.Morphology +class MorphologyOnStreamFactory(WithStreamFactory, BaseLineStringFactory): + class Meta: + model = models.Stream + + @classmethod + def create(cls, **kwargs): + stream = cls._generate(enums.CREATE_STRATEGY, kwargs) + morphology = stream.topologies.filter(morphology__isnull=False).get().morphology + return morphology + + class PlanLayoutTypeFactory(django.DjangoModelFactory): class Meta: model = models.PlanLayoutType @@ -110,7 +121,7 @@ class Meta: label = Sequence(lambda n: f'Control type {n}') -class LandFactory(BaseLineStringFactory): +class LandFactory(WithStreamFactory, BaseLineStringFactory): class Meta: model = models.Land @@ -128,7 +139,7 @@ class Meta: label = Sequence(lambda n: 'Status type {}'.format(n)) -class StatusFactory(BaseLineStringFactory): +class StatusFactory(WithStreamFactory, BaseLineStringFactory): class Meta: model = models.Status @@ -141,7 +152,7 @@ def status_types(obj, create, extracted=None, **kwargs): obj.status_types.set(extracted) -class StatusOnStreamFactory(BaseLineStringFactory): +class StatusOnStreamFactory(WithStreamFactory, BaseLineStringFactory): class Meta: model = Stream diff --git a/georiviere/description/tests/test_views.py b/georiviere/description/tests/test_views.py index b7348217..86940023 100644 --- a/georiviere/description/tests/test_views.py +++ b/georiviere/description/tests/test_views.py @@ -8,8 +8,9 @@ from georiviere.river.tests.factories import StreamFactory from georiviere.tests import CommonRiverTest from .factories import LandFactory, LandTypeFactory, UsageFactory, UsageTypeFactory, StatusTypeFactory, \ - StatusOnStreamFactory, ControlTypeFactory -from ..models import Land, Status, Usage + StatusOnStreamFactory, MorphologyOnStreamFactory, PlanLayoutTypeFactory, ControlTypeFactory + +from ..models import Land, Morphology, Status, Usage @override_settings(MEDIA_ROOT=TemporaryDirectory().name) @@ -136,3 +137,57 @@ def get_good_data(self): def _check_update_geom_permission(self, response): self.assertIn(b'.modifiable = false;', response.content) + + +@override_settings(MEDIA_ROOT=TemporaryDirectory().name) +class MorphologyViewTestCase(TopologyTestCase): + model = Morphology + modelfactory = MorphologyOnStreamFactory + + def get_expected_json_attrs(self): + return { + 'id': self.obj.pk, + 'date_update': '2020-03-17T00:00:00Z', + 'date_insert': '2020-03-17T00:00:00Z', + 'description': '', + 'bank_state_left': None, + 'bank_state_right': None, + 'full_edge_height': 0.0, + 'full_edge_width': 0.0, + 'facies_diversity': None, + 'good_working_space_left': None, + 'good_working_space_right': None, + 'granulometric_diversity': None, + 'habitats_diversity': None, + 'main_flow': None, + 'main_habitat': None, + 'secondary_flows': [], + 'secondary_habitats': [], + 'sediment_dynamic': None, + 'plan_layout': None, + 'length': self.obj.length, + 'geom_3d': self.obj.geom_3d.ewkt, + 'ascent': self.obj.ascent, + 'descent': self.obj.descent, + 'min_elevation': self.obj.min_elevation, + 'max_elevation': self.obj.max_elevation, + 'slope': self.obj.slope, + 'geom': self.obj.geom.ewkt, + 'topology': self.obj.topology.pk, + } + + def get_good_data(self): + structure = StructureFactory.create() + plan_layout = PlanLayoutTypeFactory.create() + stream = StreamFactory.create() + + topology = stream.topologies.filter(morphology__isnull=False).get() + temp_data = self.modelfactory.build(structure=structure) + return { + 'geom': temp_data.geom.ewkt, + 'plan_layout': plan_layout.pk, + 'topology': topology.pk, + } + + def _check_update_geom_permission(self, response): + self.assertIn(b'.modifiable = false;', response.content) diff --git a/georiviere/finances_administration/tests/factories.py b/georiviere/finances_administration/tests/factories.py index 551b357b..926439bb 100644 --- a/georiviere/finances_administration/tests/factories.py +++ b/georiviere/finances_administration/tests/factories.py @@ -6,6 +6,7 @@ from georiviere.studies.tests.factories import StudyFactory from georiviere.finances_administration import models +from georiviere.river.tests.factories import WithStreamFactory class AdministrativeDeferralFactory(factory.django.DjangoModelFactory): @@ -36,7 +37,7 @@ class Meta: label = factory.Sequence(lambda n: "Domain %s" % n) -class AdministrativeFileFactory(factory.django.DjangoModelFactory): +class AdministrativeFileFactory(WithStreamFactory, factory.django.DjangoModelFactory): class Meta: model = models.AdministrativeFile diff --git a/georiviere/knowledge/tests/factories.py b/georiviere/knowledge/tests/factories.py index 71556781..c63e9111 100644 --- a/georiviere/knowledge/tests/factories.py +++ b/georiviere/knowledge/tests/factories.py @@ -2,6 +2,7 @@ from mapentity.tests.factories import PointFactory from georiviere.knowledge import models +from georiviere.river.tests.factories import WithStreamFactory class KnowledgeTypeFactory(django.DjangoModelFactory): @@ -11,7 +12,7 @@ class Meta: label = fuzzy.FuzzyText() -class KnowledgeFactory(PointFactory): +class KnowledgeFactory(WithStreamFactory, PointFactory): class Meta: model = models.Knowledge diff --git a/georiviere/maintenance/tests/factories.py b/georiviere/maintenance/tests/factories.py index a4844613..1bd8e58f 100644 --- a/georiviere/maintenance/tests/factories.py +++ b/georiviere/maintenance/tests/factories.py @@ -1,6 +1,8 @@ from factory import django, Sequence, SubFactory, post_generation +from georiviere.description.tests.factories import LandFactory from georiviere.maintenance import models +from georiviere.river.tests.factories import StreamFactory, WithStreamFactory class InterventionStatusFactory(django.DjangoModelFactory): @@ -44,3 +46,27 @@ class Meta: def create_intervention(obj, create, extracted, **kwargs): if obj.pk: obj.disorders.add(InterventionDisorderFactory.create()) + + +class InterventionWithTargetFactory(InterventionFactory): + class Meta: + model = models.Intervention + + @post_generation + def create_target_intervention(obj, create, extracted, **kwargs): + land = LandFactory.create(geom='SRID=2154;POINT (700040 6600040)') + obj.target = land + if create: + obj.save() + + @post_generation + def with_stream(obj, create, with_stream): + # First generate create_target_intervention and then with stream. + # We add the with stream here and do not use WithStreamFactory because it needs to be after + # create_target_intervention + if not create or not with_stream: + return + if with_stream and obj.geom: + # Status / Morphology is already on a stream + # It should not add this next stream in distance to source + StreamFactory.create(geom_around=obj.geom) \ No newline at end of file diff --git a/georiviere/maintenance/tests/test_views.py b/georiviere/maintenance/tests/test_views.py index 2390bf0a..c5e80c83 100644 --- a/georiviere/maintenance/tests/test_views.py +++ b/georiviere/maintenance/tests/test_views.py @@ -7,7 +7,7 @@ from georiviere.maintenance.models import Intervention from georiviere.maintenance.tests.factories import ( InterventionFactory, InterventionStatusFactory, - InterventionDisorderFactory, InterventionStakeFactory + InterventionDisorderFactory, InterventionStakeFactory, InterventionWithTargetFactory ) from georiviere.knowledge.tests.factories import KnowledgeFactory @@ -56,6 +56,7 @@ def get_good_data(self): class InterventionWithTargetViewsTest(InterventionViewsTest): """Test Intervention linked to a target""" + modelfactory = InterventionWithTargetFactory def get_good_data(self): """Test creation of an intervention not linked to a knowledge""" diff --git a/georiviere/river/tests/factories.py b/georiviere/river/tests/factories.py index 115e95c6..691100c3 100644 --- a/georiviere/river/tests/factories.py +++ b/georiviere/river/tests/factories.py @@ -12,6 +12,17 @@ class Meta: model = ClassificationWaterPolicy +class WithStreamFactory(factory.django.DjangoModelFactory): + @factory.post_generation + def with_stream(obj, create, with_stream): + if not create or not with_stream: + return + if with_stream and obj.geom: + # Status / Morphology is already on a stream + # It should not add this next stream in distance to source + StreamFactory.create(geom_around=obj.geom) + + class StreamFactory(BaseLineStringFactory): name = factory.Sequence(lambda n: 'stream-%d' % n) structure = factory.SubFactory(StructureFactory) diff --git a/georiviere/tests/factories.py b/georiviere/tests/factories.py index 777dc796..ddb0456e 100644 --- a/georiviere/tests/factories.py +++ b/georiviere/tests/factories.py @@ -25,6 +25,21 @@ def geom(self): linestring.transform(2154) return linestring + @factory.post_generation + def geom_around(obj, create, geometry): + if not create: + return + if geometry: + coords = obj.geom.coords + if type(geometry.coords[0]) == tuple: + coords = coords + (tuple([coord + 1 for coord in geometry.coords[0]]), ) + else: + coords = coords + (tuple([coord + 1 for coord in geometry.coords]), ) + + linestring = LineString(coords, srid=2154) + obj.geom = linestring + obj.save() + class UserAllPermsFactory(UserFactory): is_staff = True From 0d78073f4816f3b325e4056f60021256057f4c2d Mon Sep 17 00:00:00 2001 From: LePetitTim Date: Tue, 7 Mar 2023 10:38:15 +0100 Subject: [PATCH 03/19] =?UTF-8?q?=E2=9C=A8=20Add=20functions=20Closestpoin?= =?UTF-8?q?t=20/=20linelocatepoint/=20length?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- georiviere/functions.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/georiviere/functions.py b/georiviere/functions.py index 546c4f4c..f074e9ad 100644 --- a/georiviere/functions.py +++ b/georiviere/functions.py @@ -4,6 +4,20 @@ from georiviere.fields import ElevationInfosField +class ClosestPoint(GeomOutputGeoFunc): + geom_param_pos = (0, 1) + + +class LineLocatePoint(GeoFunc): + geom_param_pos = (0, 1) + output_field = FloatField() + + +class Length(GeoFunc): + """ ST_Length postgis function """ + output_field = FloatField() + + class ElevationInfos(GeoFunc): function = 'ft_elevation_infos' geom_param_pos = (0, ) From be3dea783826c55c4cb15f44406f05dd95451e77 Mon Sep 17 00:00:00 2001 From: LePetitTim Date: Tue, 7 Mar 2023 10:39:32 +0100 Subject: [PATCH 04/19] =?UTF-8?q?=E2=9C=A8=20Add=20signals=20distance=20to?= =?UTF-8?q?=20source?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- georiviere/main/apps.py | 12 +++++++ georiviere/main/models.py | 18 ++++++++++ georiviere/river/models.py | 14 +++++--- georiviere/river/signals.py | 48 ++++++++++++++++++++++++++- georiviere/river/tests/test_models.py | 11 ++++-- georiviere/tests/__init__.py | 19 +++++++++++ 6 files changed, 115 insertions(+), 7 deletions(-) diff --git a/georiviere/main/apps.py b/georiviere/main/apps.py index 38ec2015..3cb9f0b9 100644 --- a/georiviere/main/apps.py +++ b/georiviere/main/apps.py @@ -1,7 +1,19 @@ from django.apps import AppConfig from django.utils.translation import gettext_lazy as _ +from django.apps import apps + +from django.db.models.signals import post_delete, post_save class MainConfig(AppConfig): name = 'georiviere.main' verbose_name = _("Main") + + def ready(self): + from . import signals + from mapentity.models import MapEntityMixin + from georiviere.river.models import Stream + for model in apps.get_models(): + if issubclass(model, MapEntityMixin) and model != Stream: + post_save.connect(signals.save_objects_generate_distance_to_source, sender=model) + post_delete.connect(signals.delete_objects_remove_distance_to_source, sender=model) diff --git a/georiviere/main/models.py b/georiviere/main/models.py index d7c6d837..4fec2de9 100644 --- a/georiviere/main/models.py +++ b/georiviere/main/models.py @@ -1,4 +1,6 @@ from django.conf import settings +from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.models import ContentType from django.db import models from django.utils.translation import gettext_lazy as _ from paperclip.models import FileType as BaseFileType, Attachment as BaseAttachment @@ -35,6 +37,22 @@ class AddPropertyBufferMixin(AddPropertyMixin): @classmethod def within_buffer(cls, topology): + qs = cls.objects.none() + if not topology.geom: + return qs area = topology.geom.buffer(settings.BASE_INTERSECTION_MARGIN) qs = cls.objects.filter(geom__intersects=area) return qs + + +class DistanceToSource(models.Model): + distance = models.FloatField(verbose_name=_("Distance"), default=0) + stream = models.ForeignKey('river.Stream', on_delete=models.CASCADE) + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + object_id = models.PositiveIntegerField() + content_object = GenericForeignKey('content_type', 'object_id') + + class Meta: + verbose_name = _("Distance to source") + verbose_name_plural = _("Distance to sources") + unique_together = ('content_type', 'object_id', 'stream') diff --git a/georiviere/river/models.py b/georiviere/river/models.py index 3afe376f..bfadda4c 100644 --- a/georiviere/river/models.py +++ b/georiviere/river/models.py @@ -1,6 +1,7 @@ from django.conf import settings +from django.contrib.contenttypes.models import ContentType from django.contrib.gis.db import models -from django.contrib.gis.geos import GEOSGeometry, Point +from django.contrib.gis.geos import Point from django.db.models import Q from django.utils.translation import gettext_lazy as _ @@ -11,10 +12,13 @@ from georiviere.main.models import AddPropertyBufferMixin from georiviere.altimetry import AltimetryMixin +from georiviere.finances_administration.models import AdministrativeFile from georiviere.functions import ClosestPoint, LineSubString from georiviere.knowledge.models import Knowledge, FollowUp +from georiviere.main.models import DistanceToSource from georiviere.observations.models import Station from georiviere.proceeding.models import Proceeding +from georiviere.maintenance.models import Intervention from georiviere.studies.models import Study from georiviere.watershed.mixins import WatershedPropertiesMixin @@ -132,9 +136,11 @@ def snap(self, point): def distance_to_source(self, element): """Returns distance from element to stream source""" - if hasattr(element, 'geom') and isinstance(element.geom, GEOSGeometry): - return self.source_location.distance(element.geom) - return None + ct = ContentType.objects.get_for_model(element) + try: + return DistanceToSource.objects.get(stream=self, content_type=ct, object_id=element.pk).distance + except DistanceToSource.DoesNotExist: + return None class Topology(models.Model): diff --git a/georiviere/river/signals.py b/georiviere/river/signals.py index 90510903..27dd6e12 100644 --- a/georiviere/river/signals.py +++ b/georiviere/river/signals.py @@ -1,8 +1,17 @@ +from django.apps import apps +from django.conf import settings +from django.contrib.contenttypes.models import ContentType +from django.contrib.gis.db.models.functions import Distance from django.db.models.signals import post_save from django.db.models.fields.reverse_related import OneToOneRel +from django.db.models import F, FloatField, Case, When from django.dispatch import receiver -from georiviere.river.models import Stream, Topology +from georiviere.functions import ClosestPoint, Length, LineSubString, LineLocatePoint +from georiviere.main.models import DistanceToSource +from georiviere.river.models import Stream, Topology, TopologyMixin + +from mapentity.models import MapEntityMixin @receiver(post_save, sender=Stream) @@ -16,3 +25,40 @@ def save_stream(sender, instance, **kwargs): else: for topology in instance.topologies.all(): topology.save() + + +@receiver(post_save, sender=Stream) +def save_stream_generate_distance_to_source(sender, instance, **kwargs): + + for model in apps.get_models(): + if issubclass(model, MapEntityMixin) and not issubclass(model, TopologyMixin) and model != Stream and 'geom' in [field.name for field in model._meta.get_fields()]: + distances_to_sources = [] + area = instance.geom.buffer(settings.BASE_INTERSECTION_MARGIN) + for object_topology in model.objects.annotate( + locate_source=LineLocatePoint(instance.geom, + instance.source_location), + locate_object=LineLocatePoint(instance.geom, + ClosestPoint(instance.geom, + F('geom'))), + locate=Length(LineSubString(instance.geom, + Case( + When(locate_source__gte=F('locate_object'), then=F('locate_object')), + default=F('locate_source') + ), + Case( + When(locate_source__gte=F('locate_object'), then=F('locate_source')), + default=F('locate_object') + ))) + Distance(F('geom'), + instance.geom, + output_field=FloatField()) + Distance(instance.geom, + instance.source_location, + output_field=FloatField()) + ).filter(geom__intersects=area): + distances_to_sources.append(DistanceToSource.objects.update_or_create( + object_id=object_topology.pk, + content_type=ContentType.objects.get_for_model(object_topology._meta.model), + stream=instance, + defaults={"distance": object_topology.locate} + )[0].pk) + if not kwargs['created']: + DistanceToSource.objects.exclude(pk__in=distances_to_sources).filter(stream=instance.pk, content_type=ContentType.objects.get_for_model(model)).delete() diff --git a/georiviere/river/tests/test_models.py b/georiviere/river/tests/test_models.py index 7b986275..2304392a 100644 --- a/georiviere/river/tests/test_models.py +++ b/georiviere/river/tests/test_models.py @@ -4,7 +4,7 @@ from georiviere.finances_administration.tests.factories import AdministrativeFileFactory from georiviere.description.tests.factories import MorphologyFactory, StatusFactory, UsageFactory -from georiviere.river.models import Stream +from georiviere.river.models import DistanceToSource, Stream from georiviere.river.tests.factories import TopologyFactory, StreamFactory, ClassificationWaterPolicyFactory @@ -54,8 +54,15 @@ def test_get_map_image_extent(self): def test_distance_to_source(self): """Test distance from a given object to stream source according to differents geom""" - self.assertAlmostEqual(self.stream1.distance_to_source(self.usage_point), 20) + self.assertAlmostEqual(self.stream1.distance_to_source(self.usage_point), 28.2842712) self.assertEqual(self.stream1.distance_to_source(self.administrative_file), None) + self.assertEqual(DistanceToSource.objects.count(), 6) + usage_point = UsageFactory.create( + geom=Point(10000, 10010) + ) + self.assertEqual(DistanceToSource.objects.count(), 8) + usage_point.delete() + self.assertEqual(DistanceToSource.objects.count(), 6) class SnapTest(TestCase): diff --git a/georiviere/tests/__init__.py b/georiviere/tests/__init__.py index 04ac7e64..405b4d65 100644 --- a/georiviere/tests/__init__.py +++ b/georiviere/tests/__init__.py @@ -1,6 +1,10 @@ from django.contrib.auth.models import Permission +from django.contrib.contenttypes.models import ContentType + from mapentity.tests import MapEntityTest +from georiviere.main.models import DistanceToSource +from georiviere.river.models import Stream from georiviere.tests.factories import UserAllPermsFactory from geotrek.authent.tests.factories import StructureFactory @@ -102,3 +106,18 @@ def test_update_not_same_structure_no_permission(self): obj = self.modelfactory(structure=structure) response = self.client.get(obj.get_update_url()) self.assertRedirects(response, obj.get_detail_url()) + + def test_distance_to_source_is_available(self): + if self.model is None or not hasattr(self.modelfactory, 'with_stream'): + return # Abstract test should not run + self.login() + obj = self.modelfactory.create(with_stream=True) + response = self.client.get(obj.get_detail_url()) + self.assertEqual(response.status_code, 200) + self.assertContains(response, + f''' Date: Tue, 7 Mar 2023 10:40:12 +0100 Subject: [PATCH 05/19] =?UTF-8?q?=E2=9C=A8=20Show=20distance=20to=20source?= =?UTF-8?q?=20directly=20even=20withtout=20distance=20to=20source=20(0m)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/templates/main/_detail_valuelist_source_fragment.html | 2 +- .../main/templates/main/_detail_valuelist_streams_fragment.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/georiviere/main/templates/main/_detail_valuelist_source_fragment.html b/georiviere/main/templates/main/_detail_valuelist_source_fragment.html index fd6ab904..fc2a5659 100644 --- a/georiviere/main/templates/main/_detail_valuelist_source_fragment.html +++ b/georiviere/main/templates/main/_detail_valuelist_source_fragment.html @@ -4,7 +4,7 @@ {% if v.enumeration %}{{ v.enumeration }}. {% endif %} {{ v.text|safe }} - {% if v.distance_to_source %} ({{ v.distance_to_source|floatformat }} m){% endif %} + ({{ v.distance_to_source|floatformat }} m) {% if forloop.last %}{% endif %} {% empty %} diff --git a/georiviere/main/templates/main/_detail_valuelist_streams_fragment.html b/georiviere/main/templates/main/_detail_valuelist_streams_fragment.html index 89636e4d..e33c8701 100644 --- a/georiviere/main/templates/main/_detail_valuelist_streams_fragment.html +++ b/georiviere/main/templates/main/_detail_valuelist_streams_fragment.html @@ -3,7 +3,7 @@ {% if forloop.first %}
    {% endif %} {{ v.text|safe }} - {% if v.distance_to_source %} ({{ v.distance_to_source|floatformat }} m){% endif %} + ({{ v.distance_to_source|floatformat }} m) {% if forloop.last %}
{% endif %} {% empty %} From 1b209b72a89ceb6217f76c267f2dddfb72c5505c Mon Sep 17 00:00:00 2001 From: LePetitTim Date: Tue, 7 Mar 2023 10:40:42 +0100 Subject: [PATCH 06/19] =?UTF-8?q?=E2=9C=A8=20Show=20distance=20to=20source?= =?UTF-8?q?=20even=20for=20topologies=20(morpho,=20status)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../river/templates/river/river_detail_fragment.html | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/georiviere/river/templates/river/river_detail_fragment.html b/georiviere/river/templates/river/river_detail_fragment.html index 64eda559..f7084ee7 100644 --- a/georiviere/river/templates/river/river_detail_fragment.html +++ b/georiviere/river/templates/river/river_detail_fragment.html @@ -8,12 +8,7 @@

{% trans "River" %}

{% trans "Stream" %} - {% if object.topology %} -
{{ object.topology.stream.name }} - {% else %} - {% valuelist_streams object.streams object %} - {% endif %} - + {% valuelist_streams object.streams object %} {% endif %} From 786644232e483fe7a85a0ebb2ce5618ee3855c04 Mon Sep 17 00:00:00 2001 From: LePetitTim Date: Tue, 7 Mar 2023 11:15:30 +0100 Subject: [PATCH 07/19] =?UTF-8?q?=E2=9C=A8=20Add=20with=20streamfactory=20?= =?UTF-8?q?on=20studies,=20stations=20...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../finances_administration/tests/factories.py | 14 ++++++++++++-- georiviere/observations/tests/factories.py | 3 ++- georiviere/proceeding/tests/factories.py | 4 +++- georiviere/studies/tests/factories.py | 4 +++- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/georiviere/finances_administration/tests/factories.py b/georiviere/finances_administration/tests/factories.py index 926439bb..c865a6d8 100644 --- a/georiviere/finances_administration/tests/factories.py +++ b/georiviere/finances_administration/tests/factories.py @@ -6,7 +6,7 @@ from georiviere.studies.tests.factories import StudyFactory from georiviere.finances_administration import models -from georiviere.river.tests.factories import WithStreamFactory +from georiviere.river.tests.factories import StreamFactory class AdministrativeDeferralFactory(factory.django.DjangoModelFactory): @@ -37,7 +37,7 @@ class Meta: label = factory.Sequence(lambda n: "Domain %s" % n) -class AdministrativeFileFactory(WithStreamFactory, factory.django.DjangoModelFactory): +class AdministrativeFileFactory(factory.django.DjangoModelFactory): class Meta: model = models.AdministrativeFile @@ -45,6 +45,16 @@ class Meta: begin_date = '2010-01-01' end_date = '2012-01-01' + @factory.post_generation + def with_stream(obj, create, with_stream): + if not create or not with_stream: + return + StudyOperationFactory.create(administrative_file=obj) + if with_stream and obj.geom: + # Status / Morphology is already on a stream + # It should not add this next stream in distance to source + StreamFactory.create(geom_around=obj.geom) + class AdministrativeFilePhaseFactory(factory.django.DjangoModelFactory): class Meta: diff --git a/georiviere/observations/tests/factories.py b/georiviere/observations/tests/factories.py index f0241ce2..8e7d13f6 100644 --- a/georiviere/observations/tests/factories.py +++ b/georiviere/observations/tests/factories.py @@ -4,6 +4,7 @@ from factory import post_generation, django, fuzzy, Sequence, SubFactory from .. import models +from georiviere.river.tests.factories import WithStreamFactory class StationProfileFactory(django.DjangoModelFactory): @@ -14,7 +15,7 @@ class Meta: label = fuzzy.FuzzyText() -class StationFactory(django.DjangoModelFactory): +class StationFactory(WithStreamFactory, django.DjangoModelFactory): class Meta: model = models.Station diff --git a/georiviere/proceeding/tests/factories.py b/georiviere/proceeding/tests/factories.py index d7291cbd..fec541e9 100644 --- a/georiviere/proceeding/tests/factories.py +++ b/georiviere/proceeding/tests/factories.py @@ -3,8 +3,10 @@ from georiviere.tests.factories import BaseLineStringFactory from georiviere.proceeding import models +from georiviere.river.tests.factories import WithStreamFactory -class ProceedingFactory(BaseLineStringFactory): + +class ProceedingFactory(WithStreamFactory, BaseLineStringFactory): class Meta: model = models.Proceeding diff --git a/georiviere/studies/tests/factories.py b/georiviere/studies/tests/factories.py index 0a53ab6c..f76bd302 100644 --- a/georiviere/studies/tests/factories.py +++ b/georiviere/studies/tests/factories.py @@ -3,6 +3,8 @@ from .. import models +from georiviere.river.tests.factories import WithStreamFactory + class StudyTypeFactory(django.DjangoModelFactory): class Meta: @@ -11,7 +13,7 @@ class Meta: label = fuzzy.FuzzyText() -class StudyFactory(PointFactory): +class StudyFactory(WithStreamFactory, PointFactory): class Meta: model = models.Study From 52befc8c436984eba7e1f8fde08cdd9e0894b28e Mon Sep 17 00:00:00 2001 From: LePetitTim Date: Tue, 7 Mar 2023 11:20:11 +0100 Subject: [PATCH 08/19] =?UTF-8?q?=E2=9C=A8=20Add=20generation=20distance?= =?UTF-8?q?=20to=20source=20when=20element=20is=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- georiviere/main/signals.py | 61 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 georiviere/main/signals.py diff --git a/georiviere/main/signals.py b/georiviere/main/signals.py new file mode 100644 index 00000000..07694936 --- /dev/null +++ b/georiviere/main/signals.py @@ -0,0 +1,61 @@ +from django.conf import settings + +from django.contrib.gis.db.models.functions import Distance, Transform +from django.contrib.contenttypes.models import ContentType +from django.db.models import F, FloatField, Case, When + +from georiviere.functions import ClosestPoint, Length, LineSubString, LineLocatePoint +from georiviere.river.models import Stream +from georiviere.main.models import DistanceToSource + + +def annotate_distance_to_source(streams, instance): + if streams: + streams = streams.annotate( + locate_source=LineLocatePoint(F('geom'), F('source_location')), + locate_object=LineLocatePoint(F('geom'), ClosestPoint(F('geom'), instance.geom)), + locate=Length(LineSubString(F('geom'), + Case( + When(locate_source__gte=F('locate_object'), then=F('locate_object')), + default=F('locate_source') + ), + Case( + When(locate_source__gte=F('locate_object'), then=F('locate_source')), + default=F('locate_object') + ))) + Distance(instance.geom, + F('geom'), + output_field=FloatField()) + Distance(F('geom'), + F('source_location'), + output_field=FloatField()) + ) + return streams + + +def save_objects_generate_distance_to_source(sender, instance, **kwargs): + if not hasattr(instance, 'get_topology'): + streams = annotate_distance_to_source(instance.streams, instance) + for stream in streams: + DistanceToSource.objects.update_or_create( + object_id=instance.pk, + content_type=ContentType.objects.get_for_model(instance._meta.model), + stream=stream, + defaults={"distance": stream.locate} + ) + if not kwargs.get('created'): + DistanceToSource.objects.filter(object_id=instance.pk, + content_type=ContentType.objects.get_for_model(instance._meta.model), + ).exclude(stream__in=streams).delete() + + elif hasattr(instance, 'topology'): + stream = annotate_distance_to_source(Stream.objects.all(), instance).get(pk=instance.topology.stream.pk) + DistanceToSource.objects.update_or_create( + object_id=instance.pk, + content_type=ContentType.objects.get_for_model(instance._meta.model), + stream=stream, + defaults={"distance": stream.locate} + ) + + +def delete_objects_remove_distance_to_source(sender, instance, **kwargs): + content_type = ContentType.objects.get_for_model(instance._meta.model) + DistanceToSource.objects.filter(content_type=content_type, object_id=instance.pk).delete() From b8c0dad64e0f5a004e3c6007b68d8a4b5ff9b8e7 Mon Sep 17 00:00:00 2001 From: LePetitTim Date: Tue, 7 Mar 2023 11:20:28 +0100 Subject: [PATCH 09/19] =?UTF-8?q?=E2=9C=A8=20Add=20migrations=20distance?= =?UTF-8?q?=20to=20source?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/migrations/0010_distancetosource.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 georiviere/main/migrations/0010_distancetosource.py diff --git a/georiviere/main/migrations/0010_distancetosource.py b/georiviere/main/migrations/0010_distancetosource.py new file mode 100644 index 00000000..8b9d3e57 --- /dev/null +++ b/georiviere/main/migrations/0010_distancetosource.py @@ -0,0 +1,31 @@ +# Generated by Django 3.1.14 on 2023-03-06 09:13 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('river', '0016_auto_20220324_1419'), + ('contenttypes', '0002_remove_content_type_name'), + ('main', '0009_auto_20230223_1541'), + ] + + operations = [ + migrations.CreateModel( + name='DistanceToSource', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('distance', models.FloatField(default=0, verbose_name='Distance')), + ('object_id', models.PositiveIntegerField()), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), + ('stream', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='river.stream')), + ], + options={ + 'verbose_name': 'Distance to source', + 'verbose_name_plural': 'Distance to sources', + 'unique_together': {('content_type', 'object_id', 'stream')}, + }, + ), + ] From 648e964d6f735c19f96e8e3baed78390a0084db9 Mon Sep 17 00:00:00 2001 From: LePetitTim Date: Tue, 7 Mar 2023 11:31:54 +0100 Subject: [PATCH 10/19] =?UTF-8?q?=F0=9F=90=9B=20fix=20linting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- georiviere/functions.py | 4 --- georiviere/main/signals.py | 44 +++++++++++------------ georiviere/maintenance/tests/factories.py | 4 +-- georiviere/river/models.py | 2 +- 4 files changed, 24 insertions(+), 30 deletions(-) diff --git a/georiviere/functions.py b/georiviere/functions.py index f074e9ad..da2824f1 100644 --- a/georiviere/functions.py +++ b/georiviere/functions.py @@ -4,10 +4,6 @@ from georiviere.fields import ElevationInfosField -class ClosestPoint(GeomOutputGeoFunc): - geom_param_pos = (0, 1) - - class LineLocatePoint(GeoFunc): geom_param_pos = (0, 1) output_field = FloatField() diff --git a/georiviere/main/signals.py b/georiviere/main/signals.py index 07694936..778c4fe6 100644 --- a/georiviere/main/signals.py +++ b/georiviere/main/signals.py @@ -1,6 +1,4 @@ -from django.conf import settings - -from django.contrib.gis.db.models.functions import Distance, Transform +from django.contrib.gis.db.models.functions import Distance from django.contrib.contenttypes.models import ContentType from django.db.models import F, FloatField, Case, When @@ -12,22 +10,22 @@ def annotate_distance_to_source(streams, instance): if streams: streams = streams.annotate( - locate_source=LineLocatePoint(F('geom'), F('source_location')), - locate_object=LineLocatePoint(F('geom'), ClosestPoint(F('geom'), instance.geom)), - locate=Length(LineSubString(F('geom'), - Case( - When(locate_source__gte=F('locate_object'), then=F('locate_object')), - default=F('locate_source') - ), - Case( - When(locate_source__gte=F('locate_object'), then=F('locate_source')), - default=F('locate_object') - ))) + Distance(instance.geom, - F('geom'), - output_field=FloatField()) + Distance(F('geom'), - F('source_location'), - output_field=FloatField()) - ) + locate_source=LineLocatePoint(F('geom'), F('source_location')), + locate_object=LineLocatePoint(F('geom'), ClosestPoint(F('geom'), instance.geom)), + locate=Length(LineSubString(F('geom'), + Case( + When(locate_source__gte=F('locate_object'), then=F('locate_object')), + default=F('locate_source')), + Case( + When(locate_source__gte=F('locate_object'), then=F('locate_source')), + default=F('locate_object')))) + Distance( + instance.geom, + F('geom'), + output_field=FloatField()) + Distance( + F('geom'), + F('source_location'), + output_field=FloatField()) + ) return streams @@ -36,10 +34,10 @@ def save_objects_generate_distance_to_source(sender, instance, **kwargs): streams = annotate_distance_to_source(instance.streams, instance) for stream in streams: DistanceToSource.objects.update_or_create( - object_id=instance.pk, - content_type=ContentType.objects.get_for_model(instance._meta.model), - stream=stream, - defaults={"distance": stream.locate} + object_id=instance.pk, + content_type=ContentType.objects.get_for_model(instance._meta.model), + stream=stream, + defaults={"distance": stream.locate} ) if not kwargs.get('created'): DistanceToSource.objects.filter(object_id=instance.pk, diff --git a/georiviere/maintenance/tests/factories.py b/georiviere/maintenance/tests/factories.py index 1bd8e58f..9a7a4c55 100644 --- a/georiviere/maintenance/tests/factories.py +++ b/georiviere/maintenance/tests/factories.py @@ -2,7 +2,7 @@ from georiviere.description.tests.factories import LandFactory from georiviere.maintenance import models -from georiviere.river.tests.factories import StreamFactory, WithStreamFactory +from georiviere.river.tests.factories import StreamFactory class InterventionStatusFactory(django.DjangoModelFactory): @@ -69,4 +69,4 @@ def with_stream(obj, create, with_stream): if with_stream and obj.geom: # Status / Morphology is already on a stream # It should not add this next stream in distance to source - StreamFactory.create(geom_around=obj.geom) \ No newline at end of file + StreamFactory.create(geom_around=obj.geom) diff --git a/georiviere/river/models.py b/georiviere/river/models.py index bfadda4c..220ad6ad 100644 --- a/georiviere/river/models.py +++ b/georiviere/river/models.py @@ -206,4 +206,4 @@ class Meta: Stream.add_property('proceedings', Proceeding.within_buffer, _("Proceedings")) Intervention.add_property('streams', Stream.within_buffer, _("Stream")) -AdministrativeFile.add_property('streams', Stream.within_buffer, _("Stream")) \ No newline at end of file +AdministrativeFile.add_property('streams', Stream.within_buffer, _("Stream")) From c326e9d5f1ec9535e2adddf87aec1ef02d85265d Mon Sep 17 00:00:00 2001 From: LePetitTim Date: Wed, 8 Mar 2023 12:07:07 +0100 Subject: [PATCH 11/19] =?UTF-8?q?=E2=9C=A8=20Add=20distance=20to=20source?= =?UTF-8?q?=20calculation=20+=20fix=20url=20cut=20topology=20fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- georiviere/main/decorators.py | 32 ++++ georiviere/river/forms.py | 1 + .../static/river/css/distance-to-source.css | 4 + .../river/images/distance-to-source.svg | 1 + .../river/static/river/js/cut-topology.js | 18 +- .../static/river/js/distance-to-source.js | 160 ++++++++++++++++++ georiviere/river/static/river/js/main.js | 14 ++ .../river/river_extrabody_fragment.html | 4 +- georiviere/river/urls.py | 7 +- georiviere/river/views.py | 46 ++++- georiviere/settings/__init__.py | 4 +- 11 files changed, 284 insertions(+), 7 deletions(-) create mode 100644 georiviere/main/decorators.py create mode 100644 georiviere/river/static/river/css/distance-to-source.css create mode 100644 georiviere/river/static/river/images/distance-to-source.svg create mode 100644 georiviere/river/static/river/js/distance-to-source.js diff --git a/georiviere/main/decorators.py b/georiviere/main/decorators.py new file mode 100644 index 00000000..0b882a13 --- /dev/null +++ b/georiviere/main/decorators.py @@ -0,0 +1,32 @@ +from functools import wraps + +from django.shortcuts import redirect +from django.contrib import messages +from django.http import HttpResponseRedirect +from django.utils.translation import gettext_lazy as _ + + +def same_structure_required_with_fallback(redirect_to, fallback_redirect_to): + """ + A decorator for class-based views. It relies on ``self.get_object()`` + method object, and assumes decorated views to handle ``StructureRelated`` + objects. + """ + def decorator(view_func): + @wraps(view_func) + def _wrapped_view(self, request, *args, **kwargs): + result = view_func(self, request, *args, **kwargs) + if isinstance(result, HttpResponseRedirect): + return result + + obj = hasattr(self, 'get_object') and self.get_object() or getattr(self, 'object', None) + if not obj: + return redirect(fallback_redirect_to, *args, **kwargs) + + if obj.same_structure(request.user): + return result + messages.warning(request, _('Access to the requested resource is restricted by structure. You have been redirected.')) + + return redirect(redirect_to, *args, **kwargs) + return _wrapped_view + return decorator diff --git a/georiviere/river/forms.py b/georiviere/river/forms.py index f275b6b4..3f29d194 100644 --- a/georiviere/river/forms.py +++ b/georiviere/river/forms.py @@ -1,4 +1,5 @@ from django import forms +from django.contrib.gis.forms.fields import GeometryField from geotrek.common.forms import CommonForm diff --git a/georiviere/river/static/river/css/distance-to-source.css b/georiviere/river/static/river/css/distance-to-source.css new file mode 100644 index 00000000..933bdd4f --- /dev/null +++ b/georiviere/river/static/river/css/distance-to-source.css @@ -0,0 +1,4 @@ +.distance-control { + background-image: url('../images/distance-to-source.svg'); + background-size: 60%; +} diff --git a/georiviere/river/static/river/images/distance-to-source.svg b/georiviere/river/static/river/images/distance-to-source.svg new file mode 100644 index 00000000..b893818c --- /dev/null +++ b/georiviere/river/static/river/images/distance-to-source.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/georiviere/river/static/river/js/cut-topology.js b/georiviere/river/static/river/js/cut-topology.js index 88c84e8b..cbf0cefd 100644 --- a/georiviere/river/static/river/js/cut-topology.js +++ b/georiviere/river/static/river/js/cut-topology.js @@ -175,7 +175,23 @@ L.Handler.PointTopology = L.Draw.Marker.extend({ L.GeometryUtil.extract(this._map, this._guidesLayer, L.GeometryUtil.locateOnLine(this._map, this._guidesLayer, e.latlng), 0)); - marker.bindPopup(content.html()).openPopup(); + $.ajax({ + url: window.SETTINGS.urls['distance_to_source'], + type: 'GET', + dataType: 'json', + data: { + "lat_distance": e.latlng.lat, + "lng_distance": e.latlng.lng + }, + success: function(data) { + this._helpText = data; + $('#distance-on-topology').text(data['distance'] + " m") + marker.bindPopup(content.html()).openPopup(); + }, + error: function(data) { + marker.bindPopup(content.html()).openPopup(); + } + }); }, this); marker.on('unsnap', function (e) { marker.closePopup(); diff --git a/georiviere/river/static/river/js/distance-to-source.js b/georiviere/river/static/river/js/distance-to-source.js new file mode 100644 index 00000000..e10fe653 --- /dev/null +++ b/georiviere/river/static/river/js/distance-to-source.js @@ -0,0 +1,160 @@ +L.Mixin.ActivableDistanceControl = { + activable: function (activable) { + this._activable = activable; + if (this._container) { + if (activable) + L.DomUtil.removeClass(this._container, 'control-disabled'); + else + L.DomUtil.addClass(this._container, 'control-disabled'); + } + this.handler.on('enabled', function (e) { + L.DomUtil.addClass(this._container, 'enabled'); + }, this); + this.handler.on('disabled', function (e) { + L.DomUtil.removeClass(this._container, 'enabled'); + }, this); + }, + + setState: function (state) { + if (state) { + this.handler.enable.call(this.handler); + this.handler.fire('enabled'); + } + else { + this.handler.disable.call(this.handler); + this.handler.fire('disabled'); + } + }, + + toggle: function() { + this._activable = !!this._activable; // from undefined to false :) + + if (!this._activable) + return; // do nothing if not activable + + this.setState(!this.handler.enabled()); + }, +}; + + +L.Control.ExclusiveDistanceActivation = L.Class.extend({ + initialize: function () { + this._controls = []; + }, + + add: function (control) { + this._controls.push(control); + var self = this; + control.activable(true); + control.handler.on('enabled', function (e) { + // When this control is enabled, activate this one, + // disable the others and prevent them to be activable. + $.each(self._controls, function (i, c) { + if (c != control) { + c.activable(false); + } + }); + }, this); + + control.handler.on('disabled', function (e) { + // When this control is disabled, re-enable the others ! + // Careful, this will not take care of previous state :) + $.each(self._controls, function (i, c) { + c.activable(true); + }); + }, this); + }, +}); + + +L.Control.PointDistance = L.Control.extend({ + includes: L.Mixin.ActivableDistanceControl, + + statics: { + TITLE: 'Distance to source', + }, + + options: { + position: 'topleft', + }, + + initialize: function (map, guidesLayer, field, options) { + L.Control.prototype.initialize.call(this, options); + this.handler = new L.Handler.PointDistance(map, guidesLayer.layer, options); + this.handler.on('added', this.toggle, this); + }, + + onAdd: function (map) { + this._container = L.DomUtil.create('div', 'leaflet-draw leaflet-control leaflet-bar leaflet-control-zoom'); + var link = L.DomUtil.create('a', 'leaflet-control-zoom-out distance-control', this._container); + link.href = '#'; + link.title = L.Control.PointDistance.TITLE; + + L.DomEvent.addListener(link, 'click', L.DomEvent.stopPropagation) + .addListener(link, 'click', L.DomEvent.preventDefault) + .addListener(link, 'click', this.toggle, this); + return this._container; + } +}); + + +L.Handler.PointDistance = L.Draw.Marker.extend({ + initialize: function (map, guidesLayer, options) { + L.Draw.Marker.prototype.initialize.call(this, map, options); + this._guidesLayer = guidesLayer; + this._helpText = tr("Click map to place marker, get the distance to the nearest stream's source."); + this._distanceMarker = null; + map.on('draw:created', this._onDrawn, this); + }, + + addHooks: function () { + L.Draw.Marker.prototype.addHooks.call(this); + if (this._map) { + this._tooltip.updateContent({ text: this._helpText }); + } + }, + + reset: function() { + if (this._distanceMarker) { + this._map.removeLayer(this._distanceMarker); + } + }, + + _onDrawn: function (e) { + if (e.layerType === 'marker') { + if (this._distanceMarker !== null) { + this._map.removeLayer(this._distanceMarker); + } + + this.fire('distance:created'); + this._distanceMarker = L.marker(e.layer.getLatLng()); + this._getDistance(this._distanceMarker); + } + }, + + _getDistance: function (marker) { + marker.addTo(this._map); + $.ajax({ + url: window.SETTINGS.urls['distance_to_source'], + type: 'GET', + dataType: 'json', + data: { + "lat_distance": marker._latlng.lat, + "lng_distance": marker._latlng.lng + }, + success: function(data) { + this._helpText = data; + + marker.bindPopup("
" + data['distance'] + " m
").openPopup() + } + }); + this._map.on('popupclose', function(e) { + + if (e.popup._source._map.hasLayer(marker)) { + e.popup._source._map.removeLayer(marker); + } + + + }); + }, +}); diff --git a/georiviere/river/static/river/js/main.js b/georiviere/river/static/river/js/main.js index b8730236..fb07e5dc 100644 --- a/georiviere/river/static/river/js/main.js +++ b/georiviere/river/static/river/js/main.js @@ -74,5 +74,19 @@ $(window).on('entity:map', function (e, data) { } }); + } + else { + var distanceControl = new L.Control.PointDistance(map, layer, {}); + var exclusive = new L.Control.ExclusiveDistanceActivation(); + map.distanceControl = map.addControl(distanceControl); + exclusive.add(distanceControl); + distanceControl.handler.on('enabled', function(){ + if (! loaded_river) { + map.addLayer(layer); + loaded_river = true; + } + distanceControl.handler.reset(); + }, this); + } }); diff --git a/georiviere/river/templates/river/river_extrabody_fragment.html b/georiviere/river/templates/river/river_extrabody_fragment.html index f038661b..3732c3e1 100644 --- a/georiviere/river/templates/river/river_extrabody_fragment.html +++ b/georiviere/river/templates/river/river_extrabody_fragment.html @@ -7,6 +7,7 @@ L.Control.PointTopology.TITLE = "{% trans 'Cut topology' %}"; window.SETTINGS.urls['stream_layer'] = "{% url "river:stream_layer" %}"; + window.SETTINGS.urls['distance_to_source'] = "{% url "river:distance_to_source" %}"; @@ -14,7 +15,8 @@