From 97b3ab4d514e646c9216eefabff8540fcf09bbe6 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Wed, 10 Apr 2024 15:46:08 +0200 Subject: [PATCH 001/213] Add TrekGeometry view with get and post methods --- geotrek/core/static/core/multipath.js | 30 +++++++++++++++++++ .../core/core_extrabody_fragment.html | 1 + geotrek/core/urls.py | 3 +- geotrek/core/views.py | 10 +++++++ 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index b6d9172055..8c1eab44fa 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -486,9 +486,38 @@ L.Handler.MultiPath = L.Handler.extend({ return -1; }, + + getCookie: function(name) { + var cookieValue = null; + if (document.cookie && document.cookie !== '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = cookies[i].trim(); + if (cookie.substring(0, name.length + 1) === (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + }, + computePaths: function() { if (this.canCompute()) { + console.log('computePaths:', 'graph', this.graph, 'steps', this.steps) var computed_paths = Geotrek.shortestPath(this.graph, this.steps); + console.log('computed_paths:' , computed_paths) + let csrftoken = this.getCookie('csrftoken'); + fetch(window.SETTINGS.urls['trek_geometry'], { + method: 'POST', + headers: { + "X-CSRFToken": csrftoken + } + }).then(response => response.text()) + .then(data => console.log(data)) + .catch(e => { + console.log("computePaths", e) + }) this._onComputedPaths(computed_paths); } }, @@ -756,6 +785,7 @@ L.Handler.MultiPath = L.Handler.extend({ }, onComputedPaths: function(data) { + console.log("onComputedPaths: data = ", data) var self = this; var topology = Geotrek.TopologyHelper.buildTopologyFromComputedPath(this.idToLayer, data); diff --git a/geotrek/core/templates/core/core_extrabody_fragment.html b/geotrek/core/templates/core/core_extrabody_fragment.html index 395c77cbfb..a674f6e444 100644 --- a/geotrek/core/templates/core/core_extrabody_fragment.html +++ b/geotrek/core/templates/core/core_extrabody_fragment.html @@ -7,6 +7,7 @@ window.SETTINGS.urls['path_layer'] = "{% url "core:path-drf-list" format="geojson" %}"; window.SETTINGS.urls['trail_layer'] = "{% url "core:trail-drf-list" format="geojson" %}"; window.SETTINGS.urls['path_graph'] = "{% url "core:path-drf-graph" %}"; + window.SETTINGS.urls['trek_geometry'] = "{% url "core:trek_geometry" %}"; diff --git a/geotrek/core/urls.py b/geotrek/core/urls.py index 33c001e34a..3cb59794b0 100644 --- a/geotrek/core/urls.py +++ b/geotrek/core/urls.py @@ -7,7 +7,7 @@ from .models import Path, Trail from .views import ( PathGPXDetail, PathKMLDetail, TrailGPXDetail, TrailKMLDetail, - MultiplePathDelete + MultiplePathDelete, TrekGeometry ) register_converter(LangConverter, 'lang') @@ -24,6 +24,7 @@ name="trail_gpx_detail"), path('api//trails//trail_.kml', TrailKMLDetail.as_view(), name="trail_kml_detail"), + path('api/treks/geometry', TrekGeometry.as_view(), name="trek_geometry"), ] urlpatterns += registry.register(Path, AltimetryEntityOptions, menu=(settings.PATH_MODEL_ENABLED and settings.TREKKING_TOPOLOGY_ENABLED)) diff --git a/geotrek/core/views.py b/geotrek/core/views.py index 5154f27c85..131b9c8c58 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -15,6 +15,7 @@ from django.utils.translation import gettext as _ from django.views.decorators.cache import cache_control from django.views.decorators.http import last_modified as cache_last_modified +from django.views import View from django.views.generic import TemplateView from django.views.generic.detail import BaseDetailView from mapentity.serializers import GPXSerializer @@ -439,3 +440,12 @@ def get_queryset(self): else: qs = qs.defer('geom', 'geom_3d') return qs + + +class TrekGeometry(View): + + def get(self, request): + return HttpResponse("This is a GET") + + def post(self, request): + return HttpResponse("This is a POST") From 58766b193ffe731b25b0d5e526ec0a38c38d5b85 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Wed, 10 Apr 2024 16:10:39 +0200 Subject: [PATCH 002/213] Add a parameter to TrekGeometry post method --- geotrek/core/static/core/multipath.js | 10 ++++++---- geotrek/core/views.py | 4 +++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index 8c1eab44fa..50514f3c8c 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -511,10 +511,12 @@ L.Handler.MultiPath = L.Handler.extend({ fetch(window.SETTINGS.urls['trek_geometry'], { method: 'POST', headers: { - "X-CSRFToken": csrftoken - } - }).then(response => response.text()) - .then(data => console.log(data)) + "X-CSRFToken": csrftoken, + content_type: "application/json" + }, + body: JSON.stringify({param: 2}) + }).then(response => response.json()) + .then(data => console.log(data.res)) .catch(e => { console.log("computePaths", e) }) diff --git a/geotrek/core/views.py b/geotrek/core/views.py index 131b9c8c58..bd6dc207ce 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -448,4 +448,6 @@ def get(self, request): return HttpResponse("This is a GET") def post(self, request): - return HttpResponse("This is a POST") + import json + params = json.loads(request.body.decode()) + return HttpResponse(json.dumps({'res': 2 * params['param']})) From 74f047739960aef905c921acb0e78985775b17f9 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Fri, 12 Apr 2024 10:47:07 +0200 Subject: [PATCH 003/213] Add graph and steps info being sent to TrekGeometry view --- geotrek/core/static/core/dijkstra.js | 8 +++++--- geotrek/core/static/core/multipath.js | 27 ++++++++++++++++++++++----- geotrek/core/views.py | 9 ++++----- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/geotrek/core/static/core/dijkstra.js b/geotrek/core/static/core/dijkstra.js index e45905acb3..3b71dd0125 100644 --- a/geotrek/core/static/core/dijkstra.js +++ b/geotrek/core/static/core/dijkstra.js @@ -17,6 +17,7 @@ Geotrek.Dijkstra = (function() { // Warning - weight is in fact edge.length in our data l.push({'node_id': node_dest_id, 'weight': graph_edges[edge_id].length}); }); + console.log("getPairWeightNode", l) return l; } @@ -81,9 +82,9 @@ Geotrek.Dijkstra = (function() { // Mark as visited (won't be chosen) current_djk_node.visited = true; // we could del it out of djk - + current_node_id = current_djk_node.node; - + // Last point if (is_destination(current_node_id)) break; @@ -192,6 +193,7 @@ Geotrek.shortestPath = (function() { * Returns list of paths, and null if not found. */ var paths = []; + console.log('tzhrhtrztszth') for (var j = 0; j < steps.length - 1; j++) { var path = computeTwoStepsPath(graph, steps[j], steps[j + 1]); if (!path) @@ -217,7 +219,7 @@ Geotrek.shortestPath = (function() { // path : Array of { start: Node_id, end: Node_id, edge: Edge, weight: Int (edge.length) } // weight: Int // } - + console.log("AZERTY") var weighted_path = Geotrek.Dijkstra.get_shortest_path_from_graph(graph, from_nodes, to_nodes); // restore graph diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index 50514f3c8c..6a9e29a899 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -505,8 +505,20 @@ L.Handler.MultiPath = L.Handler.extend({ computePaths: function() { if (this.canCompute()) { console.log('computePaths:', 'graph', this.graph, 'steps', this.steps) + + var sent_steps = [] + this.steps.forEach((step) => { + var sent_step = { + path_length: step.path_length, + percent_distance: step.percent_distance, + edge_id: step.polyline.properties.id, + } + sent_steps.push(sent_step) + }) + var computed_paths = Geotrek.shortestPath(this.graph, this.steps); console.log('computed_paths:' , computed_paths) + let csrftoken = this.getCookie('csrftoken'); fetch(window.SETTINGS.urls['trek_geometry'], { method: 'POST', @@ -514,12 +526,17 @@ L.Handler.MultiPath = L.Handler.extend({ "X-CSRFToken": csrftoken, content_type: "application/json" }, - body: JSON.stringify({param: 2}) - }).then(response => response.json()) - .then(data => console.log(data.res)) - .catch(e => { - console.log("computePaths", e) + body: JSON.stringify({ + graph: this.graph, + steps: sent_steps, }) + }) + .then(response => response.json()) + .then(data => console.log('response:', data)) + .catch(e => { + console.log("computePaths", e) + }) + this._onComputedPaths(computed_paths); } }, diff --git a/geotrek/core/views.py b/geotrek/core/views.py index bd6dc207ce..2c0c5ed945 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -1,5 +1,6 @@ import logging from collections import defaultdict +import json from django.conf import settings from django.contrib import messages @@ -444,10 +445,8 @@ def get_queryset(self): class TrekGeometry(View): - def get(self, request): - return HttpResponse("This is a GET") - def post(self, request): - import json params = json.loads(request.body.decode()) - return HttpResponse(json.dumps({'res': 2 * params['param']})) + print(params['graph']) + print(params['steps']) + return HttpResponse(json.dumps(params['graph'])) From 6e16158caa40c7db9590fda986cec601977a8f66 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Fri, 12 Apr 2024 16:55:19 +0200 Subject: [PATCH 004/213] Add computing of the dijkstra matrix and start and end nodes (only handling whole edges with their default direction) --- geotrek/core/static/core/multipath.js | 12 ++-- geotrek/core/views.py | 83 +++++++++++++++++++++++++-- requirements.txt | 3 + setup.py | 1 + 4 files changed, 89 insertions(+), 10 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index 6a9e29a899..cadc4ef7d4 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -503,8 +503,13 @@ L.Handler.MultiPath = L.Handler.extend({ }, computePaths: function() { + if (this.canCompute()) { - console.log('computePaths:', 'graph', this.graph, 'steps', this.steps) + + var computed_paths = Geotrek.shortestPath(this.graph, this.steps); + console.log('computed_paths:' , computed_paths) + + let csrftoken = this.getCookie('csrftoken'); var sent_steps = [] this.steps.forEach((step) => { @@ -516,10 +521,7 @@ L.Handler.MultiPath = L.Handler.extend({ sent_steps.push(sent_step) }) - var computed_paths = Geotrek.shortestPath(this.graph, this.steps); - console.log('computed_paths:' , computed_paths) - - let csrftoken = this.getCookie('csrftoken'); + console.log('computePaths:', 'graph', this.graph, 'steps', sent_steps) fetch(window.SETTINGS.urls['trek_geometry'], { method: 'POST', headers: { diff --git a/geotrek/core/views.py b/geotrek/core/views.py index 2c0c5ed945..eb4a3be3d4 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -9,7 +9,7 @@ from django.core.cache import caches from django.db.models import Sum, Prefetch from django.http import HttpResponseRedirect -from django.http.response import HttpResponse +from django.http.response import HttpResponse, JsonResponse from django.shortcuts import redirect from django.urls import reverse from django.utils.decorators import method_decorator @@ -26,6 +26,11 @@ from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer from rest_framework.response import Response + +import numpy as np +from scipy.sparse.csgraph import dijkstra +from scipy.sparse import csr_matrix + from geotrek.authent.decorators import same_structure_required from geotrek.common.functions import Length from geotrek.common.mixins.views import CustomColumnsMixin @@ -445,8 +450,76 @@ def get_queryset(self): class TrekGeometry(View): + def get_cs_graph(self): + + def get_edge_id_by_nodes(node1, node2): + for value in node1[1].values(): + if value in node2[1].values(): + return value + return None + + def get_edge_weight(edge_id): + edge = self.edges.get(str(edge_id)) + if edge is None: + return None + return edge.get('length') + + array = [] + for node1 in self.nodes.items(): + key1, value1 = node1 + row = [] + for node2 in self.nodes.items(): + key2, _ = node2 + if key1 == key2: + # If it's the same node, the weight is 0 + row.append(0) + elif key2 in value1.keys(): + # If the nodes are linked by a single edge, the weight is + # the edge length + edge_id = get_edge_id_by_nodes(node1, node2) + edge_weight = get_edge_weight(edge_id) + if edge_weight is not None: + row.append(edge_weight) + else: + # If the nodes are not directly linked, the weight is 0 + row.append(0) + array.append(row) + + return np.array(array) + + def get_start_and_end_node_ids(self): + # For each step, get its associated edge: + step_edges = [self.edges.get(str(x.get('edge_id'))) for x in self.steps] + + # For each of these edges, get its starting and ending node + bound_nodes = [(x.get('nodes_id')[0], x.get('nodes_id')[1]) for x in step_edges] + + path_starting_node = bound_nodes[0][0] + path_ending_node = bound_nodes[-1][-1] + return path_starting_node, path_ending_node + def post(self, request): - params = json.loads(request.body.decode()) - print(params['graph']) - print(params['steps']) - return HttpResponse(json.dumps(params['graph'])) + try: + params = json.loads(request.body.decode()) + self.steps = params['steps'] + graph = params['graph'] + self.nodes = graph['nodes'] + self.edges = graph['edges'] + except: + print("TrekGeometry POST: incorrect parameters") + # TODO: Bad request + + cs_graph = self.get_cs_graph() + matrix = csr_matrix(cs_graph) + + start_node, end_node = self.get_start_and_end_node_ids() + print("start_node, end_node", start_node, end_node) + + result = dijkstra(matrix, return_predecessors=True, indices=2, + directed=False) + print(result) + + return JsonResponse({ + 'graph': params['graph'], + 'steps': params['steps'], + }) diff --git a/requirements.txt b/requirements.txt index 677f02efc1..dcebf5183f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -240,6 +240,7 @@ numpy==1.23.4 # via # large-image # large-image-source-vips + # scipy # shapely packaging==24.0 # via @@ -324,6 +325,8 @@ ruamel-yaml-clib==0.2.7 # via ruamel-yaml sentry-sdk==2.8.0 # via geotrek (setup.py) +scipy==1.10.1 + # via geotrek (setup.py) shapely==2.0.4 # via pyopenair simplekml==1.3.6 diff --git a/setup.py b/setup.py index 34fca1b696..3d975a59e1 100644 --- a/setup.py +++ b/setup.py @@ -70,6 +70,7 @@ def run(self): "django-clearcache", "pyopenair", "django-treebeard", + "scipy", # prod, 'gunicorn', 'sentry-sdk', From b200b8766891fc6c4605d67fe63241c6bad5e8cf Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Mon, 15 Apr 2024 10:23:21 +0200 Subject: [PATCH 005/213] Add computing of the path to follow (node to node) (handles whole edges with their default direction) --- geotrek/core/static/core/dijkstra.js | 3 -- geotrek/core/static/core/multipath.js | 8 ++--- geotrek/core/views.py | 44 +++++++++++++++++++++++---- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/geotrek/core/static/core/dijkstra.js b/geotrek/core/static/core/dijkstra.js index 3b71dd0125..1578988f01 100644 --- a/geotrek/core/static/core/dijkstra.js +++ b/geotrek/core/static/core/dijkstra.js @@ -17,7 +17,6 @@ Geotrek.Dijkstra = (function() { // Warning - weight is in fact edge.length in our data l.push({'node_id': node_dest_id, 'weight': graph_edges[edge_id].length}); }); - console.log("getPairWeightNode", l) return l; } @@ -193,7 +192,6 @@ Geotrek.shortestPath = (function() { * Returns list of paths, and null if not found. */ var paths = []; - console.log('tzhrhtrztszth') for (var j = 0; j < steps.length - 1; j++) { var path = computeTwoStepsPath(graph, steps[j], steps[j + 1]); if (!path) @@ -219,7 +217,6 @@ Geotrek.shortestPath = (function() { // path : Array of { start: Node_id, end: Node_id, edge: Edge, weight: Int (edge.length) } // weight: Int // } - console.log("AZERTY") var weighted_path = Geotrek.Dijkstra.get_shortest_path_from_graph(graph, from_nodes, to_nodes); // restore graph diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index cadc4ef7d4..ae80b016f1 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -521,7 +521,7 @@ L.Handler.MultiPath = L.Handler.extend({ sent_steps.push(sent_step) }) - console.log('computePaths:', 'graph', this.graph, 'steps', sent_steps) + console.log('computePaths:', 'graph', this.graph, 'steps', this.steps) fetch(window.SETTINGS.urls['trek_geometry'], { method: 'POST', headers: { @@ -592,9 +592,9 @@ L.Handler.MultiPath = L.Handler.extend({ this.fire('computed_paths', { 'computed_paths': new_computed_paths, 'new_edges': this.all_edges, - 'old': old_computed_paths, - 'marker_source': this.marker_source, - 'marker_dest': this.marker_dest + // 'old': old_computed_paths, + // 'marker_source': this.marker_source, + // 'marker_dest': this.marker_dest }); }, diff --git a/geotrek/core/views.py b/geotrek/core/views.py index eb4a3be3d4..f4bae02074 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -496,7 +496,7 @@ def get_start_and_end_node_ids(self): path_starting_node = bound_nodes[0][0] path_ending_node = bound_nodes[-1][-1] - return path_starting_node, path_ending_node + return str(path_starting_node), str(path_ending_node) def post(self, request): try: @@ -511,15 +511,47 @@ def post(self, request): cs_graph = self.get_cs_graph() matrix = csr_matrix(cs_graph) + print("cs_graph:", cs_graph) - start_node, end_node = self.get_start_and_end_node_ids() - print("start_node, end_node", start_node, end_node) + start_node_id, end_node_id = self.get_start_and_end_node_ids() + print("start_node_id, end_node_id", start_node_id, end_node_id) - result = dijkstra(matrix, return_predecessors=True, indices=2, + # Tuples (index, id) for all nodes -> for interpreting the results + self.nodes_idx_per_id = list(enumerate(self.nodes.keys())) + print(self.nodes_idx_per_id) + + def get_node_idx_per_id(node_id): + for (index, id) in self.nodes_idx_per_id: + if node_id == id: + return index + return None + + def get_node_id_per_idx(node_idx): + for (index, id) in self.nodes_idx_per_id: + if node_idx == index: + return id + return None + + start_node_idx = get_node_idx_per_id(start_node_id) + end_node_idx = get_node_idx_per_id(end_node_id) + result = dijkstra(matrix, return_predecessors=True, indices=start_node_idx, directed=False) print(result) + # Retracing the path index by index, from end to start + predecessors = result[1] + current_node_id, current_node_idx = end_node_id, end_node_idx + path = [current_node_id] + while current_node_id != start_node_id: + # print("current_node_id", current_node_id, "current_node_idx", current_node_idx) + # print('start_node_id', start_node_id) + # print(type(current_node_id), type(start_node_id)) + current_node_idx = predecessors[current_node_idx] + current_node_id = get_node_id_per_idx(current_node_idx) + path.append(current_node_id) + + print(path) + return JsonResponse({ - 'graph': params['graph'], - 'steps': params['steps'], + 'path': path, }) From c0e554c5caa4350bed35f09690a66bd1aa7b4a8e Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Mon, 15 Apr 2024 11:41:08 +0200 Subject: [PATCH 006/213] Modify path in response: it's now from start to end --- geotrek/core/static/core/multipath.js | 14 +++++++++++--- geotrek/core/views.py | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index ae80b016f1..b6c4856ab9 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -507,7 +507,6 @@ L.Handler.MultiPath = L.Handler.extend({ if (this.canCompute()) { var computed_paths = Geotrek.shortestPath(this.graph, this.steps); - console.log('computed_paths:' , computed_paths) let csrftoken = this.getCookie('csrftoken'); @@ -538,6 +537,14 @@ L.Handler.MultiPath = L.Handler.extend({ .catch(e => { console.log("computePaths", e) }) + + var refacto_computed_path = { + 'from_pop': this.steps[0], + 'to_pop': this.steps[1], + // 'path': { + // 'weight': + // }, + } this._onComputedPaths(computed_paths); } @@ -582,13 +589,14 @@ L.Handler.MultiPath = L.Handler.extend({ }, _onComputedPaths: function(new_computed_paths) { - var self = this; - var old_computed_paths = this.computed_paths; + // var self = this; + // var old_computed_paths = this.computed_paths; this.computed_paths = new_computed_paths; // compute and store all edges of the new paths (usefull for further computation) this.all_edges = this._extractAllEdges(new_computed_paths); + console.log('computed_paths', new_computed_paths, 'new_edges', this.all_edges) this.fire('computed_paths', { 'computed_paths': new_computed_paths, 'new_edges': this.all_edges, diff --git a/geotrek/core/views.py b/geotrek/core/views.py index f4bae02074..969af59728 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -553,5 +553,5 @@ def get_node_id_per_idx(node_idx): print(path) return JsonResponse({ - 'path': path, + 'path': path[::-1], }) From f356ab9962d9a0bf19c4e76d7bad49cb2c0b3c13 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Mon, 15 Apr 2024 12:28:37 +0200 Subject: [PATCH 007/213] Refacto: nodes idx to id correspondence --- geotrek/core/static/core/multipath.js | 2 ++ geotrek/core/views.py | 19 +++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index b6c4856ab9..ff32a649cc 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -521,6 +521,8 @@ L.Handler.MultiPath = L.Handler.extend({ }) console.log('computePaths:', 'graph', this.graph, 'steps', this.steps) + console.log('sent_steps', sent_steps) + fetch(window.SETTINGS.urls['trek_geometry'], { method: 'POST', headers: { diff --git a/geotrek/core/views.py b/geotrek/core/views.py index 969af59728..40f2863669 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -517,20 +517,19 @@ def post(self, request): print("start_node_id, end_node_id", start_node_id, end_node_id) # Tuples (index, id) for all nodes -> for interpreting the results - self.nodes_idx_per_id = list(enumerate(self.nodes.keys())) - print(self.nodes_idx_per_id) + self.nodes_ids = list(self.nodes.keys()) + print('self.nodes_ids', self.nodes_ids) def get_node_idx_per_id(node_id): - for (index, id) in self.nodes_idx_per_id: - if node_id == id: - return index - return None + try: + return self.nodes_ids.index(node_id) + except ValueError: + return None def get_node_id_per_idx(node_idx): - for (index, id) in self.nodes_idx_per_id: - if node_idx == index: - return id - return None + if node_idx >= len(self.nodes_ids): + return None + return self.nodes_ids[node_idx] start_node_idx = get_node_idx_per_id(start_node_id) end_node_idx = get_node_idx_per_id(end_node_id) From 5feecc12e13cb7dfdbe1471a7ee8fa64f2444ea4 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Mon, 15 Apr 2024 16:34:19 +0200 Subject: [PATCH 008/213] Add structure of algorithm handling partial edges and more than 2 steps --- geotrek/core/static/core/multipath.js | 6 +++ geotrek/core/views.py | 57 +++++++++++++++++++++------ 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index ff32a649cc..13c6193f91 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -960,6 +960,7 @@ Geotrek.PointOnPolyline.prototype.addToGraph = function(graph) { var self = this; + // Gettings the corresponding edge and its nodes var edge = graph.edges[this.polyline.properties.id] , first_node_id = edge.nodes_id[0] , last_node_id = edge.nodes_id[1]; @@ -971,6 +972,7 @@ Geotrek.PointOnPolyline.prototype.addToGraph = function(graph) { ; var new_node_id = Geotrek.getNextId(); + console.log('new_node_id', new_node_id) var edge1 = {'id': Geotrek.getNextId(), 'length': dist_start_point, 'nodes_id': [first_node_id, new_node_id] }; var edge2 = {'id': Geotrek.getNextId(), 'length': dist_end_point, 'nodes_id': [new_node_id, last_node_id]}; @@ -988,6 +990,9 @@ Geotrek.PointOnPolyline.prototype.addToGraph = function(graph) { $.extend(graph.nodes[first_node_id], first_node); $.extend(graph.nodes[last_node_id], last_node); // + // console.log('Temp graph.nodes[new_node_id]', graph.nodes[new_node_id]) + // console.log('Temp graph.nodes', graph.nodes) + // console.log('Temp graph', graph) function rmFromGraph() { delete graph.edges[edge1.id]; @@ -998,6 +1003,7 @@ Geotrek.PointOnPolyline.prototype.addToGraph = function(graph) { delete graph.nodes[last_node_id][new_node_id]; } + return { self: self, new_node_id: new_node_id, diff --git a/geotrek/core/views.py b/geotrek/core/views.py index 40f2863669..24f8c49e2b 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -498,17 +498,34 @@ def get_start_and_end_node_ids(self): path_ending_node = bound_nodes[-1][-1] return str(path_starting_node), str(path_ending_node) - def post(self, request): - try: - params = json.loads(request.body.decode()) - self.steps = params['steps'] - graph = params['graph'] - self.nodes = graph['nodes'] - self.edges = graph['edges'] - except: - print("TrekGeometry POST: incorrect parameters") - # TODO: Bad request - + def compute_list_of_paths(self): + list_of_paths = [] + for i in range(len(self.steps) - 1): + from_step = self.steps[i] + to_step = self.steps[i + 1] + path = self.compute_two_steps_path(from_step, to_step) + # TODO: + # path['from_pop'] = + # path['to_pop'] = + list_of_paths.append(path) + return list_of_paths + + def compute_two_steps_path(self, from_step, to_step): + # Adding the steps to the graph + self.add_step_to_graph(from_step) + self.add_step_to_graph(to_step) + + # TODO: dijkstra + path = self.get_shortest_path() + + # Restoring the graph (removing the steps) + # TODO + return path + + def add_step_to_graph(self, step): + ... + + def get_shortest_path(self): cs_graph = self.get_cs_graph() matrix = csr_matrix(cs_graph) print("cs_graph:", cs_graph) @@ -549,8 +566,22 @@ def get_node_id_per_idx(node_idx): current_node_id = get_node_id_per_idx(current_node_idx) path.append(current_node_id) - print(path) + path.reverse() + return path + + def post(self, request): + try: + params = json.loads(request.body.decode()) + self.steps = params['steps'] + graph = params['graph'] + self.nodes = graph['nodes'] + self.edges = graph['edges'] + except: + print("TrekGeometry POST: incorrect parameters") + # TODO: Bad request + + paths = self.compute_list_of_paths() return JsonResponse({ - 'path': path[::-1], + 'paths': paths, }) From 113995a13459909a163ed071dbf5cddebc56e24d Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Tue, 16 Apr 2024 12:10:32 +0200 Subject: [PATCH 009/213] ADD handling of edge percentage and more than 2 markers --- geotrek/core/static/core/multipath.js | 3 +- geotrek/core/views.py | 126 ++++++++++++++++++-------- 2 files changed, 92 insertions(+), 37 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index 13c6193f91..837f7d444c 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -960,7 +960,7 @@ Geotrek.PointOnPolyline.prototype.addToGraph = function(graph) { var self = this; - // Gettings the corresponding edge and its nodes + // Getting the corresponding edge and its nodes var edge = graph.edges[this.polyline.properties.id] , first_node_id = edge.nodes_id[0] , last_node_id = edge.nodes_id[1]; @@ -990,6 +990,7 @@ Geotrek.PointOnPolyline.prototype.addToGraph = function(graph) { $.extend(graph.nodes[first_node_id], first_node); $.extend(graph.nodes[last_node_id], last_node); // + // console.log('Temp graph.nodes[new_node_id]', graph.nodes[new_node_id]) // console.log('Temp graph.nodes', graph.nodes) // console.log('Temp graph', graph) diff --git a/geotrek/core/views.py b/geotrek/core/views.py index 24f8c49e2b..d85aaf2563 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -463,7 +463,7 @@ def get_edge_weight(edge_id): if edge is None: return None return edge.get('length') - + array = [] for node1 in self.nodes.items(): key1, value1 = node1 @@ -487,55 +487,105 @@ def get_edge_weight(edge_id): return np.array(array) - def get_start_and_end_node_ids(self): - # For each step, get its associated edge: - step_edges = [self.edges.get(str(x.get('edge_id'))) for x in self.steps] - - # For each of these edges, get its starting and ending node - bound_nodes = [(x.get('nodes_id')[0], x.get('nodes_id')[1]) for x in step_edges] - - path_starting_node = bound_nodes[0][0] - path_ending_node = bound_nodes[-1][-1] - return str(path_starting_node), str(path_ending_node) - def compute_list_of_paths(self): list_of_paths = [] + # Computing the shortest path for each pair of adjacent steps for i in range(len(self.steps) - 1): from_step = self.steps[i] to_step = self.steps[i + 1] path = self.compute_two_steps_path(from_step, to_step) # TODO: - # path['from_pop'] = - # path['to_pop'] = + # path['from_pop'] = ... + # path['to_pop'] = ... list_of_paths.append(path) return list_of_paths def compute_two_steps_path(self, from_step, to_step): # Adding the steps to the graph - self.add_step_to_graph(from_step) - self.add_step_to_graph(to_step) + from_node_info = self.add_step_to_graph(from_step) + to_node_info = self.add_step_to_graph(to_step) - # TODO: dijkstra - path = self.get_shortest_path() + path = self.get_shortest_path(from_node_info['node_id'], to_node_info['node_id']) # Restoring the graph (removing the steps) - # TODO + print('before', self.nodes, self.edges) + self.remove_step_from_graph(from_node_info) + self.remove_step_from_graph(to_node_info) + print('after', self.nodes, self.edges) return path def add_step_to_graph(self, step): - ... - - def get_shortest_path(self): + # Getting the edge this step is on + edge_id = str(step.get('edge_id')) + edge = self.edges[edge_id] + # Getting its nodes + first_node_id = str(edge.get('nodes_id')[0]) + last_node_id = str(edge.get('nodes_id')[1]) + + # Getting the length of the edges that will be created + path_distance = step.get('path_length') + percent_distance = step.get('percent_distance') + dist_to_start = path_distance * percent_distance + dist_to_end = path_distance * (1 - percent_distance) + + # Creating the new node and edges + new_node_id = self.generate_id() + edge1 = { + 'id': self.generate_id(), + 'length': dist_to_start, + 'nodes_id': [first_node_id, new_node_id], + } + edge2 = { + 'id': self.generate_id(), + 'length': dist_to_end, + 'nodes_id': [new_node_id, last_node_id], + } + first_node, last_node, new_node = {}, {}, {} + first_node[new_node_id] = new_node[first_node_id] = edge1['id'] + last_node[new_node_id] = new_node[last_node_id] = edge2['id'] + + # Adding them to the graph + self.edges[edge1['id']] = edge1 + self.edges[edge2['id']] = edge2 + self.nodes[new_node_id] = new_node + self.extend_dict(self.nodes[first_node_id], first_node) + self.extend_dict(self.nodes[last_node_id], last_node) + + # TODO: return a 'new edges' array? + return { + 'node_id': new_node_id, + 'new_edge1_id': edge1['id'], + 'new_edge2_id': edge2['id'], + 'original_egde_id': edge_id, + } + + def remove_step_from_graph(self, node_info): + # Removing the 2 new edges from the graph + del self.edges[node_info['new_edge1_id']] + del self.edges[node_info['new_edge2_id']] + + # Getting the 2 nodes of the original edge (the one the step was on) + original_edge = self.edges[node_info['original_egde_id']] + nodes_id = original_edge['nodes_id'] + first_node = self.nodes[str(nodes_id[0])] + last_node = self.nodes[str(nodes_id[1])] + + # Removing the new node from the graph + removed_node_id = node_info['node_id'] + del self.nodes[removed_node_id] + del first_node[removed_node_id] + del last_node[removed_node_id] + + def extend_dict(self, dict, source): + for key, value in source.items(): + dict[key] = value + + def get_shortest_path(self, from_node_id, to_node_id): cs_graph = self.get_cs_graph() matrix = csr_matrix(cs_graph) - print("cs_graph:", cs_graph) - - start_node_id, end_node_id = self.get_start_and_end_node_ids() - print("start_node_id, end_node_id", start_node_id, end_node_id) # Tuples (index, id) for all nodes -> for interpreting the results self.nodes_ids = list(self.nodes.keys()) - print('self.nodes_ids', self.nodes_ids) def get_node_idx_per_id(node_id): try: @@ -548,20 +598,17 @@ def get_node_id_per_idx(node_idx): return None return self.nodes_ids[node_idx] - start_node_idx = get_node_idx_per_id(start_node_id) - end_node_idx = get_node_idx_per_id(end_node_id) - result = dijkstra(matrix, return_predecessors=True, indices=start_node_idx, + from_node_idx = get_node_idx_per_id(from_node_id) + to_node_idx = get_node_idx_per_id(to_node_id) + result = dijkstra(matrix, return_predecessors=True, indices=from_node_idx, directed=False) print(result) # Retracing the path index by index, from end to start predecessors = result[1] - current_node_id, current_node_idx = end_node_id, end_node_idx + current_node_id, current_node_idx = to_node_id, to_node_idx path = [current_node_id] - while current_node_id != start_node_id: - # print("current_node_id", current_node_id, "current_node_idx", current_node_idx) - # print('start_node_id', start_node_id) - # print(type(current_node_id), type(start_node_id)) + while current_node_id != from_node_id: current_node_idx = predecessors[current_node_idx] current_node_id = get_node_id_per_idx(current_node_idx) path.append(current_node_id) @@ -569,6 +616,10 @@ def get_node_id_per_idx(node_idx): path.reverse() return path + def generate_id(self): + self.id_count += 1 + return str(self.id_count) + def post(self, request): try: params = json.loads(request.body.decode()) @@ -576,10 +627,13 @@ def post(self, request): graph = params['graph'] self.nodes = graph['nodes'] self.edges = graph['edges'] - except: + except KeyError: print("TrekGeometry POST: incorrect parameters") # TODO: Bad request + # To generate IDs for temporary nodes and edges + self.id_count = 90000000 + paths = self.compute_list_of_paths() return JsonResponse({ From 165d6ed849049b13433aea4128a999c84d373b2a Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Tue, 16 Apr 2024 16:46:16 +0200 Subject: [PATCH 010/213] Add display of a hard-coded polyline sent from the TrekGeometry view --- geotrek/core/static/core/multipath.js | 51 +++++++++++++++++---------- geotrek/core/views.py | 13 +++++-- 2 files changed, 43 insertions(+), 21 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index 837f7d444c..ef94000a03 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -535,20 +535,28 @@ L.Handler.MultiPath = L.Handler.extend({ }) }) .then(response => response.json()) - .then(data => console.log('response:', data)) - .catch(e => { - console.log("computePaths", e) - }) + .then(data => { + console.log('response:', data) + var trek = data.trek + + var refacto_computed_path = { + 'from_pop': this.steps[0], + 'to_pop': this.steps[1], + // 'path': { + // 'weight': + // }, + } - var refacto_computed_path = { - 'from_pop': this.steps[0], - 'to_pop': this.steps[1], - // 'path': { - // 'weight': - // }, - } - - this._onComputedPaths(computed_paths); + var test_computed_path = { + 'computed_paths': computed_paths, + 'trek': trek, + } + + this._onComputedPaths(test_computed_path); + }) + // .catch(e => { + // console.log("computePaths", e) + // }) } }, @@ -593,15 +601,16 @@ L.Handler.MultiPath = L.Handler.extend({ _onComputedPaths: function(new_computed_paths) { // var self = this; // var old_computed_paths = this.computed_paths; - this.computed_paths = new_computed_paths; + this.computed_paths = new_computed_paths['computed_paths']; // compute and store all edges of the new paths (usefull for further computation) - this.all_edges = this._extractAllEdges(new_computed_paths); + this.all_edges = this._extractAllEdges(new_computed_paths['computed_paths']); - console.log('computed_paths', new_computed_paths, 'new_edges', this.all_edges) + console.log('computed_paths', new_computed_paths['computed_paths'], 'new_edges', this.all_edges) this.fire('computed_paths', { - 'computed_paths': new_computed_paths, + 'computed_paths': new_computed_paths['computed_paths'], 'new_edges': this.all_edges, + 'trek': new_computed_paths['trek'], // 'old': old_computed_paths, // 'marker_source': this.marker_source, // 'marker_dest': this.marker_dest @@ -749,7 +758,8 @@ L.Handler.MultiPath = L.Handler.extend({ } } })(); - this.markPath.updateGeom(layer); + + this.markPath.updateGeom(layer); }, getMarkers: function() { @@ -817,10 +827,15 @@ L.Handler.MultiPath = L.Handler.extend({ onComputedPaths: function(data) { console.log("onComputedPaths: data = ", data) + var self = this; var topology = Geotrek.TopologyHelper.buildTopologyFromComputedPath(this.idToLayer, data); this.showPathGeom(topology.layer); + + // Hard-coded polyline + L.geoJson(data.trek, {color:"red" }).addTo(self.map); + this.fire('computed_topology', {topology:topology.serialized}); // ## ONCE ## diff --git a/geotrek/core/views.py b/geotrek/core/views.py index d85aaf2563..d05996d1a9 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -508,10 +508,8 @@ def compute_two_steps_path(self, from_step, to_step): path = self.get_shortest_path(from_node_info['node_id'], to_node_info['node_id']) # Restoring the graph (removing the steps) - print('before', self.nodes, self.edges) self.remove_step_from_graph(from_node_info) self.remove_step_from_graph(to_node_info) - print('after', self.nodes, self.edges) return path def add_step_to_graph(self, step): @@ -602,7 +600,7 @@ def get_node_id_per_idx(node_idx): to_node_idx = get_node_idx_per_id(to_node_id) result = dijkstra(matrix, return_predecessors=True, indices=from_node_idx, directed=False) - print(result) + print('predecessors', result[1]) # Retracing the path index by index, from end to start predecessors = result[1] @@ -638,4 +636,13 @@ def post(self, request): return JsonResponse({ 'paths': paths, + 'trek': self.trek, }) + + trek = { + "type": "LineString", + "coordinates": [ + [5.8921902,44.6888518,1451.0],[5.8920368,44.688848,1450.0],[5.8918196,44.6888427,1450.0],[5.8916025,44.6888373,1450.0],[5.8913771,44.6887051,1450.0],[5.8913211,44.6886722,1450.0],[5.8911518,44.6885729,1449.0],[5.8909265,44.6884407,1447.0],[5.8907777,44.6884446,1446.0],[5.8907187,44.6885704,1444.0],[5.8906597,44.6886963,1442.0],[5.8906033,44.6888946,1440.0],[5.890547,44.6890929,1439.0],[5.890488,44.6893027,1437.0],[5.890429,44.6895124,1435.0],[5.8901876,44.6895086,1433.0],[5.8902719,44.6896295,1431.0],[5.8902885,44.6896336,1430.0],[5.8903788,44.68967,1429.0],[5.8905075,44.6897414,1428.0],[5.8905388,44.6897847,1428.0],[5.890582,44.6898403,1427.0],[5.8904582,44.6898609,1427.0],[5.8903831,44.68988,1426.0],[5.8903027,44.6898952,1426.0],[5.8902007,44.6899295,1426.0],[5.8901364,44.6899524,1425.0],[5.890072,44.6899867,1425.0],[5.890013,44.6900134,1425.0],[5.8899647,44.6900287,1425.0],[5.8898681,44.6900439,1424.0],[5.889793,44.6900478,1424.0],[5.8896858,44.6900401,1424.0],[5.8895946,44.6900211,1424.0],[5.8895007,44.6899951,1424.0],[5.889498,44.6899944,1424.0],[5.8894122,44.6899677,1424.0],[5.889321,44.6899448,1424.0],[5.8892459,44.6899181,1424.0],[5.88916,44.689899,1423.0],[5.889101,44.6898761,1423.0],[5.8890313,44.6898533,1423.0],[5.8889776,44.6898342,1423.0],[5.8889079,44.6898037,1423.0],[5.8888489,44.6897732,1423.0],[5.8887684,44.6897465,1423.0],[5.8886665,44.6897198,1423.0],[5.8885753,44.6896969,1423.0],[5.8884627,44.689674,1423.0],[5.88835,44.689674,1423.0],[5.8882052,44.6896664,1423.0],[5.8880711,44.6896511,1423.0],[5.8879316,44.6896244,1422.0],[5.8877599,44.6895977,1421.0],[5.8876204,44.6895749,1421.0],[5.8875024,44.6895482,1420.0],[5.8873469,44.6895062,1418.0],[5.8872074,44.6894643,1417.0],[5.8870679,44.6894261,1416.0],[5.8869553,44.6893804,1415.0],[5.8868694,44.6893308,1413.0],[5.8867782,44.6892659,1412.0],[5.8866978,44.6892011,1410.0],[5.8866012,44.689102,1408.0],[5.8864993,44.6890181,1407.0],[5.8863813,44.6889265,1406.0],[5.8863062,44.6888769,1405.0],[5.8862042,44.6888007,1403.0],[5.8860916,44.6887282,1402.0],[5.8860058,44.688671,1401.0],[5.8859199,44.68861,1400.0],[5.8858341,44.6885375,1399.0],[5.8857322,44.6884765,1398.0],[5.8856463,44.6884079,1396.0],[5.8855551,44.6883583,1395.0],[5.8854892,44.688321,1394.0],[5.8854126,44.6883059,1392.0],[5.8853212,44.6883461,1390.0],[5.8851847,44.6884668,1389.0],[5.8851528,44.6885146,1388.0],[5.8850831,44.6885985,1386.0],[5.8850402,44.6886596,1383.0],[5.8849919,44.6887015,1381.0],[5.8849436,44.6887549,1379.0],[5.8849007,44.6888197,1378.0],[5.8848578,44.6888846,1376.0],[5.8848149,44.6889608,1375.0],[5.8847773,44.6890295,1373.0],[5.8847344,44.6891134,1372.0],[5.8847129,44.6891897,1370.0],[5.8846968,44.6892659,1369.0],[5.8846754,44.6893422,1368.0],[5.8846378,44.6894071,1367.0],[5.8845842,44.6894604,1365.0],[5.8845359,44.6895062,1364.0],[5.8844393,44.6895596,1363.0],[5.8843535,44.6895863,1361.0],[5.8842838,44.6896092,1360.0],[5.8842087,44.6896359,1359.0],[5.8841014,44.6896626,1358.0],[5.8839995,44.6896855,1356.0],[5.8838922,44.6897236,1355.0],[5.8838117,44.6897388,1354.0],[5.8837473,44.6897617,1353.0],[5.8836776,44.6897732,1352.0],[5.8836025,44.6897808,1350.0],[5.883522,44.6897961,1349.0],[5.8834523,44.6898266,1348.0],[5.883404,44.6898609,1347.0],[5.8833611,44.6898914,1345.0],[5.8832806,44.689941,1344.0],[5.883227,44.6899906,1343.0],[5.8831787,44.6900401,1341.0],[5.8831465,44.6900706,1341.0],[5.8831143,44.690105,1339.0],[5.8830768,44.6901393,1338.0],[5.8830339,44.6901774,1337.0],[5.8830329,44.6901792,1336.0],[5.8830124,44.6902156,1335.0],[5.8830017,44.6902766,1335.0],[5.8830017,44.6903262,1334.0],[5.8830017,44.6903719,1332.0],[5.882991,44.6904177,1331.0],[5.8829641,44.6904558,1330.0],[5.8828837,44.690555,1328.0],[5.8828032,44.6906274,1327.0],[5.8827066,44.690738,1325.0],[5.8826369,44.6908219,1323.0],[5.8825833,44.690902,1321.0],[5.8824921,44.6910393,1319.0],[5.8824867,44.691108,1317.0],[5.8824599,44.6911766,1315.0],[5.8824438,44.6912605,1314.0],[5.8824384,44.6913368,1312.0],[5.8824277,44.6914054,1311.0],[5.8824062,44.6914741,1309.0],[5.8823794,44.6915465,1308.0],[5.8823633,44.6916113,1307.0],[5.8823633,44.6916914,1306.0],[5.8823633,44.6918058,1305.0],[5.8824116,44.6918745,1304.0],[5.8824706,44.6919469,1304.0],[5.8825243,44.6920041,1303.0],[5.8826101,44.6920766,1302.0],[5.8827013,44.6921147,1302.0],[5.8827925,44.6921491,1301.0],[5.8828837,44.6921719,1300.0],[5.882948,44.6922558,1299.0],[5.8829856,44.6923588,1298.0],[5.8830285,44.6924274,1297.0],[5.8830285,44.6924846,1296.0],[5.8830339,44.6925685,1295.0],[5.8830446,44.6926181,1294.0],[5.88305,44.6926868,1293.0],[5.88305,44.6927478,1291.0],[5.8830124,44.6928012,1290.0],[5.8829749,44.6928736,1289.0],[5.8829856,44.6929308,1289.0],[5.8830285,44.6929995,1288.0],[5.8830661,44.6930452,1288.0],[5.8831197,44.693091,1287.0],[5.883168,44.6931215,1286.0],[5.8832324,44.6931787,1285.0],[5.8832431,44.6932207,1284.0],[5.8832055,44.6932473,1283.0],[5.8830768,44.6932397,1282.0],[5.8829856,44.6932321,1280.0],[5.8828676,44.6932283,1279.0],[5.8827549,44.6932283,1277.0],[5.882653,44.6932283,1276.0],[5.8825886,44.6932397,1274.0],[5.8825082,44.6932626,1273.0],[5.882476,44.6933351,1272.0],[5.8824331,44.6934266,1272.0],[5.8824277,44.6935105,1272.0],[5.8824652,44.6935677,1273.0],[5.8825135,44.6936478,1273.0],[5.8826881,44.6937303,1274.0],[5.8828571,44.6938504,1274.0],[5.8830261,44.6939705,1275.0],[5.8830797,44.6940582,1276.0],[5.8830985,44.6942413,1278.0],[5.8831173,44.6944243,1279.0],[5.8832299,44.6946112,1281.0],[5.8833962,44.6947027,1283.0],[5.8835625,44.6947179,1285.0],[5.8837449,44.6946493,1287.0],[5.8838844,44.6945501,1290.0],[5.8840239,44.694451,1292.0],[5.8841553,44.6942927,1295.0],[5.8842867,44.6941345,1297.0],[5.8843887,44.6940239,1299.0],[5.8845603,44.6941269,1302.0],[5.8846622,44.6943137,1304.0],[5.8846944,44.6944052,1306.0],[5.8847964,44.6944014,1308.0],[5.8848178,44.6942718,1309.0],[5.8848446,44.6940906,1311.0],[5.8848715,44.6939095,1312.0],[5.884909,44.6937322,1314.0],[5.8849466,44.6935548,1315.0],[5.8850056,44.6934061,1317.0],[5.8851665,44.693593,1318.0],[5.8852416,44.6937417,1319.0],[5.8853167,44.6938904,1320.0],[5.8853972,44.6940105,1321.0],[5.8854776,44.6941307,1322.0],[5.8855205,44.694287,1324.0],[5.8855635,44.6944434,1325.0],[5.8856815,44.6946302,1326.0],[5.8858558,44.6947713,1328.0],[5.8860302,44.6949124,1330.0],[5.8861482,44.6950459,1331.0],[5.8862662,44.6951793,1333.0],[5.8863842,44.6953128,1335.0],[5.88647,44.6955073,1337.0],[5.8865559,44.6957018,1339.0],[5.8865773,44.6959229,1341.0],[5.8866578,44.6960526,1343.0],[5.8867383,44.6961822,1345.0],[5.8868724,44.6962394,1347.0],[5.8869904,44.6962051,1350.0],[5.8870548,44.6960602,1352.0],[5.887264,44.6959306,1353.0],[5.8873981,44.6959801,1355.0],[5.8874249,44.6961822,1357.0],[5.8875751,44.6962852,1359.0],[5.8877924,44.6963767,1361.0],[5.8880096,44.6964682,1363.0],[5.8881706,44.6965731,1364.0],[5.8883315,44.696678,1366.0],[5.8884978,44.6967314,1368.0],[5.8886641,44.6967847,1371.0],[5.8888626,44.6968839,1373.0],[5.8890181,44.6969754,1376.0],[5.8891737,44.6971508,1379.0],[5.8893293,44.6973262,1382.0],[5.8895036,44.6974692,1385.0],[5.889678,44.6976122,1389.0],[5.8898577,44.6977476,1392.0],[5.8900374,44.6978829,1395.0],[5.8901742,44.6979916,1398.0],[5.890311,44.6981003,1401.0],[5.8905148,44.6981664,1404.0],[5.8907187,44.6982325,1407.0],[5.8909225,44.6982986,1409.0],[5.8911478,44.6983787,1411.0],[5.8913731,44.6984587,1414.0],[5.8915341,44.6984816,1416.0],[5.891695,44.6985045,1417.0],[5.8919437,44.6985245,1419.0],[5.8919848,44.6985135,1421.0],[5.8920041,44.6984949,1422.0],[5.8920106,44.698475,1423.0],[5.8920001,44.6984419,1425.0],[5.8919898,44.6983656,1425.0],[5.8921548,44.6984415,1426.0],[5.8923197,44.6985173,1427.0],[5.8924852,44.6985671,1427.0],[5.8926681,44.6986118,1428.0],[5.8928961,44.6986473,1428.0],[5.8930978,44.6986655,1428.0],[5.8932996,44.6986836,1428.0],[5.8934754,44.6987123,1428.0],[5.8935803,44.6987384,1427.0],[5.8938566,44.698788,1427.0],[5.8941329,44.6988375,1426.0],[5.8944093,44.698887,1426.0],[5.8946856,44.6989365,1425.0],[5.8949442,44.6990199,1425.0],[5.8952028,44.6991032,1424.0],[5.8954614,44.6991865,1423.0],[5.895729,44.6992976,1423.0],[5.8959517,44.6993929,1423.0],[5.8961743,44.6994883,1423.0],[5.8963123,44.6995452,1422.0],[5.8964503,44.6996021,1422.0],[5.8966022,44.699681,1422.0],[5.8968022,44.699764,1422.0],[5.8969605,44.6998094,1422.0],[5.8971189,44.6998548,1422.0],[5.8973456,44.6999651,1422.0],[5.897569,44.7000412,1422.0],[5.89773,44.7000564,1421.0],[5.8978909,44.7000717,1421.0],[5.8981162,44.7001136,1421.0],[5.8983227,44.7001575,1420.0],[5.8985293,44.7002013,1420.0],[5.8987117,44.7002452,1420.0],[5.898894,44.700289,1420.0],[5.899063,44.700371,1419.0],[5.899232,44.700453,1419.0],[5.8994287,44.7005114,1419.0],[5.8996254,44.7005699,1418.0],[5.8997935,44.7006198,1418.0],[5.8998221,44.7006284,1417.0],[5.9000447,44.7006646,1417.0],[5.9002673,44.7007008,1416.0],[5.9004944,44.7007644,1415.0],[5.9007215,44.7008279,1414.0],[5.9009486,44.7008915,1413.0],[5.9011981,44.7009201,1412.0],[5.9014475,44.7009486,1411.0],[5.9016084,44.7009639,1409.0],[5.9017694,44.7009792,1408.0],[5.9020483,44.7010363,1407.0],[5.9023273,44.7010935,1405.0],[5.9024936,44.7011069,1403.0],[5.9026599,44.7011202,1402.0],[5.9028691,44.7010783,1400.0],[5.9030908,44.7010236,1398.0],[5.9033125,44.700969,1395.0],[5.9035343,44.7009143,1393.0],[5.9037542,44.7009315,1391.0],[5.9039741,44.7009486,1388.0],[5.9042263,44.7009868,1386.0],[5.9044784,44.7010249,1384.0],[5.9046793,44.7010416,1383.0],[5.9049153,44.70095,1383.0],[5.9049153,44.70095,1383.0],[5.9050548,44.7008166,1383.0],[5.9051943,44.7006831,1384.0],[5.9052318,44.7004696,1385.0],[5.9052694,44.7002561,1386.0],[5.9051514,44.7001798,1388.0],[5.9050065,44.7002828,1389.0],[5.9048617,44.7003857,1391.0],[5.9046865,44.7002739,1391.0],[5.9045112,44.700162,1392.0],[5.904336,44.7000502,1393.0],[5.9041622,44.699919,1393.0],[5.9039884,44.6997878,1394.0],[5.9038146,44.6996567,1395.0],[5.9036407,44.6995255,1395.0],[5.9034669,44.6993943,1396.0],[5.9033436,44.6992113,1396.0],[5.9032202,44.6990283,1397.0],[5.9030968,44.6988453,1397.0],[5.9029734,44.6986622,1398.0],[5.9027696,44.698525,1398.0],[5.9025657,44.6983877,1398.0],[5.9025067,44.698258,1399.0],[5.9024477,44.6981284,1399.0],[5.9023511,44.6979835,1399.0],[5.9022546,44.6978386,1400.0],[5.9020783,44.697673,1400.0],[5.9019021,44.6975074,1401.0],[5.9017258,44.6973418,1402.0],[5.9015495,44.6971762,1403.0],[5.9013733,44.6970106,1405.0],[5.901197,44.696845,1407.0],[5.9010208,44.6966794,1409.0],[5.9008491,44.6965406,1410.0],[5.9006774,44.6964018,1412.0],[5.9005058,44.696263,1413.0],[5.9003341,44.6961242,1415.0],[5.9001625,44.6959854,1417.0],[5.8999801,44.6959167,1418.0],[5.8997977,44.6958481,1420.0],[5.8996153,44.6957006,1421.0],[5.8994329,44.6955532,1423.0],[5.8992505,44.6954057,1424.0],[5.8990681,44.6952583,1425.0],[5.8988857,44.6951108,1427.0],[5.8987033,44.6949634,1429.0],[5.8985236,44.694795,1431.0],[5.8983439,44.6946265,1433.0],[5.8981642,44.6944581,1435.0],[5.8979845,44.6942897,1436.0],[5.8978048,44.6941213,1438.0],[5.8976251,44.6939528,1440.0],[5.8974132,44.6938623,1442.0],[5.8972013,44.6937717,1444.0],[5.8969894,44.6936811,1446.0],[5.8967775,44.6935906,1447.0],[5.8966114,44.693406,1449.0],[5.8964453,44.6932214,1450.0],[5.8962793,44.6930368,1452.0],[5.8961132,44.6928523,1453.0],[5.8959471,44.6926677,1454.0],[5.8958559,44.6926257,1455.0],[5.8957325,44.6925685,1456.0],[5.8955877,44.6925342,1457.0],[5.8954697,44.6924999,1457.0],[5.8954321,44.6924427,1457.0],[5.8953034,44.6924656,1457.0],[5.8951693,44.6924618,1457.0],[5.8950512,44.6924389,1457.0],[5.89496,44.6923893,1456.0],[5.8948259,44.6922711,1455.0],[5.8947026,44.6921109,1454.0],[5.8945738,44.6920003,1453.0],[5.894502,44.6919513,1453.0],[5.8942876,44.6919083,1452.0],[5.8943217,44.6917982,1451.0],[5.8943217,44.6916139,1451.0],[5.8943217,44.6914296,1451.0],[5.8943217,44.6912452,1450.0],[5.8943031,44.6910795,1450.0],[5.8942845,44.6909138,1450.0],[5.8942804,44.690772,1450.0],[5.8942762,44.6906302,1450.0],[5.8943003,44.6905332,1450.0],[5.8943034,44.6905196,1450.0],[5.8943223,44.6904218,1450.0],[5.8943274,44.6903983,1450.0],[5.8943264,44.6903037,1451.0],[5.8943016,44.6901908,1451.0],[5.8942096,44.6899856,1451.0],[5.8941176,44.6897803,1452.0],[5.8940492,44.6896533,1452.0],[5.8939808,44.6895263,1452.0],[5.8938898,44.6894277,1452.0],[5.8938607,44.6894006,1452.0],[5.8938116,44.6894532,1452.0],[5.8937345,44.6894021,1452.0],[5.8935994,44.6893299,1452.0],[5.8933763,44.6892272,1452.0],[5.8931531,44.6891245,1452.0],[5.8928828,44.6890403,1451.0],[5.8926124,44.688956,1451.0],[5.8923942,44.6888881,1451.0] + ] + } + From 188d9692cc7ca10aaf69cea102948a0211b22dc0 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Tue, 16 Apr 2024 17:03:44 +0200 Subject: [PATCH 011/213] The graph is directly taken from the backend --- geotrek/core/static/core/multipath.js | 1 - geotrek/core/views.py | 21 +++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index ef94000a03..735dabfcfa 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -530,7 +530,6 @@ L.Handler.MultiPath = L.Handler.extend({ content_type: "application/json" }, body: JSON.stringify({ - graph: this.graph, steps: sent_steps, }) }) diff --git a/geotrek/core/views.py b/geotrek/core/views.py index d05996d1a9..98456eb757 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -459,7 +459,7 @@ def get_edge_id_by_nodes(node1, node2): return None def get_edge_weight(edge_id): - edge = self.edges.get(str(edge_id)) + edge = self.edges.get(edge_id) if edge is None: return None return edge.get('length') @@ -514,11 +514,11 @@ def compute_two_steps_path(self, from_step, to_step): def add_step_to_graph(self, step): # Getting the edge this step is on - edge_id = str(step.get('edge_id')) + edge_id = step.get('edge_id') edge = self.edges[edge_id] # Getting its nodes - first_node_id = str(edge.get('nodes_id')[0]) - last_node_id = str(edge.get('nodes_id')[1]) + first_node_id = edge.get('nodes_id')[0] + last_node_id = edge.get('nodes_id')[1] # Getting the length of the edges that will be created path_distance = step.get('path_length') @@ -565,8 +565,8 @@ def remove_step_from_graph(self, node_info): # Getting the 2 nodes of the original edge (the one the step was on) original_edge = self.edges[node_info['original_egde_id']] nodes_id = original_edge['nodes_id'] - first_node = self.nodes[str(nodes_id[0])] - last_node = self.nodes[str(nodes_id[1])] + first_node = self.nodes[nodes_id[0]] + last_node = self.nodes[nodes_id[1]] # Removing the new node from the graph removed_node_id = node_info['node_id'] @@ -616,15 +616,12 @@ def get_node_id_per_idx(node_idx): def generate_id(self): self.id_count += 1 - return str(self.id_count) + return self.id_count def post(self, request): try: params = json.loads(request.body.decode()) self.steps = params['steps'] - graph = params['graph'] - self.nodes = graph['nodes'] - self.edges = graph['edges'] except KeyError: print("TrekGeometry POST: incorrect parameters") # TODO: Bad request @@ -632,6 +629,10 @@ def post(self, request): # To generate IDs for temporary nodes and edges self.id_count = 90000000 + graph = graph_lib.graph_edges_nodes_of_qs(Path.objects.exclude(draft=True)) + self.nodes = graph['nodes'] + self.edges = graph['edges'] + paths = self.compute_list_of_paths() return JsonResponse({ From 253ef5bed1bacfb79e83454b86ea7f29dfe48a38 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Wed, 17 Apr 2024 15:39:55 +0200 Subject: [PATCH 012/213] Add handling of GPS coordinates as input --- geotrek/core/static/core/multipath.js | 15 ++++++++-- geotrek/core/views.py | 40 ++++++++++++++++++--------- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index 735dabfcfa..0e07406834 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -510,12 +510,21 @@ L.Handler.MultiPath = L.Handler.extend({ let csrftoken = this.getCookie('csrftoken'); + // var sent_steps_old = [] + // this.steps.forEach((step) => { + // var sent_step = { + // path_length: step.path_length, + // percent_distance: step.percent_distance, + // edge_id: step.polyline.properties.id, + // } + // sent_steps_old.push(sent_step) + // }) + var sent_steps = [] this.steps.forEach((step) => { var sent_step = { - path_length: step.path_length, - percent_distance: step.percent_distance, - edge_id: step.polyline.properties.id, + lat: step.ll.lat, + lng: step.ll.lng, } sent_steps.push(sent_step) }) diff --git a/geotrek/core/views.py b/geotrek/core/views.py index 98456eb757..5c4cb00b70 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -6,6 +6,7 @@ from django.contrib import messages from django.contrib.auth.decorators import permission_required from django.contrib.gis.db.models.functions import Transform +from django.contrib.gis.geos import Point from django.core.cache import caches from django.db.models import Sum, Prefetch from django.http import HttpResponseRedirect @@ -33,6 +34,7 @@ from geotrek.authent.decorators import same_structure_required from geotrek.common.functions import Length +from geotrek.common.utils import sqlfunction from geotrek.common.mixins.views import CustomColumnsMixin from geotrek.common.mixins.forms import FormsetMixin from geotrek.common.permissions import PublicOrReadPermMixin @@ -494,9 +496,6 @@ def compute_list_of_paths(self): from_step = self.steps[i] to_step = self.steps[i + 1] path = self.compute_two_steps_path(from_step, to_step) - # TODO: - # path['from_pop'] = ... - # path['to_pop'] = ... list_of_paths.append(path) return list_of_paths @@ -505,7 +504,8 @@ def compute_two_steps_path(self, from_step, to_step): from_node_info = self.add_step_to_graph(from_step) to_node_info = self.add_step_to_graph(to_step) - path = self.get_shortest_path(from_node_info['node_id'], to_node_info['node_id']) + path = self.get_shortest_path(from_node_info['node_id'], + to_node_info['node_id']) # Restoring the graph (removing the steps) self.remove_step_from_graph(from_node_info) @@ -513,18 +513,30 @@ def compute_two_steps_path(self, from_step, to_step): return path def add_step_to_graph(self, step): - # Getting the edge this step is on - edge_id = step.get('edge_id') + # Creating a Point corresponding to this step + point = Point(step['lng'], step['lat'], srid=settings.API_SRID) + point.transform(settings.SRID) + + # Getting the Path (and corresponding graph edge) this Point is on + base_path = Path.closest(point) + edge_id = base_path.pk edge = self.edges[edge_id] - # Getting its nodes + + # Getting the edge nodes first_node_id = edge.get('nodes_id')[0] last_node_id = edge.get('nodes_id')[1] + # Getting the percentage of the Path this Point is on + base_path_str = "'{}'".format(base_path.geom) + point_str = "'{}'".format(point) + percent_distance = sqlfunction('SELECT ST_LineLocatePoint', + base_path_str, point_str)[0] + + path_length = base_path.length + # Getting the length of the edges that will be created - path_distance = step.get('path_length') - percent_distance = step.get('percent_distance') - dist_to_start = path_distance * percent_distance - dist_to_end = path_distance * (1 - percent_distance) + dist_to_start = path_length * percent_distance + dist_to_end = path_length * (1 - percent_distance) # Creating the new node and edges new_node_id = self.generate_id() @@ -550,12 +562,13 @@ def add_step_to_graph(self, step): self.extend_dict(self.nodes[last_node_id], last_node) # TODO: return a 'new edges' array? - return { + new_node_info = { 'node_id': new_node_id, 'new_edge1_id': edge1['id'], 'new_edge2_id': edge2['id'], 'original_egde_id': edge_id, } + return new_node_info def remove_step_from_graph(self, node_info): # Removing the 2 new edges from the graph @@ -615,8 +628,9 @@ def get_node_id_per_idx(node_idx): return path def generate_id(self): + new_id = self.id_count self.id_count += 1 - return self.id_count + return new_id def post(self, request): try: From 5c31da3fd06a254daa28a21da550353c435990f5 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Wed, 17 Apr 2024 16:31:21 +0200 Subject: [PATCH 013/213] Add use of Point ewkt when using sql --- geotrek/core/views.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/geotrek/core/views.py b/geotrek/core/views.py index 5c4cb00b70..476fe288e1 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -528,7 +528,7 @@ def add_step_to_graph(self, step): # Getting the percentage of the Path this Point is on base_path_str = "'{}'".format(base_path.geom) - point_str = "'{}'".format(point) + point_str = "'{}'".format(point.ewkt) percent_distance = sqlfunction('SELECT ST_LineLocatePoint', base_path_str, point_str)[0] @@ -626,6 +626,9 @@ def get_node_id_per_idx(node_idx): path.reverse() return path + + def convert_paths_to_polyline(self, paths_list): + return [] def generate_id(self): new_id = self.id_count @@ -649,8 +652,11 @@ def post(self, request): paths = self.compute_list_of_paths() + path_polyline = self.convert_paths_to_polyline(paths) + return JsonResponse({ 'paths': paths, + 'path_polyline': path_polyline, 'trek': self.trek, }) From 4305b0ffbe4af5c8c0909e22b767ec50ba9a6c2e Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Wed, 17 Apr 2024 16:45:48 +0200 Subject: [PATCH 014/213] Add TODOs for next step: obtaining a LineString from dijkstra results --- geotrek/core/views.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/geotrek/core/views.py b/geotrek/core/views.py index 476fe288e1..545e28e1eb 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -627,7 +627,16 @@ def get_node_id_per_idx(node_idx): path.reverse() return path - def convert_paths_to_polyline(self, paths_list): + def convert_paths_to_geojson(self, paths_list): + # TODO: + # For each computed path, save its corresponding LineStrings + # (before resetting the graph -> paths must contain LineStrings, not node ids) + # real edge -> edge_id is Path pk + # temp edge -> create a temp LineString + # concat the LineStrings: + # create a MultiLineString + # use MultiLineString.merged() + # use GEOSGeometry.geojson (LineString.geojson?) return [] def generate_id(self): @@ -652,11 +661,11 @@ def post(self, request): paths = self.compute_list_of_paths() - path_polyline = self.convert_paths_to_polyline(paths) + path_geojson = self.convert_paths_to_geojson(paths) return JsonResponse({ 'paths': paths, - 'path_polyline': path_polyline, + 'path_geojson': path_geojson, 'trek': self.trek, }) From 20ab44e3ce7aa1a4a6a17546e988eff689a8de3e Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Wed, 17 Apr 2024 17:34:33 +0200 Subject: [PATCH 015/213] Starting to work on converting the resulting nodes list into LineStrings --- geotrek/core/views.py | 78 +++++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/geotrek/core/views.py b/geotrek/core/views.py index 545e28e1eb..c0fce41920 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -6,7 +6,7 @@ from django.contrib import messages from django.contrib.auth.decorators import permission_required from django.contrib.gis.db.models.functions import Transform -from django.contrib.gis.geos import Point +from django.contrib.gis.geos import Point, LineString, MultiLineString from django.core.cache import caches from django.db.models import Sum, Prefetch from django.http import HttpResponseRedirect @@ -451,15 +451,9 @@ def get_queryset(self): class TrekGeometry(View): - + def get_cs_graph(self): - def get_edge_id_by_nodes(node1, node2): - for value in node1[1].values(): - if value in node2[1].values(): - return value - return None - def get_edge_weight(edge_id): edge = self.edges.get(edge_id) if edge is None: @@ -478,7 +472,7 @@ def get_edge_weight(edge_id): elif key2 in value1.keys(): # If the nodes are linked by a single edge, the weight is # the edge length - edge_id = get_edge_id_by_nodes(node1, node2) + edge_id = self.get_edge_id_by_nodes(node1, node2) edge_weight = get_edge_weight(edge_id) if edge_weight is not None: row.append(edge_weight) @@ -489,28 +483,55 @@ def get_edge_weight(edge_id): return np.array(array) + def get_edge_id_by_nodes(self, node1, node2): + for value in node1[1].values(): + if value in node2[1].values(): + return value + return None + def compute_list_of_paths(self): - list_of_paths = [] + total_line_strings = [] # Computing the shortest path for each pair of adjacent steps for i in range(len(self.steps) - 1): from_step = self.steps[i] to_step = self.steps[i + 1] - path = self.compute_two_steps_path(from_step, to_step) - list_of_paths.append(path) - return list_of_paths + line_strings = self.compute_two_steps_line_strings(from_step, to_step) + total_line_strings += line_strings + return total_line_strings - def compute_two_steps_path(self, from_step, to_step): + def compute_two_steps_line_strings(self, from_step, to_step): # Adding the steps to the graph from_node_info = self.add_step_to_graph(from_step) to_node_info = self.add_step_to_graph(to_step) - path = self.get_shortest_path(from_node_info['node_id'], - to_node_info['node_id']) + shortest_path = self.get_shortest_path(from_node_info['node_id'], + to_node_info['node_id']) + line_strings = self.node_list_to_line_strings(shortest_path) # Restoring the graph (removing the steps) self.remove_step_from_graph(from_node_info) self.remove_step_from_graph(to_node_info) - return path + return line_strings + + def node_list_to_line_strings(self, node_list): + print("node_list", node_list) + # Getting a LineString for each pair of adjacent nodes in the path + for i in range(len(node_list) - 1): + # Getting the id of the edge corresponding to these nodes + node1 = self.nodes[node_list[i]] + node2 = self.nodes[node_list[i + 1]] + edge_id = self.get_edge_id_by_nodes(node1, node2) + + # If it's a real edge (i.e. it corresponds to a whole path), + # we get its LineString + ... # TODO: edge_id is Path pk + + # If it's an edge created because of a marker, a temporary + # LineString is created + ... # TODO: use ST_LineSubstring? + + + def add_step_to_graph(self, step): # Creating a Point corresponding to this step @@ -613,7 +634,6 @@ def get_node_id_per_idx(node_idx): to_node_idx = get_node_idx_per_id(to_node_id) result = dijkstra(matrix, return_predecessors=True, indices=from_node_idx, directed=False) - print('predecessors', result[1]) # Retracing the path index by index, from end to start predecessors = result[1] @@ -627,17 +647,9 @@ def get_node_id_per_idx(node_idx): path.reverse() return path - def convert_paths_to_geojson(self, paths_list): - # TODO: - # For each computed path, save its corresponding LineStrings - # (before resetting the graph -> paths must contain LineStrings, not node ids) - # real edge -> edge_id is Path pk - # temp edge -> create a temp LineString - # concat the LineStrings: - # create a MultiLineString - # use MultiLineString.merged() - # use GEOSGeometry.geojson (LineString.geojson?) - return [] + def merge_line_strings(self, line_strings): + multi_line_string = MultiLineString(line_strings) + return multi_line_string.merged def generate_id(self): new_id = self.id_count @@ -659,12 +671,14 @@ def post(self, request): self.nodes = graph['nodes'] self.edges = graph['edges'] - paths = self.compute_list_of_paths() + line_strings = self.compute_list_of_paths() + merged_line_string = self.merge_line_strings(line_strings) - path_geojson = self.convert_paths_to_geojson(paths) + # TODO: use GEOSGeometry.geojson (LineString?) + path_geojson = merged_line_string.geojson return JsonResponse({ - 'paths': paths, + 'paths': line_strings, 'path_geojson': path_geojson, 'trek': self.trek, }) From f6fd6c043197134174981b04ec74d0538655d5cc Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Thu, 18 Apr 2024 16:29:31 +0200 Subject: [PATCH 016/213] Add conversion of dijstra results into a displayed Linestring --- geotrek/core/static/core/multipath.js | 19 ++-- geotrek/core/views.py | 130 ++++++++++++++++++-------- 2 files changed, 97 insertions(+), 52 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index 0e07406834..beea2d01a7 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -530,7 +530,6 @@ L.Handler.MultiPath = L.Handler.extend({ }) console.log('computePaths:', 'graph', this.graph, 'steps', this.steps) - console.log('sent_steps', sent_steps) fetch(window.SETTINGS.urls['trek_geometry'], { method: 'POST', @@ -545,20 +544,21 @@ L.Handler.MultiPath = L.Handler.extend({ .then(response => response.json()) .then(data => { console.log('response:', data) - var trek = data.trek var refacto_computed_path = { 'from_pop': this.steps[0], 'to_pop': this.steps[1], - // 'path': { - // 'weight': - // }, } var test_computed_path = { 'computed_paths': computed_paths, - 'trek': trek, + 'geojson': data.geojson, + 'trek': data.geojson, } + + console.log("geojson", data.geojson/* , "trek", data.trek */) + // var mainmap = window.maps[0] + // mainmap.addLayers this._onComputedPaths(test_computed_path); }) @@ -614,7 +614,6 @@ L.Handler.MultiPath = L.Handler.extend({ // compute and store all edges of the new paths (usefull for further computation) this.all_edges = this._extractAllEdges(new_computed_paths['computed_paths']); - console.log('computed_paths', new_computed_paths['computed_paths'], 'new_edges', this.all_edges) this.fire('computed_paths', { 'computed_paths': new_computed_paths['computed_paths'], 'new_edges': this.all_edges, @@ -834,15 +833,13 @@ L.Handler.MultiPath = L.Handler.extend({ }, onComputedPaths: function(data) { - console.log("onComputedPaths: data = ", data) - var self = this; var topology = Geotrek.TopologyHelper.buildTopologyFromComputedPath(this.idToLayer, data); this.showPathGeom(topology.layer); // Hard-coded polyline - L.geoJson(data.trek, {color:"red" }).addTo(self.map); + L.geoJson(data.trek, {color:"blue", weight: 10}).addTo(self.map); this.fire('computed_topology', {topology:topology.serialized}); @@ -995,8 +992,6 @@ Geotrek.PointOnPolyline.prototype.addToGraph = function(graph) { ; var new_node_id = Geotrek.getNextId(); - console.log('new_node_id', new_node_id) - var edge1 = {'id': Geotrek.getNextId(), 'length': dist_start_point, 'nodes_id': [first_node_id, new_node_id] }; var edge2 = {'id': Geotrek.getNextId(), 'length': dist_end_point, 'nodes_id': [new_node_id, last_node_id]}; diff --git a/geotrek/core/views.py b/geotrek/core/views.py index c0fce41920..34d50b24dd 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -8,6 +8,7 @@ from django.contrib.gis.db.models.functions import Transform from django.contrib.gis.geos import Point, LineString, MultiLineString from django.core.cache import caches +from django.db import connection from django.db.models import Sum, Prefetch from django.http import HttpResponseRedirect from django.http.response import HttpResponse, JsonResponse @@ -461,18 +462,16 @@ def get_edge_weight(edge_id): return edge.get('length') array = [] - for node1 in self.nodes.items(): - key1, value1 = node1 + for key1, value1 in self.nodes.items(): row = [] - for node2 in self.nodes.items(): - key2, _ = node2 + for key2, value2 in self.nodes.items(): if key1 == key2: # If it's the same node, the weight is 0 row.append(0) elif key2 in value1.keys(): # If the nodes are linked by a single edge, the weight is # the edge length - edge_id = self.get_edge_id_by_nodes(node1, node2) + edge_id = self.get_edge_id_by_nodes(value1, value2) edge_weight = get_edge_weight(edge_id) if edge_weight is not None: row.append(edge_weight) @@ -484,14 +483,14 @@ def get_edge_weight(edge_id): return np.array(array) def get_edge_id_by_nodes(self, node1, node2): - for value in node1[1].values(): - if value in node2[1].values(): + for value in node1.values(): + if value in node2.values(): return value return None def compute_list_of_paths(self): total_line_strings = [] - # Computing the shortest path for each pair of adjacent steps + # Compute the shortest path for each pair of adjacent steps for i in range(len(self.steps) - 1): from_step = self.steps[i] to_step = self.steps[i + 1] @@ -500,54 +499,91 @@ def compute_list_of_paths(self): return total_line_strings def compute_two_steps_line_strings(self, from_step, to_step): - # Adding the steps to the graph + # Add the steps to the graph + # TODO: only add them if the step is a marker? from_node_info = self.add_step_to_graph(from_step) to_node_info = self.add_step_to_graph(to_step) shortest_path = self.get_shortest_path(from_node_info['node_id'], to_node_info['node_id']) - line_strings = self.node_list_to_line_strings(shortest_path) + line_strings = self.node_list_to_line_strings(shortest_path, + from_node_info, to_node_info) - # Restoring the graph (removing the steps) + # Restore the graph (remove the steps) self.remove_step_from_graph(from_node_info) self.remove_step_from_graph(to_node_info) return line_strings - def node_list_to_line_strings(self, node_list): - print("node_list", node_list) - # Getting a LineString for each pair of adjacent nodes in the path + def node_list_to_line_strings(self, node_list, from_node_info, to_node_info): + line_strings = [] + # Get a LineString for each pair of adjacent nodes in the path for i in range(len(node_list) - 1): - # Getting the id of the edge corresponding to these nodes - node1 = self.nodes[node_list[i]] - node2 = self.nodes[node_list[i + 1]] - edge_id = self.get_edge_id_by_nodes(node1, node2) + + # If it's the first or last edge of this subpath (it can be both!), + # then the edge is temporary (i.e. created because of a step) + if i == 0 or i == len(node_list) - 2: + # Start and end percentages of the line substring to be created + start_fraction = 0 + end_fraction = 1 + if i == 0: + original_path = Path.objects.get(pk=from_node_info['original_egde_id']) + start_fraction = from_node_info['percent_of_edge'] + if i == len(node_list) - 2: + original_path = Path.objects.get(pk=to_node_info['original_egde_id']) + end_fraction = to_node_info['percent_of_edge'] + + line_substring = self.create_line_substring( + original_path.geom, + start_fraction, + end_fraction + ) + line_strings.append(line_substring) # If it's a real edge (i.e. it corresponds to a whole path), - # we get its LineString - ... # TODO: edge_id is Path pk + # we use its LineString + else: + # Get the id of the edge corresponding to these nodes + node1 = self.nodes[node_list[i]] + node2 = self.nodes[node_list[i + 1]] + edge_id = self.get_edge_id_by_nodes(node1, node2) + + path = Path.objects.get(pk=edge_id) + line_strings.append(path.geom) - # If it's an edge created because of a marker, a temporary - # LineString is created - ... # TODO: use ST_LineSubstring? + return line_strings + + def create_line_substring(self, geometry, start_fraction, end_fraction): + sql = """ + SELECT ST_AsText(ST_LineSubstring('{}'::geometry, {}, {})) + """.format(geometry, start_fraction, end_fraction) + cursor = connection.cursor() + cursor.execute(sql) + result = cursor.fetchone()[0] + # Convert the string into an array of arrays of floats + coords_str = result.split('(')[1].split(')')[0] + str_points_array = [elem.split(' ') for elem in coords_str.split(',')] + arr = [[float(nb) for nb in sub_array] for sub_array in str_points_array] + line_substring = LineString(arr, srid=settings.SRID) + return line_substring def add_step_to_graph(self, step): - # Creating a Point corresponding to this step + # Create a Point corresponding to this step point = Point(step['lng'], step['lat'], srid=settings.API_SRID) point.transform(settings.SRID) - # Getting the Path (and corresponding graph edge) this Point is on + # Get the Path (and corresponding graph edge) this Point is on base_path = Path.closest(point) edge_id = base_path.pk edge = self.edges[edge_id] - # Getting the edge nodes + # Get the edge nodes first_node_id = edge.get('nodes_id')[0] last_node_id = edge.get('nodes_id')[1] - # Getting the percentage of the Path this Point is on + # Get the percentage of the Path this Point is on base_path_str = "'{}'".format(base_path.geom) point_str = "'{}'".format(point.ewkt) percent_distance = sqlfunction('SELECT ST_LineLocatePoint', @@ -555,11 +591,11 @@ def add_step_to_graph(self, step): path_length = base_path.length - # Getting the length of the edges that will be created + # Get the length of the edges that will be created dist_to_start = path_length * percent_distance dist_to_end = path_length * (1 - percent_distance) - # Creating the new node and edges + # Create the new node and edges new_node_id = self.generate_id() edge1 = { 'id': self.generate_id(), @@ -575,7 +611,7 @@ def add_step_to_graph(self, step): first_node[new_node_id] = new_node[first_node_id] = edge1['id'] last_node[new_node_id] = new_node[last_node_id] = edge2['id'] - # Adding them to the graph + # Add them to the graph self.edges[edge1['id']] = edge1 self.edges[edge2['id']] = edge2 self.nodes[new_node_id] = new_node @@ -588,21 +624,22 @@ def add_step_to_graph(self, step): 'new_edge1_id': edge1['id'], 'new_edge2_id': edge2['id'], 'original_egde_id': edge_id, + 'percent_of_edge': percent_distance, } return new_node_info def remove_step_from_graph(self, node_info): - # Removing the 2 new edges from the graph + # Remove the 2 new edges from the graph del self.edges[node_info['new_edge1_id']] del self.edges[node_info['new_edge2_id']] - # Getting the 2 nodes of the original edge (the one the step was on) + # Get the 2 nodes of the original edge (the one the step was on) original_edge = self.edges[node_info['original_egde_id']] nodes_id = original_edge['nodes_id'] first_node = self.nodes[nodes_id[0]] last_node = self.nodes[nodes_id[1]] - # Removing the new node from the graph + # Remove the new node from the graph removed_node_id = node_info['node_id'] del self.nodes[removed_node_id] del first_node[removed_node_id] @@ -616,7 +653,7 @@ def get_shortest_path(self, from_node_id, to_node_id): cs_graph = self.get_cs_graph() matrix = csr_matrix(cs_graph) - # Tuples (index, id) for all nodes -> for interpreting the results + # List of all nodes indexes -> to interprete dijkstra results self.nodes_ids = list(self.nodes.keys()) def get_node_idx_per_id(node_id): @@ -635,7 +672,7 @@ def get_node_id_per_idx(node_idx): result = dijkstra(matrix, return_predecessors=True, indices=from_node_idx, directed=False) - # Retracing the path index by index, from end to start + # Retrace the path index by index, from end to start predecessors = result[1] current_node_id, current_node_idx = to_node_id, to_node_idx path = [current_node_id] @@ -648,9 +685,19 @@ def get_node_id_per_idx(node_idx): return path def merge_line_strings(self, line_strings): - multi_line_string = MultiLineString(line_strings) + rounded_line_strings = [ + self.round_line_string_coordinates(ls) for ls in line_strings + ] + multi_line_string = MultiLineString(rounded_line_strings, srid=settings.SRID) return multi_line_string.merged + def round_line_string_coordinates(self, line_string): + # TODO: see which precision level is best + coords = line_string.coords + new_coords = [[round(nb, 4) for nb in pt_coord] for pt_coord in coords] + new_line_string = LineString(new_coords, srid=line_string.srid) + return new_line_string + def generate_id(self): new_id = self.id_count self.id_count += 1 @@ -673,14 +720,17 @@ def post(self, request): line_strings = self.compute_list_of_paths() merged_line_string = self.merge_line_strings(line_strings) + merged_line_string.transform(settings.API_SRID) # TODO: use GEOSGeometry.geojson (LineString?) - path_geojson = merged_line_string.geojson + geojson = merged_line_string.geojson + print(geojson, type(geojson)) return JsonResponse({ - 'paths': line_strings, - 'path_geojson': path_geojson, - 'trek': self.trek, + 'geojson': { + 'type': 'LineString', + 'coordinates': merged_line_string.coords, + }, }) trek = { From e66168532303e6e7f258e902f43b5b0214710a4c Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Tue, 23 Apr 2024 10:10:51 +0200 Subject: [PATCH 017/213] Correctng some comments --- geotrek/core/views.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/geotrek/core/views.py b/geotrek/core/views.py index 34d50b24dd..993a21dbf4 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -452,7 +452,7 @@ def get_queryset(self): class TrekGeometry(View): - + def get_cs_graph(self): def get_edge_weight(edge_id): @@ -500,7 +500,6 @@ def compute_list_of_paths(self): def compute_two_steps_line_strings(self, from_step, to_step): # Add the steps to the graph - # TODO: only add them if the step is a marker? from_node_info = self.add_step_to_graph(from_step) to_node_info = self.add_step_to_graph(to_step) @@ -513,14 +512,14 @@ def compute_two_steps_line_strings(self, from_step, to_step): self.remove_step_from_graph(from_node_info) self.remove_step_from_graph(to_node_info) return line_strings - + def node_list_to_line_strings(self, node_list, from_node_info, to_node_info): line_strings = [] # Get a LineString for each pair of adjacent nodes in the path for i in range(len(node_list) - 1): # If it's the first or last edge of this subpath (it can be both!), - # then the edge is temporary (i.e. created because of a step) + # then the edge is temporary (i.e. created because of a step) if i == 0 or i == len(node_list) - 2: # Start and end percentages of the line substring to be created start_fraction = 0 @@ -551,10 +550,10 @@ def node_list_to_line_strings(self, node_list, from_node_info, to_node_info): line_strings.append(path.geom) return line_strings - + def create_line_substring(self, geometry, start_fraction, end_fraction): sql = """ - SELECT ST_AsText(ST_LineSubstring('{}'::geometry, {}, {})) + SELECT ST_AsText(ST_LineSubstring('{}'::geometry, {}, {})) """.format(geometry, start_fraction, end_fraction) cursor = connection.cursor() @@ -653,7 +652,7 @@ def get_shortest_path(self, from_node_id, to_node_id): cs_graph = self.get_cs_graph() matrix = csr_matrix(cs_graph) - # List of all nodes indexes -> to interprete dijkstra results + # List of all nodes IDs -> to interprete dijkstra results self.nodes_ids = list(self.nodes.keys()) def get_node_idx_per_id(node_id): @@ -672,7 +671,7 @@ def get_node_id_per_idx(node_idx): result = dijkstra(matrix, return_predecessors=True, indices=from_node_idx, directed=False) - # Retrace the path index by index, from end to start + # Retrace the path ID by ID, from end to start predecessors = result[1] current_node_id, current_node_idx = to_node_id, to_node_idx path = [current_node_id] @@ -683,7 +682,7 @@ def get_node_id_per_idx(node_idx): path.reverse() return path - + def merge_line_strings(self, line_strings): rounded_line_strings = [ self.round_line_string_coordinates(ls) for ls in line_strings From 4ad3bd9ca8e060886b36532352871260aa2db79e Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Tue, 23 Apr 2024 14:52:04 +0200 Subject: [PATCH 018/213] Fix path not computed if adjacent steps are on the same edge --- geotrek/core/views.py | 180 +++++++++++++++++++++++++++++++----------- 1 file changed, 135 insertions(+), 45 deletions(-) diff --git a/geotrek/core/views.py b/geotrek/core/views.py index 993a21dbf4..ed7756483d 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -498,10 +498,51 @@ def compute_list_of_paths(self): total_line_strings += line_strings return total_line_strings + def add_steps_to_graph(self, from_step, to_step): + + def create_point_from_coords(lat, lng): + point = Point(lng, lat, srid=settings.API_SRID) + point.transform(settings.SRID) + return point + + def fill_point_closest_path_info(point): + # Path and edge info + point['base_path'] = base_path = Path.closest(point['geom']) + point['edge_id'] = edge_id = base_path.pk + point['edge'] = edge = self.edges[edge_id] + + # Nodes linked by this edge + point['first_node_id'] = edge.get('nodes_id')[0] + point['last_node_id'] = edge.get('nodes_id')[1] + + # Percentage of the Path this point is on + base_path_str = f"'{base_path.geom}'" + point_str = f"'{point['geom'].ewkt}'" + percent_distance = sqlfunction('SELECT ST_LineLocatePoint', + base_path_str, point_str)[0] + point['percent_distance'] = percent_distance + + # Create a Point corresponding to each step + from_point, to_point = {}, {} + from_point['geom'] = create_point_from_coords(from_step['lat'], from_step['lng']) + to_point['geom'] = create_point_from_coords(to_step['lat'], to_step['lng']) + + # Get the Path (and corresponding graph edge info) each Point is on + fill_point_closest_path_info(from_point) + fill_point_closest_path_info(to_point) + + # If the steps are on the same edge, it's divided into 3 new edges + if from_point['edge_id'] == to_point['edge_id']: + from_node_info, to_node_info = self.split_edge_in_three(from_point, + to_point) + # If they are on different edges, both are divided into two new edges + else: + from_node_info = self.split_edge_in_two(from_point) + to_node_info = self.split_edge_in_two(to_point) + return (from_node_info, to_node_info) + def compute_two_steps_line_strings(self, from_step, to_step): - # Add the steps to the graph - from_node_info = self.add_step_to_graph(from_step) - to_node_info = self.add_step_to_graph(to_step) + from_node_info, to_node_info = self.add_steps_to_graph(from_step, to_step) shortest_path = self.get_shortest_path(from_node_info['node_id'], to_node_info['node_id']) @@ -568,81 +609,130 @@ def create_line_substring(self, geometry, start_fraction, end_fraction): line_substring = LineString(arr, srid=settings.SRID) return line_substring - def add_step_to_graph(self, step): - # Create a Point corresponding to this step - point = Point(step['lng'], step['lat'], srid=settings.API_SRID) - point.transform(settings.SRID) - - # Get the Path (and corresponding graph edge) this Point is on - base_path = Path.closest(point) - edge_id = base_path.pk - edge = self.edges[edge_id] - - # Get the edge nodes - first_node_id = edge.get('nodes_id')[0] - last_node_id = edge.get('nodes_id')[1] - - # Get the percentage of the Path this Point is on - base_path_str = "'{}'".format(base_path.geom) - point_str = "'{}'".format(point.ewkt) - percent_distance = sqlfunction('SELECT ST_LineLocatePoint', - base_path_str, point_str)[0] - - path_length = base_path.length + def split_edge_in_two(self, point_info): # Get the length of the edges that will be created - dist_to_start = path_length * percent_distance - dist_to_end = path_length * (1 - percent_distance) + path_length = point_info['base_path'].length + dist_to_start = path_length * point_info['percent_distance'] + dist_to_end = path_length * (1 - point_info['percent_distance']) # Create the new node and edges new_node_id = self.generate_id() edge1 = { 'id': self.generate_id(), 'length': dist_to_start, - 'nodes_id': [first_node_id, new_node_id], + 'nodes_id': [point_info['first_node_id'], new_node_id], } edge2 = { 'id': self.generate_id(), 'length': dist_to_end, - 'nodes_id': [new_node_id, last_node_id], + 'nodes_id': [new_node_id, point_info['last_node_id']], } first_node, last_node, new_node = {}, {}, {} - first_node[new_node_id] = new_node[first_node_id] = edge1['id'] - last_node[new_node_id] = new_node[last_node_id] = edge2['id'] + first_node[new_node_id] = new_node[point_info['first_node_id']] = edge1['id'] + last_node[new_node_id] = new_node[point_info['last_node_id']] = edge2['id'] # Add them to the graph self.edges[edge1['id']] = edge1 self.edges[edge2['id']] = edge2 self.nodes[new_node_id] = new_node - self.extend_dict(self.nodes[first_node_id], first_node) - self.extend_dict(self.nodes[last_node_id], last_node) + self.extend_dict(self.nodes[point_info['first_node_id']], first_node) + self.extend_dict(self.nodes[point_info['last_node_id']], last_node) - # TODO: return a 'new edges' array? new_node_info = { 'node_id': new_node_id, 'new_edge1_id': edge1['id'], 'new_edge2_id': edge2['id'], - 'original_egde_id': edge_id, - 'percent_of_edge': percent_distance, + 'prev_node_id': point_info['first_node_id'], + 'next_node_id': point_info['last_node_id'], + 'original_egde_id': point_info['edge_id'], + 'percent_of_edge': point_info['percent_distance'], } return new_node_info + def split_edge_in_three(self, from_point, to_point): + # Get the length of the edges that will be created + path_length = from_point['base_path'].length + dist_to_start = path_length * from_point['percent_distance'] + dist_to_end = path_length * (1 - to_point['percent_distance']) + dist_middle = path_length - dist_to_start - dist_to_end + + # Create the new nodes and edges + new_node_id_1 = self.generate_id() + new_node_id_2 = self.generate_id() + edge1 = { + 'id': self.generate_id(), + 'length': dist_to_start, + 'nodes_id': [from_point['first_node_id'], new_node_id_1], + } + edge2 = { + 'id': self.generate_id(), + 'length': dist_middle, + 'nodes_id': [new_node_id_1, new_node_id_2], + } + edge3 = { + 'id': self.generate_id(), + 'length': dist_to_end, + 'nodes_id': [new_node_id_2, from_point['last_node_id']], + } + + # Link them together and to the existing nodes + first_node, last_node, new_node_1, new_node_2 = {}, {}, {}, {} + first_node[new_node_id_1] = new_node_1[from_point['first_node_id']] = edge1['id'] + new_node_1[new_node_id_2] = new_node_2[new_node_id_1] = edge2['id'] + new_node_2[from_point['last_node_id']] = last_node[new_node_id_2] = edge3['id'] + + # Add them to the graph + self.edges[edge1['id']] = edge1 + self.edges[edge2['id']] = edge2 + self.edges[edge3['id']] = edge3 + self.nodes[new_node_id_1] = new_node_1 + self.nodes[new_node_id_2] = new_node_2 + self.extend_dict(self.nodes[from_point['first_node_id']], first_node) + self.extend_dict(self.nodes[from_point['last_node_id']], last_node) + + new_node_info_1 = { + 'node_id': new_node_id_1, + 'new_edge1_id': edge1['id'], + 'new_edge2_id': edge2['id'], + 'prev_node_id': from_point['first_node_id'], + 'next_node_id': new_node_id_2, + 'original_egde_id': from_point['edge_id'], + 'percent_of_edge': from_point['percent_distance'], + } + new_node_info_2 = { + 'node_id': new_node_id_2, + 'new_edge1_id': edge2['id'], + 'prev_node_id': new_node_id_1, + 'next_node_id': from_point['last_node_id'], + 'new_edge2_id': edge3['id'], + 'original_egde_id': from_point['edge_id'], + 'percent_of_edge': to_point['percent_distance'], + } + return new_node_info_1, new_node_info_2 + def remove_step_from_graph(self, node_info): - # Remove the 2 new edges from the graph - del self.edges[node_info['new_edge1_id']] - del self.edges[node_info['new_edge2_id']] - # Get the 2 nodes of the original edge (the one the step was on) - original_edge = self.edges[node_info['original_egde_id']] - nodes_id = original_edge['nodes_id'] - first_node = self.nodes[nodes_id[0]] - last_node = self.nodes[nodes_id[1]] + # Remove the 2 new edges from the graph: + # They will have already been deleted if this is the 2nd step and + # both steps are on the same path + if self.edges.get(node_info['new_edge1_id']) is not None: + del self.edges[node_info['new_edge1_id']] + if self.edges.get(node_info['new_edge2_id']) is not None: + del self.edges[node_info['new_edge2_id']] + + # Get the 2 nodes this temporary node is linked to + prev_node = self.nodes.get(node_info['prev_node_id']) + next_node = self.nodes.get(node_info['next_node_id']) # Remove the new node from the graph removed_node_id = node_info['node_id'] del self.nodes[removed_node_id] - del first_node[removed_node_id] - del last_node[removed_node_id] + if prev_node is not None: + # It will have already been deleted if this is the 2nd step and + # both steps are on the same path + del prev_node[removed_node_id] + del next_node[removed_node_id] def extend_dict(self, dict, source): for key, value in source.items(): From c229a6ce4aa2029e19d234e619d5cb7f4e392727 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Tue, 23 Apr 2024 15:15:15 +0200 Subject: [PATCH 019/213] Fix negative dijkstra weight when we're going backwards relative to a Path direction --- geotrek/core/views.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/geotrek/core/views.py b/geotrek/core/views.py index ed7756483d..78407ce424 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -594,7 +594,7 @@ def node_list_to_line_strings(self, node_list, from_node_info, to_node_info): def create_line_substring(self, geometry, start_fraction, end_fraction): sql = """ - SELECT ST_AsText(ST_LineSubstring('{}'::geometry, {}, {})) + SELECT ST_AsText(ST_SmartLineSubstring('{}'::geometry, {}, {})) """.format(geometry, start_fraction, end_fraction) cursor = connection.cursor() @@ -653,8 +653,14 @@ def split_edge_in_two(self, point_info): def split_edge_in_three(self, from_point, to_point): # Get the length of the edges that will be created path_length = from_point['base_path'].length - dist_to_start = path_length * from_point['percent_distance'] - dist_to_end = path_length * (1 - to_point['percent_distance']) + start_percent = from_point['percent_distance'] + end_percent = to_point['percent_distance'] + + # If we're going backwards relative to the Path direction + start_percent, end_percent = end_percent, start_percent + + dist_to_start = path_length * start_percent + dist_to_end = path_length * (1 - end_percent) dist_middle = path_length - dist_to_start - dist_to_end # Create the new nodes and edges From 0ced11775137910b2abc0edb4b48e55b4dd6e576 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Tue, 23 Apr 2024 15:42:01 +0200 Subject: [PATCH 020/213] Fix path not computed when going backwards on a Path if going through several edges --- geotrek/core/views.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/geotrek/core/views.py b/geotrek/core/views.py index 78407ce424..56b08721cb 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -558,13 +558,26 @@ def node_list_to_line_strings(self, node_list, from_node_info, to_node_info): line_strings = [] # Get a LineString for each pair of adjacent nodes in the path for i in range(len(node_list) - 1): - + # Get the id of the edge corresponding to these nodes + node1 = self.nodes[node_list[i]] + node2 = self.nodes[node_list[i + 1]] + edge_id = self.get_edge_id_by_nodes(node1, node2) + + # If this pair of nodes requires to go backwards relative to a + # Path direction (i.e. the edge 2nd node is the 1st of this pair) + backwards = False + if self.edges[edge_id]['nodes_id'][1] == node_list[i]: + backwards = True + # If it's the first or last edge of this subpath (it can be both!), # then the edge is temporary (i.e. created because of a step) if i == 0 or i == len(node_list) - 2: # Start and end percentages of the line substring to be created start_fraction = 0 end_fraction = 1 + if backwards: + start_fraction, end_fraction = end_fraction, start_fraction + if i == 0: original_path = Path.objects.get(pk=from_node_info['original_egde_id']) start_fraction = from_node_info['percent_of_edge'] @@ -582,11 +595,6 @@ def node_list_to_line_strings(self, node_list, from_node_info, to_node_info): # If it's a real edge (i.e. it corresponds to a whole path), # we use its LineString else: - # Get the id of the edge corresponding to these nodes - node1 = self.nodes[node_list[i]] - node2 = self.nodes[node_list[i + 1]] - edge_id = self.get_edge_id_by_nodes(node1, node2) - path = Path.objects.get(pk=edge_id) line_strings.append(path.geom) From 7fc0002b70f008642cca1beedf04c3c59419f7b6 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Thu, 25 Apr 2024 16:03:26 +0200 Subject: [PATCH 021/213] Starting to display fetched route instead of locally computed route --- geotrek/core/static/core/multipath.js | 30 ++------- geotrek/core/static/core/topology_helper.js | 75 ++++++++++++++------- geotrek/core/views.py | 8 --- 3 files changed, 56 insertions(+), 57 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index beea2d01a7..46afac820e 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -335,7 +335,7 @@ L.Handler.MultiPath = L.Handler.extend({ // reset state this.steps = []; - this.computed_paths = []; + //this.computed_paths = []; this.all_edges = []; this.marker_source = this.marker_dest = null; @@ -508,18 +508,6 @@ L.Handler.MultiPath = L.Handler.extend({ var computed_paths = Geotrek.shortestPath(this.graph, this.steps); - let csrftoken = this.getCookie('csrftoken'); - - // var sent_steps_old = [] - // this.steps.forEach((step) => { - // var sent_step = { - // path_length: step.path_length, - // percent_distance: step.percent_distance, - // edge_id: step.polyline.properties.id, - // } - // sent_steps_old.push(sent_step) - // }) - var sent_steps = [] this.steps.forEach((step) => { var sent_step = { @@ -529,12 +517,10 @@ L.Handler.MultiPath = L.Handler.extend({ sent_steps.push(sent_step) }) - console.log('computePaths:', 'graph', this.graph, 'steps', this.steps) - fetch(window.SETTINGS.urls['trek_geometry'], { method: 'POST', headers: { - "X-CSRFToken": csrftoken, + "X-CSRFToken": this.getCookie('csrftoken'), content_type: "application/json" }, body: JSON.stringify({ @@ -545,15 +531,9 @@ L.Handler.MultiPath = L.Handler.extend({ .then(data => { console.log('response:', data) - var refacto_computed_path = { - 'from_pop': this.steps[0], - 'to_pop': this.steps[1], - } - var test_computed_path = { 'computed_paths': computed_paths, 'geojson': data.geojson, - 'trek': data.geojson, } console.log("geojson", data.geojson/* , "trek", data.trek */) @@ -609,7 +589,7 @@ L.Handler.MultiPath = L.Handler.extend({ _onComputedPaths: function(new_computed_paths) { // var self = this; // var old_computed_paths = this.computed_paths; - this.computed_paths = new_computed_paths['computed_paths']; + //this.computed_paths = new_computed_paths['computed_paths']; // compute and store all edges of the new paths (usefull for further computation) this.all_edges = this._extractAllEdges(new_computed_paths['computed_paths']); @@ -617,7 +597,7 @@ L.Handler.MultiPath = L.Handler.extend({ this.fire('computed_paths', { 'computed_paths': new_computed_paths['computed_paths'], 'new_edges': this.all_edges, - 'trek': new_computed_paths['trek'], + 'geojson': new_computed_paths['geojson'], // 'old': old_computed_paths, // 'marker_source': this.marker_source, // 'marker_dest': this.marker_dest @@ -839,7 +819,7 @@ L.Handler.MultiPath = L.Handler.extend({ this.showPathGeom(topology.layer); // Hard-coded polyline - L.geoJson(data.trek, {color:"blue", weight: 10}).addTo(self.map); + //L.geoJson(data.geojson, {color:"blue", weight: 10}).addTo(self.map); this.fire('computed_topology', {topology:topology.serialized}); diff --git a/geotrek/core/static/core/topology_helper.js b/geotrek/core/static/core/topology_helper.js index b1b23e01b7..9d9864edd1 100644 --- a/geotrek/core/static/core/topology_helper.js +++ b/geotrek/core/static/core/topology_helper.js @@ -259,44 +259,71 @@ Geotrek.TopologyHelper = (function() { * @param data : computed_path */ function buildTopologyFromComputedPath(idToLayer, data) { - if (!data.computed_paths) { - return { - layer: null, - serialized: null - } - } + // if (!data.computed_paths) { + // return { + // layer: null, + // serialized: null + // } + // } + var geojson = data['geojson'] + var pathLayer = L.geoJson(geojson, {color:"blue", weight: 10}) var computed_paths = data['computed_paths'] , edges = data['new_edges'] , offset = 0.0 // TODO: input for offset , data = [] , layer = L.featureGroup(); + console.log("computed_paths", computed_paths) console.debug('----'); console.debug('Topology has ' + computed_paths.length + ' sub-topologies.'); - for (var i = 0; i < computed_paths.length; i++ ) { - var cpath = computed_paths[i], - paths = $.map(edges[i], function(edge) { return edge.id; }), - polylines = $.map(edges[i], function(edge) { return idToLayer(edge.id); }); + // for (var i = 0; i < computed_paths.length; i++ ) { + // var cpath = computed_paths[i], + // paths = $.map(edges[i], function(edge) { return edge.id; }), + // polylines = $.map(edges[i], function(edge) { return idToLayer(edge.id); }); + + // var topo = buildSubTopology(paths, + // polylines, + // cpath.from_pop.ll, + // cpath.to_pop.ll, + // offset); + // if (topo === null) break; - var topo = buildSubTopology(paths, - polylines, - cpath.from_pop.ll, - cpath.to_pop.ll, - offset); - if (topo === null) break; + // data.push(topo); + // console.debug('subtopo[' + i + '] : ' + JSON.stringify(topo)); - data.push(topo); - console.debug('subtopo[' + i + '] : ' + JSON.stringify(topo)); + // // Geometry for each sub-topology + // var group_layer = buildGeometryFromTopology(topo, idToLayer); + // // group_layer.from_pop = cpath.from_pop; + // // group_layer.to_pop = cpath.to_pop; + // // group_layer.step_idx = i; + // layer.addLayer(group_layer); + // } - // Geometry for each sub-topology - var group_layer = buildGeometryFromTopology(topo, idToLayer); - group_layer.from_pop = cpath.from_pop; - group_layer.to_pop = cpath.to_pop; - group_layer.step_idx = i; - layer.addLayer(group_layer); + var latlngs = [] + for (var i = 0; i < geojson.coordinates.length - 1; i++) { + var currentCoords = geojson.coordinates[i] + var nextCoords = geojson.coordinates[i + 1] + var newCoords = [ + { + lat: currentCoords[1], + lng: currentCoords[0], + }, + { + lat: nextCoords[1], + lng: nextCoords[0], + }, + ] + latlngs.push(newCoords) } + console.log("latlngs", latlngs) + + var group_layer = L.multiPolyline(latlngs); + group_layer.step_idx = 0 + layer.addLayer(group_layer); + + console.log(pathLayer) console.debug('----'); return { diff --git a/geotrek/core/views.py b/geotrek/core/views.py index 56b08721cb..1540b5621d 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -835,11 +835,3 @@ def post(self, request): 'coordinates': merged_line_string.coords, }, }) - - trek = { - "type": "LineString", - "coordinates": [ - [5.8921902,44.6888518,1451.0],[5.8920368,44.688848,1450.0],[5.8918196,44.6888427,1450.0],[5.8916025,44.6888373,1450.0],[5.8913771,44.6887051,1450.0],[5.8913211,44.6886722,1450.0],[5.8911518,44.6885729,1449.0],[5.8909265,44.6884407,1447.0],[5.8907777,44.6884446,1446.0],[5.8907187,44.6885704,1444.0],[5.8906597,44.6886963,1442.0],[5.8906033,44.6888946,1440.0],[5.890547,44.6890929,1439.0],[5.890488,44.6893027,1437.0],[5.890429,44.6895124,1435.0],[5.8901876,44.6895086,1433.0],[5.8902719,44.6896295,1431.0],[5.8902885,44.6896336,1430.0],[5.8903788,44.68967,1429.0],[5.8905075,44.6897414,1428.0],[5.8905388,44.6897847,1428.0],[5.890582,44.6898403,1427.0],[5.8904582,44.6898609,1427.0],[5.8903831,44.68988,1426.0],[5.8903027,44.6898952,1426.0],[5.8902007,44.6899295,1426.0],[5.8901364,44.6899524,1425.0],[5.890072,44.6899867,1425.0],[5.890013,44.6900134,1425.0],[5.8899647,44.6900287,1425.0],[5.8898681,44.6900439,1424.0],[5.889793,44.6900478,1424.0],[5.8896858,44.6900401,1424.0],[5.8895946,44.6900211,1424.0],[5.8895007,44.6899951,1424.0],[5.889498,44.6899944,1424.0],[5.8894122,44.6899677,1424.0],[5.889321,44.6899448,1424.0],[5.8892459,44.6899181,1424.0],[5.88916,44.689899,1423.0],[5.889101,44.6898761,1423.0],[5.8890313,44.6898533,1423.0],[5.8889776,44.6898342,1423.0],[5.8889079,44.6898037,1423.0],[5.8888489,44.6897732,1423.0],[5.8887684,44.6897465,1423.0],[5.8886665,44.6897198,1423.0],[5.8885753,44.6896969,1423.0],[5.8884627,44.689674,1423.0],[5.88835,44.689674,1423.0],[5.8882052,44.6896664,1423.0],[5.8880711,44.6896511,1423.0],[5.8879316,44.6896244,1422.0],[5.8877599,44.6895977,1421.0],[5.8876204,44.6895749,1421.0],[5.8875024,44.6895482,1420.0],[5.8873469,44.6895062,1418.0],[5.8872074,44.6894643,1417.0],[5.8870679,44.6894261,1416.0],[5.8869553,44.6893804,1415.0],[5.8868694,44.6893308,1413.0],[5.8867782,44.6892659,1412.0],[5.8866978,44.6892011,1410.0],[5.8866012,44.689102,1408.0],[5.8864993,44.6890181,1407.0],[5.8863813,44.6889265,1406.0],[5.8863062,44.6888769,1405.0],[5.8862042,44.6888007,1403.0],[5.8860916,44.6887282,1402.0],[5.8860058,44.688671,1401.0],[5.8859199,44.68861,1400.0],[5.8858341,44.6885375,1399.0],[5.8857322,44.6884765,1398.0],[5.8856463,44.6884079,1396.0],[5.8855551,44.6883583,1395.0],[5.8854892,44.688321,1394.0],[5.8854126,44.6883059,1392.0],[5.8853212,44.6883461,1390.0],[5.8851847,44.6884668,1389.0],[5.8851528,44.6885146,1388.0],[5.8850831,44.6885985,1386.0],[5.8850402,44.6886596,1383.0],[5.8849919,44.6887015,1381.0],[5.8849436,44.6887549,1379.0],[5.8849007,44.6888197,1378.0],[5.8848578,44.6888846,1376.0],[5.8848149,44.6889608,1375.0],[5.8847773,44.6890295,1373.0],[5.8847344,44.6891134,1372.0],[5.8847129,44.6891897,1370.0],[5.8846968,44.6892659,1369.0],[5.8846754,44.6893422,1368.0],[5.8846378,44.6894071,1367.0],[5.8845842,44.6894604,1365.0],[5.8845359,44.6895062,1364.0],[5.8844393,44.6895596,1363.0],[5.8843535,44.6895863,1361.0],[5.8842838,44.6896092,1360.0],[5.8842087,44.6896359,1359.0],[5.8841014,44.6896626,1358.0],[5.8839995,44.6896855,1356.0],[5.8838922,44.6897236,1355.0],[5.8838117,44.6897388,1354.0],[5.8837473,44.6897617,1353.0],[5.8836776,44.6897732,1352.0],[5.8836025,44.6897808,1350.0],[5.883522,44.6897961,1349.0],[5.8834523,44.6898266,1348.0],[5.883404,44.6898609,1347.0],[5.8833611,44.6898914,1345.0],[5.8832806,44.689941,1344.0],[5.883227,44.6899906,1343.0],[5.8831787,44.6900401,1341.0],[5.8831465,44.6900706,1341.0],[5.8831143,44.690105,1339.0],[5.8830768,44.6901393,1338.0],[5.8830339,44.6901774,1337.0],[5.8830329,44.6901792,1336.0],[5.8830124,44.6902156,1335.0],[5.8830017,44.6902766,1335.0],[5.8830017,44.6903262,1334.0],[5.8830017,44.6903719,1332.0],[5.882991,44.6904177,1331.0],[5.8829641,44.6904558,1330.0],[5.8828837,44.690555,1328.0],[5.8828032,44.6906274,1327.0],[5.8827066,44.690738,1325.0],[5.8826369,44.6908219,1323.0],[5.8825833,44.690902,1321.0],[5.8824921,44.6910393,1319.0],[5.8824867,44.691108,1317.0],[5.8824599,44.6911766,1315.0],[5.8824438,44.6912605,1314.0],[5.8824384,44.6913368,1312.0],[5.8824277,44.6914054,1311.0],[5.8824062,44.6914741,1309.0],[5.8823794,44.6915465,1308.0],[5.8823633,44.6916113,1307.0],[5.8823633,44.6916914,1306.0],[5.8823633,44.6918058,1305.0],[5.8824116,44.6918745,1304.0],[5.8824706,44.6919469,1304.0],[5.8825243,44.6920041,1303.0],[5.8826101,44.6920766,1302.0],[5.8827013,44.6921147,1302.0],[5.8827925,44.6921491,1301.0],[5.8828837,44.6921719,1300.0],[5.882948,44.6922558,1299.0],[5.8829856,44.6923588,1298.0],[5.8830285,44.6924274,1297.0],[5.8830285,44.6924846,1296.0],[5.8830339,44.6925685,1295.0],[5.8830446,44.6926181,1294.0],[5.88305,44.6926868,1293.0],[5.88305,44.6927478,1291.0],[5.8830124,44.6928012,1290.0],[5.8829749,44.6928736,1289.0],[5.8829856,44.6929308,1289.0],[5.8830285,44.6929995,1288.0],[5.8830661,44.6930452,1288.0],[5.8831197,44.693091,1287.0],[5.883168,44.6931215,1286.0],[5.8832324,44.6931787,1285.0],[5.8832431,44.6932207,1284.0],[5.8832055,44.6932473,1283.0],[5.8830768,44.6932397,1282.0],[5.8829856,44.6932321,1280.0],[5.8828676,44.6932283,1279.0],[5.8827549,44.6932283,1277.0],[5.882653,44.6932283,1276.0],[5.8825886,44.6932397,1274.0],[5.8825082,44.6932626,1273.0],[5.882476,44.6933351,1272.0],[5.8824331,44.6934266,1272.0],[5.8824277,44.6935105,1272.0],[5.8824652,44.6935677,1273.0],[5.8825135,44.6936478,1273.0],[5.8826881,44.6937303,1274.0],[5.8828571,44.6938504,1274.0],[5.8830261,44.6939705,1275.0],[5.8830797,44.6940582,1276.0],[5.8830985,44.6942413,1278.0],[5.8831173,44.6944243,1279.0],[5.8832299,44.6946112,1281.0],[5.8833962,44.6947027,1283.0],[5.8835625,44.6947179,1285.0],[5.8837449,44.6946493,1287.0],[5.8838844,44.6945501,1290.0],[5.8840239,44.694451,1292.0],[5.8841553,44.6942927,1295.0],[5.8842867,44.6941345,1297.0],[5.8843887,44.6940239,1299.0],[5.8845603,44.6941269,1302.0],[5.8846622,44.6943137,1304.0],[5.8846944,44.6944052,1306.0],[5.8847964,44.6944014,1308.0],[5.8848178,44.6942718,1309.0],[5.8848446,44.6940906,1311.0],[5.8848715,44.6939095,1312.0],[5.884909,44.6937322,1314.0],[5.8849466,44.6935548,1315.0],[5.8850056,44.6934061,1317.0],[5.8851665,44.693593,1318.0],[5.8852416,44.6937417,1319.0],[5.8853167,44.6938904,1320.0],[5.8853972,44.6940105,1321.0],[5.8854776,44.6941307,1322.0],[5.8855205,44.694287,1324.0],[5.8855635,44.6944434,1325.0],[5.8856815,44.6946302,1326.0],[5.8858558,44.6947713,1328.0],[5.8860302,44.6949124,1330.0],[5.8861482,44.6950459,1331.0],[5.8862662,44.6951793,1333.0],[5.8863842,44.6953128,1335.0],[5.88647,44.6955073,1337.0],[5.8865559,44.6957018,1339.0],[5.8865773,44.6959229,1341.0],[5.8866578,44.6960526,1343.0],[5.8867383,44.6961822,1345.0],[5.8868724,44.6962394,1347.0],[5.8869904,44.6962051,1350.0],[5.8870548,44.6960602,1352.0],[5.887264,44.6959306,1353.0],[5.8873981,44.6959801,1355.0],[5.8874249,44.6961822,1357.0],[5.8875751,44.6962852,1359.0],[5.8877924,44.6963767,1361.0],[5.8880096,44.6964682,1363.0],[5.8881706,44.6965731,1364.0],[5.8883315,44.696678,1366.0],[5.8884978,44.6967314,1368.0],[5.8886641,44.6967847,1371.0],[5.8888626,44.6968839,1373.0],[5.8890181,44.6969754,1376.0],[5.8891737,44.6971508,1379.0],[5.8893293,44.6973262,1382.0],[5.8895036,44.6974692,1385.0],[5.889678,44.6976122,1389.0],[5.8898577,44.6977476,1392.0],[5.8900374,44.6978829,1395.0],[5.8901742,44.6979916,1398.0],[5.890311,44.6981003,1401.0],[5.8905148,44.6981664,1404.0],[5.8907187,44.6982325,1407.0],[5.8909225,44.6982986,1409.0],[5.8911478,44.6983787,1411.0],[5.8913731,44.6984587,1414.0],[5.8915341,44.6984816,1416.0],[5.891695,44.6985045,1417.0],[5.8919437,44.6985245,1419.0],[5.8919848,44.6985135,1421.0],[5.8920041,44.6984949,1422.0],[5.8920106,44.698475,1423.0],[5.8920001,44.6984419,1425.0],[5.8919898,44.6983656,1425.0],[5.8921548,44.6984415,1426.0],[5.8923197,44.6985173,1427.0],[5.8924852,44.6985671,1427.0],[5.8926681,44.6986118,1428.0],[5.8928961,44.6986473,1428.0],[5.8930978,44.6986655,1428.0],[5.8932996,44.6986836,1428.0],[5.8934754,44.6987123,1428.0],[5.8935803,44.6987384,1427.0],[5.8938566,44.698788,1427.0],[5.8941329,44.6988375,1426.0],[5.8944093,44.698887,1426.0],[5.8946856,44.6989365,1425.0],[5.8949442,44.6990199,1425.0],[5.8952028,44.6991032,1424.0],[5.8954614,44.6991865,1423.0],[5.895729,44.6992976,1423.0],[5.8959517,44.6993929,1423.0],[5.8961743,44.6994883,1423.0],[5.8963123,44.6995452,1422.0],[5.8964503,44.6996021,1422.0],[5.8966022,44.699681,1422.0],[5.8968022,44.699764,1422.0],[5.8969605,44.6998094,1422.0],[5.8971189,44.6998548,1422.0],[5.8973456,44.6999651,1422.0],[5.897569,44.7000412,1422.0],[5.89773,44.7000564,1421.0],[5.8978909,44.7000717,1421.0],[5.8981162,44.7001136,1421.0],[5.8983227,44.7001575,1420.0],[5.8985293,44.7002013,1420.0],[5.8987117,44.7002452,1420.0],[5.898894,44.700289,1420.0],[5.899063,44.700371,1419.0],[5.899232,44.700453,1419.0],[5.8994287,44.7005114,1419.0],[5.8996254,44.7005699,1418.0],[5.8997935,44.7006198,1418.0],[5.8998221,44.7006284,1417.0],[5.9000447,44.7006646,1417.0],[5.9002673,44.7007008,1416.0],[5.9004944,44.7007644,1415.0],[5.9007215,44.7008279,1414.0],[5.9009486,44.7008915,1413.0],[5.9011981,44.7009201,1412.0],[5.9014475,44.7009486,1411.0],[5.9016084,44.7009639,1409.0],[5.9017694,44.7009792,1408.0],[5.9020483,44.7010363,1407.0],[5.9023273,44.7010935,1405.0],[5.9024936,44.7011069,1403.0],[5.9026599,44.7011202,1402.0],[5.9028691,44.7010783,1400.0],[5.9030908,44.7010236,1398.0],[5.9033125,44.700969,1395.0],[5.9035343,44.7009143,1393.0],[5.9037542,44.7009315,1391.0],[5.9039741,44.7009486,1388.0],[5.9042263,44.7009868,1386.0],[5.9044784,44.7010249,1384.0],[5.9046793,44.7010416,1383.0],[5.9049153,44.70095,1383.0],[5.9049153,44.70095,1383.0],[5.9050548,44.7008166,1383.0],[5.9051943,44.7006831,1384.0],[5.9052318,44.7004696,1385.0],[5.9052694,44.7002561,1386.0],[5.9051514,44.7001798,1388.0],[5.9050065,44.7002828,1389.0],[5.9048617,44.7003857,1391.0],[5.9046865,44.7002739,1391.0],[5.9045112,44.700162,1392.0],[5.904336,44.7000502,1393.0],[5.9041622,44.699919,1393.0],[5.9039884,44.6997878,1394.0],[5.9038146,44.6996567,1395.0],[5.9036407,44.6995255,1395.0],[5.9034669,44.6993943,1396.0],[5.9033436,44.6992113,1396.0],[5.9032202,44.6990283,1397.0],[5.9030968,44.6988453,1397.0],[5.9029734,44.6986622,1398.0],[5.9027696,44.698525,1398.0],[5.9025657,44.6983877,1398.0],[5.9025067,44.698258,1399.0],[5.9024477,44.6981284,1399.0],[5.9023511,44.6979835,1399.0],[5.9022546,44.6978386,1400.0],[5.9020783,44.697673,1400.0],[5.9019021,44.6975074,1401.0],[5.9017258,44.6973418,1402.0],[5.9015495,44.6971762,1403.0],[5.9013733,44.6970106,1405.0],[5.901197,44.696845,1407.0],[5.9010208,44.6966794,1409.0],[5.9008491,44.6965406,1410.0],[5.9006774,44.6964018,1412.0],[5.9005058,44.696263,1413.0],[5.9003341,44.6961242,1415.0],[5.9001625,44.6959854,1417.0],[5.8999801,44.6959167,1418.0],[5.8997977,44.6958481,1420.0],[5.8996153,44.6957006,1421.0],[5.8994329,44.6955532,1423.0],[5.8992505,44.6954057,1424.0],[5.8990681,44.6952583,1425.0],[5.8988857,44.6951108,1427.0],[5.8987033,44.6949634,1429.0],[5.8985236,44.694795,1431.0],[5.8983439,44.6946265,1433.0],[5.8981642,44.6944581,1435.0],[5.8979845,44.6942897,1436.0],[5.8978048,44.6941213,1438.0],[5.8976251,44.6939528,1440.0],[5.8974132,44.6938623,1442.0],[5.8972013,44.6937717,1444.0],[5.8969894,44.6936811,1446.0],[5.8967775,44.6935906,1447.0],[5.8966114,44.693406,1449.0],[5.8964453,44.6932214,1450.0],[5.8962793,44.6930368,1452.0],[5.8961132,44.6928523,1453.0],[5.8959471,44.6926677,1454.0],[5.8958559,44.6926257,1455.0],[5.8957325,44.6925685,1456.0],[5.8955877,44.6925342,1457.0],[5.8954697,44.6924999,1457.0],[5.8954321,44.6924427,1457.0],[5.8953034,44.6924656,1457.0],[5.8951693,44.6924618,1457.0],[5.8950512,44.6924389,1457.0],[5.89496,44.6923893,1456.0],[5.8948259,44.6922711,1455.0],[5.8947026,44.6921109,1454.0],[5.8945738,44.6920003,1453.0],[5.894502,44.6919513,1453.0],[5.8942876,44.6919083,1452.0],[5.8943217,44.6917982,1451.0],[5.8943217,44.6916139,1451.0],[5.8943217,44.6914296,1451.0],[5.8943217,44.6912452,1450.0],[5.8943031,44.6910795,1450.0],[5.8942845,44.6909138,1450.0],[5.8942804,44.690772,1450.0],[5.8942762,44.6906302,1450.0],[5.8943003,44.6905332,1450.0],[5.8943034,44.6905196,1450.0],[5.8943223,44.6904218,1450.0],[5.8943274,44.6903983,1450.0],[5.8943264,44.6903037,1451.0],[5.8943016,44.6901908,1451.0],[5.8942096,44.6899856,1451.0],[5.8941176,44.6897803,1452.0],[5.8940492,44.6896533,1452.0],[5.8939808,44.6895263,1452.0],[5.8938898,44.6894277,1452.0],[5.8938607,44.6894006,1452.0],[5.8938116,44.6894532,1452.0],[5.8937345,44.6894021,1452.0],[5.8935994,44.6893299,1452.0],[5.8933763,44.6892272,1452.0],[5.8931531,44.6891245,1452.0],[5.8928828,44.6890403,1451.0],[5.8926124,44.688956,1451.0],[5.8923942,44.6888881,1451.0] - ] - } - From 0c7ea24b5c7d355433de7e3cfdce77bd34190418 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Thu, 25 Apr 2024 17:04:13 +0200 Subject: [PATCH 022/213] Preparing topology_helper to handle several linestrings --- geotrek/core/static/core/topology_helper.js | 108 ++++++++++---------- 1 file changed, 56 insertions(+), 52 deletions(-) diff --git a/geotrek/core/static/core/topology_helper.js b/geotrek/core/static/core/topology_helper.js index 9d9864edd1..6c4a72e298 100644 --- a/geotrek/core/static/core/topology_helper.js +++ b/geotrek/core/static/core/topology_helper.js @@ -265,70 +265,74 @@ Geotrek.TopologyHelper = (function() { // serialized: null // } // } - var geojson = data['geojson'] - var pathLayer = L.geoJson(geojson, {color:"blue", weight: 10}) - + + //var pathLayer = L.geoJson(geojson, {color:"blue", weight: 10}) + var computed_paths = data['computed_paths'] - , edges = data['new_edges'] - , offset = 0.0 // TODO: input for offset - , data = [] - , layer = L.featureGroup(); - console.log("computed_paths", computed_paths) + , edges = data['new_edges'] + , offset = 0.0 // TODO: input for offset + //, data = [] + console.log("computed_paths", computed_paths) console.debug('----'); console.debug('Topology has ' + computed_paths.length + ' sub-topologies.'); - + // for (var i = 0; i < computed_paths.length; i++ ) { - // var cpath = computed_paths[i], - // paths = $.map(edges[i], function(edge) { return edge.id; }), - // polylines = $.map(edges[i], function(edge) { return idToLayer(edge.id); }); - - // var topo = buildSubTopology(paths, - // polylines, - // cpath.from_pop.ll, - // cpath.to_pop.ll, - // offset); - // if (topo === null) break; - - // data.push(topo); - // console.debug('subtopo[' + i + '] : ' + JSON.stringify(topo)); + // var cpath = computed_paths[i], + // paths = $.map(edges[i], function(edge) { return edge.id; }), + // polylines = $.map(edges[i], function(edge) { return idToLayer(edge.id); }); + + // var topo = buildSubTopology(paths, + // polylines, + // cpath.from_pop.ll, + // cpath.to_pop.ll, + // offset); + // if (topo === null) break; + + // data.push(topo); + // console.debug('subtopo[' + i + '] : ' + JSON.stringify(topo)); + + // // Geometry for each sub-topology + // var group_layer = buildGeometryFromTopology(topo, idToLayer); + // // group_layer.from_pop = cpath.from_pop; + // // group_layer.to_pop = cpath.to_pop; + // // group_layer.step_idx = i; + // layer.addLayer(group_layer); + // } - // // Geometry for each sub-topology - // var group_layer = buildGeometryFromTopology(topo, idToLayer); - // // group_layer.from_pop = cpath.from_pop; - // // group_layer.to_pop = cpath.to_pop; - // // group_layer.step_idx = i; - // layer.addLayer(group_layer); - // } - - var latlngs = [] - for (var i = 0; i < geojson.coordinates.length - 1; i++) { - var currentCoords = geojson.coordinates[i] - var nextCoords = geojson.coordinates[i + 1] - var newCoords = [ - { - lat: currentCoords[1], - lng: currentCoords[0], - }, - { - lat: nextCoords[1], - lng: nextCoords[0], - }, - ] - latlngs.push(newCoords) + var geojson = [data.geojson] + var layer = L.featureGroup(); + for (var i = 0; i < geojson.length; i++) { + var latlngs = [] + var lineString = geojson[i] + for (var j = 0; j < lineString.coordinates.length - 1; j++) { + var currentCoords = lineString.coordinates[j] + var nextCoords = lineString.coordinates[j + 1] + var newCoords = [ + { + lat: currentCoords[1], + lng: currentCoords[0], + }, + { + lat: nextCoords[1], + lng: nextCoords[0], + }, + ] + latlngs.push(newCoords) + } + console.log("latlngs", latlngs) + var group_layer = L.multiPolyline(latlngs); + group_layer.step_idx = i + layer.addLayer(group_layer); } - console.log("latlngs", latlngs) - - var group_layer = L.multiPolyline(latlngs); - group_layer.step_idx = 0 - layer.addLayer(group_layer); - console.log(pathLayer) + //console.log(pathLayer) console.debug('----'); return { layer: layer, - serialized: data + serialized: null + //serialized: data }; } From c5eea3bf513e24e6bf04a65f4d7a1bd4b7aff821 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Thu, 25 Apr 2024 17:17:48 +0200 Subject: [PATCH 023/213] Automating the geojson generation in the view --- geotrek/core/static/core/multipath.js | 4 ++-- geotrek/core/views.py | 12 ++---------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index 46afac820e..e778c8979b 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -533,10 +533,10 @@ L.Handler.MultiPath = L.Handler.extend({ var test_computed_path = { 'computed_paths': computed_paths, - 'geojson': data.geojson, + 'geojson': data, } - console.log("geojson", data.geojson/* , "trek", data.trek */) + console.log("geojson", data/* , "trek", data.trek */) // var mainmap = window.maps[0] // mainmap.addLayers diff --git a/geotrek/core/views.py b/geotrek/core/views.py index 1540b5621d..f3fd09f0f8 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -825,13 +825,5 @@ def post(self, request): merged_line_string = self.merge_line_strings(line_strings) merged_line_string.transform(settings.API_SRID) - # TODO: use GEOSGeometry.geojson (LineString?) - geojson = merged_line_string.geojson - print(geojson, type(geojson)) - - return JsonResponse({ - 'geojson': { - 'type': 'LineString', - 'coordinates': merged_line_string.coords, - }, - }) + geojson = json.loads(merged_line_string.geojson) + return JsonResponse(geojson) From 93cfd65288e8752c894cc6217b4ede0b1529b61f Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Fri, 26 Apr 2024 12:40:22 +0200 Subject: [PATCH 024/213] Add handling of several steps --- geotrek/core/static/core/topology_helper.js | 2 +- geotrek/core/views.py | 28 +++++++++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/geotrek/core/static/core/topology_helper.js b/geotrek/core/static/core/topology_helper.js index 6c4a72e298..dc42ff7afa 100644 --- a/geotrek/core/static/core/topology_helper.js +++ b/geotrek/core/static/core/topology_helper.js @@ -300,7 +300,7 @@ Geotrek.TopologyHelper = (function() { // layer.addLayer(group_layer); // } - var geojson = [data.geojson] + var geojson = data.geojson var layer = L.featureGroup(); for (var i = 0; i < geojson.length; i++) { var latlngs = [] diff --git a/geotrek/core/views.py b/geotrek/core/views.py index f3fd09f0f8..7240308715 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -489,14 +489,15 @@ def get_edge_id_by_nodes(self, node1, node2): return None def compute_list_of_paths(self): - total_line_strings = [] + all_line_strings = [] # Compute the shortest path for each pair of adjacent steps for i in range(len(self.steps) - 1): from_step = self.steps[i] to_step = self.steps[i + 1] line_strings = self.compute_two_steps_line_strings(from_step, to_step) - total_line_strings += line_strings - return total_line_strings + merged_line_string = self.merge_line_strings(line_strings) + all_line_strings.append(merged_line_string) + return all_line_strings def add_steps_to_graph(self, from_step, to_step): @@ -568,7 +569,7 @@ def node_list_to_line_strings(self, node_list, from_node_info, to_node_info): backwards = False if self.edges[edge_id]['nodes_id'][1] == node_list[i]: backwards = True - + # If it's the first or last edge of this subpath (it can be both!), # then the edge is temporary (i.e. created because of a step) if i == 0 or i == len(node_list) - 2: @@ -822,8 +823,19 @@ def post(self, request): self.edges = graph['edges'] line_strings = self.compute_list_of_paths() - merged_line_string = self.merge_line_strings(line_strings) - merged_line_string.transform(settings.API_SRID) - geojson = json.loads(merged_line_string.geojson) - return JsonResponse(geojson) + # merged_line_string = self.merge_line_strings(line_strings) + # merged_line_string.transform(settings.API_SRID) + # geojson = json.loads(merged_line_string.geojson) + + # multi_line_string = MultiLineString(line_strings, srid=settings.SRID) + # multi_line_string.transform(settings.API_SRID) + # geojson = json.loads(multi_line_string.geojson) + + geojsons = [] + for ls in line_strings: + ls.transform(settings.API_SRID) + geojson = json.loads(ls.geojson) + geojsons.append(geojson) + + return JsonResponse(geojsons, safe=False) From a03d6f75610a8b866711033c8d97cdfa06738693 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Fri, 26 Apr 2024 13:25:06 +0200 Subject: [PATCH 025/213] Replacing MultiLineString by GeometryCollection in Response --- geotrek/core/static/core/topology_helper.js | 102 +++++++------------- geotrek/core/views.py | 25 +++-- 2 files changed, 47 insertions(+), 80 deletions(-) diff --git a/geotrek/core/static/core/topology_helper.js b/geotrek/core/static/core/topology_helper.js index dc42ff7afa..3bede1a60e 100644 --- a/geotrek/core/static/core/topology_helper.js +++ b/geotrek/core/static/core/topology_helper.js @@ -259,81 +259,51 @@ Geotrek.TopologyHelper = (function() { * @param data : computed_path */ function buildTopologyFromComputedPath(idToLayer, data) { - // if (!data.computed_paths) { - // return { - // layer: null, - // serialized: null + + // var geojson = data.geojson + // var layer = L.featureGroup(); + // for (var i = 0; i < geojson.length; i++) { + // var latlngs = [] + // var lineString = geojson[i] + // for (var j = 0; j < lineString.coordinates.length - 1; j++) { + // var currentCoords = lineString.coordinates[j] + // var nextCoords = lineString.coordinates[j + 1] + // var newCoords = [ + // { + // lat: currentCoords[1], + // lng: currentCoords[0], + // }, + // { + // lat: nextCoords[1], + // lng: nextCoords[0], + // }, + // ] + // latlngs.push(newCoords) // } + // var group_layer = L.multiPolyline(latlngs); + // group_layer.step_idx = i + // layer.addLayer(group_layer); // } - - //var pathLayer = L.geoJson(geojson, {color:"blue", weight: 10}) - - var computed_paths = data['computed_paths'] - , edges = data['new_edges'] - , offset = 0.0 // TODO: input for offset - //, data = [] - console.log("computed_paths", computed_paths) - - console.debug('----'); - console.debug('Topology has ' + computed_paths.length + ' sub-topologies.'); - - // for (var i = 0; i < computed_paths.length; i++ ) { - // var cpath = computed_paths[i], - // paths = $.map(edges[i], function(edge) { return edge.id; }), - // polylines = $.map(edges[i], function(edge) { return idToLayer(edge.id); }); - - // var topo = buildSubTopology(paths, - // polylines, - // cpath.from_pop.ll, - // cpath.to_pop.ll, - // offset); - // if (topo === null) break; - - // data.push(topo); - // console.debug('subtopo[' + i + '] : ' + JSON.stringify(topo)); - - // // Geometry for each sub-topology - // var group_layer = buildGeometryFromTopology(topo, idToLayer); - // // group_layer.from_pop = cpath.from_pop; - // // group_layer.to_pop = cpath.to_pop; - // // group_layer.step_idx = i; - // layer.addLayer(group_layer); - // } + // console.log("layer", layer) - var geojson = data.geojson - var layer = L.featureGroup(); - for (var i = 0; i < geojson.length; i++) { - var latlngs = [] - var lineString = geojson[i] - for (var j = 0; j < lineString.coordinates.length - 1; j++) { - var currentCoords = lineString.coordinates[j] - var nextCoords = lineString.coordinates[j + 1] - var newCoords = [ - { - lat: currentCoords[1], - lng: currentCoords[0], - }, - { - lat: nextCoords[1], - lng: nextCoords[0], - }, - ] - latlngs.push(newCoords) - } - console.log("latlngs", latlngs) - var group_layer = L.multiPolyline(latlngs); - group_layer.step_idx = i - layer.addLayer(group_layer); + var layer_test = L.geoJson(data.geojson) + for (var i = 0; i < layer_test.__layerArray.length; i++) { + l = layer_test.__layerArray[i] + l.step_idx = i } - - //console.log(pathLayer) - console.debug('----'); + console.log("layer_test", layer_test) return { - layer: layer, + layer: layer_test, serialized: null //serialized: data }; + + // return { + // layer: layer, + // serialized: null + // // serialized: data + // }; } return { diff --git a/geotrek/core/views.py b/geotrek/core/views.py index 7240308715..a2f6b461b8 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -6,7 +6,7 @@ from django.contrib import messages from django.contrib.auth.decorators import permission_required from django.contrib.gis.db.models.functions import Transform -from django.contrib.gis.geos import Point, LineString, MultiLineString +from django.contrib.gis.geos import Point, LineString, MultiLineString, GeometryCollection from django.core.cache import caches from django.db import connection from django.db.models import Sum, Prefetch @@ -824,18 +824,15 @@ def post(self, request): line_strings = self.compute_list_of_paths() - # merged_line_string = self.merge_line_strings(line_strings) - # merged_line_string.transform(settings.API_SRID) - # geojson = json.loads(merged_line_string.geojson) + multi_line_string = GeometryCollection(line_strings, srid=settings.SRID) + multi_line_string.transform(settings.API_SRID) + geojson = json.loads(multi_line_string.geojson) + return JsonResponse(geojson) - # multi_line_string = MultiLineString(line_strings, srid=settings.SRID) - # multi_line_string.transform(settings.API_SRID) - # geojson = json.loads(multi_line_string.geojson) + # geojsons = [] + # for ls in line_strings: + # ls.transform(settings.API_SRID) + # geojson = json.loads(ls.geojson) + # geojsons.append(geojson) - geojsons = [] - for ls in line_strings: - ls.transform(settings.API_SRID) - geojson = json.loads(ls.geojson) - geojsons.append(geojson) - - return JsonResponse(geojsons, safe=False) + # return JsonResponse(geojsons, safe=False) From 7b8a371d069fdc6a15075ef4bd75c79bcb4e5a4e Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Fri, 26 Apr 2024 13:43:49 +0200 Subject: [PATCH 026/213] Fixing the organization of layers in the displayed path --- geotrek/core/static/core/topology_helper.js | 48 ++++----------------- geotrek/core/views.py | 8 ---- 2 files changed, 8 insertions(+), 48 deletions(-) diff --git a/geotrek/core/static/core/topology_helper.js b/geotrek/core/static/core/topology_helper.js index 3bede1a60e..294e25739d 100644 --- a/geotrek/core/static/core/topology_helper.js +++ b/geotrek/core/static/core/topology_helper.js @@ -260,50 +260,18 @@ Geotrek.TopologyHelper = (function() { */ function buildTopologyFromComputedPath(idToLayer, data) { - // var geojson = data.geojson - // var layer = L.featureGroup(); - // for (var i = 0; i < geojson.length; i++) { - // var latlngs = [] - // var lineString = geojson[i] - // for (var j = 0; j < lineString.coordinates.length - 1; j++) { - // var currentCoords = lineString.coordinates[j] - // var nextCoords = lineString.coordinates[j + 1] - // var newCoords = [ - // { - // lat: currentCoords[1], - // lng: currentCoords[0], - // }, - // { - // lat: nextCoords[1], - // lng: nextCoords[0], - // }, - // ] - // latlngs.push(newCoords) - // } - // var group_layer = L.multiPolyline(latlngs); - // group_layer.step_idx = i - // layer.addLayer(group_layer); - // } - // console.log("layer", layer) - - var layer_test = L.geoJson(data.geojson) - for (var i = 0; i < layer_test.__layerArray.length; i++) { - l = layer_test.__layerArray[i] - l.step_idx = i - } - console.log("layer_test", layer_test) + var layer = L.featureGroup(); + data.geojson.geometries.forEach((geom, i) => { + var sub_layer = L.geoJson(geom); + sub_layer.step_idx = i + layer.addLayer(sub_layer); + }) return { - layer: layer_test, + layer: layer, serialized: null - //serialized: data + // serialized: data }; - - // return { - // layer: layer, - // serialized: null - // // serialized: data - // }; } return { diff --git a/geotrek/core/views.py b/geotrek/core/views.py index a2f6b461b8..125a237923 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -828,11 +828,3 @@ def post(self, request): multi_line_string.transform(settings.API_SRID) geojson = json.loads(multi_line_string.geojson) return JsonResponse(geojson) - - # geojsons = [] - # for ls in line_strings: - # ls.transform(settings.API_SRID) - # geojson = json.loads(ls.geojson) - # geojsons.append(geojson) - - # return JsonResponse(geojsons, safe=False) From 89d842c86387864614f350bf44edc5a558a2590b Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Fri, 26 Apr 2024 15:52:47 +0200 Subject: [PATCH 027/213] Removing old code related to the graph --- geotrek/core/static/core/dijkstra.js | 250 ----------------- .../static/core/geotrek.forms.topology.js | 21 -- geotrek/core/static/core/multipath.js | 103 +------ geotrek/core/static/core/topology_helper.js | 254 +----------------- 4 files changed, 4 insertions(+), 624 deletions(-) delete mode 100644 geotrek/core/static/core/dijkstra.js diff --git a/geotrek/core/static/core/dijkstra.js b/geotrek/core/static/core/dijkstra.js deleted file mode 100644 index 1578988f01..0000000000 --- a/geotrek/core/static/core/dijkstra.js +++ /dev/null @@ -1,250 +0,0 @@ -var Geotrek = Geotrek || {}; - -Geotrek.Dijkstra = (function() { - - // TODO: doc - function get_shortest_path_from_graph(graph, from_ids, to_ids, exclude_from_to) { - // coerce int to string - from_ids = $.map(from_ids, function(k) { return '' + k; }); - to_ids = $.map(to_ids, function(k) { return '' + k; }); - - var graph_nodes = graph.nodes; - var graph_edges = graph.edges; - - function getPairWeightNode(node_id) { - var l = []; - $.each(graph_nodes[node_id], function(node_dest_id, edge_id) { - // Warning - weight is in fact edge.length in our data - l.push({'node_id': node_dest_id, 'weight': graph_edges[edge_id].length}); - }); - return l; - } - - function is_source(node_id) { - return (from_ids.indexOf(node_id) != -1) - } - function is_destination(node_id) { - return (to_ids.indexOf(node_id) != -1) - } - - var djk = {}; - - // weight is smallest so far: take it whatever happens - from_ids.forEach(function(node_id) { - djk[node_id] = {'prev': null, 'node': node_id, 'weight': 0, 'visited': false}; - }); - - // return the ID of an unvisited node that has the less weight (less djk weight) - // TODO: performance -> shoud not contain visited node, should be sorted by weight - function djk_get_next_id () { - var nodes_id = Object.keys(djk); - var mini_weight = Number.MAX_VALUE; - var mini_id = null; - var node_djk = null; - var weight = null; - - for (var k = 0; k < nodes_id.length; k++) { - var node_id = nodes_id[k]; - node_djk = djk[node_id]; - weight = node_djk.weight; - - // if already visited - skip - if (node_djk.visited === true) - continue; - - // Weight can't get lower - take it - if (weight == 0) - return node_id; - - // Otherwise try to find the minimum - if (weight < mini_weight) { - mini_id = node_id; - mini_weight = weight; - } - } - return mini_id; - } - - - var djk_current_node, current_node_id; - - while (true) { - // Get the next node to visit - djk_current_node = djk_get_next_id(); - - // Last node exhausted - we didn't find a path - if (djk_current_node === null) - return null; - - // The node exist - var current_djk_node = djk[djk_current_node]; - // Mark as visited (won't be chosen) - current_djk_node.visited = true; - // we could del it out of djk - - current_node_id = current_djk_node.node; - - // Last point - if (is_destination(current_node_id)) - break; - - // refactor to get next - var pairs_weight_node = getPairWeightNode(current_node_id); - - - // if current_djk_node.weight > ... BREAK - for (var i = 0; i < pairs_weight_node.length; i++) { - var edge_weight = pairs_weight_node[i].weight; - var next_node_id = pairs_weight_node[i].node_id; - - var next_weight = current_djk_node.weight + edge_weight; - - var djk_next_node = djk[next_node_id]; - - // push new node or update it - if (djk_next_node) { - // update node ? - if (djk_next_node.visited === true) - continue; - - // If its weight is inferior, this node has a better previous edge already - // Do not update it - if (djk_next_node.weight < next_weight) - continue; - - djk_next_node.weight = next_weight; - djk_next_node.prev = current_djk_node; - - } else { - // push node - djk[next_node_id] = { - 'prev': current_djk_node - , 'node': next_node_id - , 'weight': next_weight - , 'visited': false - }; - - } - } - }; - - - var path = []; - // Extract path - // current_djk_node is the destination - var final_weight = current_djk_node.weight; - var tmp = current_djk_node; - while (!is_source(tmp.node)) { - path.push(tmp.node); - tmp = tmp.prev; - } - - - if (exclude_from_to) { - path.shift(); // remove last node - } else { - path.push(tmp.node); // push first node - } - - // miss first and last step - path.reverse(); - - var i, j, full_path = []; - for (i = 0; i < path.length - 1; i++) { - var node_1 = path[i], node_2 = path[i+1]; - var edge = graph_edges[graph_nodes[node_1][node_2]]; - - // start end and edge are just ids - full_path.push({ - 'start': node_1, - 'end': node_2, - 'edge': edge, - 'weight': edge.length - }); - } - - return { - 'path': full_path - , 'weight': final_weight - }; - - }; - - return { - 'get_shortest_path_from_graph': get_shortest_path_from_graph - }; -})(); - - - -// Computed_paths: -// -// Returns: -// Array of { -// path : Array of { start: Node_id, end: Node_id, edge: Edge, weight: Int (edge.length) } -// weight: Int -// } -// -Geotrek.shortestPath = (function() { - - function computePaths(graph, steps) { - /* - * Returns list of paths, and null if not found. - */ - var paths = []; - for (var j = 0; j < steps.length - 1; j++) { - var path = computeTwoStepsPath(graph, steps[j], steps[j + 1]); - if (!path) - return null; - - path.from_pop = steps[j]; - path.to_pop = steps[j+1]; - paths.push(path); - } - return paths; - } - - function computeTwoStepsPath(graph, from_pop, to_pop) { - - // alter graph - var from_pop_opt = from_pop.addToGraph(graph) - , to_pop_opt = to_pop.addToGraph(graph); - - var from_nodes = [ from_pop_opt.new_node_id ] - , to_nodes = [ to_pop_opt.new_node_id ]; - - // weighted_path: { - // path : Array of { start: Node_id, end: Node_id, edge: Edge, weight: Int (edge.length) } - // weight: Int - // } - var weighted_path = Geotrek.Dijkstra.get_shortest_path_from_graph(graph, from_nodes, to_nodes); - - // restore graph - from_pop_opt.rmFromGraph(); - to_pop_opt.rmFromGraph(); - - if(!weighted_path) - return null; - - // Some path component may use an edge that does not belong to the graph - // (a transient edge that was created from a transient point - a marker). - // In this case, the path component gets a new `real_edge' attribute - // which is the edge that the virtual edge is part of. - var pops_opt = [ from_pop_opt, to_pop_opt ]; - $.each(weighted_path.path, function(i, path_component) { - var edge_id = path_component.edge.id; - // Those PointOnPolylines knows the virtual edge and the initial one - for (var i = 0; i < pops_opt.length; i++) { - var pop_opt = pops_opt[i], - edge = pop_opt.new_edges[edge_id]; - if (edge !== undefined) { - path_component.real_edge = pop_opt.initial_edge; - break; - } - } - }); - return weighted_path; - } - - return computePaths; -})(); diff --git a/geotrek/core/static/core/geotrek.forms.topology.js b/geotrek/core/static/core/geotrek.forms.topology.js index 2150efdc01..f60262c8f9 100644 --- a/geotrek/core/static/core/geotrek.forms.topology.js +++ b/geotrek/core/static/core/geotrek.forms.topology.js @@ -123,27 +123,6 @@ MapEntity.GeometryField.TopologyField = MapEntity.GeometryField.extend({ this.load(); return; } - - // Path layer is ready, load graph ! - this._pathsLayer.fire('data:loading'); - var url = window.SETTINGS.urls.path_graph; - $.getJSON(url, this._onGraphLoaded.bind(this)) - .error(graphError.bind(this)); - - function graphError(jqXHR, textStatus, errorThrown) { - this._pathsLayer.fire('data:loaded'); - $(this._map._container).addClass('map-error'); - console.error("Could not load url '" + window.SETTINGS.urls.path_graph + "': " + textStatus); - console.error(errorThrown); - } - }, - - _onGraphLoaded: function (graph) { - // Load graph - this._lineControl.setGraph(graph); - this.load(); - // Stop spinning ! - this._pathsLayer.fire('data:loaded'); }, load: function () { diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index e778c8979b..6144105743 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -173,14 +173,6 @@ L.Control.LineTopology = L.Control.extend({ this.handler = new L.Handler.MultiPath(map, guidesLayer, options); }, - setGraph: function (graph) { - /** - * Set the Dikjstra graph - */ - this.handler.setGraph(graph); - this.activable(true); - }, - onAdd: function (map) { this._container = L.DomUtil.create('div', 'leaflet-draw leaflet-control leaflet-bar leaflet-control-zoom'); var link = L.DomUtil.create('a', 'leaflet-control-zoom-out linetopology-control', this._container); @@ -191,7 +183,7 @@ L.Control.LineTopology = L.Control.extend({ .addListener(link, 'click', L.DomEvent.preventDefault) .addListener(link, 'click', this.toggle, this); - // Control is not activable until paths and graph are loaded + // Control is not activable until paths are loaded this.activable(false); return this._container; @@ -242,8 +234,6 @@ L.Handler.MultiPath = L.Handler.extend({ this._guidesLayer = guidesLayer; this.options = options; - this.graph = null; - // markers this.markersFactory = this.getMarkers(); @@ -294,9 +284,6 @@ L.Handler.MultiPath = L.Handler.extend({ this.on('computed_paths', this.onComputedPaths, this); }, - setGraph: function (graph) { - this.graph = graph; - }, setState: function(state, autocompute) { autocompute = autocompute === undefined ? true : autocompute; @@ -460,9 +447,6 @@ L.Handler.MultiPath = L.Handler.extend({ }, canCompute: function() { - if (!this.graph) - return false; - if (this.steps.length < 2) return false; @@ -506,8 +490,6 @@ L.Handler.MultiPath = L.Handler.extend({ if (this.canCompute()) { - var computed_paths = Geotrek.shortestPath(this.graph, this.steps); - var sent_steps = [] this.steps.forEach((step) => { var sent_step = { @@ -532,7 +514,7 @@ L.Handler.MultiPath = L.Handler.extend({ console.log('response:', data) var test_computed_path = { - 'computed_paths': computed_paths, + 'computed_paths': null, 'geojson': data, } @@ -587,20 +569,10 @@ L.Handler.MultiPath = L.Handler.extend({ }, _onComputedPaths: function(new_computed_paths) { - // var self = this; - // var old_computed_paths = this.computed_paths; - //this.computed_paths = new_computed_paths['computed_paths']; - - // compute and store all edges of the new paths (usefull for further computation) - this.all_edges = this._extractAllEdges(new_computed_paths['computed_paths']); this.fire('computed_paths', { - 'computed_paths': new_computed_paths['computed_paths'], - 'new_edges': this.all_edges, 'geojson': new_computed_paths['geojson'], - // 'old': old_computed_paths, - // 'marker_source': this.marker_source, - // 'marker_dest': this.marker_dest + }); }, @@ -815,12 +787,7 @@ L.Handler.MultiPath = L.Handler.extend({ onComputedPaths: function(data) { var self = this; var topology = Geotrek.TopologyHelper.buildTopologyFromComputedPath(this.idToLayer, data); - this.showPathGeom(topology.layer); - - // Hard-coded polyline - //L.geoJson(data.geojson, {color:"blue", weight: 10}).addTo(self.map); - this.fire('computed_topology', {topology:topology.serialized}); // ## ONCE ## @@ -840,7 +807,6 @@ L.Handler.MultiPath = L.Handler.extend({ dragTimer = date; - for (var i = 0; i < self.steps.length; i++) { // Compare point rather than ll var marker_ll = self.steps[i].marker.getLatLng(); @@ -951,66 +917,3 @@ Geotrek.PointOnPolyline.prototype.toggleActivate = function(activate) { Geotrek.PointOnPolyline.prototype.isValid = function(graph) { return (this.ll && this.polyline); }; - -// Alter the graph: adding two edges and one node (the polyline gets break in two parts by the point) -// The polyline MUST be an edge of the graph. -Geotrek.PointOnPolyline.prototype.addToGraph = function(graph) { - if (! this.isValid()) - return null; - - var self = this; - - // Getting the corresponding edge and its nodes - var edge = graph.edges[this.polyline.properties.id] - , first_node_id = edge.nodes_id[0] - , last_node_id = edge.nodes_id[1]; - - // To which nodes dist start_point/end_point corresponds ? - // The edge.nodes_id are ordered, it corresponds to polylines: coords[0] and coords[coords.length - 1] - var dist_start_point = this.percent_distance * this.path_length - , dist_end_point = (1 - this.percent_distance) * this.path_length - ; - - var new_node_id = Geotrek.getNextId(); - var edge1 = {'id': Geotrek.getNextId(), 'length': dist_start_point, 'nodes_id': [first_node_id, new_node_id] }; - var edge2 = {'id': Geotrek.getNextId(), 'length': dist_end_point, 'nodes_id': [new_node_id, last_node_id]}; - - var first_node = {}, last_node = {}, new_node = {}; - first_node[new_node_id] = new_node[first_node_id] = edge1.id; - last_node[new_node_id] = new_node[last_node_id] = edge2.id; - - // - var new_edges = {}; - new_edges[edge1.id] = graph.edges[edge1.id] = edge1; - new_edges[edge2.id] = graph.edges[edge2.id] = edge2; - - graph.nodes[new_node_id] = new_node; - $.extend(graph.nodes[first_node_id], first_node); - $.extend(graph.nodes[last_node_id], last_node); - // - - // console.log('Temp graph.nodes[new_node_id]', graph.nodes[new_node_id]) - // console.log('Temp graph.nodes', graph.nodes) - // console.log('Temp graph', graph) - - function rmFromGraph() { - delete graph.edges[edge1.id]; - delete graph.edges[edge2.id]; - - delete graph.nodes[new_node_id]; - delete graph.nodes[first_node_id][new_node_id]; - delete graph.nodes[last_node_id][new_node_id]; - } - - - return { - self: self, - new_node_id: new_node_id, - new_edges: new_edges, - dist_start_point: dist_start_point, - dist_end_point: dist_end_point, - initial_edge: edge, - rmFromGraph: rmFromGraph - }; -}; - diff --git a/geotrek/core/static/core/topology_helper.js b/geotrek/core/static/core/topology_helper.js index 294e25739d..47a585cc81 100644 --- a/geotrek/core/static/core/topology_helper.js +++ b/geotrek/core/static/core/topology_helper.js @@ -2,258 +2,6 @@ var Geotrek = Geotrek || {}; Geotrek.TopologyHelper = (function() { - /** - * This static function takes a list of Dijkstra results, and returns - * a serialized topology, as expected by form widget, as well as a - * multiline geometry for highlight the result. - */ - function buildSubTopology(paths, polylines, ll_start, ll_end, offset) { - var polyline_start = polylines[0] - , polyline_end = polylines[polylines.length - 1] - , single_path = paths.length == 1 - , cleanup = true - , positions = {}; - - if (!polyline_start || !polyline_end) { - console.error("Could not compute distances without polylines."); - return null; // TODO: clean-up before give-up ? - } - - var pk_start = L.GeometryUtil.locateOnLine(polyline_start._map, polyline_start, ll_start), - pk_end = L.GeometryUtil.locateOnLine(polyline_end._map, polyline_end, ll_end); - console.debug('Start on layer ' + polyline_start.properties.id + ' ' + pk_start + ' ' + ll_start.toString()); - console.debug('End on layer ' + polyline_end.properties.id + ' ' + pk_end + ' ' + ll_end.toString()); - - if (single_path) { - var path_pk = paths[0], - lls = polyline_start.getLatLngs(); - - single_path_loop = lls[0].equals(lls[lls.length-1]); - if (single_path_loop) - cleanup = false; - - if (single_path_loop && Math.abs(pk_end - pk_start) > 0.5) { - /* - * A - * //=|---+ - * +// | It is shorter to go through - * \\ | extremeties than the whole loop - * \\=|---+ - * B - */ - if (pk_end - pk_start > 0.5) { - paths = [path_pk, path_pk]; - positions[0] = [pk_start, 0.0]; - positions[1] = [1.0, pk_end]; - } - else if (pk_end - pk_start < -0.5) { - paths = [path_pk, path_pk]; - positions[0] = [pk_end, 0.0]; - positions[1] = [1.0, pk_start]; - } - } - else { - /* A B - * +----|=====|----> - * - * B A - * +----|=====|----> - */ - paths = $.unique(paths); - positions[0] = [pk_start, pk_end]; - } - } - else if (paths.length == 3 && polyline_start == polyline_end) { - var start_lls = polylines[0].getLatLngs() - , mid_lls = polylines[1].getLatLngs(); - cleanup = false; - if (pk_start < pk_end) { - positions[0] = [pk_start, 0.0]; - if (start_lls[0].equals(mid_lls[0])) - positions[1] = [0.0, 1.0]; - else - positions[1] = [1.0, 0.0]; - positions[2] = [1.0, pk_end]; - } - else { - positions[0] = [pk_start, 1.0]; - if (start_lls[0].equals(mid_lls[0])) - positions[1] = [1.0, 0.0]; - else - positions[1] = [0.0, 1.0]; - positions[2] = [0.0, pk_end]; - } - } - else { - /* - * Add first portion of line - */ - var start_lls = polyline_start.getLatLngs(), - first_end = start_lls[start_lls.length-1], - start_on_loop = start_lls[0].equals(first_end); - - if (L.GeometryUtil.startsAtExtremity(polyline_start, polylines[1])) { - var next_lls = polylines[1].getLatLngs(), - next_end = next_lls[next_lls.length-1], - share_end = first_end.equals(next_end), - two_paths_loop = first_end.equals(next_lls[0]); - if ((start_on_loop && pk_start > 0.5) || - (share_end && (pk_start + pk_end) >= 1) || - (two_paths_loop && (pk_start - pk_end) > 0)) { - /* - * A - * /--|===+ B - * +/ \\+==|--- - * \ / - * \-----+ - * - * A B - * +----|------><-------|---- - * - * +----|=====|><=======|---- - * - */ - positions[0] = [pk_start, 1.0]; - } - else { - /* - * A B - * <----|------++-------|---- - * - * <----|=====|++=======|---- - * - */ - positions[0] = [pk_start, 0.0]; - } - } else { - /* - * A B - * +----|------>+-------|---- - * - * +----|=====|>+=======|---- - * - */ - positions[0] = [pk_start, 1.0]; - } - - /* - * Add all intermediary lines - */ - for (var i=1; i 0.5) || - (share_end && (pk_start + pk_end) >= 1) || - (two_paths_loop && (pk_start - pk_end) <= 0)) { - /* - * B - * A //==|-+ - * ---|==+// | - * \ | - * \-----+ - * - * A B - * -----|------><-------|----+ - * - * -----|======>|+======>----> - */ - positions[polylines.length - 1] = [1.0, pk_end]; - } - else { - /* - * A B - * -----|------++-------|----> - * - * -----|======+|=======>----> - */ - positions[polylines.length - 1] = [0.0, pk_end]; - } - } else { - /* - * A B - * -----|------+<-------|----+ - * - * -----|=====|+<=======|----+ - */ - positions[polylines.length - 1] = [1.0, pk_end]; - } - } - - // Clean-up : - // We basically remove all points where position is [x,x] - // This can happen at extremity points... - - if (cleanup) { - var cleanpaths = [], - cleanpositions = {}; - for (var i=0; i < paths.length; i++) { - var path = paths[i]; - if (i in positions) { - if (positions[i][0] != positions[i][1] && cleanpaths.indexOf(path) == -1) { - cleanpaths.push(path); - cleanpositions[i] = positions[i]; - } - } - else { - cleanpaths.push(path); - } - } - paths = cleanpaths; - positions = cleanpositions; - } - - // Safety warning. - if (paths.length === 0) - console.error('Empty topology. Expect problems. (' + JSON.stringify({positions:positions, paths:paths}) + ')'); - - return { - offset: offset, // Float for offset - positions: positions, // Positions on paths - paths: paths // List of pks - }; - } - - /** - * @param topology {Object} with ``offset``, ``positions`` and ``paths`` as returned by buildSubTopology() - * @param idToLayer {function} callback to obtain layer from id - * @returns L.multiPolyline - */ - function buildGeometryFromTopology(topology, idToLayer) { - var latlngs = []; - for (var i=0; i < topology.paths.length; i++) { - var path = topology.paths[i], - positions = topology.positions[i], - polyline = idToLayer(path); - if (positions) { - latlngs.push(L.GeometryUtil.extract(polyline._map, polyline, positions[0], positions[1])); - } - else { - console.warn('Topology problem: ' + i + ' not in ' + JSON.stringify(topology.positions)); - } - } - return L.multiPolyline(latlngs); - } - /** * @param idToLayer : callback to obtain a layer object from a pk/id. * @param data : computed_path @@ -270,7 +18,7 @@ Geotrek.TopologyHelper = (function() { return { layer: layer, serialized: null - // serialized: data + // TODO serialized: data }; } From b9e43e3588e605edc099493abbdd6360f05aba54 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Fri, 26 Apr 2024 16:12:31 +0200 Subject: [PATCH 028/213] Removing old code --- geotrek/core/static/core/multipath.js | 95 ++++++--------------- geotrek/core/static/core/topology_helper.js | 28 ------ 2 files changed, 26 insertions(+), 97 deletions(-) delete mode 100644 geotrek/core/static/core/topology_helper.js diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index 6144105743..879b80a84a 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -281,10 +281,9 @@ L.Handler.MultiPath = L.Handler.extend({ init(); })(); - this.on('computed_paths', this.onComputedPaths, this); + this.on('fetched_route', this.onFetchedRoute, this); }, - setState: function(state, autocompute) { autocompute = autocompute === undefined ? true : autocompute; var self = this; @@ -322,8 +321,6 @@ L.Handler.MultiPath = L.Handler.extend({ // reset state this.steps = []; - //this.computed_paths = []; - this.all_edges = []; this.marker_source = this.marker_dest = null; }, @@ -413,7 +410,7 @@ L.Handler.MultiPath = L.Handler.extend({ this.steps.splice(idx, 0, pop); // Insert pop at position idx pop.events.on('valid', function() { - self.computePaths(); + self.fetchRoute(); }); return pop; @@ -434,7 +431,7 @@ L.Handler.MultiPath = L.Handler.extend({ function removeViaStep() { self.steps.splice(self.getStepIdx(pop), 1); self.map.removeLayer(marker); - self.computePaths(); + self.fetchRoute(); } function removeOnClick() { marker.on('click', removeViaStep); } @@ -446,7 +443,7 @@ L.Handler.MultiPath = L.Handler.extend({ pop.toggleActivate(); }, - canCompute: function() { + canFetchRoute: function() { if (this.steps.length < 2) return false; @@ -486,9 +483,9 @@ L.Handler.MultiPath = L.Handler.extend({ return cookieValue; }, - computePaths: function() { + fetchRoute: function() { - if (this.canCompute()) { + if (this.canFetchRoute()) { var sent_steps = [] this.steps.forEach((step) => { @@ -512,70 +509,15 @@ L.Handler.MultiPath = L.Handler.extend({ .then(response => response.json()) .then(data => { console.log('response:', data) - - var test_computed_path = { - 'computed_paths': null, - 'geojson': data, - } - - console.log("geojson", data/* , "trek", data.trek */) - // var mainmap = window.maps[0] - // mainmap.addLayers - - this._onComputedPaths(test_computed_path); + var route = {'geojson': data} + this.fire('fetched_route', route); }) // .catch(e => { - // console.log("computePaths", e) + // console.log("fetchRoute", e) // }) } }, - - // Extract the complete edges list from the first to the last one - _eachInnerComputedPathsEdges: function(computed_paths, f) { - if (computed_paths) { - computed_paths.forEach(function(cpath) { - cpath.path.forEach(function(path_component) { - f(path_component.edge); - }); - }); - } - }, - - // Extract the complete edges list from the first to the last one - _extractAllEdges: function(computed_paths) { - if (! computed_paths) - return []; - - var edges = $.map(computed_paths, function(cpath) { - - var dups = $.map(cpath.path, function(path_component) { - return path_component.real_edge || path_component.edge; - }); - - // Remove adjacent duplicates - var dedup = []; - for (var i=0; i { + var sub_layer = L.geoJson(geom); + sub_layer.step_idx = i + layer.addLayer(sub_layer); + }) + + return { + layer: layer, + serialized: null + // TODO serialized: data + }; + }, + + onFetchedRoute: function(data) { var self = this; - var topology = Geotrek.TopologyHelper.buildTopologyFromComputedPath(this.idToLayer, data); + var topology = this.buildRouteLayers(data); this.showPathGeom(topology.layer); this.fire('computed_topology', {topology:topology.serialized}); diff --git a/geotrek/core/static/core/topology_helper.js b/geotrek/core/static/core/topology_helper.js deleted file mode 100644 index 47a585cc81..0000000000 --- a/geotrek/core/static/core/topology_helper.js +++ /dev/null @@ -1,28 +0,0 @@ -var Geotrek = Geotrek || {}; - -Geotrek.TopologyHelper = (function() { - - /** - * @param idToLayer : callback to obtain a layer object from a pk/id. - * @param data : computed_path - */ - function buildTopologyFromComputedPath(idToLayer, data) { - - var layer = L.featureGroup(); - data.geojson.geometries.forEach((geom, i) => { - var sub_layer = L.geoJson(geom); - sub_layer.step_idx = i - layer.addLayer(sub_layer); - }) - - return { - layer: layer, - serialized: null - // TODO serialized: data - }; - } - - return { - buildTopologyFromComputedPath: buildTopologyFromComputedPath - }; -})(); From 6a877a705bdf02f26f60c5c077e9f4eb8e0e7e8f Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Fri, 26 Apr 2024 16:17:19 +0200 Subject: [PATCH 029/213] Removing old code --- geotrek/core/static/core/multipath.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index 879b80a84a..55c9e63896 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -244,7 +244,6 @@ L.Handler.MultiPath = L.Handler.extend({ return guidesLayer.getLayer(id); }; - /* * Draggable via steps * @@ -258,7 +257,6 @@ L.Handler.MultiPath = L.Handler.extend({ }, this); // Draggable marker initialisation and step creation - var draggable_marker = null; var self = this; (function() { function dragstart(e) { @@ -737,7 +735,7 @@ L.Handler.MultiPath = L.Handler.extend({ return { layer: layer, serialized: null - // TODO serialized: data + // TODO: set serialized to something }; }, @@ -809,14 +807,6 @@ L.Handler.MultiPath = L.Handler.extend({ }); - -Geotrek.getNextId = (function() { - var next_id = 90000000; - return function() { - return next_id++; - }; -})(); - // pol: point on polyline Geotrek.PointOnPolyline = function (marker) { this.marker = marker; From 3ddb0778fbcfbdadf1c98da698aa55cddfd2885f Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Mon, 29 Apr 2024 11:37:18 +0200 Subject: [PATCH 030/213] Making the TrekGeometry post method into a PathViewSet action --- geotrek/core/graph.py | 388 ++++++++++++++++- geotrek/core/static/core/multipath.js | 7 +- .../core/core_extrabody_fragment.html | 2 +- geotrek/core/urls.py | 3 +- geotrek/core/views.py | 401 +----------------- 5 files changed, 404 insertions(+), 397 deletions(-) diff --git a/geotrek/core/graph.py b/geotrek/core/graph.py index ea27af8e33..d1040a4cb6 100644 --- a/geotrek/core/graph.py +++ b/geotrek/core/graph.py @@ -1,5 +1,17 @@ import math from collections import defaultdict +import json + +from django.db import connection +from django.conf import settings +from django.contrib.gis.geos import Point, LineString, MultiLineString, GeometryCollection + +import numpy as np +from scipy.sparse.csgraph import dijkstra +from scipy.sparse import csr_matrix + +from geotrek.common.utils import sqlfunction +from .models import Path def path_modifier(path): @@ -29,7 +41,7 @@ def graph_edges_nodes_of_qs(qs): } - coord_point are tuple of float + coord_poPathRouterint are tuple of float """ key_modifier = get_key_optimizer() @@ -55,3 +67,377 @@ def graph_edges_nodes_of_qs(qs): 'edges': dict(edges), 'nodes': dict(nodes), } + + +class PathRouter: + def __init__(self): + # To generate IDs for temporary nodes and edges: + self.id_count = 90000000 + + graph = graph_edges_nodes_of_qs(Path.objects.exclude(draft=True)) + self.nodes = graph['nodes'] + self.edges = graph['edges'] + + def get_route(self, steps): + self.steps = steps + line_strings = self.compute_list_of_paths() + + multi_line_string = GeometryCollection(line_strings, srid=settings.SRID) + multi_line_string.transform(settings.API_SRID) + geojson = json.loads(multi_line_string.geojson) + + return geojson + + def get_cs_graph(self): + + def get_edge_weight(edge_id): + edge = self.edges.get(edge_id) + if edge is None: + return None + return edge.get('length') + + array = [] + for key1, value1 in self.nodes.items(): + row = [] + for key2, value2 in self.nodes.items(): + if key1 == key2: + # If it's the same node, the weight is 0 + row.append(0) + elif key2 in value1.keys(): + # If the nodes are linked by a single edge, the weight is + # the edge length + edge_id = self.get_edge_id_by_nodes(value1, value2) + edge_weight = get_edge_weight(edge_id) + if edge_weight is not None: + row.append(edge_weight) + else: + # If the nodes are not directly linked, the weight is 0 + row.append(0) + array.append(row) + + return np.array(array) + + def get_edge_id_by_nodes(self, node1, node2): + for value in node1.values(): + if value in node2.values(): + return value + return None + + def compute_list_of_paths(self): + all_line_strings = [] + # Compute the shortest path for each pair of adjacent steps + for i in range(len(self.steps) - 1): + from_step = self.steps[i] + to_step = self.steps[i + 1] + line_strings = self.compute_two_steps_line_strings(from_step, to_step) + merged_line_string = self.merge_line_strings(line_strings) + all_line_strings.append(merged_line_string) + return all_line_strings + + def add_steps_to_graph(self, from_step, to_step): + + def create_point_from_coords(lat, lng): + point = Point(lng, lat, srid=settings.API_SRID) + point.transform(settings.SRID) + return point + + def fill_point_closest_path_info(point): + # Path and edge info + point['base_path'] = base_path = Path.closest(point['geom']) + point['edge_id'] = edge_id = base_path.pk + point['edge'] = edge = self.edges[edge_id] + + # Nodes linked by this edge + point['first_node_id'] = edge.get('nodes_id')[0] + point['last_node_id'] = edge.get('nodes_id')[1] + + # Percentage of the Path this point is on + base_path_str = f"'{base_path.geom}'" + point_str = f"'{point['geom'].ewkt}'" + percent_distance = sqlfunction('SELECT ST_LineLocatePoint', + base_path_str, point_str)[0] + point['percent_distance'] = percent_distance + + # Create a Point corresponding to each step + from_point, to_point = {}, {} + from_point['geom'] = create_point_from_coords(from_step['lat'], from_step['lng']) + to_point['geom'] = create_point_from_coords(to_step['lat'], to_step['lng']) + + # Get the Path (and corresponding graph edge info) each Point is on + fill_point_closest_path_info(from_point) + fill_point_closest_path_info(to_point) + + # If the steps are on the same edge, it's divided into 3 new edges + if from_point['edge_id'] == to_point['edge_id']: + from_node_info, to_node_info = self.split_edge_in_three(from_point, + to_point) + # If they are on different edges, both are divided into two new edges + else: + from_node_info = self.split_edge_in_two(from_point) + to_node_info = self.split_edge_in_two(to_point) + return (from_node_info, to_node_info) + + def compute_two_steps_line_strings(self, from_step, to_step): + from_node_info, to_node_info = self.add_steps_to_graph(from_step, to_step) + + shortest_path = self.get_shortest_path(from_node_info['node_id'], + to_node_info['node_id']) + line_strings = self.node_list_to_line_strings(shortest_path, + from_node_info, to_node_info) + + # Restore the graph (remove the steps) + self.remove_step_from_graph(from_node_info) + self.remove_step_from_graph(to_node_info) + return line_strings + + def node_list_to_line_strings(self, node_list, from_node_info, to_node_info): + line_strings = [] + # Get a LineString for each pair of adjacent nodes in the path + for i in range(len(node_list) - 1): + # Get the id of the edge corresponding to these nodes + node1 = self.nodes[node_list[i]] + node2 = self.nodes[node_list[i + 1]] + edge_id = self.get_edge_id_by_nodes(node1, node2) + + # If this pair of nodes requires to go backwards relative to a + # Path direction (i.e. the edge 2nd node is the 1st of this pair) + backwards = False + if self.edges[edge_id]['nodes_id'][1] == node_list[i]: + backwards = True + + # If it's the first or last edge of this subpath (it can be both!), + # then the edge is temporary (i.e. created because of a step) + if i == 0 or i == len(node_list) - 2: + # Start and end percentages of the line substring to be created + start_fraction = 0 + end_fraction = 1 + if backwards: + start_fraction, end_fraction = end_fraction, start_fraction + + if i == 0: + original_path = Path.objects.get(pk=from_node_info['original_egde_id']) + start_fraction = from_node_info['percent_of_edge'] + if i == len(node_list) - 2: + original_path = Path.objects.get(pk=to_node_info['original_egde_id']) + end_fraction = to_node_info['percent_of_edge'] + + line_substring = self.create_line_substring( + original_path.geom, + start_fraction, + end_fraction + ) + line_strings.append(line_substring) + + # If it's a real edge (i.e. it corresponds to a whole path), + # we use its LineString + else: + path = Path.objects.get(pk=edge_id) + line_strings.append(path.geom) + + return line_strings + + def create_line_substring(self, geometry, start_fraction, end_fraction): + sql = """ + SELECT ST_AsText(ST_SmartLineSubstring('{}'::geometry, {}, {})) + """.format(geometry, start_fraction, end_fraction) + + cursor = connection.cursor() + cursor.execute(sql) + result = cursor.fetchone()[0] + + # Convert the string into an array of arrays of floats + coords_str = result.split('(')[1].split(')')[0] + str_points_array = [elem.split(' ') for elem in coords_str.split(',')] + arr = [[float(nb) for nb in sub_array] for sub_array in str_points_array] + + line_substring = LineString(arr, srid=settings.SRID) + return line_substring + + def split_edge_in_two(self, point_info): + + # Get the length of the edges that will be created + path_length = point_info['base_path'].length + dist_to_start = path_length * point_info['percent_distance'] + dist_to_end = path_length * (1 - point_info['percent_distance']) + + # Create the new node and edges + new_node_id = self.generate_id() + edge1 = { + 'id': self.generate_id(), + 'length': dist_to_start, + 'nodes_id': [point_info['first_node_id'], new_node_id], + } + edge2 = { + 'id': self.generate_id(), + 'length': dist_to_end, + 'nodes_id': [new_node_id, point_info['last_node_id']], + } + first_node, last_node, new_node = {}, {}, {} + first_node[new_node_id] = new_node[point_info['first_node_id']] = edge1['id'] + last_node[new_node_id] = new_node[point_info['last_node_id']] = edge2['id'] + + # Add them to the graph + self.edges[edge1['id']] = edge1 + self.edges[edge2['id']] = edge2 + self.nodes[new_node_id] = new_node + self.extend_dict(self.nodes[point_info['first_node_id']], first_node) + self.extend_dict(self.nodes[point_info['last_node_id']], last_node) + + new_node_info = { + 'node_id': new_node_id, + 'new_edge1_id': edge1['id'], + 'new_edge2_id': edge2['id'], + 'prev_node_id': point_info['first_node_id'], + 'next_node_id': point_info['last_node_id'], + 'original_egde_id': point_info['edge_id'], + 'percent_of_edge': point_info['percent_distance'], + } + return new_node_info + + def split_edge_in_three(self, from_point, to_point): + # Get the length of the edges that will be created + path_length = from_point['base_path'].length + start_percent = from_point['percent_distance'] + end_percent = to_point['percent_distance'] + + # If we're going backwards relative to the Path direction + start_percent, end_percent = end_percent, start_percent + + dist_to_start = path_length * start_percent + dist_to_end = path_length * (1 - end_percent) + dist_middle = path_length - dist_to_start - dist_to_end + + # Create the new nodes and edges + new_node_id_1 = self.generate_id() + new_node_id_2 = self.generate_id() + edge1 = { + 'id': self.generate_id(), + 'length': dist_to_start, + 'nodes_id': [from_point['first_node_id'], new_node_id_1], + } + edge2 = { + 'id': self.generate_id(), + 'length': dist_middle, + 'nodes_id': [new_node_id_1, new_node_id_2], + } + edge3 = { + 'id': self.generate_id(), + 'length': dist_to_end, + 'nodes_id': [new_node_id_2, from_point['last_node_id']], + } + + # Link them together and to the existing nodes + first_node, last_node, new_node_1, new_node_2 = {}, {}, {}, {} + first_node[new_node_id_1] = new_node_1[from_point['first_node_id']] = edge1['id'] + new_node_1[new_node_id_2] = new_node_2[new_node_id_1] = edge2['id'] + new_node_2[from_point['last_node_id']] = last_node[new_node_id_2] = edge3['id'] + + # Add them to the graph + self.edges[edge1['id']] = edge1 + self.edges[edge2['id']] = edge2 + self.edges[edge3['id']] = edge3 + self.nodes[new_node_id_1] = new_node_1 + self.nodes[new_node_id_2] = new_node_2 + self.extend_dict(self.nodes[from_point['first_node_id']], first_node) + self.extend_dict(self.nodes[from_point['last_node_id']], last_node) + + new_node_info_1 = { + 'node_id': new_node_id_1, + 'new_edge1_id': edge1['id'], + 'new_edge2_id': edge2['id'], + 'prev_node_id': from_point['first_node_id'], + 'next_node_id': new_node_id_2, + 'original_egde_id': from_point['edge_id'], + 'percent_of_edge': from_point['percent_distance'], + } + new_node_info_2 = { + 'node_id': new_node_id_2, + 'new_edge1_id': edge2['id'], + 'prev_node_id': new_node_id_1, + 'next_node_id': from_point['last_node_id'], + 'new_edge2_id': edge3['id'], + 'original_egde_id': from_point['edge_id'], + 'percent_of_edge': to_point['percent_distance'], + } + return new_node_info_1, new_node_info_2 + + def remove_step_from_graph(self, node_info): + + # Remove the 2 new edges from the graph: + # They will have already been deleted if this is the 2nd step and + # both steps are on the same path + if self.edges.get(node_info['new_edge1_id']) is not None: + del self.edges[node_info['new_edge1_id']] + if self.edges.get(node_info['new_edge2_id']) is not None: + del self.edges[node_info['new_edge2_id']] + + # Get the 2 nodes this temporary node is linked to + prev_node = self.nodes.get(node_info['prev_node_id']) + next_node = self.nodes.get(node_info['next_node_id']) + + # Remove the new node from the graph + removed_node_id = node_info['node_id'] + del self.nodes[removed_node_id] + if prev_node is not None: + # It will have already been deleted if this is the 2nd step and + # both steps are on the same path + del prev_node[removed_node_id] + del next_node[removed_node_id] + + def extend_dict(self, dict, source): + for key, value in source.items(): + dict[key] = value + + def get_shortest_path(self, from_node_id, to_node_id): + cs_graph = self.get_cs_graph() + matrix = csr_matrix(cs_graph) + + # List of all nodes IDs -> to interprete dijkstra results + self.nodes_ids = list(self.nodes.keys()) + + def get_node_idx_per_id(node_id): + try: + return self.nodes_ids.index(node_id) + except ValueError: + return None + + def get_node_id_per_idx(node_idx): + if node_idx >= len(self.nodes_ids): + return None + return self.nodes_ids[node_idx] + + from_node_idx = get_node_idx_per_id(from_node_id) + to_node_idx = get_node_idx_per_id(to_node_id) + result = dijkstra(matrix, return_predecessors=True, indices=from_node_idx, + directed=False) + + # Retrace the path ID by ID, from end to start + predecessors = result[1] + current_node_id, current_node_idx = to_node_id, to_node_idx + path = [current_node_id] + while current_node_id != from_node_id: + current_node_idx = predecessors[current_node_idx] + current_node_id = get_node_id_per_idx(current_node_idx) + path.append(current_node_id) + + path.reverse() + return path + + def merge_line_strings(self, line_strings): + rounded_line_strings = [ + self.round_line_string_coordinates(ls) for ls in line_strings + ] + multi_line_string = MultiLineString(rounded_line_strings, srid=settings.SRID) + return multi_line_string.merged + + def round_line_string_coordinates(self, line_string): + # TODO: see which precision level is best + coords = line_string.coords + new_coords = [[round(nb, 4) for nb in pt_coord] for pt_coord in coords] + new_line_string = LineString(new_coords, srid=line_string.srid) + return new_line_string + + def generate_id(self): + new_id = self.id_count + self.id_count += 1 + return new_id diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index 55c9e63896..d9948876fe 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -465,7 +465,6 @@ L.Handler.MultiPath = L.Handler.extend({ return -1; }, - getCookie: function(name) { var cookieValue = null; if (document.cookie && document.cookie !== '') { @@ -494,11 +493,11 @@ L.Handler.MultiPath = L.Handler.extend({ sent_steps.push(sent_step) }) - fetch(window.SETTINGS.urls['trek_geometry'], { + fetch(window.SETTINGS.urls['route_geometry'], { method: 'POST', headers: { - "X-CSRFToken": this.getCookie('csrftoken'), - content_type: "application/json" + "X-CSRFToken": this.getCookie('csrftoken'), + 'Content-Type': 'application/json; charset=UTF-8', }, body: JSON.stringify({ steps: sent_steps, diff --git a/geotrek/core/templates/core/core_extrabody_fragment.html b/geotrek/core/templates/core/core_extrabody_fragment.html index a674f6e444..a5aad80fe7 100644 --- a/geotrek/core/templates/core/core_extrabody_fragment.html +++ b/geotrek/core/templates/core/core_extrabody_fragment.html @@ -7,7 +7,7 @@ window.SETTINGS.urls['path_layer'] = "{% url "core:path-drf-list" format="geojson" %}"; window.SETTINGS.urls['trail_layer'] = "{% url "core:trail-drf-list" format="geojson" %}"; window.SETTINGS.urls['path_graph'] = "{% url "core:path-drf-graph" %}"; - window.SETTINGS.urls['trek_geometry'] = "{% url "core:trek_geometry" %}"; + window.SETTINGS.urls['route_geometry'] = "{% url "core:path-drf-route-geometry" %}"; diff --git a/geotrek/core/urls.py b/geotrek/core/urls.py index 3cb59794b0..33c001e34a 100644 --- a/geotrek/core/urls.py +++ b/geotrek/core/urls.py @@ -7,7 +7,7 @@ from .models import Path, Trail from .views import ( PathGPXDetail, PathKMLDetail, TrailGPXDetail, TrailKMLDetail, - MultiplePathDelete, TrekGeometry + MultiplePathDelete ) register_converter(LangConverter, 'lang') @@ -24,7 +24,6 @@ name="trail_gpx_detail"), path('api//trails//trail_.kml', TrailKMLDetail.as_view(), name="trail_kml_detail"), - path('api/treks/geometry', TrekGeometry.as_view(), name="trek_geometry"), ] urlpatterns += registry.register(Path, AltimetryEntityOptions, menu=(settings.PATH_MODEL_ENABLED and settings.TREKKING_TOPOLOGY_ENABLED)) diff --git a/geotrek/core/views.py b/geotrek/core/views.py index 125a237923..fa5e363058 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -6,19 +6,16 @@ from django.contrib import messages from django.contrib.auth.decorators import permission_required from django.contrib.gis.db.models.functions import Transform -from django.contrib.gis.geos import Point, LineString, MultiLineString, GeometryCollection from django.core.cache import caches -from django.db import connection from django.db.models import Sum, Prefetch from django.http import HttpResponseRedirect -from django.http.response import HttpResponse, JsonResponse +from django.http.response import HttpResponse from django.shortcuts import redirect from django.urls import reverse from django.utils.decorators import method_decorator from django.utils.translation import gettext as _ from django.views.decorators.cache import cache_control from django.views.decorators.http import last_modified as cache_last_modified -from django.views import View from django.views.generic import TemplateView from django.views.generic.detail import BaseDetailView from mapentity.serializers import GPXSerializer @@ -28,14 +25,8 @@ from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer from rest_framework.response import Response - -import numpy as np -from scipy.sparse.csgraph import dijkstra -from scipy.sparse import csr_matrix - from geotrek.authent.decorators import same_structure_required from geotrek.common.functions import Length -from geotrek.common.utils import sqlfunction from geotrek.common.mixins.views import CustomColumnsMixin from geotrek.common.mixins.forms import FormsetMixin from geotrek.common.permissions import PublicOrReadPermMixin @@ -345,6 +336,17 @@ def merge_path(self, request, *args, **kwargs): return Response(response) + @action(methods=['POST'], detail=False, url_path="route-geometry", renderer_classes=[JSONRenderer]) + def route_geometry(self, request, *args, **kwargs): + try: + params = request.data + steps = params['steps'] + path_router = graph_lib.PathRouter() + response = path_router.get_route(steps) # TODO check params + except KeyError: + response = {'error': 'TODO'} # TODO + return Response(response) + class CertificationTrailMixin(FormsetMixin): context_name = 'certificationtrail_formset' @@ -449,382 +451,3 @@ def get_queryset(self): else: qs = qs.defer('geom', 'geom_3d') return qs - - -class TrekGeometry(View): - - def get_cs_graph(self): - - def get_edge_weight(edge_id): - edge = self.edges.get(edge_id) - if edge is None: - return None - return edge.get('length') - - array = [] - for key1, value1 in self.nodes.items(): - row = [] - for key2, value2 in self.nodes.items(): - if key1 == key2: - # If it's the same node, the weight is 0 - row.append(0) - elif key2 in value1.keys(): - # If the nodes are linked by a single edge, the weight is - # the edge length - edge_id = self.get_edge_id_by_nodes(value1, value2) - edge_weight = get_edge_weight(edge_id) - if edge_weight is not None: - row.append(edge_weight) - else: - # If the nodes are not directly linked, the weight is 0 - row.append(0) - array.append(row) - - return np.array(array) - - def get_edge_id_by_nodes(self, node1, node2): - for value in node1.values(): - if value in node2.values(): - return value - return None - - def compute_list_of_paths(self): - all_line_strings = [] - # Compute the shortest path for each pair of adjacent steps - for i in range(len(self.steps) - 1): - from_step = self.steps[i] - to_step = self.steps[i + 1] - line_strings = self.compute_two_steps_line_strings(from_step, to_step) - merged_line_string = self.merge_line_strings(line_strings) - all_line_strings.append(merged_line_string) - return all_line_strings - - def add_steps_to_graph(self, from_step, to_step): - - def create_point_from_coords(lat, lng): - point = Point(lng, lat, srid=settings.API_SRID) - point.transform(settings.SRID) - return point - - def fill_point_closest_path_info(point): - # Path and edge info - point['base_path'] = base_path = Path.closest(point['geom']) - point['edge_id'] = edge_id = base_path.pk - point['edge'] = edge = self.edges[edge_id] - - # Nodes linked by this edge - point['first_node_id'] = edge.get('nodes_id')[0] - point['last_node_id'] = edge.get('nodes_id')[1] - - # Percentage of the Path this point is on - base_path_str = f"'{base_path.geom}'" - point_str = f"'{point['geom'].ewkt}'" - percent_distance = sqlfunction('SELECT ST_LineLocatePoint', - base_path_str, point_str)[0] - point['percent_distance'] = percent_distance - - # Create a Point corresponding to each step - from_point, to_point = {}, {} - from_point['geom'] = create_point_from_coords(from_step['lat'], from_step['lng']) - to_point['geom'] = create_point_from_coords(to_step['lat'], to_step['lng']) - - # Get the Path (and corresponding graph edge info) each Point is on - fill_point_closest_path_info(from_point) - fill_point_closest_path_info(to_point) - - # If the steps are on the same edge, it's divided into 3 new edges - if from_point['edge_id'] == to_point['edge_id']: - from_node_info, to_node_info = self.split_edge_in_three(from_point, - to_point) - # If they are on different edges, both are divided into two new edges - else: - from_node_info = self.split_edge_in_two(from_point) - to_node_info = self.split_edge_in_two(to_point) - return (from_node_info, to_node_info) - - def compute_two_steps_line_strings(self, from_step, to_step): - from_node_info, to_node_info = self.add_steps_to_graph(from_step, to_step) - - shortest_path = self.get_shortest_path(from_node_info['node_id'], - to_node_info['node_id']) - line_strings = self.node_list_to_line_strings(shortest_path, - from_node_info, to_node_info) - - # Restore the graph (remove the steps) - self.remove_step_from_graph(from_node_info) - self.remove_step_from_graph(to_node_info) - return line_strings - - def node_list_to_line_strings(self, node_list, from_node_info, to_node_info): - line_strings = [] - # Get a LineString for each pair of adjacent nodes in the path - for i in range(len(node_list) - 1): - # Get the id of the edge corresponding to these nodes - node1 = self.nodes[node_list[i]] - node2 = self.nodes[node_list[i + 1]] - edge_id = self.get_edge_id_by_nodes(node1, node2) - - # If this pair of nodes requires to go backwards relative to a - # Path direction (i.e. the edge 2nd node is the 1st of this pair) - backwards = False - if self.edges[edge_id]['nodes_id'][1] == node_list[i]: - backwards = True - - # If it's the first or last edge of this subpath (it can be both!), - # then the edge is temporary (i.e. created because of a step) - if i == 0 or i == len(node_list) - 2: - # Start and end percentages of the line substring to be created - start_fraction = 0 - end_fraction = 1 - if backwards: - start_fraction, end_fraction = end_fraction, start_fraction - - if i == 0: - original_path = Path.objects.get(pk=from_node_info['original_egde_id']) - start_fraction = from_node_info['percent_of_edge'] - if i == len(node_list) - 2: - original_path = Path.objects.get(pk=to_node_info['original_egde_id']) - end_fraction = to_node_info['percent_of_edge'] - - line_substring = self.create_line_substring( - original_path.geom, - start_fraction, - end_fraction - ) - line_strings.append(line_substring) - - # If it's a real edge (i.e. it corresponds to a whole path), - # we use its LineString - else: - path = Path.objects.get(pk=edge_id) - line_strings.append(path.geom) - - return line_strings - - def create_line_substring(self, geometry, start_fraction, end_fraction): - sql = """ - SELECT ST_AsText(ST_SmartLineSubstring('{}'::geometry, {}, {})) - """.format(geometry, start_fraction, end_fraction) - - cursor = connection.cursor() - cursor.execute(sql) - result = cursor.fetchone()[0] - - # Convert the string into an array of arrays of floats - coords_str = result.split('(')[1].split(')')[0] - str_points_array = [elem.split(' ') for elem in coords_str.split(',')] - arr = [[float(nb) for nb in sub_array] for sub_array in str_points_array] - - line_substring = LineString(arr, srid=settings.SRID) - return line_substring - - def split_edge_in_two(self, point_info): - - # Get the length of the edges that will be created - path_length = point_info['base_path'].length - dist_to_start = path_length * point_info['percent_distance'] - dist_to_end = path_length * (1 - point_info['percent_distance']) - - # Create the new node and edges - new_node_id = self.generate_id() - edge1 = { - 'id': self.generate_id(), - 'length': dist_to_start, - 'nodes_id': [point_info['first_node_id'], new_node_id], - } - edge2 = { - 'id': self.generate_id(), - 'length': dist_to_end, - 'nodes_id': [new_node_id, point_info['last_node_id']], - } - first_node, last_node, new_node = {}, {}, {} - first_node[new_node_id] = new_node[point_info['first_node_id']] = edge1['id'] - last_node[new_node_id] = new_node[point_info['last_node_id']] = edge2['id'] - - # Add them to the graph - self.edges[edge1['id']] = edge1 - self.edges[edge2['id']] = edge2 - self.nodes[new_node_id] = new_node - self.extend_dict(self.nodes[point_info['first_node_id']], first_node) - self.extend_dict(self.nodes[point_info['last_node_id']], last_node) - - new_node_info = { - 'node_id': new_node_id, - 'new_edge1_id': edge1['id'], - 'new_edge2_id': edge2['id'], - 'prev_node_id': point_info['first_node_id'], - 'next_node_id': point_info['last_node_id'], - 'original_egde_id': point_info['edge_id'], - 'percent_of_edge': point_info['percent_distance'], - } - return new_node_info - - def split_edge_in_three(self, from_point, to_point): - # Get the length of the edges that will be created - path_length = from_point['base_path'].length - start_percent = from_point['percent_distance'] - end_percent = to_point['percent_distance'] - - # If we're going backwards relative to the Path direction - start_percent, end_percent = end_percent, start_percent - - dist_to_start = path_length * start_percent - dist_to_end = path_length * (1 - end_percent) - dist_middle = path_length - dist_to_start - dist_to_end - - # Create the new nodes and edges - new_node_id_1 = self.generate_id() - new_node_id_2 = self.generate_id() - edge1 = { - 'id': self.generate_id(), - 'length': dist_to_start, - 'nodes_id': [from_point['first_node_id'], new_node_id_1], - } - edge2 = { - 'id': self.generate_id(), - 'length': dist_middle, - 'nodes_id': [new_node_id_1, new_node_id_2], - } - edge3 = { - 'id': self.generate_id(), - 'length': dist_to_end, - 'nodes_id': [new_node_id_2, from_point['last_node_id']], - } - - # Link them together and to the existing nodes - first_node, last_node, new_node_1, new_node_2 = {}, {}, {}, {} - first_node[new_node_id_1] = new_node_1[from_point['first_node_id']] = edge1['id'] - new_node_1[new_node_id_2] = new_node_2[new_node_id_1] = edge2['id'] - new_node_2[from_point['last_node_id']] = last_node[new_node_id_2] = edge3['id'] - - # Add them to the graph - self.edges[edge1['id']] = edge1 - self.edges[edge2['id']] = edge2 - self.edges[edge3['id']] = edge3 - self.nodes[new_node_id_1] = new_node_1 - self.nodes[new_node_id_2] = new_node_2 - self.extend_dict(self.nodes[from_point['first_node_id']], first_node) - self.extend_dict(self.nodes[from_point['last_node_id']], last_node) - - new_node_info_1 = { - 'node_id': new_node_id_1, - 'new_edge1_id': edge1['id'], - 'new_edge2_id': edge2['id'], - 'prev_node_id': from_point['first_node_id'], - 'next_node_id': new_node_id_2, - 'original_egde_id': from_point['edge_id'], - 'percent_of_edge': from_point['percent_distance'], - } - new_node_info_2 = { - 'node_id': new_node_id_2, - 'new_edge1_id': edge2['id'], - 'prev_node_id': new_node_id_1, - 'next_node_id': from_point['last_node_id'], - 'new_edge2_id': edge3['id'], - 'original_egde_id': from_point['edge_id'], - 'percent_of_edge': to_point['percent_distance'], - } - return new_node_info_1, new_node_info_2 - - def remove_step_from_graph(self, node_info): - - # Remove the 2 new edges from the graph: - # They will have already been deleted if this is the 2nd step and - # both steps are on the same path - if self.edges.get(node_info['new_edge1_id']) is not None: - del self.edges[node_info['new_edge1_id']] - if self.edges.get(node_info['new_edge2_id']) is not None: - del self.edges[node_info['new_edge2_id']] - - # Get the 2 nodes this temporary node is linked to - prev_node = self.nodes.get(node_info['prev_node_id']) - next_node = self.nodes.get(node_info['next_node_id']) - - # Remove the new node from the graph - removed_node_id = node_info['node_id'] - del self.nodes[removed_node_id] - if prev_node is not None: - # It will have already been deleted if this is the 2nd step and - # both steps are on the same path - del prev_node[removed_node_id] - del next_node[removed_node_id] - - def extend_dict(self, dict, source): - for key, value in source.items(): - dict[key] = value - - def get_shortest_path(self, from_node_id, to_node_id): - cs_graph = self.get_cs_graph() - matrix = csr_matrix(cs_graph) - - # List of all nodes IDs -> to interprete dijkstra results - self.nodes_ids = list(self.nodes.keys()) - - def get_node_idx_per_id(node_id): - try: - return self.nodes_ids.index(node_id) - except ValueError: - return None - - def get_node_id_per_idx(node_idx): - if node_idx >= len(self.nodes_ids): - return None - return self.nodes_ids[node_idx] - - from_node_idx = get_node_idx_per_id(from_node_id) - to_node_idx = get_node_idx_per_id(to_node_id) - result = dijkstra(matrix, return_predecessors=True, indices=from_node_idx, - directed=False) - - # Retrace the path ID by ID, from end to start - predecessors = result[1] - current_node_id, current_node_idx = to_node_id, to_node_idx - path = [current_node_id] - while current_node_id != from_node_id: - current_node_idx = predecessors[current_node_idx] - current_node_id = get_node_id_per_idx(current_node_idx) - path.append(current_node_id) - - path.reverse() - return path - - def merge_line_strings(self, line_strings): - rounded_line_strings = [ - self.round_line_string_coordinates(ls) for ls in line_strings - ] - multi_line_string = MultiLineString(rounded_line_strings, srid=settings.SRID) - return multi_line_string.merged - - def round_line_string_coordinates(self, line_string): - # TODO: see which precision level is best - coords = line_string.coords - new_coords = [[round(nb, 4) for nb in pt_coord] for pt_coord in coords] - new_line_string = LineString(new_coords, srid=line_string.srid) - return new_line_string - - def generate_id(self): - new_id = self.id_count - self.id_count += 1 - return new_id - - def post(self, request): - try: - params = json.loads(request.body.decode()) - self.steps = params['steps'] - except KeyError: - print("TrekGeometry POST: incorrect parameters") - # TODO: Bad request - - # To generate IDs for temporary nodes and edges - self.id_count = 90000000 - - graph = graph_lib.graph_edges_nodes_of_qs(Path.objects.exclude(draft=True)) - self.nodes = graph['nodes'] - self.edges = graph['edges'] - - line_strings = self.compute_list_of_paths() - - multi_line_string = GeometryCollection(line_strings, srid=settings.SRID) - multi_line_string.transform(settings.API_SRID) - geojson = json.loads(multi_line_string.geojson) - return JsonResponse(geojson) From 366622b71c87e595a46bcb2ed2b960a108a237ee Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Mon, 29 Apr 2024 12:13:34 +0200 Subject: [PATCH 031/213] Reordering PathRouter methods --- geotrek/core/graph.py | 272 +++++++++++++++++++++--------------------- 1 file changed, 136 insertions(+), 136 deletions(-) diff --git a/geotrek/core/graph.py b/geotrek/core/graph.py index d1040a4cb6..a1c5596ec2 100644 --- a/geotrek/core/graph.py +++ b/geotrek/core/graph.py @@ -78,6 +78,11 @@ def __init__(self): self.nodes = graph['nodes'] self.edges = graph['edges'] + def generate_id(self): + new_id = self.id_count + self.id_count += 1 + return new_id + def get_route(self, steps): self.steps = steps line_strings = self.compute_list_of_paths() @@ -88,41 +93,6 @@ def get_route(self, steps): return geojson - def get_cs_graph(self): - - def get_edge_weight(edge_id): - edge = self.edges.get(edge_id) - if edge is None: - return None - return edge.get('length') - - array = [] - for key1, value1 in self.nodes.items(): - row = [] - for key2, value2 in self.nodes.items(): - if key1 == key2: - # If it's the same node, the weight is 0 - row.append(0) - elif key2 in value1.keys(): - # If the nodes are linked by a single edge, the weight is - # the edge length - edge_id = self.get_edge_id_by_nodes(value1, value2) - edge_weight = get_edge_weight(edge_id) - if edge_weight is not None: - row.append(edge_weight) - else: - # If the nodes are not directly linked, the weight is 0 - row.append(0) - array.append(row) - - return np.array(array) - - def get_edge_id_by_nodes(self, node1, node2): - for value in node1.values(): - if value in node2.values(): - return value - return None - def compute_list_of_paths(self): all_line_strings = [] # Compute the shortest path for each pair of adjacent steps @@ -134,6 +104,19 @@ def compute_list_of_paths(self): all_line_strings.append(merged_line_string) return all_line_strings + def compute_two_steps_line_strings(self, from_step, to_step): + from_node_info, to_node_info = self.add_steps_to_graph(from_step, to_step) + + shortest_path = self.get_shortest_path(from_node_info['node_id'], + to_node_info['node_id']) + line_strings = self.node_list_to_line_strings(shortest_path, + from_node_info, to_node_info) + + # Restore the graph (remove the steps) + self.remove_step_from_graph(from_node_info) + self.remove_step_from_graph(to_node_info) + return line_strings + def add_steps_to_graph(self, from_step, to_step): def create_point_from_coords(lat, lng): @@ -177,81 +160,28 @@ def fill_point_closest_path_info(point): to_node_info = self.split_edge_in_two(to_point) return (from_node_info, to_node_info) - def compute_two_steps_line_strings(self, from_step, to_step): - from_node_info, to_node_info = self.add_steps_to_graph(from_step, to_step) - - shortest_path = self.get_shortest_path(from_node_info['node_id'], - to_node_info['node_id']) - line_strings = self.node_list_to_line_strings(shortest_path, - from_node_info, to_node_info) - - # Restore the graph (remove the steps) - self.remove_step_from_graph(from_node_info) - self.remove_step_from_graph(to_node_info) - return line_strings - - def node_list_to_line_strings(self, node_list, from_node_info, to_node_info): - line_strings = [] - # Get a LineString for each pair of adjacent nodes in the path - for i in range(len(node_list) - 1): - # Get the id of the edge corresponding to these nodes - node1 = self.nodes[node_list[i]] - node2 = self.nodes[node_list[i + 1]] - edge_id = self.get_edge_id_by_nodes(node1, node2) - - # If this pair of nodes requires to go backwards relative to a - # Path direction (i.e. the edge 2nd node is the 1st of this pair) - backwards = False - if self.edges[edge_id]['nodes_id'][1] == node_list[i]: - backwards = True - - # If it's the first or last edge of this subpath (it can be both!), - # then the edge is temporary (i.e. created because of a step) - if i == 0 or i == len(node_list) - 2: - # Start and end percentages of the line substring to be created - start_fraction = 0 - end_fraction = 1 - if backwards: - start_fraction, end_fraction = end_fraction, start_fraction - - if i == 0: - original_path = Path.objects.get(pk=from_node_info['original_egde_id']) - start_fraction = from_node_info['percent_of_edge'] - if i == len(node_list) - 2: - original_path = Path.objects.get(pk=to_node_info['original_egde_id']) - end_fraction = to_node_info['percent_of_edge'] - - line_substring = self.create_line_substring( - original_path.geom, - start_fraction, - end_fraction - ) - line_strings.append(line_substring) - - # If it's a real edge (i.e. it corresponds to a whole path), - # we use its LineString - else: - path = Path.objects.get(pk=edge_id) - line_strings.append(path.geom) - - return line_strings - - def create_line_substring(self, geometry, start_fraction, end_fraction): - sql = """ - SELECT ST_AsText(ST_SmartLineSubstring('{}'::geometry, {}, {})) - """.format(geometry, start_fraction, end_fraction) + def remove_step_from_graph(self, node_info): - cursor = connection.cursor() - cursor.execute(sql) - result = cursor.fetchone()[0] + # Remove the 2 new edges from the graph: + # They will have already been deleted if this is the 2nd step and + # both steps are on the same path + if self.edges.get(node_info['new_edge1_id']) is not None: + del self.edges[node_info['new_edge1_id']] + if self.edges.get(node_info['new_edge2_id']) is not None: + del self.edges[node_info['new_edge2_id']] - # Convert the string into an array of arrays of floats - coords_str = result.split('(')[1].split(')')[0] - str_points_array = [elem.split(' ') for elem in coords_str.split(',')] - arr = [[float(nb) for nb in sub_array] for sub_array in str_points_array] + # Get the 2 nodes this temporary node is linked to + prev_node = self.nodes.get(node_info['prev_node_id']) + next_node = self.nodes.get(node_info['next_node_id']) - line_substring = LineString(arr, srid=settings.SRID) - return line_substring + # Remove the new node from the graph + removed_node_id = node_info['node_id'] + del self.nodes[removed_node_id] + if prev_node is not None: + # It will have already been deleted if this is the 2nd step and + # both steps are on the same path + del prev_node[removed_node_id] + del next_node[removed_node_id] def split_edge_in_two(self, point_info): @@ -361,30 +291,7 @@ def split_edge_in_three(self, from_point, to_point): } return new_node_info_1, new_node_info_2 - def remove_step_from_graph(self, node_info): - - # Remove the 2 new edges from the graph: - # They will have already been deleted if this is the 2nd step and - # both steps are on the same path - if self.edges.get(node_info['new_edge1_id']) is not None: - del self.edges[node_info['new_edge1_id']] - if self.edges.get(node_info['new_edge2_id']) is not None: - del self.edges[node_info['new_edge2_id']] - - # Get the 2 nodes this temporary node is linked to - prev_node = self.nodes.get(node_info['prev_node_id']) - next_node = self.nodes.get(node_info['next_node_id']) - - # Remove the new node from the graph - removed_node_id = node_info['node_id'] - del self.nodes[removed_node_id] - if prev_node is not None: - # It will have already been deleted if this is the 2nd step and - # both steps are on the same path - del prev_node[removed_node_id] - del next_node[removed_node_id] - - def extend_dict(self, dict, source): + def extend_dict(self, dict, source): # TODO: use dict.update? for key, value in source.items(): dict[key] = value @@ -423,6 +330,104 @@ def get_node_id_per_idx(node_idx): path.reverse() return path + def get_cs_graph(self): + + def get_edge_weight(edge_id): + edge = self.edges.get(edge_id) + if edge is None: + return None + return edge.get('length') + + array = [] + for key1, value1 in self.nodes.items(): + row = [] + for key2, value2 in self.nodes.items(): + if key1 == key2: + # If it's the same node, the weight is 0 + row.append(0) + elif key2 in value1.keys(): + # If the nodes are linked by a single edge, the weight is + # the edge length + edge_id = self.get_edge_id_by_nodes(value1, value2) + edge_weight = get_edge_weight(edge_id) + if edge_weight is not None: + row.append(edge_weight) + else: + # If the nodes are not directly linked, the weight is 0 + row.append(0) + array.append(row) + + return np.array(array) + + def node_list_to_line_strings(self, node_list, from_node_info, to_node_info): + line_strings = [] + # Get a LineString for each pair of adjacent nodes in the path + for i in range(len(node_list) - 1): + # Get the id of the edge corresponding to these nodes + node1 = self.nodes[node_list[i]] + node2 = self.nodes[node_list[i + 1]] + edge_id = self.get_edge_id_by_nodes(node1, node2) + + # If this pair of nodes requires to go backwards relative to a + # Path direction (i.e. the edge 2nd node is the 1st of this pair) + backwards = False + if self.edges[edge_id]['nodes_id'][1] == node_list[i]: + backwards = True + + # If it's the first or last edge of this subpath (it can be both!), + # then the edge is temporary (i.e. created because of a step) + if i == 0 or i == len(node_list) - 2: + # Start and end percentages of the line substring to be created + start_fraction = 0 + end_fraction = 1 + if backwards: + start_fraction, end_fraction = end_fraction, start_fraction + + if i == 0: + original_path = Path.objects.get(pk=from_node_info['original_egde_id']) + start_fraction = from_node_info['percent_of_edge'] + if i == len(node_list) - 2: + original_path = Path.objects.get(pk=to_node_info['original_egde_id']) + end_fraction = to_node_info['percent_of_edge'] + + line_substring = self.create_line_substring( + original_path.geom, + start_fraction, + end_fraction + ) + line_strings.append(line_substring) + + # If it's a real edge (i.e. it corresponds to a whole path), + # we use its LineString + else: + path = Path.objects.get(pk=edge_id) + line_strings.append(path.geom) + + return line_strings + + def get_edge_id_by_nodes(self, node1, node2): + for value in node1.values(): + if value in node2.values(): + return value + return None + + def create_line_substring(self, geometry, start_fraction, end_fraction): + sql = """ + SELECT ST_AsText(ST_SmartLineSubstring('{}'::geometry, {}, {})) + """.format(geometry, start_fraction, end_fraction) + + cursor = connection.cursor() + cursor.execute(sql) + result = cursor.fetchone()[0] + + # Convert the string into an array of arrays of floats + coords_str = result.split('(')[1].split(')')[0] + str_points_array = [elem.split(' ') for elem in coords_str.split(',')] + arr = [[float(nb) for nb in sub_array] for sub_array in str_points_array] + + line_substring = LineString(arr, srid=settings.SRID) + return line_substring + def merge_line_strings(self, line_strings): rounded_line_strings = [ self.round_line_string_coordinates(ls) for ls in line_strings @@ -436,8 +441,3 @@ def round_line_string_coordinates(self, line_string): new_coords = [[round(nb, 4) for nb in pt_coord] for pt_coord in coords] new_line_string = LineString(new_coords, srid=line_string.srid) return new_line_string - - def generate_id(self): - new_id = self.id_count - self.id_count += 1 - return new_id From b4de5d83002caadd40bff72588dbe6433a3c7bf3 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Mon, 29 Apr 2024 12:32:47 +0200 Subject: [PATCH 032/213] Removing graph action of PathViewSet --- .../core/core_extrabody_fragment.html | 1 - geotrek/core/views.py | 23 ------------------- 2 files changed, 24 deletions(-) diff --git a/geotrek/core/templates/core/core_extrabody_fragment.html b/geotrek/core/templates/core/core_extrabody_fragment.html index a5aad80fe7..ea608bb658 100644 --- a/geotrek/core/templates/core/core_extrabody_fragment.html +++ b/geotrek/core/templates/core/core_extrabody_fragment.html @@ -6,7 +6,6 @@ window.SETTINGS.urls['path_layer'] = "{% url "core:path-drf-list" format="geojson" %}"; window.SETTINGS.urls['trail_layer'] = "{% url "core:trail-drf-list" format="geojson" %}"; - window.SETTINGS.urls['path_graph'] = "{% url "core:path-drf-graph" %}"; window.SETTINGS.urls['route_geometry'] = "{% url "core:path-drf-route-geometry" %}"; diff --git a/geotrek/core/views.py b/geotrek/core/views.py index fa5e363058..0259294798 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -278,29 +278,6 @@ def get_filter_count_infos(self, qs): data = super().get_filter_count_infos(qs) return f"{data} ({round(qs.aggregate(sumPath=Sum(Length('geom') / 1000)).get('sumPath') or 0, 1)} km)" - @method_decorator(cache_control(max_age=0, must_revalidate=True)) - @method_decorator(cache_last_modified(lambda x: Path.no_draft_latest_updated())) - @action(methods=['GET'], detail=False, url_path='graph.json', renderer_classes=[JSONRenderer, BrowsableAPIRenderer]) - def graph(self, request, *args, **kwargs): - """ Return a graph of the path. """ - cache = caches['fat'] - key = 'path_graph_json' - - result = cache.get(key) - latest = Path.no_draft_latest_updated() - - if result and latest: - cache_latest, graph = result - # Not empty and still valid - if cache_latest and cache_latest >= latest: - return Response(graph) - - # cache does not exist or is not up-to-date, rebuild the graph and cache it - graph = graph_lib.graph_edges_nodes_of_qs(Path.objects.exclude(draft=True)) - - cache.set(key, (latest, graph)) - return Response(graph) - @method_decorator(permission_required('core.change_path')) @action(methods=['POST'], detail=False, renderer_classes=[JSONRenderer]) def merge_path(self, request, *args, **kwargs): From 02cb662afcf04b5ff5dea2147f9f6637bd8ed746 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Mon, 29 Apr 2024 13:43:30 +0200 Subject: [PATCH 033/213] Moving initial graph generation into the PathRouter class --- geotrek/core/graph.py | 95 +++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 49 deletions(-) diff --git a/geotrek/core/graph.py b/geotrek/core/graph.py index a1c5596ec2..ea687295dc 100644 --- a/geotrek/core/graph.py +++ b/geotrek/core/graph.py @@ -14,69 +14,66 @@ from .models import Path -def path_modifier(path): - length = 0.0 if math.isnan(path.length) else path.length - return {"id": path.pk, "length": length} - - -def get_key_optimizer(): - next_id = iter(range(1, 1000000)).__next__ - mapping = defaultdict(next_id) - return lambda x: mapping[x] +class PathRouter: + def __init__(self): + # To generate IDs for temporary nodes and edges: + self.id_count = 90000000 + graph = self.graph_edges_nodes_of_qs(Path.objects.exclude(draft=True)) + self.nodes = graph['nodes'] + self.edges = graph['edges'] -def graph_edges_nodes_of_qs(qs): - """ - return a graph on the form: - nodes { - coord_point_a { - coord_point_b: edge_id + def graph_edges_nodes_of_qs(self, qs): + """ + return a graph on the form: + nodes { + coord_point_a { + coord_point_b: edge_id + } } - } - edges { - edge_id: { - nodes: [point_a, point_b, ...] - ** extra settings (length etc.) + edges { + edge_id: { + nodes: [point_a, point_b, ...] + ** extra settings (length etc.) + } } - } - coord_poPathRouterint are tuple of float - """ + coord_poPathRouterint are tuple of float + """ - key_modifier = get_key_optimizer() - value_modifier = path_modifier + def path_modifier(path): + length = 0.0 if math.isnan(path.length) else path.length + return {"id": path.pk, "length": length} - edges = defaultdict(dict) - nodes = defaultdict(dict) + def get_key_optimizer(): + next_id = iter(range(1, 1000000)).__next__ + mapping = defaultdict(next_id) + return lambda x: mapping[x] - for path in qs: - coords = path.geom.coords - start_point, end_point = coords[0], coords[-1] - k_start_point, k_end_point = key_modifier(start_point), key_modifier(end_point) + key_modifier = get_key_optimizer() + value_modifier = path_modifier - v_path = value_modifier(path) - v_path['nodes_id'] = [k_start_point, k_end_point] - edge_id = v_path['id'] + edges = defaultdict(dict) + nodes = defaultdict(dict) - nodes[k_start_point][k_end_point] = edge_id - nodes[k_end_point][k_start_point] = edge_id - edges[edge_id] = v_path + for path in qs: + coords = path.geom.coords + start_point, end_point = coords[0], coords[-1] + k_start_point, k_end_point = key_modifier(start_point), key_modifier(end_point) - return { - 'edges': dict(edges), - 'nodes': dict(nodes), - } + v_path = value_modifier(path) + v_path['nodes_id'] = [k_start_point, k_end_point] + edge_id = v_path['id'] + nodes[k_start_point][k_end_point] = edge_id + nodes[k_end_point][k_start_point] = edge_id + edges[edge_id] = v_path -class PathRouter: - def __init__(self): - # To generate IDs for temporary nodes and edges: - self.id_count = 90000000 - - graph = graph_edges_nodes_of_qs(Path.objects.exclude(draft=True)) - self.nodes = graph['nodes'] - self.edges = graph['edges'] + return { + 'edges': dict(edges), + 'nodes': dict(nodes), + } def generate_id(self): new_id = self.id_count From 25bd1125bf76955d6d01e877f51ba1a2f10513ca Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Mon, 29 Apr 2024 13:45:05 +0200 Subject: [PATCH 034/213] Removing no longer used imports --- geotrek/core/views.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/geotrek/core/views.py b/geotrek/core/views.py index 0259294798..f1c29c5ac2 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -1,12 +1,10 @@ import logging from collections import defaultdict -import json from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import permission_required from django.contrib.gis.db.models.functions import Transform -from django.core.cache import caches from django.db.models import Sum, Prefetch from django.http import HttpResponseRedirect from django.http.response import HttpResponse @@ -14,15 +12,13 @@ from django.urls import reverse from django.utils.decorators import method_decorator from django.utils.translation import gettext as _ -from django.views.decorators.cache import cache_control -from django.views.decorators.http import last_modified as cache_last_modified from django.views.generic import TemplateView from django.views.generic.detail import BaseDetailView from mapentity.serializers import GPXSerializer from mapentity.views import (MapEntityList, MapEntityDetail, MapEntityDocument, MapEntityCreate, MapEntityUpdate, MapEntityDelete, MapEntityFormat, LastModifiedMixin) from rest_framework.decorators import action -from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer +from rest_framework.renderers import JSONRenderer from rest_framework.response import Response from geotrek.authent.decorators import same_structure_required From 51904fffef85c7ca3042341cc78649c85a3d9bac Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Mon, 29 Apr 2024 16:46:18 +0200 Subject: [PATCH 035/213] Renaming graph.py to path_router.py --- geotrek/core/{graph.py => path_router.py} | 0 geotrek/core/tests/test_graph.py | 2 +- geotrek/core/views.py | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) rename geotrek/core/{graph.py => path_router.py} (100%) diff --git a/geotrek/core/graph.py b/geotrek/core/path_router.py similarity index 100% rename from geotrek/core/graph.py rename to geotrek/core/path_router.py diff --git a/geotrek/core/tests/test_graph.py b/geotrek/core/tests/test_graph.py index d63c7b8be3..cde6fc2d24 100644 --- a/geotrek/core/tests/test_graph.py +++ b/geotrek/core/tests/test_graph.py @@ -6,7 +6,7 @@ from django.urls import reverse from mapentity.tests.factories import UserFactory -from geotrek.core.graph import graph_edges_nodes_of_qs +from geotrek.core.path_router import graph_edges_nodes_of_qs from geotrek.core.models import Path from geotrek.core.tests.factories import PathFactory diff --git a/geotrek/core/views.py b/geotrek/core/views.py index f1c29c5ac2..40d588a48e 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -27,7 +27,7 @@ from geotrek.common.mixins.forms import FormsetMixin from geotrek.common.permissions import PublicOrReadPermMixin from geotrek.common.viewsets import GeotrekMapentityViewSet -from . import graph as graph_lib +from .path_router import PathRouter from .filters import PathFilterSet, TrailFilterSet from .forms import PathForm, TrailForm, CertificationTrailFormSet from .models import AltimetryMixin, Path, Trail, Topology, CertificationTrail @@ -314,7 +314,7 @@ def route_geometry(self, request, *args, **kwargs): try: params = request.data steps = params['steps'] - path_router = graph_lib.PathRouter() + path_router = PathRouter() response = path_router.get_route(steps) # TODO check params except KeyError: response = {'error': 'TODO'} # TODO From b68bfc01e24cf16ab254186b6414ee2c8fd38e4f Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Mon, 29 Apr 2024 17:06:21 +0200 Subject: [PATCH 036/213] Adding parameters error handling for the route_geometry action --- geotrek/core/views.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/geotrek/core/views.py b/geotrek/core/views.py index 40d588a48e..d4106630c9 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -313,11 +313,23 @@ def merge_path(self, request, *args, **kwargs): def route_geometry(self, request, *args, **kwargs): try: params = request.data - steps = params['steps'] + steps = params.get('steps') + + if steps is None: + raise Exception("Request parameters should contain a 'steps' array") + + if len(steps) < 2: + raise Exception("There must be at least 2 steps") + + for step in steps: + if step.get('lat') is None or step.get('lng') is None: + raise Exception("Each step should contain a latitude and a longitude") + path_router = PathRouter() - response = path_router.get_route(steps) # TODO check params - except KeyError: - response = {'error': 'TODO'} # TODO + response = path_router.get_route(steps) + + except Exception as exc: + response = {'error': '%s' % exc, } return Response(response) From 34f7b3946e49df0a5f210585ccaec3d52dbfa7a2 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Mon, 29 Apr 2024 17:11:24 +0200 Subject: [PATCH 037/213] Using dict.update instead of a custom method --- geotrek/core/path_router.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/geotrek/core/path_router.py b/geotrek/core/path_router.py index ea687295dc..2abc758eca 100644 --- a/geotrek/core/path_router.py +++ b/geotrek/core/path_router.py @@ -207,8 +207,8 @@ def split_edge_in_two(self, point_info): self.edges[edge1['id']] = edge1 self.edges[edge2['id']] = edge2 self.nodes[new_node_id] = new_node - self.extend_dict(self.nodes[point_info['first_node_id']], first_node) - self.extend_dict(self.nodes[point_info['last_node_id']], last_node) + self.nodes[point_info['first_node_id']].update(first_node) + self.nodes[point_info['last_node_id']].update(last_node) new_node_info = { 'node_id': new_node_id, @@ -265,8 +265,8 @@ def split_edge_in_three(self, from_point, to_point): self.edges[edge3['id']] = edge3 self.nodes[new_node_id_1] = new_node_1 self.nodes[new_node_id_2] = new_node_2 - self.extend_dict(self.nodes[from_point['first_node_id']], first_node) - self.extend_dict(self.nodes[from_point['last_node_id']], last_node) + self.nodes[from_point['first_node_id']].update(first_node) + self.nodes[from_point['last_node_id']].update(last_node) new_node_info_1 = { 'node_id': new_node_id_1, @@ -288,10 +288,6 @@ def split_edge_in_three(self, from_point, to_point): } return new_node_info_1, new_node_info_2 - def extend_dict(self, dict, source): # TODO: use dict.update? - for key, value in source.items(): - dict[key] = value - def get_shortest_path(self, from_node_id, to_node_id): cs_graph = self.get_cs_graph() matrix = csr_matrix(cs_graph) From 7853a8385e350e7dbe814bf480bc6db64d70c93c Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Tue, 30 Apr 2024 14:38:29 +0200 Subject: [PATCH 038/213] Adding status codes to Responses --- geotrek/core/views.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/geotrek/core/views.py b/geotrek/core/views.py index d4106630c9..38cbcfd0fb 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -314,23 +314,22 @@ def route_geometry(self, request, *args, **kwargs): try: params = request.data steps = params.get('steps') - if steps is None: raise Exception("Request parameters should contain a 'steps' array") - if len(steps) < 2: raise Exception("There must be at least 2 steps") - for step in steps: if step.get('lat') is None or step.get('lng') is None: raise Exception("Each step should contain a latitude and a longitude") + except Exception as exc: + return Response({'error': '%s' % exc, }, 400) + try: path_router = PathRouter() - response = path_router.get_route(steps) - + response, status = path_router.get_route(steps), 200 except Exception as exc: - response = {'error': '%s' % exc, } - return Response(response) + response, status = {'error': '%s' % exc, }, 500 + return Response(response, status) class CertificationTrailMixin(FormsetMixin): From a4c53cd7f75582de89d8d443d287cd615bb7cb57 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Thu, 2 May 2024 15:07:00 +0200 Subject: [PATCH 039/213] Optimizing the cs_graph generation (generating the upper triangle then filling the lower triangle symmetrically) --- geotrek/core/path_router.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/geotrek/core/path_router.py b/geotrek/core/path_router.py index 2abc758eca..d96868ce2e 100644 --- a/geotrek/core/path_router.py +++ b/geotrek/core/path_router.py @@ -331,26 +331,28 @@ def get_edge_weight(edge_id): return None return edge.get('length') - array = [] - for key1, value1 in self.nodes.items(): - row = [] - for key2, value2 in self.nodes.items(): - if key1 == key2: - # If it's the same node, the weight is 0 - row.append(0) - elif key2 in value1.keys(): + nb_of_nodes = len(self.nodes) + matrix = np.zeros((nb_of_nodes, nb_of_nodes)) + + nodes_list = list(self.nodes.items()) + for i, (key1, value1) in enumerate(nodes_list[:-1]): + # The last row is left blank and j starts at i + 1 because only the + # upper triangle is filled and the main diagonal is all zeros (the + # weight from a node to itself is 0) + for j, (key2, value2) in enumerate(nodes_list[i + 1:]): + if key2 in value1.keys(): # If the nodes are linked by a single edge, the weight is - # the edge length + # the edge length ; if not, the weight is 0 edge_id = self.get_edge_id_by_nodes(value1, value2) edge_weight = get_edge_weight(edge_id) if edge_weight is not None: - row.append(edge_weight) - else: - # If the nodes are not directly linked, the weight is 0 - row.append(0) - array.append(row) + matrix[i][j + i + 1] = edge_weight - return np.array(array) + # Fill the lower triangle of the matrix symmetrically to the upper one + lower_triangle = np.triu(matrix, 1).T + matrix += lower_triangle + + return matrix def node_list_to_line_strings(self, node_list, from_node_info, to_node_info): line_strings = [] From 42ba9b93bea6faf96d3c3554075cdd13f0924766 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Thu, 2 May 2024 17:55:25 +0200 Subject: [PATCH 040/213] Optimizing matrix generation: it is now generated at init and modified depending on the current steps --- geotrek/core/path_router.py | 109 +++++++++++++++++++++++------------- 1 file changed, 71 insertions(+), 38 deletions(-) diff --git a/geotrek/core/path_router.py b/geotrek/core/path_router.py index d96868ce2e..fd2ceecd53 100644 --- a/geotrek/core/path_router.py +++ b/geotrek/core/path_router.py @@ -22,6 +22,12 @@ def __init__(self): graph = self.graph_edges_nodes_of_qs(Path.objects.exclude(draft=True)) self.nodes = graph['nodes'] self.edges = graph['edges'] + self.dijk_matrix = self.get_cs_graph() + + def generate_id(self): + new_id = self.id_count + self.id_count += 1 + return new_id def graph_edges_nodes_of_qs(self, qs): """ @@ -75,10 +81,38 @@ def get_key_optimizer(): 'nodes': dict(nodes), } - def generate_id(self): - new_id = self.id_count - self.id_count += 1 - return new_id + def get_cs_graph(self): + + nb_of_nodes = len(self.nodes) + matrix = np.zeros((nb_of_nodes, nb_of_nodes)) + + nodes_list = list(self.nodes.items()) + for i, (key1, value1) in enumerate(nodes_list[:-1]): + # The last row is left blank and j starts at i + 1 because only the + # upper triangle is filled and the main diagonal is all zeros (the + # weight from a node to itself is 0) + for j, (key2, value2) in enumerate(nodes_list[i + 1:]): + if key2 in value1.keys(): + # If the nodes are linked by a single edge, the weight is + # the edge length ; if not, the weight is 0 + edge_id = self.get_edge_id_by_nodes(value1, value2) + edge_weight = self.get_edge_weight(edge_id) + if edge_weight is not None: + matrix[i][j + i + 1] = edge_weight + # TODO: add matrix[j + i + 1][i] = edge_weight + # instead of using triangles + + # Fill the lower triangle of the matrix symmetrically to the upper one + lower_triangle = np.triu(matrix, 1).T + matrix += lower_triangle + + return matrix + + def get_edge_weight(self, edge_id): + edge = self.edges.get(edge_id) + if edge is None: + return None + return edge.get('length') def get_route(self, steps): self.steps = steps @@ -103,6 +137,7 @@ def compute_list_of_paths(self): def compute_two_steps_line_strings(self, from_step, to_step): from_node_info, to_node_info = self.add_steps_to_graph(from_step, to_step) + self.add_steps_to_matrix(from_node_info, to_node_info) shortest_path = self.get_shortest_path(from_node_info['node_id'], to_node_info['node_id']) @@ -112,6 +147,7 @@ def compute_two_steps_line_strings(self, from_step, to_step): # Restore the graph (remove the steps) self.remove_step_from_graph(from_node_info) self.remove_step_from_graph(to_node_info) + self.remove_steps_from_matrix() return line_strings def add_steps_to_graph(self, from_step, to_step): @@ -288,9 +324,37 @@ def split_edge_in_three(self, from_point, to_point): } return new_node_info_1, new_node_info_2 + def add_steps_to_matrix(self, from_node_info, to_node_info): + length = len(self.dijk_matrix) + # Add the last two rows + new_rows = [np.zeros(length) for i in range(2)] + self.dijk_matrix = np.vstack((self.dijk_matrix, new_rows)) + # add the last two columns + new_columns = np.zeros((length + 2, 2)) + self.dijk_matrix = np.hstack((self.dijk_matrix, new_columns)) + + # Add the weights + # TODO: this is duplicate code -> create a method + from_node = self.nodes[from_node_info['node_id']] + to_node = self.nodes[to_node_info['node_id']] + for i, (key, value) in enumerate(list(self.nodes.items())): + for j, step in enumerate([from_node, to_node]): + if key in step.keys(): + edge_id = self.get_edge_id_by_nodes(step, value) + edge_weight = self.get_edge_weight(edge_id) + if edge_weight is not None: + self.dijk_matrix[i][length + j] = edge_weight + self.dijk_matrix[length + j][i] = edge_weight + + def remove_steps_from_matrix(self): + length = len(self.dijk_matrix) + # Remove the last two rows + self.dijk_matrix = np.delete(self.dijk_matrix, [length - 1, length - 2], 0) + # Remove the last two columns + self.dijk_matrix = np.delete(self.dijk_matrix, [length - 1, length - 2], 1) + def get_shortest_path(self, from_node_id, to_node_id): - cs_graph = self.get_cs_graph() - matrix = csr_matrix(cs_graph) + matrix = csr_matrix(self.dijk_matrix) # List of all nodes IDs -> to interprete dijkstra results self.nodes_ids = list(self.nodes.keys()) @@ -302,7 +366,7 @@ def get_node_idx_per_id(node_id): return None def get_node_id_per_idx(node_idx): - if node_idx >= len(self.nodes_ids): + if node_idx < 0 or node_idx >= len(self.nodes_ids): return None return self.nodes_ids[node_idx] @@ -323,37 +387,6 @@ def get_node_id_per_idx(node_idx): path.reverse() return path - def get_cs_graph(self): - - def get_edge_weight(edge_id): - edge = self.edges.get(edge_id) - if edge is None: - return None - return edge.get('length') - - nb_of_nodes = len(self.nodes) - matrix = np.zeros((nb_of_nodes, nb_of_nodes)) - - nodes_list = list(self.nodes.items()) - for i, (key1, value1) in enumerate(nodes_list[:-1]): - # The last row is left blank and j starts at i + 1 because only the - # upper triangle is filled and the main diagonal is all zeros (the - # weight from a node to itself is 0) - for j, (key2, value2) in enumerate(nodes_list[i + 1:]): - if key2 in value1.keys(): - # If the nodes are linked by a single edge, the weight is - # the edge length ; if not, the weight is 0 - edge_id = self.get_edge_id_by_nodes(value1, value2) - edge_weight = get_edge_weight(edge_id) - if edge_weight is not None: - matrix[i][j + i + 1] = edge_weight - - # Fill the lower triangle of the matrix symmetrically to the upper one - lower_triangle = np.triu(matrix, 1).T - matrix += lower_triangle - - return matrix - def node_list_to_line_strings(self, node_list, from_node_info, to_node_info): line_strings = [] # Get a LineString for each pair of adjacent nodes in the path From e2f3a69a74f8cc0dc78e01b6361c8be7a909777c Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Thu, 2 May 2024 18:02:31 +0200 Subject: [PATCH 041/213] Optimizing matrix generation by using its symmetrical property --- geotrek/core/path_router.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/geotrek/core/path_router.py b/geotrek/core/path_router.py index fd2ceecd53..c5e8ae2490 100644 --- a/geotrek/core/path_router.py +++ b/geotrek/core/path_router.py @@ -88,8 +88,8 @@ def get_cs_graph(self): nodes_list = list(self.nodes.items()) for i, (key1, value1) in enumerate(nodes_list[:-1]): - # The last row is left blank and j starts at i + 1 because only the - # upper triangle is filled and the main diagonal is all zeros (the + # i ends at len - 1 and j starts at i + 1 because the + # matrix is symmetric and the main diagonal is all zeros (the # weight from a node to itself is 0) for j, (key2, value2) in enumerate(nodes_list[i + 1:]): if key2 in value1.keys(): @@ -99,12 +99,7 @@ def get_cs_graph(self): edge_weight = self.get_edge_weight(edge_id) if edge_weight is not None: matrix[i][j + i + 1] = edge_weight - # TODO: add matrix[j + i + 1][i] = edge_weight - # instead of using triangles - - # Fill the lower triangle of the matrix symmetrically to the upper one - lower_triangle = np.triu(matrix, 1).T - matrix += lower_triangle + matrix[j + i + 1][i] = edge_weight return matrix From a884ab8716467154e369c9741219ec2ed6c7ffdd Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Fri, 3 May 2024 10:52:17 +0200 Subject: [PATCH 042/213] Refactoring: making duplicate code into a method --- geotrek/core/path_router.py | 51 ++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/geotrek/core/path_router.py b/geotrek/core/path_router.py index c5e8ae2490..3d06934840 100644 --- a/geotrek/core/path_router.py +++ b/geotrek/core/path_router.py @@ -22,7 +22,7 @@ def __init__(self): graph = self.graph_edges_nodes_of_qs(Path.objects.exclude(draft=True)) self.nodes = graph['nodes'] self.edges = graph['edges'] - self.dijk_matrix = self.get_cs_graph() + self.set_cs_graph() def generate_id(self): new_id = self.id_count @@ -81,10 +81,10 @@ def get_key_optimizer(): 'nodes': dict(nodes), } - def get_cs_graph(self): + def set_cs_graph(self): nb_of_nodes = len(self.nodes) - matrix = np.zeros((nb_of_nodes, nb_of_nodes)) + self.dijk_matrix = np.zeros((nb_of_nodes, nb_of_nodes)) nodes_list = list(self.nodes.items()) for i, (key1, value1) in enumerate(nodes_list[:-1]): @@ -92,16 +92,25 @@ def get_cs_graph(self): # matrix is symmetric and the main diagonal is all zeros (the # weight from a node to itself is 0) for j, (key2, value2) in enumerate(nodes_list[i + 1:]): - if key2 in value1.keys(): - # If the nodes are linked by a single edge, the weight is - # the edge length ; if not, the weight is 0 - edge_id = self.get_edge_id_by_nodes(value1, value2) - edge_weight = self.get_edge_weight(edge_id) - if edge_weight is not None: - matrix[i][j + i + 1] = edge_weight - matrix[j + i + 1][i] = edge_weight + row_idx = i + col_idx = j + i + 1 + self.set_edge_weight(value1, key2, row_idx, col_idx) - return matrix + def set_edge_weight(self, node1, node2_key, row_idx, col_idx): + """ + node1: dict {neighbor_node_key: edge_id, ...} + node2_key: int + row_idx: int + col_idx: int + """ + edge_id = node1.get(node2_key) + if edge_id is not None: + # If the nodes are linked by an edge, the weight is its length ; + # if not, the weight stays at 0 + edge_weight = self.get_edge_weight(edge_id) + if edge_weight is not None: + self.dijk_matrix[row_idx][col_idx] = edge_weight + self.dijk_matrix[col_idx][row_idx] = edge_weight def get_edge_weight(self, edge_id): edge = self.edges.get(edge_id) @@ -329,17 +338,13 @@ def add_steps_to_matrix(self, from_node_info, to_node_info): self.dijk_matrix = np.hstack((self.dijk_matrix, new_columns)) # Add the weights - # TODO: this is duplicate code -> create a method - from_node = self.nodes[from_node_info['node_id']] - to_node = self.nodes[to_node_info['node_id']] - for i, (key, value) in enumerate(list(self.nodes.items())): - for j, step in enumerate([from_node, to_node]): - if key in step.keys(): - edge_id = self.get_edge_id_by_nodes(step, value) - edge_weight = self.get_edge_weight(edge_id) - if edge_weight is not None: - self.dijk_matrix[i][length + j] = edge_weight - self.dijk_matrix[length + j][i] = edge_weight + from_node_key = from_node_info['node_id'] + to_node_key = to_node_info['node_id'] + for i, node1 in enumerate(list(self.nodes.values())): + for j, node2_key in enumerate([from_node_key, to_node_key]): + row_idx = i + col_idx = length + j + self.set_edge_weight(node1, node2_key, row_idx, col_idx) def remove_steps_from_matrix(self): length = len(self.dijk_matrix) From c4122221ebaf580a44f0a942235798ff38ea6048 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Fri, 3 May 2024 15:08:49 +0200 Subject: [PATCH 043/213] The route is now fetched only when the marker dragging stops --- geotrek/core/static/core/multipath.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index d9948876fe..c9d9970217 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -394,6 +394,8 @@ L.Handler.MultiPath = L.Handler.extend({ // If this was clicked, the marker should be close enough, snap it. self.forceMarkerToLayer(marker, layer); + + pop.events.fire('placed'); }, forceMarkerToLayer: function(marker, layer) { @@ -407,7 +409,7 @@ L.Handler.MultiPath = L.Handler.extend({ var pop = new Geotrek.PointOnPolyline(marker); this.steps.splice(idx, 0, pop); // Insert pop at position idx - pop.events.on('valid', function() { + pop.events.on('placed', function() { self.fetchRoute(); }); @@ -834,6 +836,9 @@ Geotrek.PointOnPolyline = function (marker) { this.ll = null; this.polyline = null; this.events.fire('invalid'); + }, + 'dragend': function onDragEnd(e) { + this.events.fire('placed'); } }; }; @@ -858,6 +863,7 @@ Geotrek.PointOnPolyline.prototype.toggleActivate = function(activate) { marker[method]('move', markerEvents.move, this); marker[method]('snap', markerEvents.snap, this); marker[method]('unsnap', markerEvents.unsnap, this); + marker[method]('dragend', markerEvents.dragend, this); }; Geotrek.PointOnPolyline.prototype.isValid = function(graph) { From 21f01547cabac014114b1df983edf0d22c7529da Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Mon, 6 May 2024 13:49:06 +0200 Subject: [PATCH 044/213] Saving the dijkstra matrix in the cache --- geotrek/core/path_router.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/geotrek/core/path_router.py b/geotrek/core/path_router.py index 3d06934840..99a808833b 100644 --- a/geotrek/core/path_router.py +++ b/geotrek/core/path_router.py @@ -5,6 +5,7 @@ from django.db import connection from django.conf import settings from django.contrib.gis.geos import Point, LineString, MultiLineString, GeometryCollection +from django.core.cache import caches import numpy as np from scipy.sparse.csgraph import dijkstra @@ -83,6 +84,17 @@ def get_key_optimizer(): def set_cs_graph(self): + # Try to retrieve the matrix from the cache + cache = caches['fat'] + key = 'dijkstra_matrix' + cached_data = cache.get(key) + latest_paths_date = Path.no_draft_latest_updated() + if cached_data and latest_paths_date: + cache_latest, matrix = cached_data + if cache_latest and cache_latest >= latest_paths_date: + self.dijk_matrix = matrix + return + nb_of_nodes = len(self.nodes) self.dijk_matrix = np.zeros((nb_of_nodes, nb_of_nodes)) @@ -96,6 +108,8 @@ def set_cs_graph(self): col_idx = j + i + 1 self.set_edge_weight(value1, key2, row_idx, col_idx) + cache.set(key, (latest_paths_date, self.dijk_matrix)) + def set_edge_weight(self, node1, node2_key, row_idx, col_idx): """ node1: dict {neighbor_node_key: edge_id, ...} From 28c74fb0f11c85687ea7dae3040af3cb9d13756e Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Mon, 6 May 2024 13:56:02 +0200 Subject: [PATCH 045/213] Saving the path graph in the cache --- geotrek/core/path_router.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/geotrek/core/path_router.py b/geotrek/core/path_router.py index 99a808833b..ee82d937e0 100644 --- a/geotrek/core/path_router.py +++ b/geotrek/core/path_router.py @@ -58,6 +58,16 @@ def get_key_optimizer(): mapping = defaultdict(next_id) return lambda x: mapping[x] + # Try to retrieve the graph from the cache + cache = caches['fat'] + key = 'path_graph' + cached_data = cache.get(key) + latest_paths_date = Path.no_draft_latest_updated() + if cached_data and latest_paths_date: + cache_latest, graph = cached_data + if cache_latest and cache_latest >= latest_paths_date: + return graph + key_modifier = get_key_optimizer() value_modifier = path_modifier @@ -77,10 +87,12 @@ def get_key_optimizer(): nodes[k_end_point][k_start_point] = edge_id edges[edge_id] = v_path - return { + graph = { 'edges': dict(edges), 'nodes': dict(nodes), } + cache.set(key, (latest_paths_date, graph)) + return graph def set_cs_graph(self): From f8dd4615f6f63731acaa986c24e37f756f05f34e Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Mon, 6 May 2024 15:18:23 +0200 Subject: [PATCH 046/213] Moving duplicate cache-related code into a single method --- geotrek/core/path_router.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/geotrek/core/path_router.py b/geotrek/core/path_router.py index ee82d937e0..6061cb5fae 100644 --- a/geotrek/core/path_router.py +++ b/geotrek/core/path_router.py @@ -30,6 +30,15 @@ def generate_id(self): self.id_count += 1 return new_id + def get_from_cache_with_latest_paths(self, cache, key): + cached_data = cache.get(key) + latest_paths_date = Path.no_draft_latest_updated() + if cached_data and latest_paths_date: + cache_latest, data = cached_data + if cache_latest and cache_latest >= latest_paths_date: + return latest_paths_date, data + return (latest_paths_date, None) + def graph_edges_nodes_of_qs(self, qs): """ return a graph on the form: @@ -61,12 +70,9 @@ def get_key_optimizer(): # Try to retrieve the graph from the cache cache = caches['fat'] key = 'path_graph' - cached_data = cache.get(key) - latest_paths_date = Path.no_draft_latest_updated() - if cached_data and latest_paths_date: - cache_latest, graph = cached_data - if cache_latest and cache_latest >= latest_paths_date: - return graph + latest_paths_date, graph = self.get_from_cache_with_latest_paths(cache, key) + if graph is not None: + return graph key_modifier = get_key_optimizer() value_modifier = path_modifier @@ -99,13 +105,10 @@ def set_cs_graph(self): # Try to retrieve the matrix from the cache cache = caches['fat'] key = 'dijkstra_matrix' - cached_data = cache.get(key) - latest_paths_date = Path.no_draft_latest_updated() - if cached_data and latest_paths_date: - cache_latest, matrix = cached_data - if cache_latest and cache_latest >= latest_paths_date: - self.dijk_matrix = matrix - return + latest_paths_date, matrix = self.get_from_cache_with_latest_paths(cache, key) + if matrix is not None: + self.dijk_matrix = matrix + return nb_of_nodes = len(self.nodes) self.dijk_matrix = np.zeros((nb_of_nodes, nb_of_nodes)) From 7050cc7251687abbfa595b3f82e762233db338c1 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Mon, 6 May 2024 17:10:29 +0200 Subject: [PATCH 047/213] Add handling of an impossible path: the view returns a status 204 and the js does not attempt to display the path --- geotrek/core/path_router.py | 9 +++++++++ geotrek/core/static/core/multipath.js | 14 ++++++++++---- geotrek/core/views.py | 4 +++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/geotrek/core/path_router.py b/geotrek/core/path_router.py index 6061cb5fae..c9261c12ca 100644 --- a/geotrek/core/path_router.py +++ b/geotrek/core/path_router.py @@ -150,6 +150,8 @@ def get_edge_weight(self, edge_id): def get_route(self, steps): self.steps = steps line_strings = self.compute_list_of_paths() + if line_strings is None: + return None multi_line_string = GeometryCollection(line_strings, srid=settings.SRID) multi_line_string.transform(settings.API_SRID) @@ -164,6 +166,8 @@ def compute_list_of_paths(self): from_step = self.steps[i] to_step = self.steps[i + 1] line_strings = self.compute_two_steps_line_strings(from_step, to_step) + if line_strings is None: + return None merged_line_string = self.merge_line_strings(line_strings) all_line_strings.append(merged_line_string) return all_line_strings @@ -174,6 +178,8 @@ def compute_two_steps_line_strings(self, from_step, to_step): shortest_path = self.get_shortest_path(from_node_info['node_id'], to_node_info['node_id']) + if shortest_path is None: + return None line_strings = self.node_list_to_line_strings(shortest_path, from_node_info, to_node_info) @@ -409,6 +415,9 @@ def get_node_id_per_idx(node_idx): current_node_id, current_node_idx = to_node_id, to_node_idx path = [current_node_id] while current_node_id != from_node_id: + if current_node_idx < 0: + # The path ends here but this node is not the destination + return None current_node_idx = predecessors[current_node_idx] current_node_id = get_node_id_per_idx(current_node_idx) path.append(current_node_id) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index c9d9970217..d258aae8bb 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -505,11 +505,17 @@ L.Handler.MultiPath = L.Handler.extend({ steps: sent_steps, }) }) - .then(response => response.json()) + .then(response => { + // console.log("response", response) + if (response.status == 200) + return response.json() + }) .then(data => { - console.log('response:', data) - var route = {'geojson': data} - this.fire('fetched_route', route); + // console.log('response data:', data) + if (data) { + var route = {'geojson': data} + this.fire('fetched_route', route); + } }) // .catch(e => { // console.log("fetchRoute", e) diff --git a/geotrek/core/views.py b/geotrek/core/views.py index 38cbcfd0fb..1142173040 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -326,7 +326,9 @@ def route_geometry(self, request, *args, **kwargs): try: path_router = PathRouter() - response, status = path_router.get_route(steps), 200 + response = path_router.get_route(steps) + status = 200 if response is not None else 204 + except Exception as exc: response, status = {'error': '%s' % exc, }, 500 return Response(response, status) From 3fdbfe7c7dc2c349317511d659a28b243eff39aa Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Mon, 6 May 2024 17:32:10 +0200 Subject: [PATCH 048/213] Modify error handling in js: using Promise.reject --- geotrek/core/static/core/multipath.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index d258aae8bb..50242d0bb5 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -506,17 +506,21 @@ L.Handler.MultiPath = L.Handler.extend({ }) }) .then(response => { - // console.log("response", response) if (response.status == 200) return response.json() + return Promise.reject(response) }) - .then(data => { - // console.log('response data:', data) - if (data) { - var route = {'geojson': data} - this.fire('fetched_route', route); - } - }) + .then( + data => { // Status code 200: + console.log('response data:', data) + if (data) { + var route = {'geojson': data} + this.fire('fetched_route', route); + } + }, + // If the promise was rejected: + response => console.log("fetchRoute:", response) + ) // .catch(e => { // console.log("fetchRoute", e) // }) From 8417b30dff174266564b52c864d28ca1ed42274c Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Mon, 13 May 2024 12:30:03 +0200 Subject: [PATCH 049/213] Correct returns when no path could be found Methods that return an array when a path is found now return [] instead of None if no path can be found --- geotrek/core/path_router.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/geotrek/core/path_router.py b/geotrek/core/path_router.py index c9261c12ca..8f0f303ba4 100644 --- a/geotrek/core/path_router.py +++ b/geotrek/core/path_router.py @@ -150,7 +150,7 @@ def get_edge_weight(self, edge_id): def get_route(self, steps): self.steps = steps line_strings = self.compute_list_of_paths() - if line_strings is None: + if line_strings == []: return None multi_line_string = GeometryCollection(line_strings, srid=settings.SRID) @@ -166,8 +166,8 @@ def compute_list_of_paths(self): from_step = self.steps[i] to_step = self.steps[i + 1] line_strings = self.compute_two_steps_line_strings(from_step, to_step) - if line_strings is None: - return None + if line_strings == []: + return [] merged_line_string = self.merge_line_strings(line_strings) all_line_strings.append(merged_line_string) return all_line_strings @@ -178,8 +178,8 @@ def compute_two_steps_line_strings(self, from_step, to_step): shortest_path = self.get_shortest_path(from_node_info['node_id'], to_node_info['node_id']) - if shortest_path is None: - return None + if shortest_path == []: + return [] line_strings = self.node_list_to_line_strings(shortest_path, from_node_info, to_node_info) @@ -417,7 +417,7 @@ def get_node_id_per_idx(node_idx): while current_node_id != from_node_id: if current_node_idx < 0: # The path ends here but this node is not the destination - return None + return [] current_node_idx = predecessors[current_node_idx] current_node_id = get_node_id_per_idx(current_node_idx) path.append(current_node_id) From 57f062c6ee1c13ac6dc9becb476cc8e0df22f270 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Mon, 13 May 2024 15:51:53 +0200 Subject: [PATCH 050/213] Replaces status 204 by status 400 when no path can be found --- geotrek/core/views.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/geotrek/core/views.py b/geotrek/core/views.py index 1142173040..0a114ee3e9 100644 --- a/geotrek/core/views.py +++ b/geotrek/core/views.py @@ -327,8 +327,11 @@ def route_geometry(self, request, *args, **kwargs): try: path_router = PathRouter() response = path_router.get_route(steps) - status = 200 if response is not None else 204 - + if response is not None: + status = 200 + else: + response = {'error': 'No path between the given points'} + status = 400 except Exception as exc: response, status = {'error': '%s' % exc, }, 500 return Response(response, status) From eced2d0f7690665f589e807c122f27ef87e4e166 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Tue, 14 May 2024 14:50:14 +0200 Subject: [PATCH 051/213] Add a spinner when the route is being fetched --- geotrek/core/static/core/multipath.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index 50242d0bb5..ad65e7971f 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -233,6 +233,7 @@ L.Handler.MultiPath = L.Handler.extend({ this._container = map._container; this._guidesLayer = guidesLayer; this.options = options; + this.spinner = new Spinner() // markers this.markersFactory = this.getMarkers(); @@ -485,7 +486,8 @@ L.Handler.MultiPath = L.Handler.extend({ fetchRoute: function() { if (this.canFetchRoute()) { - + this.spinner.spin(this._container); + var sent_steps = [] this.steps.forEach((step) => { var sent_step = { @@ -517,13 +519,18 @@ L.Handler.MultiPath = L.Handler.extend({ var route = {'geojson': data} this.fire('fetched_route', route); } + this.spinner.stop() }, // If the promise was rejected: - response => console.log("fetchRoute:", response) + response => { + console.log("fetchRoute:", response) + this.spinner.stop() + } ) - // .catch(e => { - // console.log("fetchRoute", e) - // }) + .catch(e => { + console.log("fetchRoute", e) + this.spinner.stop() + }) } }, From b07dec23a6ba3388c5390a55337bc4883d37cf2b Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Tue, 14 May 2024 16:43:54 +0200 Subject: [PATCH 052/213] Add sending of the modified steps indexes to fetchRoute --- geotrek/core/static/core/multipath.js | 58 ++++++++++++++++++--------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index ad65e7971f..d30aeddbe3 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -410,8 +410,17 @@ L.Handler.MultiPath = L.Handler.extend({ var pop = new Geotrek.PointOnPolyline(marker); this.steps.splice(idx, 0, pop); // Insert pop at position idx - pop.events.on('placed', function() { - self.fetchRoute(); + pop.events.on('placed', () => { + var current_step_idx = self.getStepIdx(pop) + + var steps_indexes = [] + if (current_step_idx > 0) + steps_indexes.push(current_step_idx - 1) + steps_indexes.push(current_step_idx) + if (current_step_idx < self.steps.length - 1) + steps_indexes.push(current_step_idx + 1) + + self.fetchRoute(steps_indexes) }); return pop; @@ -430,9 +439,10 @@ L.Handler.MultiPath = L.Handler.extend({ // remove marker on click function removeViaStep() { - self.steps.splice(self.getStepIdx(pop), 1); + var step_idx = self.getStepIdx(pop) + self.steps.splice(step_idx, 1); self.map.removeLayer(marker); - self.fetchRoute(); + self.fetchRoute([step_idx - 1, step_idx]); } function removeOnClick() { marker.on('click', removeViaStep); } @@ -444,18 +454,6 @@ L.Handler.MultiPath = L.Handler.extend({ pop.toggleActivate(); }, - canFetchRoute: function() { - if (this.steps.length < 2) - return false; - - for (var i = 0; i < this.steps.length; i++) { - if (! this.steps[i].isValid()) - return false; - } - - return true; - }, - getStepIdx: function(step) { return this.steps.indexOf(step); }, @@ -483,9 +481,33 @@ L.Handler.MultiPath = L.Handler.extend({ return cookieValue; }, - fetchRoute: function() { + fetchRoute: function(steps_indexes) { + /* + steps_indexes (optional): + list containing the indexes of the steps for which to update the route ; + if not given, the whole route is fetched + */ + + console.log("steps_indexes", steps_indexes) + + var steps_to_route = [] + steps_indexes.forEach(idx => { + steps_to_route.push(this.steps[idx]) + }) + + function canFetchRoute() { + if (steps_to_route.length < 2) + return false; + + for (var i = 0; i < steps_to_route.length; i++) { + if (!steps_to_route[i].isValid()) + return false; + } + + return true; + } - if (this.canFetchRoute()) { + if (canFetchRoute()) { this.spinner.spin(this._container); var sent_steps = [] From 6c899a070dbb20ffaa130ed351f2f77fa2e26676 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Tue, 14 May 2024 17:03:52 +0200 Subject: [PATCH 053/213] Adds fetching of the route for only the modified steps --- geotrek/core/static/core/multipath.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index d30aeddbe3..cd3cc74d8c 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -511,7 +511,7 @@ L.Handler.MultiPath = L.Handler.extend({ this.spinner.spin(this._container); var sent_steps = [] - this.steps.forEach((step) => { + steps_to_route.forEach((step) => { var sent_step = { lat: step.ll.lat, lng: step.ll.lng, @@ -538,7 +538,10 @@ L.Handler.MultiPath = L.Handler.extend({ data => { // Status code 200: console.log('response data:', data) if (data) { - var route = {'geojson': data} + var route = { + 'geojson': data, + 'modified_indexes': steps_indexes, + } this.fire('fetched_route', route); } this.spinner.stop() @@ -764,9 +767,9 @@ L.Handler.MultiPath = L.Handler.extend({ return markersFactory; }, - buildRouteLayers: function(route) { + buildRouteLayers: function(geojson) { var layer = L.featureGroup(); - route.geojson.geometries.forEach((geom, i) => { + geojson.geometries.forEach((geom, i) => { var sub_layer = L.geoJson(geom); sub_layer.step_idx = i layer.addLayer(sub_layer); @@ -781,7 +784,11 @@ L.Handler.MultiPath = L.Handler.extend({ onFetchedRoute: function(data) { var self = this; - var topology = this.buildRouteLayers(data); + + // TODO: update the layers -> use data.modified_indexes + // Option 1: update the existing sublayers of the layer in buildRouteLayers + // Option 2: create a whole new layer in buildRouteLayers + var topology = this.buildRouteLayers(data.geojson); this.showPathGeom(topology.layer); this.fire('computed_topology', {topology:topology.serialized}); From d946de202564c93c27d4e48568b935abda12af04 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Thu, 16 May 2024 14:25:44 +0200 Subject: [PATCH 054/213] Add algorithm that updates the displayed route layers --- geotrek/core/static/core/multipath.js | 169 ++++++++++++++++++++------ 1 file changed, 130 insertions(+), 39 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index cd3cc74d8c..b3406f6b76 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -232,6 +232,7 @@ L.Handler.MultiPath = L.Handler.extend({ this.map = map; this._container = map._container; this._guidesLayer = guidesLayer; + this._routeLayer = null this.options = options; this.spinner = new Spinner() @@ -245,6 +246,25 @@ L.Handler.MultiPath = L.Handler.extend({ return guidesLayer.getLayer(id); }; + this.stepIndexToLayer = function(idx) { + if (!this._routeLayer) + return null + for (var i = 0; i < this._routeLayer.length; i++) { + var layer = this._routeLayer[i] + if (layer.step_idx == idx) + return layer + } + return null; + }; + + this.layersOrderdByIdx = function() { + var layers = this._routeLayer ? this._routeLayer.__layerArray : [] + var sortedLayers = layers.toSorted((first, second) => { + return first.step_idx - second.step_idx + }) + return sortedLayers + } + /* * Draggable via steps * @@ -411,16 +431,23 @@ L.Handler.MultiPath = L.Handler.extend({ this.steps.splice(idx, 0, pop); // Insert pop at position idx pop.events.on('placed', () => { - var current_step_idx = self.getStepIdx(pop) - - var steps_indexes = [] - if (current_step_idx > 0) - steps_indexes.push(current_step_idx - 1) - steps_indexes.push(current_step_idx) - if (current_step_idx < self.steps.length - 1) - steps_indexes.push(current_step_idx + 1) - - self.fetchRoute(steps_indexes) + var currentStepIdx = self.getStepIdx(pop) + + // Create the array of step indexes before the route is updated + var oldStepsIndexes = [] + // TODO + console.log("placed") + + // Create the array of new step indexes after the route is updated + var newStepsIndexes = [] + if (currentStepIdx > 0) + newStepsIndexes.push(currentStepIdx - 1) + newStepsIndexes.push(currentStepIdx) + if (currentStepIdx < self.steps.length - 1) + newStepsIndexes.push(currentStepIdx + 1) + + // TODO: send the right params + self.fetchRoute(oldStepsIndexes, newStepsIndexes) }); return pop; @@ -442,7 +469,11 @@ L.Handler.MultiPath = L.Handler.extend({ var step_idx = self.getStepIdx(pop) self.steps.splice(step_idx, 1); self.map.removeLayer(marker); - self.fetchRoute([step_idx - 1, step_idx]); + // TODO: send the right params + self.fetchRoute( + [step_idx - 1, step_idx, step_idx + 1], + [step_idx - 1, step_idx] + ); } function removeOnClick() { marker.on('click', removeViaStep); } @@ -481,23 +512,22 @@ L.Handler.MultiPath = L.Handler.extend({ return cookieValue; }, - fetchRoute: function(steps_indexes) { + fetchRoute: function(old_steps_indexes, new_steps_indexes) { /* - steps_indexes (optional): - list containing the indexes of the steps for which to update the route ; - if not given, the whole route is fetched + old_steps_indexes: indexes of the steps for which to update the route + new_steps_indexes: indexes of these steps after the route is updated */ - console.log("steps_indexes", steps_indexes) - var steps_to_route = [] - steps_indexes.forEach(idx => { + new_steps_indexes.forEach(idx => { steps_to_route.push(this.steps[idx]) }) function canFetchRoute() { if (steps_to_route.length < 2) return false; + if (new_steps_indexes.length < 2) + return false; for (var i = 0; i < steps_to_route.length; i++) { if (!steps_to_route[i].isValid()) @@ -540,7 +570,8 @@ L.Handler.MultiPath = L.Handler.extend({ if (data) { var route = { 'geojson': data, - 'modified_indexes': steps_indexes, + 'old_steps_indexes': old_steps_indexes, + 'new_steps_indexes': new_steps_indexes, } this.fire('fetched_route', route); } @@ -552,10 +583,10 @@ L.Handler.MultiPath = L.Handler.extend({ this.spinner.stop() } ) - .catch(e => { - console.log("fetchRoute", e) - this.spinner.stop() - }) + // .catch(e => { + // console.log("fetchRoute", e) + // this.spinner.stop() + // }) } }, @@ -767,30 +798,90 @@ L.Handler.MultiPath = L.Handler.extend({ return markersFactory; }, - buildRouteLayers: function(geojson) { - var layer = L.featureGroup(); - geojson.geometries.forEach((geom, i) => { - var sub_layer = L.geoJson(geom); - sub_layer.step_idx = i - layer.addLayer(sub_layer); - }) + buildRouteLayers: function(data) { + geojson = data.geojson + old_steps_indexes = data.old_steps_indexes + new_steps_indexes = data.new_steps_indexes + + console.log("old_steps_indexes", old_steps_indexes) + console.log("new_steps_indexes", new_steps_indexes) + var newRouteLayer = L.featureGroup() + var newIndexOfPreviousLayer = -1 + + // The layers before the modified portion are added as-is + var oldLayers = this.layersOrderdByIdx() + console.log("oldLayers", oldLayers) + for (var i = 0; i < oldLayers.length && i < new_steps_indexes[0]; i++) { + newRouteLayer.addLayer(oldLayers[i]) + newIndexOfPreviousLayer = oldLayers[i].step_idx + } + + // The new steps are added + for (var i = 0; i < new_steps_indexes.length - 1; i++) { + newLayer = L.geoJson(geojson.geometries[i]) + newLayer.step_idx = ++newIndexOfPreviousLayer + newRouteLayer.addLayer(newLayer); + } + + // The last element of old_steps_indexes is where we start reusing the + // previous layers again + var old_steps_last_index = old_steps_indexes.at(-1) + var layer = this.stepIndexToLayer(old_steps_last_index) + if (layer) { + layer.step_idx = ++newIndexOfPreviousLayer + newRouteLayer.addLayer(layer) + } + + // Go through the remaining previous layers + for (var i = old_steps_last_index + 1; i < oldLayers.length; i++) { + newRouteLayer.addLayer(oldLayers[i]) + oldLayers[i].step_idx = ++newIndexOfPreviousLayer + } + + // this._routeLayer && this._routeLayer.eachLayer((oldLayer) => { + // var oldIndex = oldLayer.step_idx + // var newLayer = null + + // // If the layer is before the modified portion + // if (oldIndex < new_steps_indexes[0]) { + // newRouteLayer.addLayer(oldLayer); + // newIndexOfPreviousLayer = oldLayer.step_idx + // } + + // // The modified portion is reached: the new layers are added + // else if (oldIndex == new_steps_indexes[0]) { + // new_steps_indexes.slice(0, -1).forEach(markerIndex => { + // newLayer = L.geoJson(geom) + // newLayer.step_idx = newIndexOfPreviousLayer + 1 + // newRouteLayer.addLayer(newLayer); + // newIndexOfPreviousLayer = newLayer.step_idx + + // }) + // } + + // // After the modified portion + // else { + // oldLayer.step_idx = newIndexOfPreviousLayer + 1 + // newRouteLayer.addLayer(oldLayer) + // newIndexOfPreviousLayer = oldLayer.step_idx + // } + + // }) + + this._routeLayer = newRouteLayer return { - layer: layer, - serialized: null - // TODO: set serialized to something - }; + layer: newRouteLayer, + serialized: null, // TODO: set serialized to something + } }, onFetchedRoute: function(data) { var self = this; - // TODO: update the layers -> use data.modified_indexes - // Option 1: update the existing sublayers of the layer in buildRouteLayers - // Option 2: create a whole new layer in buildRouteLayers - var topology = this.buildRouteLayers(data.geojson); + var topology = this.buildRouteLayers(data); this.showPathGeom(topology.layer); - this.fire('computed_topology', {topology:topology.serialized}); + this.fire('computed_topology', {topology: topology.serialized}); // ## ONCE ## if (this.drawOnMouseMove) { From 4dbb444344514b00da6bc3055d1891f163dc3a62 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Thu, 16 May 2024 15:05:45 +0200 Subject: [PATCH 055/213] Add use of currently displayed layers indexes to replace them --- geotrek/core/static/core/multipath.js | 29 ++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index b3406f6b76..ebbd0a4ecb 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -233,6 +233,7 @@ L.Handler.MultiPath = L.Handler.extend({ this._container = map._container; this._guidesLayer = guidesLayer; this._routeLayer = null + this.currentStepsNb = 0 this.options = options; this.spinner = new Spinner() @@ -416,6 +417,7 @@ L.Handler.MultiPath = L.Handler.extend({ // If this was clicked, the marker should be close enough, snap it. self.forceMarkerToLayer(marker, layer); + console.log('onclick') pop.events.fire('placed'); }, @@ -433,9 +435,6 @@ L.Handler.MultiPath = L.Handler.extend({ pop.events.on('placed', () => { var currentStepIdx = self.getStepIdx(pop) - // Create the array of step indexes before the route is updated - var oldStepsIndexes = [] - // TODO console.log("placed") // Create the array of new step indexes after the route is updated @@ -446,7 +445,25 @@ L.Handler.MultiPath = L.Handler.extend({ if (currentStepIdx < self.steps.length - 1) newStepsIndexes.push(currentStepIdx + 1) - // TODO: send the right params + // Create the array of step indexes before the route is updated + var oldStepsIndexes + if (this.currentStepsNb == this.steps.length) // If a marker is being moved + oldStepsIndexes = [...newStepsIndexes] + else { // If a marker is being added + if (this.currentStepsNb == 1) // If it's the destination + oldStepsIndexes = [] + else + oldStepsIndexes = newStepsIndexes.slice(0, -1) + } + // TODO + // If mod: same OK + // If add middle: [1, 2] -> [1, 2, 3] + // If add 1st: [] -> [1, 2] OK + // If add 2nd: [0, 1] -> [0, 1, 2] + // If add 2nd to last: [2, 3] -> [2, 3, 4] + + this.currentStepsNb = this.steps.length + self.fetchRoute(oldStepsIndexes, newStepsIndexes) }); @@ -469,7 +486,8 @@ L.Handler.MultiPath = L.Handler.extend({ var step_idx = self.getStepIdx(pop) self.steps.splice(step_idx, 1); self.map.removeLayer(marker); - // TODO: send the right params + + this.currentStepsNb = this.steps.length self.fetchRoute( [step_idx - 1, step_idx, step_idx + 1], [step_idx - 1, step_idx] @@ -975,6 +993,7 @@ Geotrek.PointOnPolyline = function (marker) { this.events.fire('invalid'); }, 'dragend': function onDragEnd(e) { + console.log('dragend') this.events.fire('placed'); } }; From 466a9dd71781d156f8ab11af2ca7cf8727d9e451 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Thu, 16 May 2024 15:19:31 +0200 Subject: [PATCH 056/213] Fix replacing the currently displayed layers indexes by new ones --- geotrek/core/static/core/multipath.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index ebbd0a4ecb..f96bb372b4 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -247,18 +247,18 @@ L.Handler.MultiPath = L.Handler.extend({ return guidesLayer.getLayer(id); }; - this.stepIndexToLayer = function(idx) { - if (!this._routeLayer) + this.stepIndexToLayer = function(idx, layerArray) { + if (!layerArray) return null - for (var i = 0; i < this._routeLayer.length; i++) { - var layer = this._routeLayer[i] + for (var i = 0; i < layerArray.length; i++) { + var layer = layerArray[i] if (layer.step_idx == idx) return layer } return null; }; - this.layersOrderdByIdx = function() { + this.layersOrderedByIdx = function() { var layers = this._routeLayer ? this._routeLayer.__layerArray : [] var sortedLayers = layers.toSorted((first, second) => { return first.step_idx - second.step_idx @@ -456,9 +456,9 @@ L.Handler.MultiPath = L.Handler.extend({ oldStepsIndexes = newStepsIndexes.slice(0, -1) } // TODO - // If mod: same OK + // If mod: same // If add middle: [1, 2] -> [1, 2, 3] - // If add 1st: [] -> [1, 2] OK + // If add 1st: [] -> [1, 2] // If add 2nd: [0, 1] -> [0, 1, 2] // If add 2nd to last: [2, 3] -> [2, 3, 4] @@ -487,7 +487,7 @@ L.Handler.MultiPath = L.Handler.extend({ self.steps.splice(step_idx, 1); self.map.removeLayer(marker); - this.currentStepsNb = this.steps.length + this.currentStepsNb = self.steps.length self.fetchRoute( [step_idx - 1, step_idx, step_idx + 1], [step_idx - 1, step_idx] @@ -828,7 +828,7 @@ L.Handler.MultiPath = L.Handler.extend({ var newIndexOfPreviousLayer = -1 // The layers before the modified portion are added as-is - var oldLayers = this.layersOrderdByIdx() + var oldLayers = this.layersOrderedByIdx() console.log("oldLayers", oldLayers) for (var i = 0; i < oldLayers.length && i < new_steps_indexes[0]; i++) { newRouteLayer.addLayer(oldLayers[i]) @@ -845,7 +845,8 @@ L.Handler.MultiPath = L.Handler.extend({ // The last element of old_steps_indexes is where we start reusing the // previous layers again var old_steps_last_index = old_steps_indexes.at(-1) - var layer = this.stepIndexToLayer(old_steps_last_index) + var layer = this.stepIndexToLayer(old_steps_last_index, oldLayers) + console.log('layer', layer) if (layer) { layer.step_idx = ++newIndexOfPreviousLayer newRouteLayer.addLayer(layer) @@ -886,6 +887,7 @@ L.Handler.MultiPath = L.Handler.extend({ // } // }) + console.log("newRouteLayer", newRouteLayer.__layerArray) this._routeLayer = newRouteLayer return { From 116ef08ba2a96d8df79629b270973722fd204c4d Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Thu, 16 May 2024 16:32:27 +0200 Subject: [PATCH 057/213] Fix updating layers not working after removing a marker --- geotrek/core/static/core/multipath.js | 70 +++++---------------------- 1 file changed, 13 insertions(+), 57 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index f96bb372b4..3368101dfc 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -233,7 +233,7 @@ L.Handler.MultiPath = L.Handler.extend({ this._container = map._container; this._guidesLayer = guidesLayer; this._routeLayer = null - this.currentStepsNb = 0 + this._currentStepsNb = 0 this.options = options; this.spinner = new Spinner() @@ -417,7 +417,6 @@ L.Handler.MultiPath = L.Handler.extend({ // If this was clicked, the marker should be close enough, snap it. self.forceMarkerToLayer(marker, layer); - console.log('onclick') pop.events.fire('placed'); }, @@ -435,8 +434,6 @@ L.Handler.MultiPath = L.Handler.extend({ pop.events.on('placed', () => { var currentStepIdx = self.getStepIdx(pop) - console.log("placed") - // Create the array of new step indexes after the route is updated var newStepsIndexes = [] if (currentStepIdx > 0) @@ -447,22 +444,16 @@ L.Handler.MultiPath = L.Handler.extend({ // Create the array of step indexes before the route is updated var oldStepsIndexes - if (this.currentStepsNb == this.steps.length) // If a marker is being moved + if (this._currentStepsNb == this.steps.length) // If a marker is being moved oldStepsIndexes = [...newStepsIndexes] else { // If a marker is being added - if (this.currentStepsNb == 1) // If it's the destination + if (this._currentStepsNb == 1) // If it's the destination oldStepsIndexes = [] else oldStepsIndexes = newStepsIndexes.slice(0, -1) } - // TODO - // If mod: same - // If add middle: [1, 2] -> [1, 2, 3] - // If add 1st: [] -> [1, 2] - // If add 2nd: [0, 1] -> [0, 1, 2] - // If add 2nd to last: [2, 3] -> [2, 3, 4] - this.currentStepsNb = this.steps.length + this._currentStepsNb = this.steps.length self.fetchRoute(oldStepsIndexes, newStepsIndexes) }); @@ -487,7 +478,7 @@ L.Handler.MultiPath = L.Handler.extend({ self.steps.splice(step_idx, 1); self.map.removeLayer(marker); - this.currentStepsNb = self.steps.length + self._currentStepsNb = self.steps.length self.fetchRoute( [step_idx - 1, step_idx, step_idx + 1], [step_idx - 1, step_idx] @@ -601,10 +592,10 @@ L.Handler.MultiPath = L.Handler.extend({ this.spinner.stop() } ) - // .catch(e => { - // console.log("fetchRoute", e) - // this.spinner.stop() - // }) + .catch(e => { + console.log("fetchRoute", e) + this.spinner.stop() + }) } }, @@ -732,7 +723,7 @@ L.Handler.MultiPath = L.Handler.extend({ 'updateGeom': function(new_path_layer) { var prev_path_layer = current_path_layer; current_path_layer = new_path_layer; - + if (prev_path_layer) { self.map.removeLayer(prev_path_layer); } @@ -821,15 +812,11 @@ L.Handler.MultiPath = L.Handler.extend({ old_steps_indexes = data.old_steps_indexes new_steps_indexes = data.new_steps_indexes - console.log("old_steps_indexes", old_steps_indexes) - console.log("new_steps_indexes", new_steps_indexes) - var newRouteLayer = L.featureGroup() var newIndexOfPreviousLayer = -1 // The layers before the modified portion are added as-is var oldLayers = this.layersOrderedByIdx() - console.log("oldLayers", oldLayers) for (var i = 0; i < oldLayers.length && i < new_steps_indexes[0]; i++) { newRouteLayer.addLayer(oldLayers[i]) newIndexOfPreviousLayer = oldLayers[i].step_idx @@ -846,7 +833,6 @@ L.Handler.MultiPath = L.Handler.extend({ // previous layers again var old_steps_last_index = old_steps_indexes.at(-1) var layer = this.stepIndexToLayer(old_steps_last_index, oldLayers) - console.log('layer', layer) if (layer) { layer.step_idx = ++newIndexOfPreviousLayer newRouteLayer.addLayer(layer) @@ -858,37 +844,6 @@ L.Handler.MultiPath = L.Handler.extend({ oldLayers[i].step_idx = ++newIndexOfPreviousLayer } - // this._routeLayer && this._routeLayer.eachLayer((oldLayer) => { - // var oldIndex = oldLayer.step_idx - // var newLayer = null - - // // If the layer is before the modified portion - // if (oldIndex < new_steps_indexes[0]) { - // newRouteLayer.addLayer(oldLayer); - // newIndexOfPreviousLayer = oldLayer.step_idx - // } - - // // The modified portion is reached: the new layers are added - // else if (oldIndex == new_steps_indexes[0]) { - // new_steps_indexes.slice(0, -1).forEach(markerIndex => { - // newLayer = L.geoJson(geom) - // newLayer.step_idx = newIndexOfPreviousLayer + 1 - // newRouteLayer.addLayer(newLayer); - // newIndexOfPreviousLayer = newLayer.step_idx - - // }) - // } - - // // After the modified portion - // else { - // oldLayer.step_idx = newIndexOfPreviousLayer + 1 - // newRouteLayer.addLayer(oldLayer) - // newIndexOfPreviousLayer = oldLayer.step_idx - // } - - // }) - console.log("newRouteLayer", newRouteLayer.__layerArray) - this._routeLayer = newRouteLayer return { layer: newRouteLayer, @@ -899,6 +854,8 @@ L.Handler.MultiPath = L.Handler.extend({ onFetchedRoute: function(data) { var self = this; + if (this._routeLayer) + self.map.removeLayer(this._routeLayer) var topology = this.buildRouteLayers(data); this.showPathGeom(topology.layer); this.fire('computed_topology', {topology: topology.serialized}); @@ -916,7 +873,7 @@ L.Handler.MultiPath = L.Handler.extend({ } if (self.markersFactory.isDragging()) { return; - } + } dragTimer = date; @@ -995,7 +952,6 @@ Geotrek.PointOnPolyline = function (marker) { this.events.fire('invalid'); }, 'dragend': function onDragEnd(e) { - console.log('dragend') this.events.fire('placed'); } }; From 6ab884816c840cd7068d9d80640bc3cac2890cc7 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Fri, 17 May 2024 15:02:10 +0200 Subject: [PATCH 058/213] Bump leaflet.textpath.js to v1.1.0 --- geotrek/core/static/core/leaflet.textpath.js | 265 ++++++++++++------- 1 file changed, 169 insertions(+), 96 deletions(-) diff --git a/geotrek/core/static/core/leaflet.textpath.js b/geotrek/core/static/core/leaflet.textpath.js index f0b783334f..cc01ca02d5 100644 --- a/geotrek/core/static/core/leaflet.textpath.js +++ b/geotrek/core/static/core/leaflet.textpath.js @@ -1,105 +1,178 @@ /* + * Leaflet.TextPath - Shows text along a polyline * Inspired by Tom Mac Wright article : * http://mapbox.com/osmdev/2012/11/20/getting-serious-about-svg/ */ -var PolylineTextPath = { - - __updatePath: L.Polyline.prototype._updatePath, - __bringToFront: L.Polyline.prototype.bringToFront, - __onAdd: L.Polyline.prototype.onAdd, - __onRemove: L.Polyline.prototype.onRemove, - - onAdd: function (map) { - this.__onAdd.call(this, map); - this._textRedraw(); - }, - - onRemove: function (map) { - map = map || this._map; - if (map && this._textNode) - map._pathRoot.removeChild(this._textNode); - this.__onRemove.call(this, map); - }, - - bringToFront: function () { - this.__bringToFront.call(this); - this._textRedraw(); - }, - - _updatePath: function () { - this.__updatePath.call(this); - this._textRedraw(); - }, - - _textRedraw: function () { - var text = this._text, - options = this._textOptions; - if (text) { - this.setText(null).setText(text, options); - } - }, - - setText: function (text, options) { - this._text = text; - this._textOptions = options; - - var defaults = {repeat: false, fillColor: 'black', attributes: {}}; - options = L.Util.extend(defaults, options); - - /* If empty text, hide */ - if (!text) { - if (this._textNode) - this._map._pathRoot.removeChild(this._textNode); - return this; - } - - text = text.replace(/ /g, '\u00A0'); // Non breakable spaces - var id = 'pathdef-' + L.Util.stamp(this); - var svg = this._map._pathRoot; - this._path.setAttribute('id', id); - - if (options.repeat) { - /* Compute single pattern length */ - var pattern = L.Path.prototype._createElement('text'); +(function () { + + var __onAdd = L.Polyline.prototype.onAdd, + __onRemove = L.Polyline.prototype.onRemove, + __updatePath = L.Polyline.prototype._updatePath, + __bringToFront = L.Polyline.prototype.bringToFront; + + + var PolylineTextPath = { + + onAdd: function (map) { + __onAdd.call(this, map); + this._textRedraw(); + }, + + onRemove: function (map) { + map = map || this._map; + if (map && this._textNode) + map._pathRoot.removeChild(this._textNode); + __onRemove.call(this, map); + }, + + bringToFront: function () { + __bringToFront.call(this); + this._textRedraw(); + }, + + _updatePath: function () { + __updatePath.call(this); + this._textRedraw(); + }, + + _textRedraw: function () { + var text = this._text, + options = this._textOptions; + if (text) { + this.setText(null).setText(text, options); + } + }, + + setText: function (text, options) { + this._text = text; + this._textOptions = options; + + /* If not in SVG mode or Polyline not added to map yet return */ + /* setText will be called by onAdd, using value stored in this._text */ + if (!L.Browser.svg || typeof this._map === 'undefined') { + return this; + } + + var defaults = { + repeat: false, + fillColor: 'black', + attributes: {}, + below: false, + }; + options = L.Util.extend(defaults, options); + + /* If empty text, hide */ + if (!text) { + if (this._textNode && this._textNode.parentNode) { + this._map._pathRoot.removeChild(this._textNode); + + /* delete the node, so it will not be removed a 2nd time if the layer is later removed from the map */ + delete this._textNode; + } + return this; + } + + text = text.replace(/ /g, '\u00A0'); // Non breakable spaces + var id = 'pathdef-' + L.Util.stamp(this); + var svg = this._map._pathRoot; + this._path.setAttribute('id', id); + + if (options.repeat) { + /* Compute single pattern length */ + var pattern = L.Path.prototype._createElement('text'); + for (var attr in options.attributes) + pattern.setAttribute(attr, options.attributes[attr]); + pattern.appendChild(document.createTextNode(text)); + svg.appendChild(pattern); + var alength = pattern.getComputedTextLength(); + svg.removeChild(pattern); + + /* Create string as long as path */ + text = new Array(Math.ceil(this._path.getTotalLength() / alength)).join(text); + } + + /* Put it along the path using textPath */ + var textNode = L.Path.prototype._createElement('text'), + textPath = L.Path.prototype._createElement('textPath'); + + var dy = options.offset || this._path.getAttribute('stroke-width'); + + textPath.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", '#'+id); + textNode.setAttribute('dy', dy); for (var attr in options.attributes) - pattern.setAttribute(attr, options.attributes[attr]); - pattern.appendChild(document.createTextNode(text)); - svg.appendChild(pattern); - var alength = pattern.getComputedTextLength(); - svg.removeChild(pattern); - - /* Create string as long as path */ - text = new Array(Math.ceil(this._path.getTotalLength() / alength)).join(text); + textNode.setAttribute(attr, options.attributes[attr]); + textPath.appendChild(document.createTextNode(text)); + textNode.appendChild(textPath); + this._textNode = textNode; + + if (options.below) { + svg.insertBefore(textNode, svg.firstChild); + } + else { + svg.appendChild(textNode); + } + + /* Center text according to the path's bounding box */ + if (options.center) { + var textLength = textNode.getComputedTextLength(); + var pathLength = this._path.getTotalLength(); + /* Set the position for the left side of the textNode */ + textNode.setAttribute('dx', ((pathLength / 2) - (textLength / 2))); + } + + /* Change label rotation (if required) */ + console.log(options.orientation) + if (options.orientation) { + var rotateAngle = 0; + switch (options.orientation) { + case 'flip': + rotateAngle = 180; + break; + case 'perpendicular': + rotateAngle = 90; + break; + default: + rotateAngle = options.orientation; + } + + var rotatecenterX = (textNode.getBBox().x + textNode.getBBox().width / 2); + var rotatecenterY = (textNode.getBBox().y + textNode.getBBox().height / 2); + textNode.setAttribute('transform','rotate(' + rotateAngle + ' ' + rotatecenterX + ' ' + rotatecenterY + ')'); + } + + /* Initialize mouse events for the additional nodes */ + if (this.options.clickable) { + if (L.Browser.svg || !L.Browser.vml) { + textPath.setAttribute('class', 'leaflet-clickable'); + } + + L.DomEvent.on(textNode, 'click', this._onMouseClick, this); + + var events = ['dblclick', 'mousedown', 'mouseover', + 'mouseout', 'mousemove', 'contextmenu']; + for (var i = 0; i < events.length; i++) { + L.DomEvent.on(textNode, events[i], this._fireMouseEvent, this); + } + } + + return this; } - - /* Put it along the path using textPath */ - var textNode = L.Path.prototype._createElement('text'), - textPath = L.Path.prototype._createElement('textPath'); - - var dy = options.offset || this._path.getAttribute('stroke-width'); - - textPath.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", '#'+id); - textNode.setAttribute('dy', dy); - for (var attr in options.attributes) - textNode.setAttribute(attr, options.attributes[attr]); - textPath.appendChild(document.createTextNode(text)); - textNode.appendChild(textPath); - svg.appendChild(textNode); - this._textNode = textNode; - return this; - } -}; - -L.Polyline.include(PolylineTextPath); - -L.LayerGroup.include({ - setText: function(text, options) { - for (var layer in this._layers) { - if (typeof this._layers[layer].setText === 'function') { - this._layers[layer].setText(text, options); + }; + + L.Polyline.include(PolylineTextPath); + + L.LayerGroup.include({ + setText: function(text, options) { + for (var layer in this._layers) { + if (typeof this._layers[layer].setText === 'function') { + this._layers[layer].setText(text, options); + } } + return this; } - return this; - } -}); + }); + + + + })(); \ No newline at end of file From 25dda92c9f3ae91580e2ba1db4a018bb7e698d7d Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Fri, 17 May 2024 15:25:25 +0200 Subject: [PATCH 059/213] Move leaflet static files into a vendor directory --- .../core/static/{core => vendor}/leaflet.lineextremities.js | 0 .../leaflet.textpath.js => vendor/leaflet.textpath.v1.1.0.js} | 0 geotrek/settings/base.py | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename geotrek/core/static/{core => vendor}/leaflet.lineextremities.js (100%) rename geotrek/core/static/{core/leaflet.textpath.js => vendor/leaflet.textpath.v1.1.0.js} (100%) diff --git a/geotrek/core/static/core/leaflet.lineextremities.js b/geotrek/core/static/vendor/leaflet.lineextremities.js similarity index 100% rename from geotrek/core/static/core/leaflet.lineextremities.js rename to geotrek/core/static/vendor/leaflet.lineextremities.js diff --git a/geotrek/core/static/core/leaflet.textpath.js b/geotrek/core/static/vendor/leaflet.textpath.v1.1.0.js similarity index 100% rename from geotrek/core/static/core/leaflet.textpath.js rename to geotrek/core/static/vendor/leaflet.textpath.v1.1.0.js diff --git a/geotrek/settings/base.py b/geotrek/settings/base.py index 60ae21b2c3..79665fc062 100644 --- a/geotrek/settings/base.py +++ b/geotrek/settings/base.py @@ -477,8 +477,8 @@ def api_bbox(bbox, buffer): 'SPATIAL_EXTENT': api_bbox(SPATIAL_EXTENT, VIEWPORT_MARGIN), 'NO_GLOBALS': False, 'PLUGINS': { - 'geotrek': {'js': ['core/leaflet.lineextremities.js', - 'core/leaflet.textpath.js', + 'geotrek': {'js': ['vendor/leaflet.lineextremities.js', + 'vendor/leaflet.textpath.v1.1.0.js', 'common/points_reference.js', 'trekking/parking_location.js']}, 'topofields': {'js': ['core/geotrek.forms.snap.js', From 527ce43a3d9060ef90d9d7107bfad1351556b2c6 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Tue, 21 May 2024 10:14:33 +0200 Subject: [PATCH 060/213] Bump leaflet.lineextremities.js to v0.1.1 --- ...s.js => leaflet.lineextremities.v0.1.1.js} | 20 +++++++++++++------ geotrek/settings/base.py | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) rename geotrek/core/static/vendor/{leaflet.lineextremities.js => leaflet.lineextremities.v0.1.1.js} (90%) diff --git a/geotrek/core/static/vendor/leaflet.lineextremities.js b/geotrek/core/static/vendor/leaflet.lineextremities.v0.1.1.js similarity index 90% rename from geotrek/core/static/vendor/leaflet.lineextremities.js rename to geotrek/core/static/vendor/leaflet.lineextremities.v0.1.1.js index dc68ef8d76..3e0d887f55 100644 --- a/geotrek/core/static/vendor/leaflet.lineextremities.js +++ b/geotrek/core/static/vendor/leaflet.lineextremities.v0.1.1.js @@ -43,6 +43,14 @@ var PolylineExtremities = { // http://stackoverflow.com/a/10477334 'path': 'M 22.5, 22.5 m -20, 0 a 20,20 0 1,0 40,0 a 20,20 0 1,0 -40,0' }, + arrowM: { + 'viewBox': '0 0 10 10', + 'refX': '1', + 'refY': '5', + 'markerUnits': 'strokeWidth', + 'orient': 'auto', + 'path': 'M 0 0 L 10 5 L 0 10 z' + }, }, onAdd: function (map) { @@ -71,7 +79,7 @@ var PolylineExtremities = { /* If not in SVG mode or Polyline not added to map yet return */ /* showExtremities will be called by onAdd, using value stored in this._pattern */ if (!L.Browser.svg || typeof this._map === 'undefined') { - return this; + return this; } /* If empty pattern, hide */ @@ -87,12 +95,12 @@ var PolylineExtremities = { var defsNode; if (L.DomUtil.hasClass(svg, 'defs')) { defsNode = svg.getElementById('defs'); - - } else{ + } else { L.DomUtil.addClass(svg, 'defs'); defsNode = L.Path.prototype._createElement('defs'); defsNode.setAttribute('id', 'defs'); - svg.appendChild(defsNode); + var svgFirstChild = svg.childNodes[0]; + svg.insertBefore(defsNode, svgFirstChild); } // Add the marker to the line @@ -117,7 +125,7 @@ var PolylineExtremities = { } // Copy the path apparence to the marker - var styleProperties = ['stroke', 'stroke-opacity']; + var styleProperties = ['class', 'stroke', 'stroke-opacity']; for (var i=0; i Date: Tue, 21 May 2024 12:31:07 +0200 Subject: [PATCH 061/213] Fix missing path layer when an unlinkable via marker is removed --- geotrek/core/static/core/multipath.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index 3368101dfc..823971cb67 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -237,6 +237,9 @@ L.Handler.MultiPath = L.Handler.extend({ this.options = options; this.spinner = new Spinner() + // Is the currently displayed path valid? i.e are all its markers linkable? + this._pathIsValid = null + // markers this.markersFactory = this.getMarkers(); @@ -250,6 +253,7 @@ L.Handler.MultiPath = L.Handler.extend({ this.stepIndexToLayer = function(idx, layerArray) { if (!layerArray) return null + for (var i = 0; i < layerArray.length; i++) { var layer = layerArray[i] if (layer.step_idx == idx) @@ -589,6 +593,7 @@ L.Handler.MultiPath = L.Handler.extend({ // If the promise was rejected: response => { console.log("fetchRoute:", response) + this._pathIsValid = false this.spinner.stop() } ) @@ -817,6 +822,11 @@ L.Handler.MultiPath = L.Handler.extend({ // The layers before the modified portion are added as-is var oldLayers = this.layersOrderedByIdx() + + console.log("oldLayers", oldLayers) + console.log("old_steps_indexes", old_steps_indexes) + console.log("new_steps_indexes", new_steps_indexes) + for (var i = 0; i < oldLayers.length && i < new_steps_indexes[0]; i++) { newRouteLayer.addLayer(oldLayers[i]) newIndexOfPreviousLayer = oldLayers[i].step_idx @@ -832,7 +842,16 @@ L.Handler.MultiPath = L.Handler.extend({ // The last element of old_steps_indexes is where we start reusing the // previous layers again var old_steps_last_index = old_steps_indexes.at(-1) + console.log("this._currentStepsNb", this._currentStepsNb) + if (this._pathIsValid == false && old_steps_indexes.length > new_steps_indexes.length) { + // If an unlinkable via marker is being removed, the invalid part of + // the path was not displayed. So at this point, there is one more + // marker (being removed now), but there isn't one more layer. + // Hence, all following layer indexes must be left-shifted by 1 + old_steps_last_index-- + } var layer = this.stepIndexToLayer(old_steps_last_index, oldLayers) + console.log("layer", layer) if (layer) { layer.step_idx = ++newIndexOfPreviousLayer newRouteLayer.addLayer(layer) @@ -845,6 +864,7 @@ L.Handler.MultiPath = L.Handler.extend({ } this._routeLayer = newRouteLayer + console.log("newRouteLayer end", newRouteLayer.__layerArray) return { layer: newRouteLayer, serialized: null, // TODO: set serialized to something @@ -858,6 +878,7 @@ L.Handler.MultiPath = L.Handler.extend({ self.map.removeLayer(this._routeLayer) var topology = this.buildRouteLayers(data); this.showPathGeom(topology.layer); + this._pathIsValid = true this.fire('computed_topology', {topology: topology.serialized}); // ## ONCE ## From b83fb9f50ea5b1894dc726f04b2900aba3ec3714 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Tue, 21 May 2024 17:34:32 +0200 Subject: [PATCH 062/213] Remove console logs --- geotrek/core/static/core/multipath.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index 823971cb67..3d1c9eeb84 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -823,10 +823,6 @@ L.Handler.MultiPath = L.Handler.extend({ // The layers before the modified portion are added as-is var oldLayers = this.layersOrderedByIdx() - console.log("oldLayers", oldLayers) - console.log("old_steps_indexes", old_steps_indexes) - console.log("new_steps_indexes", new_steps_indexes) - for (var i = 0; i < oldLayers.length && i < new_steps_indexes[0]; i++) { newRouteLayer.addLayer(oldLayers[i]) newIndexOfPreviousLayer = oldLayers[i].step_idx @@ -842,7 +838,6 @@ L.Handler.MultiPath = L.Handler.extend({ // The last element of old_steps_indexes is where we start reusing the // previous layers again var old_steps_last_index = old_steps_indexes.at(-1) - console.log("this._currentStepsNb", this._currentStepsNb) if (this._pathIsValid == false && old_steps_indexes.length > new_steps_indexes.length) { // If an unlinkable via marker is being removed, the invalid part of // the path was not displayed. So at this point, there is one more @@ -851,7 +846,6 @@ L.Handler.MultiPath = L.Handler.extend({ old_steps_last_index-- } var layer = this.stepIndexToLayer(old_steps_last_index, oldLayers) - console.log("layer", layer) if (layer) { layer.step_idx = ++newIndexOfPreviousLayer newRouteLayer.addLayer(layer) @@ -864,7 +858,6 @@ L.Handler.MultiPath = L.Handler.extend({ } this._routeLayer = newRouteLayer - console.log("newRouteLayer end", newRouteLayer.__layerArray) return { layer: newRouteLayer, serialized: null, // TODO: set serialized to something From 393468c6780ae105b41fce3a94a9affd625e46ab Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Wed, 22 May 2024 15:57:45 +0200 Subject: [PATCH 063/213] Add coloring of the markers when one is isolated --- .../core/images/marker-bound-disabled.png | Bin 0 -> 1957 bytes .../core/images/marker-drag-disabled.png | Bin 0 -> 685 bytes .../core/images/marker-drag-highlighted.png | Bin 0 -> 867 bytes geotrek/core/static/core/multipath.js | 41 ++++++++++++++---- geotrek/core/static/core/style.css | 16 +++++++ 5 files changed, 48 insertions(+), 9 deletions(-) create mode 100644 geotrek/core/static/core/images/marker-bound-disabled.png create mode 100644 geotrek/core/static/core/images/marker-drag-disabled.png create mode 100644 geotrek/core/static/core/images/marker-drag-highlighted.png diff --git a/geotrek/core/static/core/images/marker-bound-disabled.png b/geotrek/core/static/core/images/marker-bound-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..62645dad37444a9da17e7e7277d4729c03992f16 GIT binary patch literal 1957 zcmV;W2U_@vP)P001cn1^@s6z>|W`000MUNkl+2U;t=7RlK0fsE<44lzb!2^OB`+^;>eR_6GcYi4$BY>> z3fr>PCdh$Zdy=fSx}6ab5!9nc59->rD=9jK6o(?VbUGa|I8{|uQDtQ%SuB=2y?ghb zG;7wZ>d(Pi1xfxn`Ce#n=%C2RNYd-|^zPj|dj0x!3rD@Zy(yqa0O|QxWn~5Z{m)$r z2??GYA0L0Qxo``RZCkh54G{)?|Ni}nLquk?nZ0%+H@BAlC<-%c_z4LKp*L^d2(!An zx`aiG7M;|9KGEE?X;VQ|RFtt_zkXC+UQSNMNp1jOX(vHc4V8qe+21svpr9afI2?5C z+BFId4IMOZ-n@KGR+LgwQj!9Lf_4syilXPwpTir8JD$_Y<`su{-LYdwdjI~te0Otm z6B+#OWFh>7het5`D!O&+7H!_VSqwBX*|>3|GG)pXs;zxP=GPX|x$DI`6axYR$j{GD z3dB!06CLm@X9u`i;#l`$@CHpx#YXu}VH8p`1PZ$gadi?k?xx2fI zK~N713ln2SMMZS`_HBxbi=*@B&l6+^4jd@prDdh$bZ}_hnUUQ|{{H^dty?#d+qG+# z8V?SYY1F7up?15SDk>_(D*yxs2TSrHymIAAGDI3kuVaS%AeWh$N!`15CsZMvLnS)7 z%pEp68I9l2l`DU7WNHHiWc~UiW!khjdhp;uBMT=38d&+}&6_lP_H5#YlTaai2o-ld z!Kr=w_R)k16VNB(5FEsJ^jBF~85I;1tk!MWvIWKCm^g7F6+V1OdKN;0VFVqH&`q8^ zS$YNEwFe;d$rIC4y2>6KjYcw=Ou`0HK=T5OL!6=8v13P1R(=r^6GQj!-zN_b5AhaV zZZTVE@X*0x04$mwmm3#0y#Dav106nmm||mN;k7sgeP3T+cEX2pZruDGOuDkLdCZtG z^ytx}Mj?P8?wl(cFkpZfYyRM^ho=WwYprzp^l5^=so10@pFz5G=^{=rlV2q#>(;GP z5E3mX2n#}i3VHJ62_+^bN^d|}ds@#x2UXXzXHV%bgbQKn)29!;eft(RQv_tiiWLIF zb&V*HPSHtR!^6X+sise#P8c&<;sJ(U!5G=Uf4}6cw6s)okSl)904Sy*brBI%gxOdAb^5hO-+r25&$DcjF9|kdSHk!feCW7VZ#QsPmUfv9c8t? z%146%U28@FVIIM27%eI+BD2LT&4wQ8+qbV&AbLa_F<@aZL@>)5diwO~{q5Vg4{jVQ zJZDOnO|X_~42G%zXQf6U{GV*}5V2T%TQe`PTMvwFyeWqb9ZGla-bFJGS+#1_y%r#A)~s33 z#ozywfk#4qe!euO#zr%)wqeuL_KG%2Y;`s&EGn$pyLWG(Ce>oyB`#g6d^>I&Racui z-W8G?S8SXd%6gvELzxA7h9~p)TveCa1YwFPTv6+>PC>ghHmdhcyAdofdJyKUtgL#z za6Frbwbesn;lhQ=q)C&cgR!%bxnLHeQ)}mzO8ytK~+m zFD_pW7{1$Vbv#6)rMr1SEje=JNb2Vm*yeG!c=6&azBpnq`0m`fBe_u@c8aP77MKVa zE0-=^qKu4;R_(P3!i^Nc2VK#~kt2~z>3Vdv_EZ)&8asBZG%pX&&Bu-%+xj^Rm3KQY zAt9lJ@33GD$(-yQ*&kX@+U*XWU45umuU>TV;zc@p_H3KB+675UN{Z#R`zI;~2b;k(!Z&RKhwB7Z@b&9g$&<&volhRz1U$-dEC0gH7vEmkU4lTFZ(3#a=+X3|{3Vq= zFO?lKWXKRXm(tVIUD|Ld0Fe3f=cjb;+<8400bRa)nQ%UV1^>U~4?ZH|+GF9ex|%D` zoHS-;kC0)Mot=%!^5s2L`=5a#9IpYe;JYg9;K75Y{}cqq=FXj)#k)SHyu9qunKS8;Ujpz|Od!m=#>nHNfRBx6 rW|8~9fWYYT<;!z<(vJR80Lc9db8@n2#5kD;00000NkvXXu0mjfjVYv~ literal 0 HcmV?d00001 diff --git a/geotrek/core/static/core/images/marker-drag-disabled.png b/geotrek/core/static/core/images/marker-drag-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..94833e975d5d87410170f8458a3423f6b117e39b GIT binary patch literal 685 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+3?vf;>QWgP7{vp8LR^8|k01Y4)igwghcq-a zGy-v5)#1a3mo8n}(A3ZfDKDm--P;NgP@4jedi_`u=jhK3X8POMqKzM-)hXw1RGhYubE3LgT31BVVAK5!r` zB)p)ou%W5(08krH*TDma4+A9*9ykc%0YPI^Q)6S}L7*&92CS!{p&1B)W*j(l@X!Gu z0_g-Y>uRbR8=FD=!$8r4JC7efc=!;|Fd%MhZY(IsYXVx+(Dd=i#|O{f16@C3^5i*l zmZqnsJb&{ zc~gDrtIup~?G8Nse@?FO>F!Y$Nwz)dRd6iUhF3Ut=9Ej$Gc+%BymU!>BNBh^*(cU# zF&a^`;txy_n?5bPsYob#>lQUm?W-%c?F;M|-uz_d*|lz)x9z>$@##&I>XN*-TdjYy ze?6@BjoaH@=J!+!J4@X;Tc=+3o!hP@J57Jp^ovs;&YGRVU7YmB;;Ov6K@6jnZeW5 K&t;ucLK6V>1b}@2 literal 0 HcmV?d00001 diff --git a/geotrek/core/static/core/images/marker-drag-highlighted.png b/geotrek/core/static/core/images/marker-drag-highlighted.png new file mode 100644 index 0000000000000000000000000000000000000000..6beaf7fc5ae3a1bc1de8f2e99d1c9a826cfe0e42 GIT binary patch literal 867 zcmXw0dq`7Z6hGhhb$3e;(z2(2Z0qLS!rYSRp_0ytF5kH_Tao6R7-e0OG}~egdpHuL z5;W}(7Yc?{2uXy9v||56Q0pZNk}wDgqN42K-PgI6ANQQ|JFnmQ?)f^(OAF%T*2Doo zyuHx23vHq*iy7+LrDy5^$ffdPM?Si-*i4(pQ&sISnM^vJl$EvJ*X9X5i3|pV;vx}% zVl6O+mQYGi7&N#V_U&~7A&g*v5iFq;7%&1TI;{k~?%N|{*rB#jf&}wvDrf|p$dFCt zq74l$H+O49O<_@T3v?wAqs^UP)=dx@+7p?s;V>o`g^e4Dg5wxi%!#g(O)YO#E=aVE!mgR6b%sJnAJtGab)84$R zv=Wwiha=HP-gM`z^6&Hw{0B-@C|Q7L`-EAA^G8a0rGl1oMH%k=hY&}i3lQTuM&+s@|UJy;HBZ zE!Fq$f0H%tnWKKIIiWV8!jda5u^UO_>gqce`^Wvk%cFst*Y2Mm8d_0ka~9Zg@~X-X zmc5+yV&crbJiql&VtMMe_1%WF z?9kH}xjQm8v>VpW1d==6JRi^Tii#)G{?u>nDzA_`ssj3BfqOwmWAW;TdGGgIlHLV| zcY2zP-R81AyT7%5wM(W~&-Ld@4`dhR)m3KPnq=U-ZvOOj?&?Q;9kAz@+HP5E{C@%K CI!Ukq literal 0 HcmV?d00001 diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index 3d1c9eeb84..3b8fad4d83 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -237,8 +237,8 @@ L.Handler.MultiPath = L.Handler.extend({ this.options = options; this.spinner = new Spinner() - // Is the currently displayed path valid? i.e are all its markers linkable? - this._pathIsValid = null + // Is the currently displayed route valid? i.e are all its markers linkable? + this._routeIsValid = null // markers this.markersFactory = this.getMarkers(); @@ -306,6 +306,7 @@ L.Handler.MultiPath = L.Handler.extend({ })(); this.on('fetched_route', this.onFetchedRoute, this); + this.on('invalid_route', this.onInvalidRoute, this); }, setState: function(state, autocompute) { @@ -459,7 +460,7 @@ L.Handler.MultiPath = L.Handler.extend({ this._currentStepsNb = this.steps.length - self.fetchRoute(oldStepsIndexes, newStepsIndexes) + self.fetchRoute(oldStepsIndexes, newStepsIndexes, pop) }); return pop; @@ -485,7 +486,8 @@ L.Handler.MultiPath = L.Handler.extend({ self._currentStepsNb = self.steps.length self.fetchRoute( [step_idx - 1, step_idx, step_idx + 1], - [step_idx - 1, step_idx] + [step_idx - 1, step_idx], + pop ); } @@ -525,10 +527,11 @@ L.Handler.MultiPath = L.Handler.extend({ return cookieValue; }, - fetchRoute: function(old_steps_indexes, new_steps_indexes) { + fetchRoute: function(old_steps_indexes, new_steps_indexes, pop) { /* old_steps_indexes: indexes of the steps for which to update the route new_steps_indexes: indexes of these steps after the route is updated + pop (PointOnPolyline): step that is being added/modified/deleted */ var steps_to_route = [] @@ -588,12 +591,12 @@ L.Handler.MultiPath = L.Handler.extend({ } this.fire('fetched_route', route); } - this.spinner.stop() + this.spinner.stop() // TODO: put this in a .finally() }, // If the promise was rejected: response => { console.log("fetchRoute:", response) - this._pathIsValid = false + this.fire('invalid_route', pop) this.spinner.stop() } ) @@ -838,7 +841,7 @@ L.Handler.MultiPath = L.Handler.extend({ // The last element of old_steps_indexes is where we start reusing the // previous layers again var old_steps_last_index = old_steps_indexes.at(-1) - if (this._pathIsValid == false && old_steps_indexes.length > new_steps_indexes.length) { + if (this._routeIsValid == false && old_steps_indexes.length > new_steps_indexes.length) { // If an unlinkable via marker is being removed, the invalid part of // the path was not displayed. So at this point, there is one more // marker (being removed now), but there isn't one more layer. @@ -871,7 +874,7 @@ L.Handler.MultiPath = L.Handler.extend({ self.map.removeLayer(this._routeLayer) var topology = this.buildRouteLayers(data); this.showPathGeom(topology.layer); - this._pathIsValid = true + this._routeIsValid = true this.fire('computed_topology', {topology: topology.serialized}); // ## ONCE ## @@ -932,6 +935,26 @@ L.Handler.MultiPath = L.Handler.extend({ }; this.map.on('mousemove', this.drawOnMouseMove); + }, + + onInvalidRoute: function(pop) { + this._routeIsValid = false + + if (this.steps.length <= 2) + return + + // Highlight the invalid marker + L.DomUtil.removeClass(pop.marker._icon, 'marker-snapped'); + L.DomUtil.addClass(pop.marker._icon, 'marker-highlighted'); + + // Set the other markers to grey + this.steps.forEach(step => { + console.log(step, pop) + if (step._leaflet_id != pop._leaflet_id) { + L.DomUtil.removeClass(step.marker._icon, 'marker-snapped'); + L.DomUtil.addClass(step.marker._icon, 'marker-disabled'); + } + }) } }); diff --git a/geotrek/core/static/core/style.css b/geotrek/core/static/core/style.css index b84268a224..8d29aa9e4e 100644 --- a/geotrek/core/static/core/style.css +++ b/geotrek/core/static/core/style.css @@ -56,6 +56,22 @@ background-image: url('images/marker-target.png'); } +.marker-source.marker-disabled { + background-image: url('images/marker-bound-disabled.png'); +} + +.marker-target.marker-disabled { + background-image: url('images/marker-bound-disabled.png'); +} + +.marker-drag.marker-disabled { + background-image: url('images/marker-drag-disabled.png'); +} + +.marker-drag.marker-highlighted { + background-image: url('images/marker-drag-highlighted.png'); +} + .marker-source.marker-snapped { background-image: url('images/marker-source-snap.png'); } From fae8b3cf6cd398063e14fc6973f9041be31b72cb Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Wed, 22 May 2024 16:04:57 +0200 Subject: [PATCH 064/213] Add coloring of all markers to normal appearance when back to normal --- geotrek/core/static/core/multipath.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index 3b8fad4d83..efb1d922d2 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -870,6 +870,13 @@ L.Handler.MultiPath = L.Handler.extend({ onFetchedRoute: function(data) { var self = this; + // Reset all the markers to 'snapped' appearance + this.steps.forEach(step => { + L.DomUtil.removeClass(step.marker._icon, 'marker-highlighted'); + L.DomUtil.removeClass(step.marker._icon, 'marker-disabled'); + L.DomUtil.addClass(step.marker._icon, 'marker-snapped'); + }) + if (this._routeLayer) self.map.removeLayer(this._routeLayer) var topology = this.buildRouteLayers(data); From 8c9330c014a20dc036056448c276f11b395933b3 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Wed, 22 May 2024 16:19:26 +0200 Subject: [PATCH 065/213] Add enabling/disabling of markers drag when a route is correct/incorrect --- geotrek/core/static/core/multipath.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index efb1d922d2..ce0c0d88f0 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -875,6 +875,7 @@ L.Handler.MultiPath = L.Handler.extend({ L.DomUtil.removeClass(step.marker._icon, 'marker-highlighted'); L.DomUtil.removeClass(step.marker._icon, 'marker-disabled'); L.DomUtil.addClass(step.marker._icon, 'marker-snapped'); + step.marker.dragging.enable(); }) if (this._routeLayer) @@ -954,12 +955,12 @@ L.Handler.MultiPath = L.Handler.extend({ L.DomUtil.removeClass(pop.marker._icon, 'marker-snapped'); L.DomUtil.addClass(pop.marker._icon, 'marker-highlighted'); - // Set the other markers to grey + // Set the other markers to grey and disable them this.steps.forEach(step => { - console.log(step, pop) if (step._leaflet_id != pop._leaflet_id) { L.DomUtil.removeClass(step.marker._icon, 'marker-snapped'); L.DomUtil.addClass(step.marker._icon, 'marker-disabled'); + step.marker.dragging.disable(); } }) } From 98b5b9a75b93b7baf18e63d6340f623ebfba4d54 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Wed, 22 May 2024 16:34:03 +0200 Subject: [PATCH 066/213] Add enabling/disabling of markers deletion when a route is correct/incorrect --- geotrek/core/static/core/multipath.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index ce0c0d88f0..5747261bab 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -875,7 +875,7 @@ L.Handler.MultiPath = L.Handler.extend({ L.DomUtil.removeClass(step.marker._icon, 'marker-highlighted'); L.DomUtil.removeClass(step.marker._icon, 'marker-disabled'); L.DomUtil.addClass(step.marker._icon, 'marker-snapped'); - step.marker.dragging.enable(); + step.marker.activate(); }) if (this._routeLayer) @@ -960,7 +960,7 @@ L.Handler.MultiPath = L.Handler.extend({ if (step._leaflet_id != pop._leaflet_id) { L.DomUtil.removeClass(step.marker._icon, 'marker-snapped'); L.DomUtil.addClass(step.marker._icon, 'marker-disabled'); - step.marker.dragging.disable(); + step.marker.deactivate(); } }) } From 8fd0b21209c150f6727e5cc989e999a80440a7e9 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Thu, 23 May 2024 13:53:25 +0200 Subject: [PATCH 067/213] Add impossibility of creating new via-steps while the route is invalid --- geotrek/core/static/core/multipath.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index 5747261bab..a645a9a9e3 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -963,6 +963,9 @@ L.Handler.MultiPath = L.Handler.extend({ step.marker.deactivate(); } }) + // Prevent from creating new via-steps + this.map.off('mousemove', this.drawOnMouseMove); + this.map.removeLayer(this.draggable_marker); } }); From db0e9523c0070937934d11a9f7d8381e0772d587 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Thu, 23 May 2024 14:31:01 +0200 Subject: [PATCH 068/213] Move spinner.stop() into a finally() --- geotrek/core/static/core/multipath.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index a645a9a9e3..7bb64ee684 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -591,19 +591,15 @@ L.Handler.MultiPath = L.Handler.extend({ } this.fire('fetched_route', route); } - this.spinner.stop() // TODO: put this in a .finally() }, // If the promise was rejected: response => { console.log("fetchRoute:", response) this.fire('invalid_route', pop) - this.spinner.stop() } ) - .catch(e => { - console.log("fetchRoute", e) - this.spinner.stop() - }) + .catch(e => console.log("fetchRoute", e)) + .finally(() => this.spinner.stop()) } }, From 60fddbc857ab2573292641a3d0e949e35152d24c Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Fri, 24 May 2024 11:49:03 +0200 Subject: [PATCH 069/213] Fix one route layer missing when an isolated via-step is corrected --- geotrek/core/static/core/multipath.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index 7bb64ee684..44de602bdf 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -234,6 +234,7 @@ L.Handler.MultiPath = L.Handler.extend({ this._guidesLayer = guidesLayer; this._routeLayer = null this._currentStepsNb = 0 + this._previousStepsNb = 0 this.options = options; this.spinner = new Spinner() @@ -458,6 +459,7 @@ L.Handler.MultiPath = L.Handler.extend({ oldStepsIndexes = newStepsIndexes.slice(0, -1) } + this._previousStepsNb = this._currentStepsNb this._currentStepsNb = this.steps.length self.fetchRoute(oldStepsIndexes, newStepsIndexes, pop) @@ -483,6 +485,7 @@ L.Handler.MultiPath = L.Handler.extend({ self.steps.splice(step_idx, 1); self.map.removeLayer(marker); + self._previousStepsNb = self._currentStepsNb self._currentStepsNb = self.steps.length self.fetchRoute( [step_idx - 1, step_idx, step_idx + 1], @@ -837,11 +840,11 @@ L.Handler.MultiPath = L.Handler.extend({ // The last element of old_steps_indexes is where we start reusing the // previous layers again var old_steps_last_index = old_steps_indexes.at(-1) - if (this._routeIsValid == false && old_steps_indexes.length > new_steps_indexes.length) { - // If an unlinkable via marker is being removed, the invalid part of - // the path was not displayed. So at this point, there is one more - // marker (being removed now), but there isn't one more layer. - // Hence, all following layer indexes must be left-shifted by 1 + if (this._routeIsValid == false && this._previousStepsNb > this._routeLayer.__layerArray.length + 1) { + // If a new via marker is isolated and is now being removed or modified, + // the invalid part of the path was not displayed. So at this point, + // there is one more step, but there isn't one more layer. + // Hence, all following layer indexes must be left-shifted by 1. old_steps_last_index-- } var layer = this.stepIndexToLayer(old_steps_last_index, oldLayers) From b665deae962dc4cbd20adb8fd8b4574cc8750a2f Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Fri, 24 May 2024 15:36:53 +0200 Subject: [PATCH 070/213] Add deletion of a marker if not dropped on a path --- geotrek/core/static/core/multipath.js | 49 ++++++++++++++++----------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index 44de602bdf..bb23648b81 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -438,6 +438,12 @@ L.Handler.MultiPath = L.Handler.extend({ this.steps.splice(idx, 0, pop); // Insert pop at position idx pop.events.on('placed', () => { + + if (!pop.isValid()) { + self.removeViaStep(pop) + return + } + var currentStepIdx = self.getStepIdx(pop) // Create the array of new step indexes after the route is updated @@ -479,30 +485,35 @@ L.Handler.MultiPath = L.Handler.extend({ var pop = this.createStep(marker, step_idx); - // remove marker on click - function removeViaStep() { - var step_idx = self.getStepIdx(pop) - self.steps.splice(step_idx, 1); - self.map.removeLayer(marker); - - self._previousStepsNb = self._currentStepsNb - self._currentStepsNb = self.steps.length - self.fetchRoute( - [step_idx - 1, step_idx, step_idx + 1], - [step_idx - 1, step_idx], - pop - ); - } + var removeOnClick = () => this.removeViaStepFromRoute(pop, step_idx) - function removeOnClick() { marker.on('click', removeViaStep); } - pop.marker.activate_cbs.push(removeOnClick); - pop.marker.deactivate_cbs.push(function() { marker.off('click', removeViaStep); }); + pop.marker.activate_cbs.push(() => marker.on('click', removeOnClick)); + pop.marker.deactivate_cbs.push(() => marker.off('click', removeOnClick)); - // marker is already activated, trigger manually removeOnClick - removeOnClick(); + // marker is already activated, enable removeOnClick manually + marker.on('click', removeOnClick) pop.toggleActivate(); }, + // Remove an existing step by clicking on it + removeViaStepFromRoute: function(pop, step_idx) { + this.removeViaStep(pop) + this._previousStepsNb = this._currentStepsNb + this._currentStepsNb = this.steps.length + this.fetchRoute( + [step_idx - 1, step_idx, step_idx + 1], + [step_idx - 1, step_idx], + pop + ); + }, + + // Remove a step from the steps list + removeViaStep: function(pop) { + var step_idx = this.getStepIdx(pop) + this.steps.splice(step_idx, 1) + this.map.removeLayer(pop.marker) + }, + getStepIdx: function(step) { return this.steps.indexOf(step); }, From 1b3d7f2c01dd21cb9ab272686f44a47d5d644d4c Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Fri, 24 May 2024 16:13:09 +0200 Subject: [PATCH 071/213] Add resetting of a marker to its previous valid position when not dropped on a path --- geotrek/core/static/core/multipath.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index bb23648b81..74fb26c723 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -440,10 +440,17 @@ L.Handler.MultiPath = L.Handler.extend({ pop.events.on('placed', () => { if (!pop.isValid()) { - self.removeViaStep(pop) + if (pop.previousPosition) { + pop.marker.setLatLng(pop.previousPosition.ll) + self.forceMarkerToLayer(pop.marker, pop.previousPosition.polyline); + } else { + self.removeViaStep(pop) + } return } + pop.previousPosition = {ll: pop.ll, polyline: pop.polyline} + var currentStepIdx = self.getStepIdx(pop) // Create the array of new step indexes after the route is updated @@ -983,9 +990,14 @@ L.Handler.MultiPath = L.Handler.extend({ // pol: point on polyline Geotrek.PointOnPolyline = function (marker) { this.marker = marker; - // if valid + + // If valid: this.ll = null; this.polyline = null; + + // To reset the pop to its previous valid position when not dropped on a path: + this.previousPosition = null; + this.path_length = null; this.percent_distance = null; this._activated = false; From 681b2354eda7f6bc8385ea03d26a61b1b3fa0daa Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Fri, 24 May 2024 16:27:46 +0200 Subject: [PATCH 072/213] Fix isolated marker highlight being removed when reset to its previous position --- geotrek/core/static/core/multipath.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index 74fb26c723..e88c0f8341 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -439,11 +439,17 @@ L.Handler.MultiPath = L.Handler.extend({ pop.events.on('placed', () => { - if (!pop.isValid()) { + if (!pop.isValid()) { // If the pop was not dropped on a path if (pop.previousPosition) { + // If the pop was on a path before, set it to its previous position pop.marker.setLatLng(pop.previousPosition.ll) self.forceMarkerToLayer(pop.marker, pop.previousPosition.polyline); + if (!this._routeIsValid) { + // If the route is not valid, the marker must stay highlighted + L.DomUtil.removeClass(pop.marker._icon, 'marker-snapped'); + } } else { + // If not, then it is a new pop: remove it self.removeViaStep(pop) } return From c59ddaa47790e1bad2d5c9a2757f3388edd9dbb2 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Fri, 24 May 2024 16:35:22 +0200 Subject: [PATCH 073/213] Remove duplicate part of code --- geotrek/core/static/core/multipath.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index e88c0f8341..7d772a6988 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -901,8 +901,6 @@ L.Handler.MultiPath = L.Handler.extend({ step.marker.activate(); }) - if (this._routeLayer) - self.map.removeLayer(this._routeLayer) var topology = this.buildRouteLayers(data); this.showPathGeom(topology.layer); this._routeIsValid = true From 553f119f8a363e0542a6bcc785b7b746fed9f39c Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Fri, 24 May 2024 16:56:25 +0200 Subject: [PATCH 074/213] Remove hiding of route layer when the start or end marker is moved --- geotrek/core/static/core/multipath.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index 7d772a6988..a7f5c72503 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -402,17 +402,11 @@ L.Handler.MultiPath = L.Handler.extend({ L.DomUtil.removeClass(this._container, 'cursor-topo-start'); L.DomUtil.addClass(this._container, 'cursor-topo-end'); marker = this.markersFactory.source(latlng); - marker.on('unsnap', function () { - this.showPathGeom(null); - }, this); this.marker_source = marker; } else { L.DomUtil.removeClass(this._container, 'cursor-topo-start'); L.DomUtil.removeClass(this._container, 'cursor-topo-end'); marker = this.markersFactory.dest(latlng) - marker.on('unsnap', function () { - this.showPathGeom(null); - }, this); this.marker_dest = marker; } From 19d100daafa520ebe28288918e200e4b2a240b04 Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Fri, 24 May 2024 17:40:30 +0200 Subject: [PATCH 075/213] Fix out of date step idx used when deleting a via point --- geotrek/core/static/core/multipath.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index a7f5c72503..db0f24d8d5 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -492,7 +492,7 @@ L.Handler.MultiPath = L.Handler.extend({ var pop = this.createStep(marker, step_idx); - var removeOnClick = () => this.removeViaStepFromRoute(pop, step_idx) + var removeOnClick = () => this.removeViaStepFromRoute(pop) pop.marker.activate_cbs.push(() => marker.on('click', removeOnClick)); pop.marker.deactivate_cbs.push(() => marker.off('click', removeOnClick)); @@ -503,8 +503,9 @@ L.Handler.MultiPath = L.Handler.extend({ }, // Remove an existing step by clicking on it - removeViaStepFromRoute: function(pop, step_idx) { - this.removeViaStep(pop) + removeViaStepFromRoute: function(pop) { + var step_idx = this.getStepIdx(pop) + this.removeViaStep(pop, step_idx) this._previousStepsNb = this._currentStepsNb this._currentStepsNb = this.steps.length this.fetchRoute( @@ -515,8 +516,7 @@ L.Handler.MultiPath = L.Handler.extend({ }, // Remove a step from the steps list - removeViaStep: function(pop) { - var step_idx = this.getStepIdx(pop) + removeViaStep: function(pop, step_idx) { this.steps.splice(step_idx, 1) this.map.removeLayer(pop.marker) }, From c17e95eaeb3a529a4399fae5cd6a21ae99c6b75f Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Mon, 27 May 2024 10:21:08 +0200 Subject: [PATCH 076/213] Add display of an error toast when a marker is incorrect --- geotrek/core/locale/de/LC_MESSAGES/django.po | 11 +++++++- geotrek/core/locale/en/LC_MESSAGES/django.po | 11 +++++++- geotrek/core/locale/es/LC_MESSAGES/django.po | 11 +++++++- geotrek/core/locale/fr/LC_MESSAGES/django.po | 11 +++++++- geotrek/core/locale/it/LC_MESSAGES/django.po | 11 +++++++- geotrek/core/locale/nl/LC_MESSAGES/django.po | 11 +++++++- geotrek/core/static/core/multipath.js | 18 +++++++++++++ geotrek/core/static/core/style.css | 26 +++++++++++++++++++ geotrek/core/templates/core/error_toast.html | 15 +++++++++++ .../core/toast_wrong_topology_route.html | 6 +++++ .../core/topology_widget_fragment.html | 5 +++- 11 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 geotrek/core/templates/core/error_toast.html create mode 100644 geotrek/core/templates/core/toast_wrong_topology_route.html diff --git a/geotrek/core/locale/de/LC_MESSAGES/django.po b/geotrek/core/locale/de/LC_MESSAGES/django.po index e288c9a74e..aa6865ffe8 100644 --- a/geotrek/core/locale/de/LC_MESSAGES/django.po +++ b/geotrek/core/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-03 15:39+0000\n" +"POT-Creation-Date: 2024-05-28 14:23+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -311,6 +311,15 @@ msgstr "" msgid "Cancel" msgstr "" +msgid "Warning" +msgstr "" + +msgid "The marker was not dropped on a path." +msgstr "" + +msgid "No routing found for this marker. Please move or delete it." +msgstr "" + msgid "No certification" msgstr "" diff --git a/geotrek/core/locale/en/LC_MESSAGES/django.po b/geotrek/core/locale/en/LC_MESSAGES/django.po index e288c9a74e..aa6865ffe8 100644 --- a/geotrek/core/locale/en/LC_MESSAGES/django.po +++ b/geotrek/core/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-03 15:39+0000\n" +"POT-Creation-Date: 2024-05-28 14:23+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -311,6 +311,15 @@ msgstr "" msgid "Cancel" msgstr "" +msgid "Warning" +msgstr "" + +msgid "The marker was not dropped on a path." +msgstr "" + +msgid "No routing found for this marker. Please move or delete it." +msgstr "" + msgid "No certification" msgstr "" diff --git a/geotrek/core/locale/es/LC_MESSAGES/django.po b/geotrek/core/locale/es/LC_MESSAGES/django.po index e288c9a74e..aa6865ffe8 100644 --- a/geotrek/core/locale/es/LC_MESSAGES/django.po +++ b/geotrek/core/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-03 15:39+0000\n" +"POT-Creation-Date: 2024-05-28 14:23+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -311,6 +311,15 @@ msgstr "" msgid "Cancel" msgstr "" +msgid "Warning" +msgstr "" + +msgid "The marker was not dropped on a path." +msgstr "" + +msgid "No routing found for this marker. Please move or delete it." +msgstr "" + msgid "No certification" msgstr "" diff --git a/geotrek/core/locale/fr/LC_MESSAGES/django.po b/geotrek/core/locale/fr/LC_MESSAGES/django.po index 5dc65d6c19..f917d7d77c 100644 --- a/geotrek/core/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/core/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-03 15:39+0000\n" +"POT-Creation-Date: 2024-05-28 14:23+0000\n" "PO-Revision-Date: 2020-09-23 07:10+0000\n" "Last-Translator: Emmanuelle Helly \n" "Language-Team: French \n" "Language-Team: LANGUAGE \n" @@ -311,6 +311,15 @@ msgstr "" msgid "Cancel" msgstr "" +msgid "Warning" +msgstr "" + +msgid "The marker was not dropped on a path." +msgstr "" + +msgid "No routing found for this marker. Please move or delete it." +msgstr "" + msgid "No certification" msgstr "" diff --git a/geotrek/core/locale/nl/LC_MESSAGES/django.po b/geotrek/core/locale/nl/LC_MESSAGES/django.po index e288c9a74e..aa6865ffe8 100644 --- a/geotrek/core/locale/nl/LC_MESSAGES/django.po +++ b/geotrek/core/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-03 15:39+0000\n" +"POT-Creation-Date: 2024-05-28 14:23+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -311,6 +311,15 @@ msgstr "" msgid "Cancel" msgstr "" +msgid "Warning" +msgstr "" + +msgid "The marker was not dropped on a path." +msgstr "" + +msgid "No routing found for this marker. Please move or delete it." +msgstr "" + msgid "No certification" msgstr "" diff --git a/geotrek/core/static/core/multipath.js b/geotrek/core/static/core/multipath.js index db0f24d8d5..0bcfff8cef 100644 --- a/geotrek/core/static/core/multipath.js +++ b/geotrek/core/static/core/multipath.js @@ -236,8 +236,20 @@ L.Handler.MultiPath = L.Handler.extend({ this._currentStepsNb = 0 this._previousStepsNb = 0 this.options = options; + this.spinner = new Spinner() + // Toast displayed when a marker was not dropped on a path: + this._unsnappedMarkerToast = new bootstrap.Toast( + document.getElementById("routing-unsnapped-marker-error-toast"), + {delay: 5000} + ) + // Toast displayed when there is no route due to a marker on an unreachable path: + this._isolatedMarkerToast = new bootstrap.Toast( + document.getElementById("routing-isolated-marker-error-toast"), + {delay: 5000} + ) + // Is the currently displayed route valid? i.e are all its markers linkable? this._routeIsValid = null @@ -434,6 +446,9 @@ L.Handler.MultiPath = L.Handler.extend({ pop.events.on('placed', () => { if (!pop.isValid()) { // If the pop was not dropped on a path + // Display an alert message + this._unsnappedMarkerToast.show() + if (pop.previousPosition) { // If the pop was on a path before, set it to its previous position pop.marker.setLatLng(pop.previousPosition.ll) @@ -963,6 +978,9 @@ L.Handler.MultiPath = L.Handler.extend({ onInvalidRoute: function(pop) { this._routeIsValid = false + // Display an alert message + this._isolatedMarkerToast.show() + if (this.steps.length <= 2) return diff --git a/geotrek/core/static/core/style.css b/geotrek/core/static/core/style.css index 8d29aa9e4e..63157bf3f6 100644 --- a/geotrek/core/static/core/style.css +++ b/geotrek/core/static/core/style.css @@ -110,3 +110,29 @@ span.aggregation { background-image: none; background-color: yellow; } + +.toast-stack { + position: absolute; + right: 1.5rem; + bottom: 1.5rem; +} + +.toast-no-path { + background-color: #f8d7da; + border-color: #f5c2c7; +} + +.toast-header-no-path { + justify-content: space-between; + color: #842029; + background-color: #f8d7da; +} + +.toast-button-no-path { + color: #842029; +} + +.toast-body-no-path { + background-color: #f8d7da; + color: #842029; +} diff --git a/geotrek/core/templates/core/error_toast.html b/geotrek/core/templates/core/error_toast.html new file mode 100644 index 0000000000..8c12dc2258 --- /dev/null +++ b/geotrek/core/templates/core/error_toast.html @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/geotrek/core/templates/core/toast_wrong_topology_route.html b/geotrek/core/templates/core/toast_wrong_topology_route.html new file mode 100644 index 0000000000..85b102e653 --- /dev/null +++ b/geotrek/core/templates/core/toast_wrong_topology_route.html @@ -0,0 +1,6 @@ +{% load i18n %} + +
+ {% include "core/error_toast.html" with id="routing-unsnapped-marker-error-toast" title=_("Warning") message=_("The marker was not dropped on a path.") %} + {% include "core/error_toast.html" with id="routing-isolated-marker-error-toast" title=_("Warning") message=_("No routing found for this marker. Please move or delete it.") %} +
\ No newline at end of file diff --git a/geotrek/core/templates/core/topology_widget_fragment.html b/geotrek/core/templates/core/topology_widget_fragment.html index ef76eb7a52..a1f23c2a2b 100644 --- a/geotrek/core/templates/core/topology_widget_fragment.html +++ b/geotrek/core/templates/core/topology_widget_fragment.html @@ -1,5 +1,8 @@ {% extends "leaflet/widget.html" %} - +{% block map %} + {{ block.super }} + {% include "core/toast_wrong_topology_route.html" %} +{% endblock map %} {% block vars %} {{ block.super }} From c487a9d2e78d2825b6ed08593b9cc7fcbfe0106c Mon Sep 17 00:00:00 2001 From: JustineFricou Date: Thu, 30 May 2024 10:31:14 +0200 Subject: [PATCH 077/213] Modify error_toast elements class names for genericity --- geotrek/core/static/core/style.css | 8 ++++---- geotrek/core/templates/core/error_toast.html | 16 ++++++---------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/geotrek/core/static/core/style.css b/geotrek/core/static/core/style.css index 63157bf3f6..d9c94b1ae8 100644 --- a/geotrek/core/static/core/style.css +++ b/geotrek/core/static/core/style.css @@ -117,22 +117,22 @@ span.aggregation { bottom: 1.5rem; } -.toast-no-path { +.toast-error { background-color: #f8d7da; border-color: #f5c2c7; } -.toast-header-no-path { +.toast-error-header { justify-content: space-between; color: #842029; background-color: #f8d7da; } -.toast-button-no-path { +.toast-error-button { color: #842029; } -.toast-body-no-path { +.toast-error-body { background-color: #f8d7da; color: #842029; } diff --git a/geotrek/core/templates/core/error_toast.html b/geotrek/core/templates/core/error_toast.html index 8c12dc2258..8d1967a173 100644 --- a/geotrek/core/templates/core/error_toast.html +++ b/geotrek/core/templates/core/error_toast.html @@ -1,15 +1,11 @@ -