From 50f46e319c7dd3a6b6a534966fc1b1ba57205172 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Sat, 26 Oct 2024 09:47:27 +0200 Subject: [PATCH] # Parcel duplicate repair * adds mechanic to repair parcels in case of unwanted parcel duplicates * optimizes filtering of geometries for parcel recalculation --- .../commands/recalculate_parcels.py | 30 ++++++------- konova/models/geometry.py | 13 +++++- konova/models/parcel.py | 42 ++++++++++++++++++- 3 files changed, 69 insertions(+), 16 deletions(-) diff --git a/konova/management/commands/recalculate_parcels.py b/konova/management/commands/recalculate_parcels.py index f42fd228..e69cab8d 100644 --- a/konova/management/commands/recalculate_parcels.py +++ b/konova/management/commands/recalculate_parcels.py @@ -8,11 +8,9 @@ Created on: 04.01.22 import datetime from django.contrib.gis.db.models.functions import Area -from django.db.models import Q -from django.utils.timezone import now from konova.management.commands.setup import BaseKonovaCommand -from konova.models import Geometry +from konova.models import Geometry, ParcelIntersection class Command(BaseKonovaCommand): @@ -36,17 +34,21 @@ class Command(BaseKonovaCommand): def recalculate_parcels(self, options: dict): force_all = options.get("force_all", False) - if force_all: - geometry_objects = Geometry.objects.all() - else: - _today = now().date() - _date_threshold = _today - datetime.timedelta(days=1) - geometry_objects = Geometry.objects.filter( - Q( - Q(parcel_update_start__date__lte=_date_threshold) | - Q(parcel_update_start__isnull=True) - ), - parcel_update_end__isnull=True + geometry_objects = Geometry.objects.all() + + if not force_all: + # Fetch all intersections + intersection_objs = ParcelIntersection.objects.filter( + geometry__in=geometry_objects + ) + # Just take the geometry ids, which seem to have intersections + geom_with_intersection_ids = intersection_objs.values_list( + "geometry__id", + flat=True + ) + # Filter those geometries out (they have intersections and do not need to be processed) + geometry_objects = geometry_objects.exclude( + id__in=geom_with_intersection_ids ) self._write_warning("=== Update parcels and districts ===") diff --git a/konova/models/geometry.py b/konova/models/geometry.py index c0d55b9e..ffe93aa9 100644 --- a/konova/models/geometry.py +++ b/konova/models/geometry.py @@ -8,7 +8,7 @@ Created on: 15.11.21 import json from django.contrib.gis.db.models import MultiPolygonField -from django.core.exceptions import ObjectDoesNotExist +from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned from django.db import models, transaction from django.utils import timezone @@ -223,6 +223,17 @@ class Geometry(BaseResource): ) parcel_obj.updated_on = _now parcels_to_update.append(parcel_obj) + except MultipleObjectsReturned: + parcel_obj = Parcel.make_unique( + district=district, + municipal=municipal, + parcel_group=parcel_group, + flr=flr_val, + flrstck_nnr=flrstck_nnr, + flrstck_zhlr=flrstck_zhlr, + ) + parcel_obj.updated_on = _now + parcels_to_update.append(parcel_obj) except ObjectDoesNotExist: # If not existing, create object but do not commit, yet parcel_obj = Parcel( diff --git a/konova/models/parcel.py b/konova/models/parcel.py index 7cf00448..7fafba52 100644 --- a/konova/models/parcel.py +++ b/konova/models/parcel.py @@ -5,7 +5,7 @@ Contact: michel.peltriaux@sgdnord.rlp.de Created on: 16.12.21 """ -from django.db import models +from django.db import models, transaction from konova.models import UuidModel @@ -158,6 +158,46 @@ class Parcel(UuidModel): def __str__(self): return f"{self.parcel_group} | {self.flr} | {self.flrstck_zhlr} | {self.flrstck_nnr}" + @classmethod + @transaction.atomic + def make_unique(cls, **kwargs): + """ Checks for duplicates of a Parcel, choose a (now) unique one, + repairs relations for ParcelIntersection and removes duplicates. + + Args: + **kwargs (): + + Returns: + unique_true (Parcel): The new unique 'true one' + """ + parcel_objs = Parcel.objects.filter(**kwargs) + + if not parcel_objs.exists(): + return None + + # Get one of the found parcels and use it as new 'true one' + unique_parcel = parcel_objs.first() + # separate it from the rest + parcel_objs = parcel_objs.exclude(id=unique_parcel.id) + + if not parcel_objs.exists(): + # There are no duplicates - all good, just return + return unique_parcel + + # Fetch existing intersections, which still point on the duplicated parcels + intersection_objs = ParcelIntersection.objects.filter( + parcel__in=parcel_objs + ) + # Change each intersection, so they point on the 'true one' parcel from now on + for intersection in intersection_objs: + intersection.parcel = unique_parcel + intersection.save() + + # Remove the duplicated parcels + parcel_objs.delete() + + return unique_parcel + class ParcelIntersection(UuidModel): """