Skip to content

Commit

Permalink
Merge pull request #3296 from GeotrekCE/add_command_reorder
Browse files Browse the repository at this point in the history
Add reorder topologies command
  • Loading branch information
LePetitTim authored Nov 3, 2022
2 parents e8e2fd1 + 18c4f91 commit 33c342b
Show file tree
Hide file tree
Showing 8 changed files with 985 additions and 23 deletions.
8 changes: 6 additions & 2 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ CHANGELOG

2.89.1+dev (XXXX-XX-XX)
-----------------------


**New features**

- Add new command to reorder pathaggregations of topologies

**Bug fixes**

- Fix APIv2 does not return sources related to published sites


2.89.1 (2022-10-20)
-----------------------

**Bug fixes**

- Prevent migration ``0033_auto_20220929_0840`` from failing by escaping Touristic Events ``participant_number``
Expand Down
444 changes: 436 additions & 8 deletions docs/install/import.rst

Large diffs are not rendered by default.

119 changes: 119 additions & 0 deletions geotrek/core/management/commands/reorder_topologies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
from django.conf import settings
from django.core.management.base import BaseCommand
from django.db.models import OneToOneRel
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 get_geom_lines(self, topology):
cursor = connection.cursor()
# We get all sublines of the topology
cursor.execute(f"""
SELECT et.id, 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_order = cursor.fetchall()
return geom_lines_order

def get_new_order(self, geom_lines):
cursor = connection.cursor()
# We use ft_Smart_MakeLine to get the order of the lines
cursor.execute(f"""SELECT * FROM ft_Smart_MakeLine(ARRAY{[geom[1] 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
fetched = cursor.fetchall()
return fetched[0][1]

def handle(self, *args, **options):
topologies = Topology.objects.filter(deleted=False)
failed_topologies = []
num_updated_topologies = 0
for topology in topologies:
geom_lines = self.get_geom_lines(topology)

new_order = self.get_new_order(geom_lines)
if new_order == []:
for field in topology._meta.get_fields():
if isinstance(field, OneToOneRel) and hasattr(topology, field.name):
failed_topologies.append(str(f'{getattr(topology, field.name).kind} id: {topology.pk}'))

if len(new_order) <= 2:
continue
order_maked_lines = new_order[1:]
orders = [result - 1 for result in order_maked_lines]
new_orders = {}
for x, geom_line in enumerate(geom_lines):
new_orders[geom_line[0]] = orders[x]

# We generate a dict with id Pathaggregation as key and new order (without points)

# We get all the Points that we didn't get for smart make line
cursor = connection.cursor()
cursor.execute(f"""
SELECT et.id, 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
""")
points = cursor.fetchall()
id_order = 0

dict_points = {}
for id_pa_point, geom_point_wkt in points:
dict_points[id_pa_point] = GEOSGeometry(geom_point_wkt, srid=settings.SRID)

points_touching = {}
# Find points aggregations that touches lines
while id_order < len(orders) - 1:
geometries_points = dict_points.values()
order_actual = orders[id_order]
order_next = orders[id_order + 1]
actual_point_end = GEOSGeometry(geom_lines[order_actual][1], srid=settings.SRID).boundary[
1] # Get end point of the geometry
next_point_start = GEOSGeometry(geom_lines[order_next][1], srid=settings.SRID).boundary[
0] # Get start point of the geometry
if actual_point_end == next_point_start and actual_point_end in geometries_points:
for id_pa_point, point_geom in dict_points.items():
if point_geom == actual_point_end:
points_touching[id_pa_point] = id_order + 1
dict_points.pop(id_pa_point)
break
id_order += 1

points_added = 0
# We add all points between the lines and remove points generated which should not be here (it happens)
for id_pa_point, order_point_touching in points_touching.items():
new_orders = {id_pa: new_order + 1 if new_order >= order_point_touching + points_added else new_order for id_pa, new_order in new_orders.items()}
new_orders[id_pa_point] = order_point_touching + points_added
points_added += 1
PathAggregation.objects.filter(topo_object=topology).exclude(id__in=new_orders.keys()).delete()

initial_order = PathAggregation.objects.filter(topo_object=topology).values_list('id', flat=True)

pas_updated = []
for pa_id in initial_order:
pa = PathAggregation.objects.get(id=pa_id)
if pa.order != new_orders[pa_id]:
pa.order = new_orders[pa_id]
pas_updated.append(pa)
PathAggregation.objects.bulk_update(pas_updated, ['order'])
if pas_updated:
num_updated_topologies += 1

if options['verbosity']:
self.stdout.write(f'{num_updated_topologies} topologies has beeen updated')

if options['verbosity'] and failed_topologies:
self.stdout.write('Topologies with errors :')
self.stdout.write('\n'.join(failed_topologies))
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;

10 changes: 7 additions & 3 deletions geotrek/core/templates/core/sql/post_20_topologies.sql
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ DECLARE
t_geom_3d geometry;
tomerge geometry[];
tomerge_3d geometry[];

smart_makeline line_infos;
smart_makeline_3d line_infos;
BEGIN
-- If Geotrek-light, don't do anything
IF NOT {{ TREKKING_TOPOLOGY_ENABLED }} THEN
Expand Down Expand Up @@ -118,9 +121,10 @@ BEGIN
tomerge := array_append(tomerge, t_geom);
tomerge_3d := array_append(tomerge_3d, t_geom_3d);
END LOOP;

egeom := ft_Smart_MakeLine(tomerge);
egeom_3d := ft_Smart_MakeLine(tomerge_3d);
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);
Expand Down
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
Loading

0 comments on commit 33c342b

Please sign in to comment.