Skip to content

Commit

Permalink
Add reorder topologies command
Browse files Browse the repository at this point in the history
  • Loading branch information
LePetitTim committed Oct 14, 2022
1 parent 9b1eca6 commit aa27b77
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 5 deletions.
75 changes: 75 additions & 0 deletions geotrek/core/management/commands/reorder_topologies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from django.core.management.base import BaseCommand
from django.db import connection

from django.contrib.gis.geos import GEOSGeometry

from geotrek.core.models import PathAggregation, Topology


class Command(BaseCommand):
help = """Reorder Pathaggregations of all topologies."""

def handle(self, *args, **options):
topologies = Topology.objects.all()
for topology in topologies:
cursor = connection.cursor()
# We get all sublines of the topology
cursor.execute(f"""
SELECT ST_ASTEXT(ST_SmartLineSubstring(t.geom, et.start_position, et.end_position))
FROM core_topology e, core_pathaggregation et, core_path t
WHERE e.id = {topology.pk} 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
""")
geom_lines = cursor.fetchall()
# We use ft_Smart_MakeLine to get the order of the lines
cursor.execute(f"""SELECT * FROM ft_Smart_MakeLine(ARRAY{[geom[0] for geom in geom_lines]}::geometry[])""")
# We remove first value (algorithme use a 0 by default to go through the lines and will always be here)
# Then we need to remove first value and remove 1 to all of them because Path aggregation's orders begin at 0
orders = [result - 1 for result in cursor.fetchall()[0][1][1:]]
# We get all the Points that we didn't get for smart make line
cursor = connection.cursor()
cursor.execute(f"""
SELECT ST_ASTEXT(ST_SmartLineSubstring(t.geom, et.start_position, et.end_position))
FROM core_topology e, core_pathaggregation et, core_path t
WHERE e.id = {topology.pk} 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
""")
geom_points = cursor.fetchall()
new_orders = []
order_point = 0
number_points = 0
id_order = 0
for geom_point_wkt in geom_points:
geom_point = GEOSGeometry(geom_point_wkt[0], srid=2154)
while id_order < len(orders) - 1:
order_actual = orders[id_order]
order_next = orders[id_order + 1]
id_order += 1
# We check if the point is on the actual line's end_point and next line's start_point to get its position
actual_point_end = GEOSGeometry(geom_lines[order_actual][0], srid=2154).boundary[1] # Get end point of the geometry
next_point_start = GEOSGeometry(geom_lines[order_next][0], srid=2154).boundary[0] # Get start point of the geometry
if geom_point == actual_point_end == next_point_start:
# If we find it position :
# we get all orders gnerated with 'smart make line' after last point (or start_point) to the actual point
new_orders.extend([order + number_points for order in orders[order_point:id_order]])
# We add the point inside the list of orders
new_orders.append(id_order + number_points)
# We keep number of points added to add the value to lines orders and points orders in the next iteration
number_points += 1
order_point = id_order
# We get next point
break
# We add all the lines orders after last points. If we have no point, we would get all lines in the order [0:)
new_orders.extend([order + number_points for order in orders[order_point:]])

pas = []
# We update every order with new orders.
# The query is always in the same order between django and sql (order by "order" and id)
# Path aggregation will be created in this order too
for pa_order, pa_id in enumerate(PathAggregation.objects.filter(topo_object=topology).order_by('order', 'id').values_list('id', flat=True)):
pa = PathAggregation.objects.get(id=pa_id)
pa.order = new_orders[pa_order]
pas.append(pa)
PathAggregation.objects.bulk_update(pas, ['order'])
14 changes: 12 additions & 2 deletions geotrek/core/templates/core/sql/post_10_utilities.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
-- Interpolate along : the opposite of ST_LocateAlong
-------------------------------------------------------------------------------

CREATE TYPE {{ schema_geotrek }}.line_infos AS (
new_geometry geometry,
new_order integer[]
);


CREATE FUNCTION {{ schema_geotrek }}.ST_InterpolateAlong(line geometry, point geometry) RETURNS RECORD AS $$
DECLARE
linear_offset float;
Expand Down Expand Up @@ -65,7 +71,7 @@ $$ LANGUAGE plpgsql;
-- A smart ST_MakeLine that will re-oder linestring before merging them
-------------------------------------------------------------------------------

CREATE FUNCTION {{ schema_geotrek }}.ft_Smart_MakeLine(lines geometry[]) RETURNS geometry AS $$
CREATE FUNCTION {{ schema_geotrek }}.ft_Smart_MakeLine(lines geometry[]) RETURNS line_infos AS $$
DECLARE
result geometry;
t_line geometry;
Expand All @@ -74,6 +80,7 @@ DECLARE
i int;
t_proceed boolean;
t_found boolean;
final_result line_infos;
BEGIN
result := ST_GeomFromText('LINESTRING EMPTY');
nblines := array_length(lines, 1);
Expand Down Expand Up @@ -125,9 +132,12 @@ BEGIN
IF NOT t_found THEN
result := ST_Union(lines);
-- RAISE NOTICE 'Cannot connect Topology paths: %', ST_AsText(ST_Union(lines));
current := ARRAY[]::integer[];
END IF;
result := ST_SetSRID(result, ST_SRID(lines[1]));
RETURN result;
final_result.new_geometry = result;
final_result.new_order = current;
RETURN final_result;
END;
$$ LANGUAGE plpgsql;

1 change: 1 addition & 0 deletions geotrek/core/templates/core/sql/pre_10_cleanup.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
-- 10

DROP TYPE IF EXISTS line_infos CASCADE;
DROP FUNCTION IF EXISTS ST_InterpolateAlong(geometry, geometry) CASCADE;
DROP FUNCTION IF EXISTS ST_Smart_Line_Substring(geometry, float, float) CASCADE;
DROP FUNCTION IF EXISTS ST_SmartLineSubstring(geometry, float, float) CASCADE;
Expand Down
137 changes: 134 additions & 3 deletions geotrek/core/tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
from unittest import mock, skipIf

from django.conf import settings
from django.contrib.gis.geos import LineString
from django.contrib.gis.geos import LineString, Point, GEOSGeometry
from django.core.management import call_command
from django.core.management.base import CommandError
from django.test import TestCase, override_settings
from django.db import IntegrityError
from django.db import connection, IntegrityError

from geotrek.authent.models import Structure
from geotrek.core.models import Path
from geotrek.core.models import Path, PathAggregation
from geotrek.core.tests.factories import PathFactory, TopologyFactory
from geotrek.trekking.tests.factories import POIFactory
import os

Expand Down Expand Up @@ -229,3 +230,133 @@ def test_load_paths_within_spatial_extent_no_srid_geom(self):
value = Path.objects.first()
self.assertEqual(value.name, 'lulu')
self.assertEqual(value.structure, self.structure)


@skipIf(not settings.TREKKING_TOPOLOGY_ENABLED, 'Test with dynamic segmentation only')
class ReorderTopologiesPathAggregationTest(TestCase):
def setUp(self):
"""
⠳ ⠞
⠳ ⠞
⠳ ⠞
⠳ ⠞
⠞ ⠳
⠞ ⠳
1 ⠞ ⠳ 2
⠞ ⠳
"""
self.path_1 = PathFactory.create(geom=LineString(Point(700000, 6600000), Point(700100, 6600100),
srid=settings.SRID))
self.path_2 = PathFactory.create(geom=LineString(Point(700000, 6600100), Point(700100, 6600000),
srid=settings.SRID))
self.path_1_a = Path.objects.get(geom=LineString(Point(700000, 6600000), Point(700050, 6600050),
srid=settings.SRID))
self.path_1_b = Path.objects.get(geom=LineString(Point(700050, 6600050), Point(700100, 6600100),
srid=settings.SRID))
self.path_2_a = Path.objects.get(geom=LineString(Point(700000, 6600100), Point(700050, 6600050),
srid=settings.SRID))
self.path_2_b = Path.objects.get(geom=LineString(Point(700050, 6600050), Point(700100, 6600000),
srid=settings.SRID))

def get_geometries(self):
geometries = []
for pathagg in PathAggregation.objects.all():
cursor = connection.cursor()
cursor.execute(f"""SELECT * FROM ST_ASTEXT(ST_SmartLineSubstring('{pathagg.path.geom.wkt}'::geometry,
{pathagg.start_position},
{pathagg.end_position}
))
""")
geom = cursor.fetchall()[0][0]
geometries.append(GEOSGeometry(geom, srid=2154))
return geometries

def test_split_reorder_1(self):
"""
Part A
⠳ 🡥
⠳ 🡥
⠳ 🡥
⠳ 🡥
🡥 🡥 Topo 1
🡥 ⠳ ⠳ Paths (1 2)
🡥 ⠳
1 🡥 ⠳ 2
🡥 ⠳
Part B
⠳ 🡥
⠳ 🡥
⠳ ⠳ 🡥
⠳ ⠳ 🡥
⠳ 🡥 🡥 Topo 1
🡥 ⠳ ⠳ Paths (1 2 3)
🡥 ⠳ ⠳
1 🡥 ⠳ ⠳ 2
🡥 3 ⠳ ⠳
"""
topo = TopologyFactory.create(paths=[(self.path_1_a, 0, 1), (self.path_1_b, 0, 1)])
PathFactory.create(geom=LineString(Point(700000, 6600090), Point(700090, 6600000), srid=settings.SRID))
call_command('reorder_topologies')
geometries = self.get_geometries()
self.assertEqual(geometries, [LineString((700000, 6600000), (700045, 6600045), srid=2154),
LineString((700045, 6600045), (700050, 6600050), srid=2154),
LineString((700050, 6600050), (700100, 6600100), srid=2154)])
self.assertEqual(list(PathAggregation.objects.filter(topo_object=topo).values_list('order', flat=True)),
[0, 1, 2])

def test_split_reorder_2(self):
"""
Part A
⠳ 🡥
⠳ 🡥
⠳ 🡥
⠳ 🡥
⠳ 🡥
🡥 🡥 Topo 1
0 ⠳ 0 Topo 1 (point)
X ⠳ x Topo 1 (2 directions)
0 ⠳ ⠳ Paths (1 2)
🡥 ⠳
🡥 ⠳
Part B
⠳ 🡥
⠳ 🡥
⠳ 🡥
⠳ ⠳ 🡥
⠳ ⠳ 🡥
⠳ 🡥 🡥 Topo 1
⠳ 0 ⠳ 0 Topo 1 (point)
X ⠳ ⠳ x Topo 1 (2 directions)
0 ⠳ ⠳ ⠳ Paths (1 2 3)
🡥 ⠳ ⠳
🡥 ⠳ ⠳
"""
topo = TopologyFactory.create(paths=[(self.path_1_a, 0, 0.95),
(self.path_1_a, 0.95, 0.95),
(self.path_1_a, 0.95, 0.5),
(self.path_1_a, 0.5, 0.5),
(self.path_1_a, 0.5, 1),
(self.path_1_b, 0, 1)])
PathFactory.create(geom=LineString(Point(700000, 6600090), Point(700090, 6600000), srid=settings.SRID))
call_command('reorder_topologies')
geometries = self.get_geometries()
self.assertEqual(geometries, [LineString((700000, 6600000), (700045, 6600045), srid=2154),
LineString((700045, 6600045), (700047.5, 6600047.5), srid=2154),
Point(700047.5, 6600047.5, srid=2154),
LineString((700047.5, 6600047.5), (700045, 6600045), srid=2154),
LineString((700045, 6600045), (700025, 6600025), srid=2154),
Point(700025, 6600025, srid=2154),
LineString((700025, 6600025), (700045, 6600045), srid=2154),
LineString((700045, 6600045), (700050, 6600050), srid=2154),
LineString((700050, 6600050), (700100, 6600100), srid=2154)])

self.assertEqual(list(PathAggregation.objects.filter(topo_object=topo).values_list('order', flat=True)),
[0, 1, 2, 3, 4, 5, 6, 7, 8])

0 comments on commit aa27b77

Please sign in to comment.