diff --git a/docs/changelog.rst b/docs/changelog.rst index ad07aa3916..1ba2d1d548 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -26,6 +26,8 @@ In preparation for HD Views developments (PR #3298) **Bug fixes** - Recreate cache folders if missing. (#3384) +- Modify site's geometry before saving to avoid edition and export of shapefiles (#3399) + 2.94.0 (2022-12-12) ----------------------- diff --git a/geotrek/common/templates/common/sql/post_10_utilities.sql b/geotrek/common/templates/common/sql/post_10_utilities.sql index 7c60e80f2f..806474efa8 100644 --- a/geotrek/common/templates/common/sql/post_10_utilities.sql +++ b/geotrek/common/templates/common/sql/post_10_utilities.sql @@ -22,3 +22,19 @@ BEGIN RETURN NEW; END; $$ LANGUAGE plpgsql; + +-- Possible to have collection of GeometryCollection +-- we need to flatten the geometrycollection to avoid problem with export and edition (#3397) + +CREATE FUNCTION {{ schema_geotrek }}.flatten_geometrycollection_iu() RETURNS trigger SECURITY DEFINER AS $$ +DECLARE + geoms geometry[]; + geom geometry; +BEGIN + FOR geom IN SELECT (ST_Dump(NEW.geom)).geom LOOP + geoms := array_append(geoms, geom); + END LOOP; + NEW.geom := ST_ForceCollection(ST_COLLECT(geoms)); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; diff --git a/geotrek/common/templates/common/sql/pre_10_cleanup.sql b/geotrek/common/templates/common/sql/pre_10_cleanup.sql index 791fbd08e8..9d47546a72 100644 --- a/geotrek/common/templates/common/sql/pre_10_cleanup.sql +++ b/geotrek/common/templates/common/sql/pre_10_cleanup.sql @@ -3,3 +3,4 @@ DROP TABLE IF EXISTS south_migrationhistory; -- legacy, replaced by django_migr DROP FUNCTION IF EXISTS ft_date_insert() CASCADE; DROP FUNCTION IF EXISTS ft_date_update() CASCADE; DROP FUNCTION IF EXISTS ft_uuid_insert() CASCADE; +DROP FUNCTION IF EXISTS flatten_geometrycollection_iu() CASCADE; diff --git a/geotrek/outdoor/migrations/0042_flatten_geometrycollection.py b/geotrek/outdoor/migrations/0042_flatten_geometrycollection.py new file mode 100644 index 0000000000..507c80c7f0 --- /dev/null +++ b/geotrek/outdoor/migrations/0042_flatten_geometrycollection.py @@ -0,0 +1,21 @@ +# Generated by Django 3.1.7 on 2021-03-15 15:12 + +from django.db import migrations + + +def flatten_geometrycollection(apps, schema_editor): + Site = apps.get_model('outdoor', 'Site') + Course = apps.get_model('outdoor', 'Course') + Site.objects.bulk_update(Site.objects.all(), ['geom']) + Course.objects.bulk_update(Course.objects.all(), ['geom']) + + +class Migration(migrations.Migration): + + dependencies = [ + ('outdoor', '0041_auto_20221110_1128'), + ] + + operations = [ + migrations.RunPython(flatten_geometrycollection, migrations.RunPython.noop), + ] diff --git a/geotrek/outdoor/models.py b/geotrek/outdoor/models.py index 776a511137..91b50a8344 100644 --- a/geotrek/outdoor/models.py +++ b/geotrek/outdoor/models.py @@ -299,6 +299,10 @@ def site_interventions(self): qs |= Q(target_id__in=topologies) & ~Q(target_type__in=not_topology_content_types) return Intervention.objects.existing().filter(qs).distinct('pk') + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + self.refresh_from_db() + Path.add_property('sites', lambda self: intersecting(Site, self), _("Sites")) Topology.add_property('sites', lambda self: intersecting(Site, self), _("Sites")) diff --git a/geotrek/outdoor/templates/outdoor/sql/post_30_geometrycollections.sql b/geotrek/outdoor/templates/outdoor/sql/post_30_geometrycollections.sql new file mode 100644 index 0000000000..72c9a43393 --- /dev/null +++ b/geotrek/outdoor/templates/outdoor/sql/post_30_geometrycollections.sql @@ -0,0 +1,12 @@ +------------------------------------------------------------------------------- +-- Compute elevation and elevation-based indicators +------------------------------------------------------------------------------- + + +CREATE TRIGGER outdoor_site_30_geometrycollection_flatten_iu_tgr +BEFORE INSERT OR UPDATE OF geom ON outdoor_site +FOR EACH ROW EXECUTE PROCEDURE flatten_geometrycollection_iu(); + +CREATE TRIGGER outdoor_course_30_geometrycollection_flatten_iu_tgr +BEFORE INSERT OR UPDATE OF geom ON outdoor_course +FOR EACH ROW EXECUTE PROCEDURE flatten_geometrycollection_iu(); diff --git a/geotrek/outdoor/tests/test_models.py b/geotrek/outdoor/tests/test_models.py index eeaf69ab8a..a5f1dcb417 100644 --- a/geotrek/outdoor/tests/test_models.py +++ b/geotrek/outdoor/tests/test_models.py @@ -2,7 +2,7 @@ from django.contrib.gis.geos import Polygon from django.contrib.gis.geos.collections import GeometryCollection -from django.contrib.gis.geos.point import Point +from django.contrib.gis.geos.point import Point, GEOSGeometry from django.test import TestCase, override_settings from geotrek.common.tests.factories import OrganismFactory @@ -25,6 +25,31 @@ def test_published_children_by_lang(self): SiteFactory(name='child3', parent=parent, published_fr=True) self.assertQuerysetEqual(parent.published_children, ['', '']) + def test_validate_collection_geometrycollection(self): + site_simple = SiteFactory.create(name='site', + geom='GEOMETRYCOLLECTION(POINT(0 0), POLYGON((1 1, 2 2, 1 2, 1 1))))') + self.assertEqual(site_simple.geom.wkt, + GEOSGeometry('GEOMETRYCOLLECTION(POINT(0 0), POLYGON((1 1, 2 2, 1 2, 1 1)))').wkt + ) + site_complex_geom = SiteFactory.create(name='site', + geom='GEOMETRYCOLLECTION(MULTIPOINT(0 0, 1 1), ' + 'POLYGON((1 1, 2 2, 1 2, 1 1))))') + self.assertEqual(site_complex_geom.geom.wkt, + GEOSGeometry('GEOMETRYCOLLECTION(POINT(0 0), POINT(1 1), POLYGON((1 1, 2 2, 1 2, 1 1)))').wkt + ) + site_multiple_point = SiteFactory.create(name='site', + geom='GEOMETRYCOLLECTION(POINT(0 0), POINT(1 1), POINT(1 2))') + self.assertEqual(site_multiple_point.geom.wkt, + GEOSGeometry('GEOMETRYCOLLECTION(POINT(0 0), POINT(1 1), POINT(1 2)))').wkt + ) + site_multiple_geomcollection = SiteFactory.create(name='site', + geom='GEOMETRYCOLLECTION(' + 'GEOMETRYCOLLECTION(POINT(0 0)),' + 'GEOMETRYCOLLECTION(POINT(1 1)), ' + 'GEOMETRYCOLLECTION(POINT(1 2)))') + self.assertEqual(site_multiple_geomcollection.geom.wkt, + 'GEOMETRYCOLLECTION (POINT (0 0), POINT (1 1), POINT (1 2))') + class SiteSuperTest(TestCase): @classmethod