Compare commits

..
6 Commits
Author SHA1 Message Date
mpeltriaux e25d549a97 # 497 Impressum link update
* updates impressum link
2025-11-28 11:44:18 +01:00
mpeltriaux 5e65b8f4dc # Geometry error message fix
* fixes bug where errors on geometry form were not rendered properly
* fixes bug where invalid geometry was written as read-only back into form (could not be corrected by user)
* adds explanatory comments to SimpleGeomForm is_valid() checks
* reorders code snippets for better understanding
* adds correcting logic to _set_geojson_properties() in case of missing properties element
2025-11-28 11:43:17 +01:00
mpeltriaux 22cddb9902 Merge pull request '# Fix for #500' (#501) from 500_Geometry_conflicts_still_visible into master
Reviewed-on: #501
2025-11-19 13:16:30 +01:00
mpeltriaux c986bd0b92 # Fix for #500
* fixes bug where de-facto deleted compensations (because of deleted intervention) would still show up as geometry conflicts on other entries
2025-11-19 13:16:09 +01:00
mpeltriaux 2c60d86177 Merge pull request '# Update netgis map client' (#498) from netgis_map_client into master
Reviewed-on: #498
2025-11-07 14:11:57 +01:00
mpeltriaux b7792ececc # Update netgis map client
* updates netgis map client (bugfix for https://github.com/sebastianpauli/netgis-client/issues/43#issuecomment-3446898016)
2025-11-07 14:11:15 +01:00
8 changed files with 603 additions and 8250 deletions

No files matched your search

+33 -14
View File
@@ -35,6 +35,7 @@ class SimpleGeomForm(BaseForm):
disabled=False,
)
_num_geometries_ignored: int = 0
empty = False
def __init__(self, *args, **kwargs):
self.read_only = kwargs.pop("read_only", True)
@@ -49,7 +50,7 @@ class SimpleGeomForm(BaseForm):
raise AttributeError
geojson = self.instance.geometry.as_feature_collection(srid=DEFAULT_SRID_RLP)
self._set_geojson_properties(geojson, title=self.instance.identifier or None)
geojson = self._set_geojson_properties(geojson, title=self.instance.identifier or None)
geom = json.dumps(geojson)
except AttributeError:
# If no geometry exists for this form, we simply set the value to None and zoom to the maximum level
@@ -62,21 +63,29 @@ class SimpleGeomForm(BaseForm):
super().is_valid()
is_valid = True
# Get geojson from form
# Make sure invalid geometry is properly rendered again to the user
# Therefore: write submitted data back into form field
# (does not matter whether we know if it is valid or invalid)
submitted_data = self.data["output"]
submitted_data = json.loads(submitted_data)
submitted_data = self._set_geojson_properties(submitted_data)
self.initialize_form_field("output", json.dumps(submitted_data))
# Get geojson from form for validity checking
geom = self.data.get("output", None)
if geom is None or len(geom) == 0:
# empty geometry is a valid geometry
geom_is_empty = geom is None or len(geom) == 0
if geom_is_empty:
# If no geometry has been submitted, we create an empty geometry object since
# an empty geometry is a valid geometry
self.cleaned_data["output"] = MultiPolygon(srid=DEFAULT_SRID_RLP).ewkt
return is_valid
geom = json.loads(geom)
# Write submitted data back into form field to make sure invalid geometry
# will be rendered again on failed submit
self.initialize_form_field("output", self.data["output"])
# Initialize features list with empty MultiPolygon, so that an empty input will result in a
# proper empty MultiPolygon object
features = []
# Prepare geometry for validity checks (create iterable dict)
geom = json.loads(geom)
features_json = geom.get("features", [])
accepted_ogr_types = [
"Polygon",
@@ -84,20 +93,23 @@ class SimpleGeomForm(BaseForm):
"MultiPolygon",
"MultiPolygon25D",
]
# Check validity for each feature of the geometry
for feature in features_json:
feature_geom = feature.get("geometry", feature)
if feature_geom is None:
# Fallback for rare cases where a feature does not contain any geometry
continue
# Try to create a geometry object from the single feature
feature_geom = json.dumps(feature_geom)
g = gdal.OGRGeometry(feature_geom, srs=DEFAULT_SRID_RLP)
flatten_geometry = g.coord_dim > 2
if flatten_geometry:
geometry_has_unwanted_dimensions = g.coord_dim > 2
if geometry_has_unwanted_dimensions:
g = self.__flatten_geom_to_2D(g)
if g.geom_type not in accepted_ogr_types:
geometry_type_is_accepted = g.geom_type not in accepted_ogr_types
if geometry_type_is_accepted:
self.add_error("output", _("Only surfaces allowed. Points or lines must be buffered."))
is_valid &= False
return is_valid
@@ -109,18 +121,23 @@ class SimpleGeomForm(BaseForm):
self._num_geometries_ignored += 1
continue
# Whatever this geometry object is -> try to create a Polygon from it
# The resulting polygon object automatically detects whether a valid polygon has been created or not
g = Polygon.from_ewkt(g.ewkt)
is_valid &= g.valid
if not g.valid:
self.add_error("output", g.valid_reason)
return is_valid
# If the resulting polygon is just a single polygon, we add it to the list of properly casted features
if isinstance(g, Polygon):
features.append(g)
elif isinstance(g, MultiPolygon):
# The resulting polygon could be of type MultiPolygon (due to multiple surfaces)
# If so, we extract all polygons from the MultiPolygon and extend the casted features list
features.extend(list(g))
# Unionize all geometry features into one new MultiPolygon
# Unionize all polygon features into one new MultiPolygon
if features:
form_geom = MultiPolygon(*features, srid=DEFAULT_SRID_RLP).unary_union
else:
@@ -129,7 +146,7 @@ class SimpleGeomForm(BaseForm):
# Make sure to convert into a MultiPolygon. Relevant if a single Polygon is provided.
form_geom = Geometry.cast_to_multipolygon(form_geom)
# Write unioned Multipolygon into cleaned data
# Write unionized Multipolygon back into cleaned data
if self.cleaned_data is None:
self.cleaned_data = {}
self.cleaned_data["output"] = form_geom.ewkt
@@ -252,6 +269,8 @@ class SimpleGeomForm(BaseForm):
"""
features = geojson.get("features", [])
for feature in features:
if not feature.get("properties", None):
feature["properties"] = {}
feature["properties"]["editable"] = not self.read_only
if title:
feature["properties"]["title"] = title
+13 -3
View File
@@ -10,6 +10,7 @@ import json
from django.contrib.gis.db.models import MultiPolygonField
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
from django.db import models, transaction
from django.db.models import Q
from django.utils import timezone
from django.contrib.gis.geos import MultiPolygon
@@ -109,17 +110,26 @@ class Geometry(BaseResource):
objs (list): The list of objects
"""
objs = []
sets = [
# Some related data sets can be processed rather easily
regular_sets = [
self.intervention_set,
self.compensation_set,
self.ema_set,
self.ecoaccount_set,
]
for _set in sets:
for _set in regular_sets:
set_objs = _set.filter(
deleted=None
)
objs += set_objs
# ... but we need a special treatment for compensations, since they can be deleted directly OR inherit their
# de-facto-deleted status from their deleted parent intervention
comp_objs = self.compensation_set.filter(
Q(deleted=None) & Q(intervention__deleted=None)
)
objs += comp_objs
return objs
def get_data_object(self):
+2 -2
View File
@@ -677,12 +677,12 @@ class GeoReferencedMixin(models.Model):
return request
instance_objs = []
conflicts = self.geometry.conflicts_geometries.all()
conflicts = self.geometry.conflicts_geometries.iterator()
for conflict in conflicts:
instance_objs += conflict.affected_geometry.get_data_objects()
conflicts = self.geometry.conflicted_by_geometries.all()
conflicts = self.geometry.conflicted_by_geometries.iterator()
for conflict in conflicts:
instance_objs += conflict.conflicting_geometry.get_data_objects()
+1 -1
View File
@@ -11,4 +11,4 @@ BASE_TITLE = "KSP - Kompensationsverzeichnis Service Portal"
BASE_FRONTEND_TITLE = "Kompensationsverzeichnis Service Portal"
TAB_TITLE_IDENTIFIER = "tab_title"
HELP_LINK = "https://dienste.naturschutz.rlp.de/doku/doku.php?id=ksp2:start"
IMPRESSUM_LINK = "https://naturschutz.rlp.de/index.php?q=impressum"
IMPRESSUM_LINK = "https://naturschutz.rlp.de/ueber-uns/impressum"
+1
View File
@@ -188,6 +188,7 @@
{
"title": "Ebene hinzufügen",
"preview": true,
"editable": true,
"wms_options": [ "https://sgx.geodatenzentrum.de/wms_topplus_open" ],
"wfs_options": [ "http://213.139.159.34:80/geoserver/uesg/wfs" ],
"wfs_proxy": "/client/proxy?",
+1 -1
View File
@@ -1 +1 @@
.netgis-attribution{position:absolute;right:0;bottom:0;padding:1mm;background:rgba(255,255,255,0.5);font-size:2.5mm;z-index:100}.netgis-client{position:relative;box-sizing:border-box;font-size:4mm;overflow:hidden}.netgis-client *{box-sizing:border-box}.netgis-client button{font-size:inherit}.netgis-contextmenu{position:absolute;width:64mm;z-index:90000}.netgis-contextmenu>*{display:block;position:relative;width:100%;height:12mm;line-height:12mm;padding:0 4mm;text-align:left;background:none}.netgis-contextmenu>label span{position:absolute;left:0;right:50%;height:100%;padding:0 4mm}.netgis-contextmenu>label span:last-of-type{left:50%;right:0;padding:2mm;padding-left:0;padding-right:4mm}.netgis-contextmenu>label span:only-of-type{left:12mm;right:0;padding:0 4mm;padding-left:0;cursor:pointer}.netgis-contextmenu>label input[type=range]{width:100%;cursor:pointer}.netgis-controls{position:absolute;width:12mm;right:4mm;bottom:8mm;overflow:hidden;z-index:100}.netgis-client.netgis-footer>.netgis-controls{bottom:44mm}.netgis-controls button{font-size:5mm!important;color:inherit;width:100%;height:12mm;padding:0!important;border:none;background-color:inherit;cursor:pointer}.netgis-dropdown{display:inline-block;position:relative;padding:0!important}.netgis-dropdown>:first-child{height:12mm;z-index:10;padding:0 4mm}.netgis-dropdown ul{display:none;position:absolute;min-width:100%;left:0;margin:0;padding:0;list-style-type:none}.netgis-dropdown li{position:relative;height:12mm}.netgis-dropdown li>button,.netgis-dropdown li>a{display:inline-block;width:100%;height:100%;text-align:left}.netgis-dropdown li>ul{display:none;top:0;left:100%;max-height:100mm;overflow-x:hidden;overflow-y:auto}.netgis-dropdown li>ul li{padding-right:5mm}.netgis-dropdown li:hover>ul{display:block}.netgis-dropdown li i:first-child{margin-right:4mm!important}.netgis-dropdown li i:last-child{margin-left:4mm!important;margin-right:0!important}.netgis-dropdown li>label{display:block;text-align:left}.netgis-dropdown li>label input{width:4mm;margin:0;margin-right:4mm}.netgis-compact .netgis-dropdown li{height:9mm;line-height:9mm}.netgis-compact .netgis-dropdown li button,.netgis-compact .netgis-dropdown li .netgis-button{padding:0 3mm}.netgis-compact .netgis-dropdown li i{margin-right:4mm}@media (max-width:599px){.netgis-dropdown.netgis-active>ul{display:block}}@media (min-width:600px){.netgis-dropdown:hover>ul{display:block}}.netgis-import .netgis-preview-map{position:absolute;width:100%;height:40mm;cursor:grab}.netgis-import .netgis-preview-map:active:hover{cursor:grabbing}.netgis-import .netgis-preview-tree{position:absolute;width:100%;top:52mm;bottom:12mm;overflow-y:auto}.netgis-import .netgis-import-submit{position:absolute;width:100%;height:12mm;bottom:0}.netgis-import .netgis-geoportal .netgis-search>label{display:block}.netgis-import .netgis-geoportal .netgis-search>label>button{top:9mm}.netgis-import .netgis-geoportal>.netgis-loader{left:0;top:0;background:rgba(255,255,255,0.66)}.netgis-import .netgis-geoportal>label>.netgis-loader{width:10mm;height:10mm;bottom:0;right:0;font-size:5mm}.netgis-import .netgis-geoportal>label>.netgis-loader i{top:2.75mm}.netgis-import .netgis-geoportal .netgis-tree{position:absolute;left:8mm;right:8mm;top:32mm;bottom:18mm;overflow:auto;padding-left:0;list-style-type:none}.netgis-import .netgis-geoportal .netgis-tree li{padding-left:0;list-style-type:none}.netgis-import .netgis-geoportal .netgis-tree label{padding-left:0;margin-bottom:0;font-weight:normal}.netgis-import .netgis-geoportal .netgis-tree .netgis-folder>details summary input{margin-left:3mm}.netgis-import .netgis-geoportal .netgis-tree .netgis-item input{margin-left:4mm}.netgis-import .netgis-geoportal>button:last-of-type{display:block;position:absolute;width:89%;right:8mm;bottom:8mm}@media (max-width:599px){.netgis-import .netgis-geoportal .netgis-tree{left:4mm;right:4mm;top:25mm}.netgis-import .netgis-geoportal button:last-of-type{width:auto;right:4mm;padding-right:8mm}}.netgis-legend{padding:3mm}.netgis-legend details{margin-bottom:2mm}.netgis-legend summary{display:block;height:12mm;line-height:12mm;font-weight:bold}.netgis-legend details>div{padding:2mm}.netgis-legend details>div img{max-width:100%}.netgis-map{position:absolute;left:0;right:0;top:12mm;bottom:0;cursor:grab}.netgis-map:active:hover{cursor:grabbing!important}.netgis-map-overlay{width:1px;height:1px;background:none}.netgis-map.netgis-clickable{cursor:pointer}.netgis-map.netgis-mode-zoom-box,.netgis-map.netgis-mode-zoom-box:active:hover,.netgis-map.netgis-mode-measure-line,.netgis-map.netgis-mode-measure-area{cursor:crosshair}.netgis-map.netgis-mode-measure-line:active:hover,.netgis-map.netgis-mode-measure-area:active:hover{cursor:grabbing}.netgis-map.netgis-mode-draw-points,.netgis-map.netgis-mode-draw-lines,.netgis-map.netgis-mode-draw-polygons,.netgis-map.netgis-mode-cut-features-draw{cursor:crosshair}.netgis-map.netgis-mode-draw-points.netgis-not-allowed,.netgis-map.netgis-mode-draw-liLine truncated
.netgis-attribution{position:absolute;right:0;bottom:0;padding:1mm;background:rgba(255,255,255,0.5);font-size:2.5mm;z-index:100}.netgis-client{position:relative;box-sizing:border-box;font-size:4mm;overflow:hidden}.netgis-client *{box-sizing:border-box}.netgis-client button{font-size:inherit}.netgis-contextmenu{position:absolute;width:64mm;z-index:90000}.netgis-contextmenu>*{display:block;position:relative;width:100%;height:12mm;line-height:12mm;padding:0 4mm;text-align:left;background:none}.netgis-contextmenu>label span{position:absolute;left:0;right:50%;height:100%;padding:0 4mm}.netgis-contextmenu>label span:last-of-type{left:50%;right:0;padding:2mm;padding-left:0;padding-right:4mm}.netgis-contextmenu>label span:only-of-type{left:12mm;right:0;padding:0 4mm;padding-left:0;cursor:pointer}.netgis-contextmenu>label input[type=range]{width:100%;cursor:pointer}.netgis-controls{position:absolute;width:12mm;right:4mm;bottom:8mm;overflow:hidden;z-index:100}.netgis-client.netgis-footer>.netgis-controls{bottom:44mm}.netgis-controls button{font-size:5mm!important;color:inherit;width:100%;height:12mm;padding:0!important;border:none;background-color:inherit;cursor:pointer}.netgis-dropdown{display:inline-block;position:relative;padding:0!important}.netgis-dropdown>:first-child{height:12mm;z-index:10;padding:0 4mm}.netgis-dropdown ul{display:none;position:absolute;min-width:100%;left:0;margin:0;padding:0;list-style-type:none}.netgis-dropdown li{position:relative;height:12mm}.netgis-dropdown li>button,.netgis-dropdown li>a{display:inline-block;width:100%;height:100%;text-align:left}.netgis-dropdown li>ul{display:none;top:0;left:100%;max-height:100mm;overflow-x:hidden;overflow-y:auto}.netgis-dropdown li>ul li{padding-right:5mm}.netgis-dropdown li:hover>ul{display:block}.netgis-dropdown li i:first-child{margin-right:4mm!important}.netgis-dropdown li i:last-child{margin-left:4mm!important;margin-right:0!important}.netgis-dropdown li>label{display:block;text-align:left}.netgis-dropdown li>label input{width:4mm;margin:0;margin-right:4mm}.netgis-compact .netgis-dropdown li{height:9mm;line-height:9mm}.netgis-compact .netgis-dropdown li button,.netgis-compact .netgis-dropdown li .netgis-button{padding:0 3mm}.netgis-compact .netgis-dropdown li i{margin-right:4mm}@media (max-width:599px){.netgis-dropdown.netgis-active>ul{display:block}}@media (min-width:600px){.netgis-dropdown:hover>ul{display:block}}.netgis-import .netgis-preview-map{position:absolute;width:100%;height:40mm;cursor:grab}.netgis-import .netgis-preview-map:active:hover{cursor:grabbing}.netgis-import .netgis-preview-tree{position:absolute;width:100%;top:52mm;bottom:12mm;overflow-y:auto}.netgis-import .netgis-import-submit{position:absolute;width:100%;height:12mm;bottom:0}.netgis-import .netgis-geoportal .netgis-search>label{display:block;position:absolute;width:auto;left:0;right:30mm}.netgis-import .netgis-geoportal .netgis-search>label>button{top:9mm}.netgis-import .netgis-geoportal .netgis-search>label>button:last-of-type{width:26mm;right:-30mm;padding:0}.netgis-import .netgis-geoportal>.netgis-loader{left:0;top:0;background:rgba(255,255,255,0.66)}.netgis-import .netgis-geoportal>label>.netgis-loader{width:10mm;height:10mm;bottom:0;right:0;font-size:5mm}.netgis-import .netgis-geoportal>label>.netgis-loader i{top:2.75mm}.netgis-import .netgis-geoportal .netgis-tree{position:absolute;left:8mm;right:8mm;top:32mm;bottom:18mm;overflow:auto;padding-left:0;list-style-type:none}.netgis-import .netgis-geoportal .netgis-tree li{padding-left:0;list-style-type:none}.netgis-import .netgis-geoportal .netgis-tree label{margin-bottom:0;font-weight:normal}.netgis-import .netgis-geoportal .netgis-tree .netgis-folder>details summary input{margin-left:3mm}.netgis-import .netgis-geoportal .netgis-tree .netgis-item input{margin-left:4mm}.netgis-import .netgis-geoportal>button:last-of-type{display:block;position:absolute;width:89%;right:8mm;bottom:8mm}@media (max-width:599px){.netgis-import .netgis-geoportal .netgis-tree{left:4mm;right:4mm;top:25mm}.netgis-import .netgis-geoportal button:last-of-type{width:auto;right:4mm;padding-right:8mm}}.netgis-legend{padding:3mm}.netgis-legend details{margin-bottom:2mm}.netgis-legend summary{display:block;height:12mm;line-height:12mm;font-weight:bold}.netgis-legend details>div{padding:2mm}.netgis-legend details>div img{max-width:100%}.netgis-map{position:absolute;left:0;right:0;top:12mm;bottom:0;cursor:grab}.netgis-map:active:hover{cursor:grabbing!important}.netgis-map-overlay{width:1px;height:1px;background:none}.netgis-map.netgis-clickable{cursor:pointer}.netgis-map.netgis-mode-zoom-box,.netgis-map.netgis-mode-zoom-box:active:hover,.netgis-map.netgis-mode-measure-line,.netgis-map.netgis-mode-measure-area{cursor:crosshair}.netgis-map.netgis-mode-measure-line:active:hover,.netgis-map.netgis-mode-measure-area:active:hover{cursor:grabbing}.netgis-map.netgis-mode-draw-points,.netgis-map.netgis-mode-draw-lines,.netgis-map.netgis-mode-draw-polygons,.netgiLine truncated
+550 -8227
View File
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -13,9 +13,9 @@
</div>
{% endif %}
{% if geom_form.geom.errors %}
{% if geom_form.output.errors %}
<div class="alert-danger p-2">
{% for error in geom_form.geom.errors %}
{% for error in geom_form.output.errors %}
<strong class="invalid">{{ error }}</strong>
<br>
{% endfor %}