From 235063c8c32d1d62358312f9d167345a8710428e Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Wed, 14 Dec 2022 12:18:18 +0100 Subject: [PATCH 1/2] #280 Schneider capability * refactors update_parcels() method in Geometry model to work on Schneider * old WFS based logic still exists as update_parcels_wfs() in Geometry model to have a fallback. Can be deleted in the future --- konova/models/geometry.py | 78 ++++++++++++++++++++++- konova/sub_settings/schneider_settings.py | 11 ++++ konova/utils/schneider/fetcher.py | 59 +++++++++++++++++ 3 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 konova/sub_settings/schneider_settings.py create mode 100644 konova/utils/schneider/fetcher.py diff --git a/konova/models/geometry.py b/konova/models/geometry.py index f216903..79a27e0 100644 --- a/konova/models/geometry.py +++ b/konova/models/geometry.py @@ -13,6 +13,7 @@ from django.utils import timezone from konova.models import BaseResource, UuidModel from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP +from konova.utils.schneider.fetcher import ParcelFetcher from konova.utils.wfs.spatial import ParcelWFSFetcher @@ -109,8 +110,8 @@ class Geometry(BaseResource): return objs @transaction.atomic - def update_parcels(self): - """ Updates underlying parcel information + def update_parcels_wfs(self): + """ Updates underlying parcel information using the WFS of LVermGeo Returns: @@ -185,6 +186,79 @@ class Geometry(BaseResource): ["calculated_on"] ) + def update_parcels(self): + """ Updates underlying parcel information + + Returns: + + """ + from konova.models import Parcel, District, ParcelIntersection, Municipal, ParcelGroup + + if self.geom.empty: + # Nothing to do + return + + parcel_fetcher = ParcelFetcher( + geometry=self + ) + fetched_parcels = parcel_fetcher.get_parcels() + + _now = timezone.now() + underlying_parcels = [] + for result in fetched_parcels: + # There could be parcels which include the word 'Flur', + # which needs to be deleted and just keep the numerical values + ## THIS CAN BE REMOVED IN THE FUTURE, WHEN 'Flur' WON'T OCCUR ANYMORE! + flr_val = result["flur"].replace("Flur ", "") + district = District.objects.get_or_create( + key=result["kreisschl"], + name=result["kreis"], + )[0] + municipal = Municipal.objects.get_or_create( + key=result["gmdschl"], + name=result["gemeinde"], + district=district, + )[0] + parcel_group = ParcelGroup.objects.get_or_create( + key=result["gemaschl"], + name=result["gemarkung"], + municipal=municipal, + )[0] + flrstck_nnr = result['flstnrnen'] + if not flrstck_nnr: + flrstck_nnr = None + flrstck_zhlr = result['flstnrzae'] + if not flrstck_zhlr: + flrstck_zhlr = None + parcel_obj = Parcel.objects.get_or_create( + district=district, + municipal=municipal, + parcel_group=parcel_group, + flr=flr_val, + flrstck_nnr=flrstck_nnr, + flrstck_zhlr=flrstck_zhlr, + )[0] + parcel_obj.district = district + parcel_obj.updated_on = _now + parcel_obj.save() + underlying_parcels.append(parcel_obj) + + # Update the linked parcels + self.parcels.clear() + self.parcels.set(underlying_parcels) + + # Set the calculated_on intermediate field, so this related data will be found on lookups + intersections_without_ts = self.parcelintersection_set.filter( + parcel__in=self.parcels.all(), + calculated_on__isnull=True, + ) + for entry in intersections_without_ts: + entry.calculated_on = _now + ParcelIntersection.objects.bulk_update( + intersections_without_ts, + ["calculated_on"] + ) + def get_underlying_parcels(self): """ Getter for related parcels and their districts diff --git a/konova/sub_settings/schneider_settings.py b/konova/sub_settings/schneider_settings.py new file mode 100644 index 0000000..caf4ec2 --- /dev/null +++ b/konova/sub_settings/schneider_settings.py @@ -0,0 +1,11 @@ +""" +Author: Michel Peltriaux +Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany +Contact: ksp-servicestelle@sgdnord.rlp.de +Created on: 14.12.22 + +""" + +base_url = "http://127.0.0.1:8002" +auth_header = "auth" +auth_header_token = "CHANGE_ME" diff --git a/konova/utils/schneider/fetcher.py b/konova/utils/schneider/fetcher.py new file mode 100644 index 0000000..0bc87f7 --- /dev/null +++ b/konova/utils/schneider/fetcher.py @@ -0,0 +1,59 @@ +""" +Author: Michel Peltriaux +Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany +Contact: ksp-servicestelle@sgdnord.rlp.de +Created on: 14.12.22 + +""" +import json +from json import JSONDecodeError + +import requests + +from konova.sub_settings import schneider_settings + + +class ParcelFetcher: + base_url = schneider_settings.base_url + auth_header = schneider_settings.auth_header + auth_header_token = schneider_settings.auth_header_token + + geometry = None + geojson = None + results = None + + def __init__(self, geometry): + if geometry is None: + raise AssertionError("Geometry must not be none") + self.geometry = geometry + + # Reduce size of geometry to avoid "intersections" because of exact border matching + geom = geometry.geom.buffer(-0.001) + self.geojson = geom.ewkt + self.results = [] + + def get_parcels(self, url: str = None): + post_url = url + if post_url is None: + post_url = f"{self.base_url}/parcel/intersect/" + + response = requests.post( + url=post_url, + data=self.geojson, + headers={ + self.auth_header: self.auth_header_token + } + ) + + try: + content = json.loads(response.content.decode("utf-8")) + except JSONDecodeError: + content = {} + next = content.get("next", None) + fetched_parcels = content.get("results", []) + self.results += fetched_parcels + + if next: + self.get_parcels(next) + + return self.results \ No newline at end of file From a786023e7545280475615bba6979ced95b18e3e4 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Thu, 22 Dec 2022 07:25:55 +0100 Subject: [PATCH 2/2] Empty value egon fix * adds support for missing values so that EGON can properly handle these entries --- intervention/utils/egon_export.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/intervention/utils/egon_export.py b/intervention/utils/egon_export.py index 30d5841..2589595 100644 --- a/intervention/utils/egon_export.py +++ b/intervention/utils/egon_export.py @@ -178,6 +178,23 @@ class EgonGmlBuilder: reg_date = self.intervention.legal.registration_date bind_date = self.intervention.legal.binding_date + # EGON needs special treatment in case of missing data + if reg_office is None: + reg_office_element = {} + else: + reg_office_element = { + "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/1053/{reg_office.atom_id}", + "#text": reg_office.long_name + } + + if cons_office is None: + cons_office_element = {} + else: + cons_office_element = { + "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/907/{cons_office.atom_id}", + "#text": cons_office.long_name + } + xml_dict = { "wfs:FeatureCollection": { "@xmlns:wfs": "http://www.opengis.net/wfs", @@ -191,14 +208,8 @@ class EgonGmlBuilder: "oneo:azEintragungsstelle": self.intervention.responsible.conservation_file_number, "oneo:azZulassungsstelle": self.intervention.responsible.registration_file_number, "oneo:bemerkungZulassungsstelle": "", - "oneo:eintragungsstelle": { - "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/907/{cons_office.atom_id if cons_office else ''}", - "#text": cons_office.long_name if cons_office else "" - }, - "oneo:zulassungsstelle": { - "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/1053/{reg_office.atom_id if reg_office else ''}", - "#text": reg_office.long_name if reg_office else "" - }, + "oneo:eintragungsstelle": cons_office_element, + "oneo:zulassungsstelle": reg_office_element, "oneo:ersatzzahlung": self._float_to_localized_string(self._sum_all_payments()), "oneo:kompensationsart": { "@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/88140/{comp_type_code}",