Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Update the 'coupled' Topology field in triggers (#5295)
  • Loading branch information
justinefricou authored Feb 26, 2026
commit aa2425074d115a3479d26791af8e03f9d41b3f92
8 changes: 2 additions & 6 deletions geotrek/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -654,17 +654,13 @@ def overlapping(cls, queryset, all_objects=None):

def mutate(self, other):
"""
Take alls attributes of the other topology specified and
save them into this one. Optionnally deletes the other.
Take all attributes of the other topology specified and save them into this one.
"""
self.offset = other.offset
self.save(update_fields=["offset"])
PathAggregation.objects.filter(topo_object=self).delete()
# The previous operation has put deleted = True (in triggers)
# and NULL in geom (see update_geometry_of_topology:: IF t_count = 0)
self.deleted = False
self.geom = other.geom
self.save(update_fields=["deleted", "geom"])
self.save(update_fields=["geom"])

# Now copy all agregations from other to self
aggrs = other.aggregations.all()
Expand Down
108 changes: 57 additions & 51 deletions geotrek/core/templates/core/sql/post_20_topologies.sql
Original file line number Diff line number Diff line change
Expand Up @@ -76,63 +76,69 @@ BEGIN

-- RAISE NOTICE 'update_geometry_of_topology (lines_only:% points_only:% t_count:%)', lines_only, points_only, t_count;

IF t_count = 0 THEN
-- No more paths, close this topology
UPDATE core_topology SET deleted = true, geom = NULL, "length" = 0 WHERE id = topology_id;
ELSIF (NOT lines_only AND t_count = 1) OR points_only THEN
-- Special case: the topology describe a point on the path
-- Note: We are faking a M-geometry in order to use LocateAlong.
-- This is handy because this function includes an offset parameter
-- which could be otherwise diffcult to handle.
SELECT geom, "offset" INTO egeom, t_offset FROM core_topology e WHERE e.id = topology_id;
-- RAISE NOTICE '% % % %', (t_offset = 0), (egeom IS NULL), (ST_IsEmpty(egeom)), (ST_X(egeom) = 0 AND ST_Y(egeom) = 0);
IF t_offset = 0 OR egeom IS NULL OR ST_IsEmpty(egeom) OR (ST_X(egeom) = 0 AND ST_Y(egeom) = 0) THEN
-- ST_LocateAlong can give no point when we try to get the startpoint or the endpoint of the line
SELECT et.start_position INTO position_point FROM core_pathaggregation et WHERE et.topo_object_id = topology_id;
IF (position_point < 0.000000000000001) THEN
SELECT ST_StartPoint(t.geom) INTO egeom
FROM core_topology e, core_pathaggregation et, core_path t
WHERE e.id = topology_id AND et.topo_object_id = e.id AND et.path_id = t.id;
ELSIF (position_point > 0.999999999999999) THEN
SELECT ST_EndPoint(t.geom) INTO egeom
FROM core_topology e, core_pathaggregation et, core_path t
WHERE e.id = topology_id AND et.topo_object_id = e.id AND et.path_id = t.id;
ELSE
SELECT ST_GeometryN(ST_LocateAlong(ST_AddMeasure(ST_Force2D(t.geom), 0, 1), et.start_position, e.offset), 1)
INTO egeom
IF t_count = 0 OR NOT TopologyIsValid(topology_id) THEN
-- If the topology is invalid or if there are no longer any path aggregations linked to this
-- topology, we decouple it from the path network.
-- TODO in a later PR: set geom_need_update to FALSE and exit the function
UPDATE core_topology SET coupled = FALSE WHERE id = topology_id;
ELSE
UPDATE core_topology SET coupled = TRUE WHERE id = topology_id;
END IF;

IF t_count > 0 THEN
IF (NOT lines_only AND t_count = 1) OR points_only THEN
-- Special case: the topology describe a point on the path
-- Note: We are faking a M-geometry in order to use LocateAlong.
-- This is handy because this function includes an offset parameter
-- which could be otherwise diffcult to handle.
SELECT geom, "offset" INTO egeom, t_offset FROM core_topology e WHERE e.id = topology_id;
-- RAISE NOTICE '% % % %', (t_offset = 0), (egeom IS NULL), (ST_IsEmpty(egeom)), (ST_X(egeom) = 0 AND ST_Y(egeom) = 0);
IF t_offset = 0 OR egeom IS NULL OR ST_IsEmpty(egeom) OR (ST_X(egeom) = 0 AND ST_Y(egeom) = 0) THEN
-- ST_LocateAlong can give no point when we try to get the startpoint or the endpoint of the line
SELECT et.start_position INTO position_point FROM core_pathaggregation et WHERE et.topo_object_id = topology_id;
IF (position_point < 0.000000000000001) THEN
SELECT ST_StartPoint(t.geom) INTO egeom
FROM core_topology e, core_pathaggregation et, core_path t
WHERE e.id = topology_id AND et.topo_object_id = e.id AND et.path_id = t.id;
ELSIF (position_point > 0.999999999999999) THEN
SELECT ST_EndPoint(t.geom) INTO egeom
FROM core_topology e, core_pathaggregation et, core_path t
WHERE e.id = topology_id AND et.topo_object_id = e.id AND et.path_id = t.id;
ELSE
SELECT ST_GeometryN(ST_LocateAlong(ST_AddMeasure(ST_Force2D(t.geom), 0, 1), et.start_position, e.offset), 1)
INTO egeom
FROM core_topology e, core_pathaggregation et, core_path t
WHERE e.id = topology_id AND et.topo_object_id = e.id AND et.path_id = t.id;
END IF;
END IF;
END IF;

egeom_3d := egeom;
ELSE
-- Regular case: the topology describe a line
-- NOTE: LineMerge and Line_Substring work on X and Y only. If two
-- points in the line have the same X/Y but a different Z, these
-- functions will see only on point. --> No problem in mountain path management.
FOR t_offset, t_geom, t_geom_3d IN SELECT e."offset", ST_SmartLineSubstring(t.geom, et.start_position, et.end_position),
ST_SmartLineSubstring(t.geom_3d, et.start_position, et.end_position)
FROM core_topology e, core_pathaggregation et, core_path t
WHERE e.id = topology_id AND et.topo_object_id = e.id AND et.path_id = t.id
AND GeometryType(ST_SmartLineSubstring(t.geom, et.start_position, et.end_position)) != 'POINT'
ORDER BY et."order", et.id -- /!\ We suppose that path aggregations were created in the right order
LOOP
tomerge := array_append(tomerge, t_geom);
tomerge_3d := array_append(tomerge_3d, t_geom_3d);
END LOOP;
SELECT * FROM ft_Smart_MakeLine(tomerge) INTO smart_makeline;
SELECT * FROM ft_Smart_MakeLine(tomerge_3d) INTO smart_makeline_3d;
egeom := smart_makeline.new_geometry;
egeom_3d := smart_makeline_3d.new_geometry;
-- Add some offset if necessary.
IF t_offset != 0 THEN
egeom := ST_GeometryN(ST_LocateBetween(ST_AddMeasure(egeom, 0, 1), 0, 1, t_offset), 1);
egeom_3d := ST_GeometryN(ST_LocateBetween(ST_AddMeasure(egeom_3d, 0, 1), 0, 1, t_offset), 1);
egeom_3d := egeom;
ELSE
-- Regular case: the topology describe a line
-- NOTE: LineMerge and Line_Substring work on X and Y only. If two
-- points in the line have the same X/Y but a different Z, these
-- functions will see only on point. --> No problem in mountain path management.
FOR t_offset, t_geom, t_geom_3d IN SELECT e."offset", ST_SmartLineSubstring(t.geom, et.start_position, et.end_position),
ST_SmartLineSubstring(t.geom_3d, et.start_position, et.end_position)
FROM core_topology e, core_pathaggregation et, core_path t
WHERE e.id = topology_id AND et.topo_object_id = e.id AND et.path_id = t.id
AND GeometryType(ST_SmartLineSubstring(t.geom, et.start_position, et.end_position)) != 'POINT'
ORDER BY et."order", et.id -- /!\ We suppose that path aggregations were created in the right order
LOOP
tomerge := array_append(tomerge, t_geom);
tomerge_3d := array_append(tomerge_3d, t_geom_3d);
END LOOP;
SELECT * FROM ft_Smart_MakeLine(tomerge) INTO smart_makeline;
SELECT * FROM ft_Smart_MakeLine(tomerge_3d) INTO smart_makeline_3d;
egeom := smart_makeline.new_geometry;
egeom_3d := smart_makeline_3d.new_geometry;
-- Add some offset if necessary.
IF t_offset != 0 THEN
egeom := ST_GeometryN(ST_LocateBetween(ST_AddMeasure(egeom, 0, 1), 0, 1, t_offset), 1);
egeom_3d := ST_GeometryN(ST_LocateBetween(ST_AddMeasure(egeom_3d, 0, 1), 0, 1, t_offset), 1);
END IF;
END IF;
END IF;

IF t_count > 0 THEN
SELECT * FROM ft_elevation_infos(egeom_3d, {{ ALTIMETRIC_PROFILE_STEP }}) INTO elevation;
UPDATE core_topology SET geom = ST_Force2D(egeom),
geom_3d = ST_Force3DZ(elevation.draped),
Expand Down
25 changes: 0 additions & 25 deletions geotrek/core/templates/core/sql/post_40_paths.sql
Original file line number Diff line number Diff line change
Expand Up @@ -100,31 +100,6 @@ BEFORE INSERT OR UPDATE OF geom ON core_path
FOR EACH ROW EXECUTE PROCEDURE elevation_path_iu();


-------------------------------------------------------------------------------
-- Change status of related objects when paths are deleted
-------------------------------------------------------------------------------

CREATE FUNCTION {{ schema_geotrek }}.paths_related_objects_d() RETURNS trigger SECURITY DEFINER AS $$
DECLARE
BEGIN
-- Mark empty topologies as deleted
UPDATE core_topology e
SET deleted = TRUE
FROM core_pathaggregation et
WHERE et.topo_object_id = e.id AND et.path_id = OLD.id AND NOT EXISTS(
SELECT * FROM core_pathaggregation
WHERE topo_object_id = e.id AND path_id != OLD.id
);

RETURN OLD;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER core_path_related_objects_d_tgr
BEFORE DELETE ON core_path
FOR EACH ROW EXECUTE PROCEDURE paths_related_objects_d();


---------------------------------------------------------------------
-- Make sure cache key (base on lastest updated) is refresh on DELETE
---------------------------------------------------------------------
Expand Down
1 change: 0 additions & 1 deletion geotrek/core/templates/core/sql/pre_10_cleanup.sql
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ DROP FUNCTION IF EXISTS elevation_troncon_iu() CASCADE;
DROP FUNCTION IF EXISTS elevation_path_iu() CASCADE;

DROP FUNCTION IF EXISTS troncons_related_objects_d() CASCADE;
DROP FUNCTION IF EXISTS paths_related_objects_d() CASCADE;

DROP FUNCTION IF EXISTS troncon_latest_updated_d() CASCADE;
DROP FUNCTION IF EXISTS path_latest_updated_d() CASCADE;
Expand Down
2 changes: 1 addition & 1 deletion geotrek/core/tests/test_sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_topology(self):
def test_pathaggregation(self):
path = PathFactory.create()
topology = TopologyFactory.create(paths=[])
self.assertIsNone(topology.geom)
self.assertEqual(topology.geom.wkt, "POINT (0 0)")
with connection.cursor() as cur:
cur.execute(f"""INSERT INTO core_pathaggregation
(
Expand Down
Loading