Skip to content

Commit

Permalink
Merge pull request #115 from Georiviere/impr_add_distance_to_source_g…
Browse files Browse the repository at this point in the history
…eneration

Add distance to source generation
  • Loading branch information
LePetitTim authored Apr 3, 2023
2 parents b665a1f + 501cad8 commit 9a1f441
Show file tree
Hide file tree
Showing 37 changed files with 658 additions and 45 deletions.
7 changes: 6 additions & 1 deletion georiviere/description/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,10 @@ class Meta:
verbose_name_plural = _("Morphologies")

def __str__(self):
return f"{self.main_flow}"
if self.main_flow:
return f"{self.main_flow}"
else:
return f"{self.pk}"

@classproperty
def name_verbose_name(cls):
Expand Down Expand Up @@ -397,6 +400,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"))
Expand All @@ -405,6 +409,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"))
Expand Down
23 changes: 17 additions & 6 deletions georiviere/description/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -25,14 +25,25 @@ 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)

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
Expand Down Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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

Expand Down
3 changes: 2 additions & 1 deletion georiviere/description/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,12 @@ class MorphologyTest(TestCase):

def test_str(self):
morpho1 = factories.MorphologyFactory.create()
pk_morpho = morpho1.pk
morpho2 = factories.MorphologyFactory.create(
main_flow=factories.FlowTypeFactory.create(label="Plat"),
full_edge_width=15.0,
)
self.assertEqual(str(morpho1), "None")
self.assertEqual(str(morpho1), str(pk_morpho))
self.assertEqual(str(morpho2), "Plat")
self.assertHTMLEqual(
morpho2.main_flow_display,
Expand Down
59 changes: 57 additions & 2 deletions georiviere/description/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
11 changes: 11 additions & 0 deletions georiviere/finances_administration/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from georiviere.studies.tests.factories import StudyFactory

from georiviere.finances_administration import models
from georiviere.river.tests.factories import StreamFactory


class AdministrativeDeferralFactory(factory.django.DjangoModelFactory):
Expand Down Expand Up @@ -44,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:
Expand Down
3 changes: 2 additions & 1 deletion georiviere/knowledge/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -11,7 +12,7 @@ class Meta:
label = fuzzy.FuzzyText()


class KnowledgeFactory(PointFactory):
class KnowledgeFactory(WithStreamFactory, PointFactory):
class Meta:
model = models.Knowledge

Expand Down
12 changes: 12 additions & 0 deletions georiviere/main/apps.py
Original file line number Diff line number Diff line change
@@ -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)
31 changes: 31 additions & 0 deletions georiviere/main/migrations/0010_distancetosource.py
Original file line number Diff line number Diff line change
@@ -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')},
},
),
]
18 changes: 18 additions & 0 deletions georiviere/main/models.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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')
59 changes: 59 additions & 0 deletions georiviere/main/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from django.contrib.gis.db.models.functions import Distance, Length, LineLocatePoint
from django.contrib.contenttypes.models import ContentType
from django.db.models import F, FloatField, Case, When

from georiviere.functions import ClosestPoint, LineSubString
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.m}
)
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.m}
)


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()
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<li{% if modelname and v.pk %} class="hoverable" data-modelname="{{ modelname }}" data-pk="{{ v.pk }}"{% endif %}>
{% if v.enumeration %}<span class="enumeration-value">{{ v.enumeration }}.&nbsp;</span>{% endif %}
{{ v.text|safe }}
{% if v.distance_to_source %} ({{ v.distance_to_source|floatformat }}&nbsp;m){% endif %}
({{ v.distance_to_source|floatformat }}&nbsp;m)
</li>
{% if forloop.last %}</ul>{% endif %}
{% empty %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{% if forloop.first %}<ul>{% endif %}
<li{% if modelname and v.pk %} class="hoverable" data-modelname="{{ modelname }}" data-pk="{{ v.pk }}"{% endif %}>
{{ v.text|safe }}
{% if v.distance_to_source %} ({{ v.distance_to_source|floatformat }}&nbsp;m){% endif %}
({{ v.distance_to_source|floatformat }}&nbsp;m)
</li>
{% if forloop.last %}</ul>{% endif %}
{% empty %}
Expand Down
Loading

0 comments on commit 9a1f441

Please sign in to comment.