Compare commits
No commits in common. "440a4b04d5bf85e521d775a5cc35dd4e67d60d6d" and "44dbee0008995defea1248e083055fb45e086fd2" have entirely different histories.
440a4b04d5
...
44dbee0008
@ -9,4 +9,3 @@ from .object import *
|
|||||||
from .deadline import *
|
from .deadline import *
|
||||||
from .document import *
|
from .document import *
|
||||||
from .geometry import *
|
from .geometry import *
|
||||||
from .parcel import *
|
|
||||||
|
@ -7,6 +7,7 @@ Created on: 15.11.21
|
|||||||
"""
|
"""
|
||||||
from django.contrib.gis.db.models import MultiPolygonField
|
from django.contrib.gis.db.models import MultiPolygonField
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
from konova.models import BaseResource, UuidModel
|
from konova.models import BaseResource, UuidModel
|
||||||
|
|
||||||
@ -21,7 +22,6 @@ class Geometry(BaseResource):
|
|||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
self.check_for_conflicts()
|
self.check_for_conflicts()
|
||||||
self.update_parcels()
|
|
||||||
|
|
||||||
def check_for_conflicts(self):
|
def check_for_conflicts(self):
|
||||||
""" Checks for new geometry overlaps
|
""" Checks for new geometry overlaps
|
||||||
@ -32,7 +32,7 @@ class Geometry(BaseResource):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
# If no geometry is given or important data is missing, we can not perform any checks
|
# If no geometry is given or important data is missing, we can not perform any checks
|
||||||
if self.geom is None:
|
if self.geom is None or (self.created is None and self.modified is None):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
self.recheck_existing_conflicts()
|
self.recheck_existing_conflicts()
|
||||||
@ -44,10 +44,7 @@ class Geometry(BaseResource):
|
|||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
for match in overlapping_geoms:
|
for match in overlapping_geoms:
|
||||||
# Make sure this conflict is not already known but in a swapped constellation
|
GeometryConflict.objects.get_or_create(conflicting_geometry=self, affected_geometry=match)
|
||||||
conflict_exists_swapped = GeometryConflict.objects.filter(conflicting_geometry=match, affected_geometry=self).exists()
|
|
||||||
if not conflict_exists_swapped:
|
|
||||||
GeometryConflict.objects.get_or_create(conflicting_geometry=self, affected_geometry=match)
|
|
||||||
|
|
||||||
def recheck_existing_conflicts(self):
|
def recheck_existing_conflicts(self):
|
||||||
""" Rechecks GeometryConflict entries
|
""" Rechecks GeometryConflict entries
|
||||||
@ -72,6 +69,7 @@ class Geometry(BaseResource):
|
|||||||
resolved_conflicts = all_conflicted_by_conflicts.exclude(id__in=still_conflicting_conflicts)
|
resolved_conflicts = all_conflicted_by_conflicts.exclude(id__in=still_conflicting_conflicts)
|
||||||
resolved_conflicts.delete()
|
resolved_conflicts.delete()
|
||||||
|
|
||||||
|
|
||||||
def get_data_objects(self):
|
def get_data_objects(self):
|
||||||
""" Getter for all objects which are related to this geometry
|
""" Getter for all objects which are related to this geometry
|
||||||
|
|
||||||
@ -92,10 +90,6 @@ class Geometry(BaseResource):
|
|||||||
objs += set_objs
|
objs += set_objs
|
||||||
return objs
|
return objs
|
||||||
|
|
||||||
def update_parcels(self):
|
|
||||||
# ToDo
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class GeometryConflict(UuidModel):
|
class GeometryConflict(UuidModel):
|
||||||
"""
|
"""
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
"""
|
|
||||||
Author: Michel Peltriaux
|
|
||||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
|
||||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
|
||||||
Created on: 16.12.21
|
|
||||||
|
|
||||||
"""
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
from konova.models import UuidModel
|
|
||||||
|
|
||||||
|
|
||||||
class Parcel(UuidModel):
|
|
||||||
""" The Parcel model holds administrative data on the covered properties.
|
|
||||||
|
|
||||||
Due to the unique but relevant naming of the administrative data, we have to use these namings as field
|
|
||||||
names in german. Any try to translate them to English result in strange or insufficient translations.
|
|
||||||
|
|
||||||
All fields have to be CharFields as well, since there are e.g. Flurstücksnummer holding e.g. '123____' which
|
|
||||||
can not be realized using numerical fields.
|
|
||||||
|
|
||||||
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)
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
flrstck_zhlr = models.CharField(
|
|
||||||
max_length=1000,
|
|
||||||
help_text="Flurstückszähler"
|
|
||||||
)
|
|
||||||
flr = models.CharField(
|
|
||||||
max_length=1000,
|
|
||||||
help_text="Flur"
|
|
||||||
)
|
|
||||||
gmrkng = models.CharField(
|
|
||||||
max_length=1000,
|
|
||||||
help_text="Gemarkung"
|
|
||||||
)
|
|
||||||
updated_on = models.DateTimeField(auto_now_add=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.gmrkng} | {self.flr} | {self.flrstck_nnr} | {self.flrstck_zhlr}"
|
|
||||||
|
|
||||||
|
|
||||||
class District(UuidModel):
|
|
||||||
""" The model District holds more coarse information, such as Kreis, Verbandsgemeinde and Gemeinde.
|
|
||||||
|
|
||||||
There might be the case that a geometry lies on a hundred Parcel entries but only on one District entry.
|
|
||||||
Therefore a geometry can have a lot of relations to Parcel entries but only a few or only a single one to one
|
|
||||||
District.
|
|
||||||
|
|
||||||
"""
|
|
||||||
gmnd = models.CharField(
|
|
||||||
max_length=1000,
|
|
||||||
help_text="Gemeinde"
|
|
||||||
)
|
|
||||||
vg = models.CharField(
|
|
||||||
max_length=1000,
|
|
||||||
help_text="Verbandsgemeinde",
|
|
||||||
)
|
|
||||||
krs = models.CharField(
|
|
||||||
max_length=1000,
|
|
||||||
help_text="Kreis"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.krs} | {self.vg} | {self.gmnd}"
|
|
@ -9,7 +9,7 @@ from django.contrib.gis.db.models.functions import Translate
|
|||||||
|
|
||||||
from konova.models import Geometry, GeometryConflict
|
from konova.models import Geometry, GeometryConflict
|
||||||
from konova.tests.test_views import BaseTestCase
|
from konova.tests.test_views import BaseTestCase
|
||||||
from konova.utils.wfs.spatial import SpatialWFSFetcher
|
from user.models import UserActionLogEntry
|
||||||
|
|
||||||
|
|
||||||
class GeometryTestCase(BaseTestCase):
|
class GeometryTestCase(BaseTestCase):
|
||||||
@ -17,11 +17,14 @@ class GeometryTestCase(BaseTestCase):
|
|||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
super().setUpTestData()
|
super().setUpTestData()
|
||||||
geom = cls.create_dummy_geometry()
|
geom = cls.create_dummy_geometry()
|
||||||
|
action = UserActionLogEntry.get_created_action(cls.superuser)
|
||||||
cls.geom_1 = Geometry.objects.create(
|
cls.geom_1 = Geometry.objects.create(
|
||||||
geom=geom,
|
geom=geom,
|
||||||
|
created=action,
|
||||||
)
|
)
|
||||||
cls.geom_2 = Geometry.objects.create(
|
cls.geom_2 = Geometry.objects.create(
|
||||||
geom=geom,
|
geom=geom,
|
||||||
|
created=action,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_geometry_conflict(self):
|
def test_geometry_conflict(self):
|
||||||
@ -31,9 +34,8 @@ class GeometryTestCase(BaseTestCase):
|
|||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
conflict = GeometryConflict.objects.all()
|
self.geom_1.check_for_conflicts()
|
||||||
self.assertEqual(1, conflict.count())
|
conflict = GeometryConflict.objects.all().first()
|
||||||
conflict = conflict.first()
|
|
||||||
self.assertEqual(conflict.conflicting_geometry, self.geom_2)
|
self.assertEqual(conflict.conflicting_geometry, self.geom_2)
|
||||||
self.assertEqual(conflict.affected_geometry, self.geom_1)
|
self.assertEqual(conflict.affected_geometry, self.geom_1)
|
||||||
|
|
||||||
@ -45,21 +47,3 @@ class GeometryTestCase(BaseTestCase):
|
|||||||
self.geom_1.check_for_conflicts()
|
self.geom_1.check_for_conflicts()
|
||||||
num_conflict = GeometryConflict.objects.all().count()
|
num_conflict = GeometryConflict.objects.all().count()
|
||||||
self.assertEqual(0, num_conflict)
|
self.assertEqual(0, num_conflict)
|
||||||
|
|
||||||
def test_wfs_fetch(self):
|
|
||||||
""" Tests the fetching functionality of SpatialWFSFetcher
|
|
||||||
|
|
||||||
+++ Test relies on the availability of the RLP Gemarkung WFS +++
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
fetcher = SpatialWFSFetcher(
|
|
||||||
base_url="http://geo5.service24.rlp.de/wfs/verwaltungsgrenzen_rp.fcgi",
|
|
||||||
version="1.1.0",
|
|
||||||
geometry=self.geom_1
|
|
||||||
)
|
|
||||||
features = fetcher.get_features(
|
|
||||||
"vermkv:fluren_rlp"
|
|
||||||
)
|
|
||||||
self.assertNotEqual(0, len(features), msg="Spatial wfs get feature did not work!")
|
|
||||||
|
@ -1,92 +0,0 @@
|
|||||||
"""
|
|
||||||
Author: Michel Peltriaux
|
|
||||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
|
||||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
|
||||||
Created on: 17.12.21
|
|
||||||
|
|
||||||
"""
|
|
||||||
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 owslib.wfs import WebFeatureService
|
|
||||||
|
|
||||||
from konova.models import Geometry
|
|
||||||
from konova.settings import DEFAULT_SRID_RLP
|
|
||||||
|
|
||||||
|
|
||||||
class BaseWFSFetcher:
|
|
||||||
# base_url represents not the capabilities url but the parameter-free base url
|
|
||||||
base_url = ""
|
|
||||||
version = ""
|
|
||||||
wfs = None
|
|
||||||
|
|
||||||
def __init__(self, base_url: str, version: str = "1.1.0", *args, **kwargs):
|
|
||||||
self.base_url = base_url
|
|
||||||
self.version = version
|
|
||||||
self.wfs = WebFeatureService(
|
|
||||||
url=base_url,
|
|
||||||
version=version,
|
|
||||||
)
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_features(self, feature_identifier: str, filter: str):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class SpatialWFSFetcher(BaseWFSFetcher):
|
|
||||||
""" Fetches features from a parcel WFS
|
|
||||||
|
|
||||||
"""
|
|
||||||
geometry = None
|
|
||||||
geometry_property_name = ""
|
|
||||||
|
|
||||||
def __init__(self, geometry: MultiPolygon, geometry_property_name: str = "msGeometry", *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.geometry = geometry
|
|
||||||
self.geometry_property_name = geometry_property_name
|
|
||||||
|
|
||||||
def _create_geometry_filter(self, geometry_operation: str, filter_srid: str = None):
|
|
||||||
""" Creates an
|
|
||||||
|
|
||||||
Args:
|
|
||||||
geometry_operation ():
|
|
||||||
filter_srid ():
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
if filter_srid is None:
|
|
||||||
filter_srid = DEFAULT_SRID_RLP
|
|
||||||
geom_gml = Geometry.objects.filter(
|
|
||||||
id=self.geometry.id
|
|
||||||
).annotate(
|
|
||||||
transformed=Transform(srid=filter_srid, expression="geom")
|
|
||||||
).annotate(
|
|
||||||
gml=AsGML('transformed')
|
|
||||||
).first().gml
|
|
||||||
_filter = f"<Filter><{geometry_operation}><PropertyName>{self.geometry_property_name}</PropertyName>{geom_gml}</{geometry_operation}></Filter>"
|
|
||||||
return _filter
|
|
||||||
|
|
||||||
def get_features(self, typenames: str, geometry_operation: str = "Intersects", filter_srid: str = None, filter: str = None):
|
|
||||||
if filter is None:
|
|
||||||
filter = self._create_geometry_filter(geometry_operation, filter_srid)
|
|
||||||
response = requests.post(
|
|
||||||
url=f"{self.base_url}?request=GetFeature&service=WFS&version={self.version}&typenames={typenames}&count=10",
|
|
||||||
data={
|
|
||||||
"filter": filter,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
content = response.content.decode("utf-8")
|
|
||||||
content = xmltodict.parse(content)
|
|
||||||
features = content.get(
|
|
||||||
"wfs:FeatureCollection",
|
|
||||||
{},
|
|
||||||
).get(
|
|
||||||
"gml:featureMember",
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
return features
|
|
||||||
|
|
@ -14,14 +14,10 @@ django-tables2==2.3.4
|
|||||||
et-xmlfile==1.1.0
|
et-xmlfile==1.1.0
|
||||||
idna==2.10
|
idna==2.10
|
||||||
importlib-metadata==2.1.1
|
importlib-metadata==2.1.1
|
||||||
itsdangerous==0.24
|
itsdangerous<1.0.0
|
||||||
openpyxl==3.0.9
|
openpyxl==3.0.9
|
||||||
OWSLib==0.25.0
|
|
||||||
psycopg2-binary==2.9.1
|
psycopg2-binary==2.9.1
|
||||||
pyproj==3.2.1
|
|
||||||
python-dateutil==2.8.2
|
|
||||||
pytz==2020.4
|
pytz==2020.4
|
||||||
PyYAML==6.0
|
|
||||||
qrcode==7.3.1
|
qrcode==7.3.1
|
||||||
requests==2.25.0
|
requests==2.25.0
|
||||||
six==1.15.0
|
six==1.15.0
|
||||||
@ -29,5 +25,4 @@ soupsieve==2.2.1
|
|||||||
sqlparse==0.4.1
|
sqlparse==0.4.1
|
||||||
urllib3==1.26.2
|
urllib3==1.26.2
|
||||||
webservices==0.7
|
webservices==0.7
|
||||||
xmltodict==0.12.0
|
|
||||||
zipp==3.4.1
|
zipp==3.4.1
|
||||||
|
Loading…
Reference in New Issue
Block a user