#49 Calculation implementation
* implements update routine for Geometry model * reorganizes fields of Parcel and District * adds tests * simplifies usage of ParcelWFSFetcher
This commit is contained in:
		
							parent
							
								
									62030c4dcc
								
							
						
					
					
						commit
						2494ecc493
					
				@ -7,8 +7,10 @@ Created on: 15.11.21
 | 
			
		||||
"""
 | 
			
		||||
from django.contrib.gis.db.models import MultiPolygonField
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
 | 
			
		||||
from konova.models import BaseResource, UuidModel
 | 
			
		||||
from konova.utils.wfs.spatial import ParcelWFSFetcher
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Geometry(BaseResource):
 | 
			
		||||
@ -21,7 +23,6 @@ class Geometry(BaseResource):
 | 
			
		||||
    def save(self, *args, **kwargs):
 | 
			
		||||
        super().save(*args, **kwargs)
 | 
			
		||||
        self.check_for_conflicts()
 | 
			
		||||
        self.update_parcels()
 | 
			
		||||
 | 
			
		||||
    def check_for_conflicts(self):
 | 
			
		||||
        """ Checks for new geometry overlaps
 | 
			
		||||
@ -93,8 +94,38 @@ class Geometry(BaseResource):
 | 
			
		||||
        return objs
 | 
			
		||||
 | 
			
		||||
    def update_parcels(self):
 | 
			
		||||
        # ToDo
 | 
			
		||||
        pass
 | 
			
		||||
        """ Updates underlying parcel information
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        from konova.models import Parcel, District
 | 
			
		||||
        parcel_fetcher = ParcelWFSFetcher(
 | 
			
		||||
            geometry_id=self.id,
 | 
			
		||||
        )
 | 
			
		||||
        typename = "ave:Flurstueck"
 | 
			
		||||
        fetched_parcels = parcel_fetcher.get_features(
 | 
			
		||||
            typename
 | 
			
		||||
        )
 | 
			
		||||
        underlying_parcels = []
 | 
			
		||||
        for result in fetched_parcels:
 | 
			
		||||
            fetched_parcel = result[typename]
 | 
			
		||||
            parcel_obj = Parcel.objects.get_or_create(
 | 
			
		||||
                flr=fetched_parcel["ave:flur"],
 | 
			
		||||
                flrstck_nnr=fetched_parcel['ave:flstnrnen'],
 | 
			
		||||
                flrstck_zhlr=fetched_parcel['ave:flstnrzae'],
 | 
			
		||||
            )[0]
 | 
			
		||||
            district = District.objects.get_or_create(
 | 
			
		||||
                gmrkng=fetched_parcel["ave:gemarkung"],
 | 
			
		||||
                gmnd=fetched_parcel["ave:gemeinde"],
 | 
			
		||||
                krs=fetched_parcel["ave:kreis"],
 | 
			
		||||
            )[0]
 | 
			
		||||
            parcel_obj.district = district
 | 
			
		||||
            parcel_obj.updated_on = timezone.now()
 | 
			
		||||
            parcel_obj.save()
 | 
			
		||||
            underlying_parcels.append(parcel_obj)
 | 
			
		||||
 | 
			
		||||
        self.parcels.set(underlying_parcels)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GeometryConflict(UuidModel):
 | 
			
		||||
 | 
			
		||||
@ -22,28 +22,30 @@ class Parcel(UuidModel):
 | 
			
		||||
    To avoid conflicts due to german Umlaute, the field names are shortened and vocals are dropped.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    geometries = models.ManyToManyField("konova.Geometry", related_name="parcels", null=True, blank=True)
 | 
			
		||||
    geometries = models.ManyToManyField("konova.Geometry", related_name="parcels", blank=True)
 | 
			
		||||
    district = models.ForeignKey("konova.District", on_delete=models.SET_NULL, null=True, blank=True, related_name="parcels")
 | 
			
		||||
    flrstck_nnr = models.CharField(
 | 
			
		||||
        max_length=1000,
 | 
			
		||||
        help_text="Flurstücksnenner"
 | 
			
		||||
        help_text="Flurstücksnenner",
 | 
			
		||||
        null=True,
 | 
			
		||||
        blank=True,
 | 
			
		||||
    )
 | 
			
		||||
    flrstck_zhlr = models.CharField(
 | 
			
		||||
        max_length=1000,
 | 
			
		||||
        help_text="Flurstückszähler"
 | 
			
		||||
        help_text="Flurstückszähler",
 | 
			
		||||
        null=True,
 | 
			
		||||
        blank=True,
 | 
			
		||||
    )
 | 
			
		||||
    flr = models.CharField(
 | 
			
		||||
        max_length=1000,
 | 
			
		||||
        help_text="Flur"
 | 
			
		||||
    )
 | 
			
		||||
    gmrkng = models.CharField(
 | 
			
		||||
        max_length=1000,
 | 
			
		||||
        help_text="Gemarkung"
 | 
			
		||||
        help_text="Flur",
 | 
			
		||||
        null=True,
 | 
			
		||||
        blank=True,
 | 
			
		||||
    )
 | 
			
		||||
    updated_on = models.DateTimeField(auto_now_add=True)
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"{self.gmrkng} | {self.flr} | {self.flrstck_nnr} | {self.flrstck_zhlr}"
 | 
			
		||||
        return f"{self.flr} | {self.flrstck_nnr} | {self.flrstck_zhlr}"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class District(UuidModel):
 | 
			
		||||
@ -54,18 +56,24 @@ class District(UuidModel):
 | 
			
		||||
    District.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    gmrkng = models.CharField(
 | 
			
		||||
        max_length=1000,
 | 
			
		||||
        help_text="Gemarkung",
 | 
			
		||||
        null=True,
 | 
			
		||||
        blank=True,
 | 
			
		||||
    )
 | 
			
		||||
    gmnd = models.CharField(
 | 
			
		||||
        max_length=1000,
 | 
			
		||||
        help_text="Gemeinde"
 | 
			
		||||
    )
 | 
			
		||||
    vg = models.CharField(
 | 
			
		||||
        max_length=1000,
 | 
			
		||||
        help_text="Verbandsgemeinde",
 | 
			
		||||
        help_text="Gemeinde",
 | 
			
		||||
        null=True,
 | 
			
		||||
        blank=True,
 | 
			
		||||
    )
 | 
			
		||||
    krs = models.CharField(
 | 
			
		||||
        max_length=1000,
 | 
			
		||||
        help_text="Kreis"
 | 
			
		||||
        help_text="Kreis",
 | 
			
		||||
        null=True,
 | 
			
		||||
        blank=True,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"{self.krs} | {self.vg} | {self.gmnd}"
 | 
			
		||||
        return f"{self.gmrkng} | {self.gmnd} | {self.krs}"
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,6 @@ Created on: 15.12.21
 | 
			
		||||
from django.contrib.gis.db.models.functions import Translate
 | 
			
		||||
 | 
			
		||||
from konova.models import Geometry, GeometryConflict
 | 
			
		||||
from konova.settings import PARCEL_WFS_USER, PARCEL_WFS_PW
 | 
			
		||||
from konova.tests.test_views import BaseTestCase
 | 
			
		||||
from konova.utils.wfs.spatial import ParcelWFSFetcher
 | 
			
		||||
 | 
			
		||||
@ -25,6 +24,22 @@ class GeometryTestCase(BaseTestCase):
 | 
			
		||||
            geom=geom,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_geometry_parcel_caluclation(self):
 | 
			
		||||
        """ Tests whether newly created geometries already have parcels calculated during save
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        has_parcels = self.geom_1.parcels.all().exists()
 | 
			
		||||
        self.assertFalse(has_parcels, msg=f"{self.geom_1.id} has parcels but should not!")
 | 
			
		||||
        self.geom_1.update_parcels()
 | 
			
		||||
        self.geom_1.refresh_from_db()
 | 
			
		||||
        parcels = self.geom_1.parcels.all()
 | 
			
		||||
        has_parcels = parcels.exists()
 | 
			
		||||
        parcel_districts = parcels.values_list("district", flat=True)
 | 
			
		||||
        self.assertTrue(has_parcels, msg=f"{self.geom_1.id} has no parcels but should!")
 | 
			
		||||
        self.assertEqual(parcels.count(), len(parcel_districts), msg=f"Not every parcel has exactly one district!")
 | 
			
		||||
 | 
			
		||||
    def test_geometry_conflict(self):
 | 
			
		||||
        """ Tests whether a geometry conflict will be present in case of identical/overlaying geometries and
 | 
			
		||||
        if the conflict will be resolved if one geometry is edited.
 | 
			
		||||
@ -50,17 +65,13 @@ class GeometryTestCase(BaseTestCase):
 | 
			
		||||
    def test_wfs_fetch(self):
 | 
			
		||||
        """ Tests the fetching functionality of ParcelWFSFetcher
 | 
			
		||||
 | 
			
		||||
        +++ Test relies on the availability of the RLP Gemarkung WFS +++
 | 
			
		||||
        +++ Test relies on the availability of the RLP Flurstück WFS +++
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        fetcher = ParcelWFSFetcher(
 | 
			
		||||
            base_url="https://www.geoportal.rlp.de/registry/wfs/519",
 | 
			
		||||
            version="2.0.0",
 | 
			
		||||
            geometry=self.geom_1,
 | 
			
		||||
            auth_user=PARCEL_WFS_USER,
 | 
			
		||||
            auth_pw=PARCEL_WFS_PW
 | 
			
		||||
            geometry_id=self.geom_1.id,
 | 
			
		||||
        )
 | 
			
		||||
        features = fetcher.get_features(
 | 
			
		||||
            "ave:Flurstueck",
 | 
			
		||||
 | 
			
		||||
@ -214,7 +214,7 @@ class BaseTestCase(TestCase):
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        polygon = Polygon.from_bbox((7.5971391556, 50.3600032354, 7.5993975756, 50.3612420894))
 | 
			
		||||
        polygon = Polygon.from_bbox((7.592449, 50.359385, 7.593382, 50.359874))
 | 
			
		||||
        polygon.srid = 4326
 | 
			
		||||
        polygon = polygon.transform(3857, clone=True)
 | 
			
		||||
        return MultiPolygon(polygon, srid=3857)  # 3857 is the default srid used for MultiPolygonField in the form
 | 
			
		||||
 | 
			
		||||
@ -10,11 +10,9 @@ from abc import abstractmethod
 | 
			
		||||
import requests
 | 
			
		||||
import xmltodict
 | 
			
		||||
from django.contrib.gis.db.models.functions import AsGML, Transform
 | 
			
		||||
from django.contrib.gis.geos import MultiPolygon
 | 
			
		||||
from requests.auth import HTTPDigestAuth
 | 
			
		||||
 | 
			
		||||
from konova.models import Geometry
 | 
			
		||||
from konova.settings import DEFAULT_SRID_RLP
 | 
			
		||||
from konova.settings import DEFAULT_SRID_RLP, PARCEL_WFS_USER, PARCEL_WFS_PW
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AbstractWFSFetcher:
 | 
			
		||||
@ -38,6 +36,9 @@ class AbstractWFSFetcher:
 | 
			
		||||
        self.auth_pw = auth_pw
 | 
			
		||||
        self.auth_user = auth_user
 | 
			
		||||
 | 
			
		||||
        self._create_auth_obj()
 | 
			
		||||
 | 
			
		||||
    def _create_auth_obj(self):
 | 
			
		||||
        if self.auth_pw is not None and self.auth_user is not None:
 | 
			
		||||
            self.auth_digest_obj = HTTPDigestAuth(
 | 
			
		||||
                self.auth_user,
 | 
			
		||||
@ -53,13 +54,20 @@ class ParcelWFSFetcher(AbstractWFSFetcher):
 | 
			
		||||
    """ Fetches features from a special parcel WFS
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    geometry = None
 | 
			
		||||
    geometry_id = None
 | 
			
		||||
    geometry_property_name = None
 | 
			
		||||
    count = 100
 | 
			
		||||
 | 
			
		||||
    def __init__(self, geometry: MultiPolygon, geometry_property_name: str = "msGeometry", *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.geometry = geometry
 | 
			
		||||
    def __init__(self, geometry_id: str, geometry_property_name: str = "msGeometry", *args, **kwargs):
 | 
			
		||||
        super().__init__(
 | 
			
		||||
            version="2.0.0",
 | 
			
		||||
            base_url="https://www.geoportal.rlp.de/registry/wfs/519",
 | 
			
		||||
            auth_user=PARCEL_WFS_USER,
 | 
			
		||||
            auth_pw=PARCEL_WFS_PW,
 | 
			
		||||
            *args,
 | 
			
		||||
            **kwargs
 | 
			
		||||
        )
 | 
			
		||||
        self.geometry_id = geometry_id
 | 
			
		||||
        self.geometry_property_name = geometry_property_name
 | 
			
		||||
 | 
			
		||||
    def _create_spatial_filter(self,
 | 
			
		||||
@ -74,10 +82,11 @@ class ParcelWFSFetcher(AbstractWFSFetcher):
 | 
			
		||||
        Returns:
 | 
			
		||||
            spatial_filter (str): The spatial filter element
 | 
			
		||||
        """
 | 
			
		||||
        from konova.models import Geometry
 | 
			
		||||
        if filter_srid is None:
 | 
			
		||||
            filter_srid = DEFAULT_SRID_RLP
 | 
			
		||||
        geom_gml = Geometry.objects.filter(
 | 
			
		||||
            id=self.geometry.id
 | 
			
		||||
            id=self.geometry_id
 | 
			
		||||
        ).annotate(
 | 
			
		||||
            transformed=Transform(srid=filter_srid, expression="geom")
 | 
			
		||||
        ).annotate(
 | 
			
		||||
@ -124,6 +133,7 @@ class ParcelWFSFetcher(AbstractWFSFetcher):
 | 
			
		||||
            typenames (str): References to parameter 'typenames' in a WFS GetFeature request
 | 
			
		||||
            spatial_operator (str): Defines the spatial operation for filtering
 | 
			
		||||
            filter_srid (str): Defines the spatial reference system, the geometry shall be transformed into for filtering
 | 
			
		||||
            start_index (str): References to parameter 'startindex' in a
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            features (list): A list of returned features
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user