Post

đŸ—ș Moteur de routage + 🧭 Visualisation Ă©dition traces gpx

đŸ—ș Moteur de routage + 🧭 Visualisation Ă©dition traces gpx

Tracer des itinĂ©aires Ă  l’aide du moteur de routage GraphHopper. Visualiser, Ă©diter et crĂ©er des traces gpx avec un fork de l’application web gpx.studio (Git)

GraphHopper et gpx.studio sont installés sur un serveur debian cwwk rnmkcy.eu 192.168.0.205

đŸ—ș GraphHopper

GraphHopper est un moteur de routage rapide et efficace dans la mĂ©moire publiĂ© sous Apache License 2.0. Il peut ĂȘtre utilisĂ© comme bibliothĂšque Java ou serveur Web autonome pour calculer la distance, le temps, instructions tour par tour et de nombreux attributs routiers pour un itinĂ©raire entre deux points ou plus.

Création dossier graphhopper

1
mkdir -p /sharenfs/docker-data/graphhopper/{data,graph-cache,srtm}

Télécharger les données OpenStreetMap France (OSM)

GraphHopper calcule des routes à partir de données OSM au format PBF (.osm.pbf).
Le lien pour toute les zones (ex : Europe, France, etc.): http://download.geofabrik.de/

đŸ“„ TĂ©lĂ©charger zone France (france-latest.osm.pbf)

Copier le fichier dans le dossier data

1
 cp france-latest.osm.pbf /sharenfs/docker-data/graphhopper/data/france.osm.pbf

Fichier de configuration (yaml)

Le fichier de configuration /sharenfs/docker-data/graphhopper/config.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
graphhopper:

  # Fichier d'entrĂ©e OpenStreetMap en PBF ou XML, peut ĂȘtre modifiĂ© via la ligne de commande 
  # -Ddw.graphhopper.datareader.file=some.pbf
  datareader.file: ""
  # Dossier local utilisé par GraphHopper pour stocker ses données
  graph.location: graph-cache


  ##### Profils de routage ####

  # Le routage peut ĂȘtre effectuĂ© uniquement pour les profils listĂ©s ci-dessous. Pour plus d'informations sur les profils 
  # et les profils personnalisés, consultez la documentation à docs/core/profiles.md ou les exemples dans 
  # web/src/test/java/com/graphhopper/application/resources/ ou la classe CustomWeighting pour les détails bruts.
  #
  # En général, un profil comprend les éléments suivants :
  # - name (obligatoire) : un identifiant unique pour le profil
  # - weighting (optionnel) : par défaut 'custom'
  # - turn_costs (optionnel) :
  #     vehicle_types: [motorcar, motor_vehicle] (types de véhicules utilisés pour les restrictions de tournage spécifiques au véhicule)
  #     u_turn_costs: 60 (pénalité de temps pour faire un demi-tour en secondes)
  #     allow_turn_penalty_in_request: true (activer la pĂ©nalitĂ© de tournage personnalisĂ©e pour les requĂȘtes au moment de l'exĂ©cution)
  #
  # Selon les champs ci-dessus, il existe d'autres propriétés utilisables, par exemple :
  # - custom_model_files : lorsque vous avez spécifié "weighting: custom", vous devez définir un ou plusieurs fichiers JSON recherchés dans
  #   custom_models.directory ou le dossier de travail qui définit le custom_model. Si vous voulez un modÚle vide, vous pouvez
  #   définir "custom_model_files: []"
  #   Vous pouvez aussi utiliser le champ `custom_model` et spécifier votre modÚle personnalisédans le profil directement.
  #
  # Pour Ă©viter les requĂȘtes de routage de longue durĂ©e, vous devriez gĂ©nĂ©ralement activer le mode speed ou hybrid pour tous les profils donnĂ©s
  # (voir ci-dessous). Ou au moins limiter le nombre de `routing.max_visited_nodes`.

  profiles:
   - name: car
     # Par défaut, car n'arrive pas avec des restrictions de tournage car cela nécessite moins de ressources. Décommentez la suite si vous en avez besoin :
#     turn_costs:
#       vehicle_types: [motorcar, motor_vehicle]
#       u_turn_costs: 60
#     pour des coûts de tournage plus avancés, voir avoid_turns.json
     custom_model_files: [car.json]
     # Par défaut, foot et bike n'ont pas de préparation CH car cela nécessite moins de ressources. Les ajouterce à "profiles_ch" si vous en avez besoin.
   - name: foot
     custom_model_files: [foot.json, foot_elevation.json]

   - name: bike
     custom_model_files: [bike.json, bike_elevation.json]

#   Décommentez les profils intégréssi vous voulez plus de profils. AprÚs le démarrage de GraphHopper, il affichera quelles encoded values vous devrez ajouter à graph.encoded_values dans ce fichier de configuration.
#
#    - name: racingbike
#      custom_model_files: [racingbike.json, bike_elevation.json]
#
#    - name: mtb
#      custom_model_files: [mtb.json, bike_elevation.json]
#
#    - name: custom_profile
#      navigation_mode: bike
#      turn_costs:
#        vehicle_types: [bicycle]
#        u_turn_costs: 10
#      custom_model_files: [your_custom_model.json]
#
#    # Voir bus.json pour plus de détails.
#    - name: bus
#      turn_costs:
#        vehicle_types: [bus, motor_vehicle]
#        u_turn_costs: 60
#      custom_model_files: [bus.json]
#
#   Vous pouvez configurer un profil avec des coûts de tournage comme "3 secondes pour gaucher" et "5 secondes pour les tournages à droite" via :
#    1. ajouter l'entrée turn_costs au profil (voir par ex. le profil car)
#    2. ajouter orientation Ă  la liste graph.encoded_values
#    3. ajouter avoid_turns.json Ă  custom_model_files
#   Éditer avoid_turns.json ou crĂ©er votre propre fichier JSON et le mettre dans "custom_models.directory". Voir aussi bike_tc.yml.
#
#   Autres modĂšles personnalisĂ©s non listĂ©s ici : car4wd.json, motorcycle.json, truck.json ou cargo-bike.json. Vous devrez peut-ĂȘtre les modifier et les tester avant une utilisation en production.
#   Voir ./core/src/main/resources/com/graphhopper/custom_models et nous dire si vous modifiez, améliorez ou créez de nouveaux modÚles !
#   Il existe aussi le modĂšle personnalisĂ© curvature.json qui peut ĂȘtre utile pour un profil motorcycle ou l'opposĂ© pour un profil truck.
#   Ensuite, spécifiez un dossier pour trouver vos propres fichiers de modÚle personnalisés :
#  custom_models.directory: custom_models


  # Mode speed :
  # Il est possible d'accélérer le routage en effectuant une préparation spéciale du graphe (Contraction Hierarchies, CH). Cela nécessite
  # plus de RAM/espace disque pour stocker le graphe prĂ©parĂ©, mais aussi signifie moins d'utilisation de mĂ©moire par requĂȘte. Avec la liste suivante
  # vous pouvez dĂ©finir pour lesquels des profils de routage ci-dessus une telle prĂ©paration doit ĂȘtre effectuĂ©e. Notez que pour supporter
  # les profils avec `turn_costs`, une préparation plus élaborée est requise (temps de préparation plus long et plus d'utilisation de mémoire
  # ) et le routage sera aussi plus lent que sans `turn_costs`.
  profiles_ch:
    - profile: car

  # Mode hybrid :
  # Similaire au mode speed, le mode hybrid (Landmarks, LM) accélÚre aussi le routage en calculant des données auxiliaires
  # Ă  l'avance. Il n'est pas aussi rapide que le mode speed, mais plus flexible.
  #
  # Utilisation avancĂ©e : Il est possible d'utiliser la mĂȘme prĂ©paration pour plusieurs profils, ce qui Ă©conomise de la mĂ©moire et du temps de prĂ©paration.
  # Pour cela, utiliser par exemple `preparation_profile: my_other_profile` oĂč `my_other_profile` est le nom d'un autre
  # profil pour lequel un profil LM existe. Important : Cela donnera des résultats de routage corrects uniquement si les poids
  # calculés pour le profil sont égaux ou supérieurs (pour chaque edge) à ceux calculés pour le profil utilisé pour la préparation (`my_other_profile`)
  profiles_lm: []


  #### Encodagedes valeurs ####

  # Ajouter des informations supplémentaires à chaque edge. Utilisé pour les détails de chemin (#1548) et les modÚles personnalisés (docs/core/custom-models.md)
  # Exemples de valeurs :
  #   average_slope,country,curvature,hazmat,hgv,hike_rating,lanes,max_height,max_length, ...
  # Pour plus de détails et une liste complÚte, voir la documentation : https://github.com/graphhopper/graphhopper/blob/master/docs/core/custom-models.md
  # Les routes privées sont bloquées par défaut. Pour désactiver cela, vous pouvez spécifier block_private=false comme option :
  #   car_access|block_private=false, bike_access|block_private=false
  # Par défaut, nous ajoutons les encoded values nécessaires pour car.json, bike.json et foot.json (les supprimer si vous ne les avez pas besoin)
  graph.encoded_values: |
    car_access, car_average_speed, country, road_class, roundabout, max_speed, road_environment,
    foot_access, foot_average_speed, foot_priority, foot_road_access, hike_rating, average_slope,
    bike_access, bike_average_speed, bike_priority, bike_road_access, bike_network, mtb_rating, ferry_speed

  #### Mode speed, hybrid et flexible ####

  # Pour accélérer la préparation CH pour plusieurs profils, vous pouvez augmenter les threads par défaut si vous avez assez de RAM.
  # Changez ce paramÚtre uniquement si vous savez ce que vous faites et si le défaut a fonctionné pour vous.
  # prepare.ch.threads: 1

  # Pour ajuster les performances vs l'utilisation de mémoire pour le mode hybrid, utiliser
  # prepare.lm.landmarks: 16

  # Rendre la préparation des landmarks parallÚle si vous avez assez de RAM. Changez ceci uniquement si vous savez ce que vous faites et si
  # le défaut a fonctionné pour vous.
  # prepare.lm.threads: 1


  #### ÉlĂ©vations ####

  # Cela populated votre graphe avec des données d'élévation utilisant SRTM. Décommentez pour désactiver (pas d'élévation). Lire plus à ce sujet dans docs/core/elevation.md

  graph.elevation.provider: srtm

  # emplacement par défaut pour le cache est /tmp/srtm
  # graph.elevation.cache_dir: ./srtmprovider/

  # Si vous avez un disque lent ou beaucoup de RAM, changez le MMAP par défaut vers :
  # graph.elevation.dataaccess: RAM_STORE

  # Pour activer l'interpolation bilinéaire lors de l'échantillonnage de l'élévation aux points (par défaut utilise nearest neighbor) :
  # graph.elevation.interpolate: bilinear

  # Réduire l'ascension/descente par edge sans changer la pente maximale :
  # graph.elevation.edge_smoothing: ramer
  # élimine les fluctuations d'élévation jusqu'à max_elevation (en mÚtres) et remplace l'élévation par une valeur basée sur la pente moyenne
  # graph.elevation.edge_smoothing.ramer.max_elevation: 5
  # Utiliser une approche de moyennage pour le lissage révélera des valeurs non affectées par les outliers et des pentes réalistes et des valeurs d'altitude totales (haut et bas)
  # graph.elevation.edge_smoothing: moving_average
  # taille de fenĂȘtre en mĂštres le long d'un chemin utilisĂ©e pour moyenner l'Ă©lĂ©vation d'un nƓud
  # graph.elevation.edge_smoothing.moving_average.window_size: 150

  # Pour augmenter la résolution du profil d'élévation, utilisez les deux paramÚtres suivants pour ajuster la résolution supplémentaire dont vous avez besoin
  # contre l'espace de stockage supplémentaire utilisé pour les géométries d'edge. Vous devriez activer l'interpolation bilinéaire quand vous utilisez
  # ces fonctionnalités (voir #1953 pour les détails).
  # - premiĂšrement, dĂ©finir la distance (en mĂštres) Ă  laquelle les Ă©chantillons d'Ă©lĂ©vation doivent ĂȘtre pris sur les edges longs
  # graph.elevation.long_edge_sampling_distance: 60
  # - secondairement, définir la tolérance d'élévation (en mÚtres) à utiliser lors de la simplification des polylignes car le défaut ignore
  #   l'élévation et supprimera les points supplémentaires que l'échantillonnage d'edge long a ajoutés
  # graph.elevation.way_point_max_distance: 10


  #### Défauts dépendants du pays pour les vitesse maxima ####

  # Cette fonctionnalité définit une vitesse maximale dans la valeur encodée 'max_speed' si aucun tag maxspeed n'a été trouvé. Elle est dépendante du pays
  # et basée sur plusieurs rÚgles. Voir https://github.com/westnordost/osm-legal-default-speeds
  # Pour utiliser, décommentez la suite, puis activez urban density ci-dessous et ajouter 'country' à graph.encoded_values
  # max_speed_calculator.enabled: true


  #### Densité urbaine (zones construites) ####

  # Cette fonctionnalité permet de classifier les routes en 'rural', 'residential' et 'city' (encoded value 'urban_density')
  # Utiliser 1 ou plus de threads pour activer la fonctionnalité
  # graph.urban_density.threads: 8
  # Utiliser des sensibilités plus hautes/plus basses si trop peu/trop de routes tombent dans les catégories correspondantes.
  # Utiliser des radii plus petits accélérerad la classification, mais changez uniquement ces valeurs si vous savez ce que vous faites.
  # Si vous n'avez pas besoin de la classification city (plutÎt lente), définissez city_radius à zéro.
  # graph.urban_density.residential_radius: 400
  # graph.urban_density.residential_sensitivity: 6000
  # graph.urban_density.city_radius: 1500
  # graph.urban_density.city_sensitivity: 1000


  #### Sous-réseaux ####

  # Dans beaucoup de cas, le réseau routier consiste en composants indépendants sans aucun trajet passant entre eux. Dans
  # le cas le plus simple, vous pouvez imaginer une Ăźle sans pont ou connexion ferry. Le paramĂštre suivant
  # permet de dĂ©finir une taille minimale (nombre d'edges) pour tels composants dĂ©tachĂ©s. Cela peut ĂȘtre utilisĂ© pour rĂ©duire le nombre
  # de cas oĂč une connexion entre deux locations n'est pas trouvĂ©e.
  prepare.min_network_size: 200
  prepare.subnetworks.threads: 1

  #### Routage ####

  # Vous pouvez dĂ©finir le nombre maximal de nƓuds visitĂ©s pendant le routage. Cela peut entraĂźner des connexions non trouvĂ©es si il n'y a pas
  # de connexion entre deux points dans les nƓuds visitĂ©s donnĂ©s. Le dĂ©faut est Integer.MAX_VALUE. Utile pour le mode flexibility
  # routing.max_visited_nodes: 1000000

  # défaut pour snap_preventions
  routing.snap_preventions_default: tunnel, bridge, ferry

  # Le temps maximum en millisecondes aprĂšs lequel une requĂȘte de routage sera annulĂ©e. Cela a quelques particularitĂ©s
  # spĂ©cifiques Ă  l'algorithme de routage, mais gĂ©nĂ©ralement cela devrait permettre la prĂ©vention des requĂȘtes de longue durĂ©e. Le dĂ©faut est Long.MAX_VALUE
  # routing.timeout_ms: 300000

  # ContrĂŽler combien de landmarks actifs sont sĂ©lectionnĂ©s par dĂ©faut, cela peut amĂ©liorer les performances de requĂȘte
  # routing.lm.active_landmarks: 4

  # Vous pouvez limiter la distance max entre deux waypoints consĂ©cutifs des requĂȘtes de routage flexible Ă  ĂȘtre infĂ©rieure ou Ă©gale
  # à la distance donnée en mÚtres. Le défaut est fixé à 1000 km.
  routing.non_ch.max_waypoint_distance: 1000000


  #### Stockage ####

  # Exclut certains types de highways pendant l'import OSM pour accélérer le processus et réduire la taille du graphe.
  # Une application typique est d'exclure les highways 'footway','cycleway','path' et peut-ĂȘtre 'track' pour
  # les véhicules motorisés. Cela conduit à un graphe plus petit et moins dense, car il y a moins de ways (obviously),
  # mais aussi car il y a moins de croisements entre highways (=junctions).
  # Un autre exemple typique est d'exclure 'motorway', 'trunk' et peut-ĂȘtre 'primary' highways pour bicycle ou pedestrian routing.
  import.osm.ignored_highways: ''
  # import.osm.ignored_highways: footway,construction,cycleway,path,steps # utiliser si vous avez uniquement des profils de véhicules motorisés uniquement
  # import.osm.ignored_highways: motorway,trunk # utiliser si vous avez uniquement des profils de véhicules non motorisés

  # écrira les noms de way dans la langue préférée (code de langue défini selon ISO 639-1 ou ISO 639-2) :
  # datareader.preferred_language: en

  # configurer l'accÚs mémoire, utiliser RAM_STORE pour les serveurs bien équipés (défaut et recommandé)
  graph.dataaccess.default_type: RAM_STORE

  # Si MMAP n'est pas adapté pour tout, vous pouvez l'utiliser pour des fichiers sélectionnés par ex. lors de l'import pour réduire l'utilisation de heap (voir #2440) :
  # graph.dataaccess.type.geometry: MMAP
  # graph.dataaccess.type.edges: MMAP
  # graph.dataaccess.type.nodes: MMAP
  # graph.dataaccess.type.nodes_ch.*: MMAP
  # graph.dataaccess.type.shortcuts_.*: MMAP

  # Si aussi pour le routage l'environnement est contraint en heap et que vous pouvez sacrifier la vitesse, vous pouvez aussi utiliser MMAP mais peut-ĂȘtre les prĂ©charger :
  # premiĂšre rĂšgle correspond
  # graph.dataaccess.mmap.preload.nodes_ch_car.*: 100
  # graph.dataaccess.mmap.preload.nodes_ch.*: 30

  #### Zones personnalisées ####

  # GraphHopper lit des fichiers GeoJSON polygon y compris leurs propriétés de ce dossier et les rend disponibles
  # à tous les tag parsers et modÚles personnalisés. Toutes les Features GeoJSON doivent avoir la propriété "id".
  # Les frontiĂšres de pays sont incluses automatiquement (voir countries.geojson).
  # custom_areas.directory: path/to/custom_areas


  #### RĂšgles de pays ####

  # GraphHopper applique des rÚgles de routage spécifiques au pays pendant l'import (pas activé par défaut).
  # Vous devez rééfaire l'import pour que les changements prennent effet.
  # country_rules.enabled: true

# Configuration du serveur Dropwizard
server:
  application_connectors:
  - type: http
    port: 8989
    # pour des raisons de sécurité, binder à localhost
    bind_host: 0.0.0.0
    # augmenter la limite de requĂȘte GET - pas nĂ©cessaire si /maps UI n'est pas utilisĂ© ou utilisĂ© sans modĂšles personnalisĂ©s
    max_request_header_size: 50k
  request_log:
      appenders: []
  admin_connectors:
  - type: http
    port: 8990
    bind_host: localhost
# Voir https://www.dropwizard.io/en/latest/manual/core.html#logging
logging:
  appenders:
    - type: file
      time_zone: UTC
      current_log_filename: logs/graphhopper.log
      log_format: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
      archive: true
      archived_log_filename_pattern: ./logs/graphhopper-%d.log.gz
      archived_file_count: 30
      never_block: true
    - type: console
      time_zone: UTC
      log_format: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
  loggers:
    "com.graphhopper.osm_warnings":
      level: DEBUG
      additive: false
      appenders:
        - type: file
          currentLogFilename: logs/osm_warnings.log
          archive: false
          logFormat: '[%level] %msg%n'

Lancer docker (bash + java et chemins)

Exécuter la commande Docker

1
2
3
4
5
6
7
8
9
docker run -d --name graphhopper --restart unless-stopped \
  -p 8989:8989 \
  -v /sharenfs/docker-data/graphhopper/data:/data \
  -v /sharenfs/docker-data/graphhopper/graph-cache:/graph-cache \
  -v /sharenfs/docker-data/graphhopper/srtm:/srtm \
  --entrypoint /bin/bash \
  israelhikingmap/graphhopper \
  -c 'java -Ddw.graphhopper.datareader.file=/data/france.osm.pbf -Ddw.graphhopper.graph.location=/graph-cache -jar *.jar server config.yml'

GraphHopper dĂ©marre et lance l’import (“start creating graph
”).
Le “connexion rĂ©-initialisĂ©e” que tu voyais vient trĂšs probablement du fait que tu testais pendant que le serveur/jetty n’était pas encore prĂȘt Ă  rĂ©pondre proprement.

Fais juste : 1) Attends la fin de build (logs)

1
docker logs -f graphhopper

2) En parallùle, teste toutes les 30s jusqu’à obtenir un 200 :

1
curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8989/

Quand ça répond correctement, on validera le point crucial pour toi : POST /route avec ton payload (profile + details + custom_model).
Copie ensuite 30-50 derniĂšres lignes du docker logs une fois l’import terminĂ© (quand tu vois “Graph created” / “Loading finished” ou similaire), et je te dis quoi mettre dans routing.ts exactement cĂŽtĂ© nouveau endpoint (et si ton custom_model est bien acceptĂ© par cette API).

Patienter environ 1h


1
curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8989/

OK, le serveur répond (HTTP 303) donc il est en place.

Remarques sur docker run

Cette commande détruit le container

1
docker rm -f graphhopper >/dev/null 2>&1 || true
  • docker rm supprime un container. docs.docker
  • -f (alias de --force) force la suppression mĂȘme si le container est en cours d’exĂ©cution (il est arrĂȘtĂ© automatiquement avant suppression). runebook
  • >/dev/null 2>&1 cache les sorties normales et erreurs.
  • || true Ă©vite que le script Ă©choue si le container n’existe pas dĂ©jĂ .

Donc cette commande dĂ©truit bien le container nommĂ© graphhopper si il existe mais ne touche pas aux volumes (/sharenfs/docker-data/graphhopper/...) car tu n’as pas utilisĂ© -v/--volumes avec rm. docs.docker

Il n’y a pas de construction (build) à chaque lancement de la commande docker run :

1
2
3
4
5
6
7
8
docker run -d --name graphhopper --restart unless-stopped \
  -p 8989:8989 \
  -v /sharenfs/docker-data/graphhopper/data:/data \
  -v /sharenfs/docker-data/graphhopper/graph-cache:/graph-cache \
  -v /sharenfs/docker-data/graphhopper/srtm:/srtm \
  --entrypoint /bin/bash \
  israelhikingmap/graphhopper \
  -c 'java -Ddw.graphhopper.datareader.file=/data/france.osm.pbf -Ddw.graphhopper.graph.location=/graph-cache -jar *.jar server config.yml'
  • La premiĂšre fois, Docker rĂ©cupĂšre l’image israelhikingmap/graphhopper (pull) si elle n’est pas locale.
  • Ensuite, docker run crĂ©e un nouveau container Ă  partir de cette image et lance le processus Java.
  • À chaque exĂ©cution de docker run avec --name graphhopper, si un container nommĂ© graphhopper existe dĂ©jĂ , tu as normalement une erreur ; dans ton cas, la commande docker rm -f graphhopper avant docker run permet de :
    • supprimer l’ancien container,
    • puis crĂ©er un nouveau avec le mĂȘme nom et les mĂȘmes volumes.

Les volumes persistent car ils sont sur le host (/sharenfs/docker-data/graphhopper/...). Donc :

  • Le graphe prĂ©calculĂ© dans /graph-cache reste,
  • Les donnĂ©es OSM dans /data restent,
  • Seul le container (l’instance) est dĂ©truit et recreated.

Il n’y a pas de rebuild de l’image à chaque fois, juste un nouveau container à partir de l’image existante.

  • docker rm -f graphhopper dĂ©truit le container, mais pas les volumes.
  • Il n’y a pas de build Ă  chaque lancement, juste un nouveau container Ă  partir de l’image existante.

Migrer graphhopper vers docker compose

Pour migrer du ‘docker run’ actuel au ‘docker compos’ sans perte de donnĂ©es

Voici la procédure pour migrer de ton docker run actuel vers Docker Compose sans perdre de données (graphe, données OSM, cache SRTM).

VĂ©rifier que le container actuel est bien arrĂȘtĂ© mais persistant

1
2
3
4
5
6
7
8
9
10
11
# Voir le container graphhopper
docker ps -a | grep graphhopper

# ArrĂȘter le container (si il est encore en cours)
docker stop graphhopper

# Supprimer le container
docker rm graphhopper

# vérification
docker ps -a | grep graphhopper

Les volumes sont sur le host, donc les données sont présentes:

  • /sharenfs/docker-data/graphhopper/data → france.osm.pbf
  • /sharenfs/docker-data/graphhopper/graph-cache → graphe prĂ©calculĂ©
  • /sharenfs/docker-data/graphhopper/srtm → cache SRTM (Ă©lĂ©vation)

Tu n’as rien Ă  copier si ces chemins sont dĂ©jĂ  utilisĂ©s par le container actuel.

Créer fichier /sharenfs/docker-data/graphhopper/docker-compose.yml équivalent à la commande docker run :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
services:
  graphhopper:
    image: israelhikingmap/graphhopper
    container_name: graphhopper
    restart: unless-stopped
    ports:
      - "8989:8989"
    environment:
      - JAVA_OPTS=-Ddw.server.applicationConnectors[0].bindHost=0.0.0.0
    volumes:
      - /sharenfs/docker-data/graphhopper/data:/data
      - /sharenfs/docker-data/graphhopper/graph-cache:/graph-cache
      - /sharenfs/docker-data/graphhopper/srtm:/srtm
      - /sharenfs/docker-data/graphhopper/config.yml:/graphhopper/config.yml
    entrypoint:
      - /bin/bash
    command:
      - '-c'
      - 'java -Ddw.graphhopper.datareader.file=/data/france.osm.pbf -Ddw.graphhopper.graph.location=/graph-cache -jar *.jar server /graphhopper/config.yml'

Se rendre dans le dossier contenant docker-compose.yml :

1
2
3
4
5
# dossier contenant docker-compose.yml
cd /sharenfs/docker-data/graphhopper/

# Démarrer le service
docker compose up -d

Utilisation :

1
2
3
4
5
6
7
8
9
10
11
# 1. Démarrer (crée le container si nécessaire)
docker compose up -d

# 2. ArrĂȘter sans dĂ©truire le container
docker compose stop

# 3. ArrĂȘter et supprimer le container (Ă©quivalent Ă  docker rm -f)
docker compose down

# 4. Si tu veux recréer le container mais garder les volumes
docker compose up -d --force-recreate
  • docker compose down supprime le container mais garde les volumes (car ils sont bind mounts sur le host).
  • --force-recreate recrĂ©e le container mĂȘme s’il n’a pas changĂ©, sans toucher aux volumes.

Ainsi, tu évites le docker rm -f manuel et tu as une gestion plus propre et reproductible.

Erreur lecture config.yml

/sharenfs/docker-data/graphhopper/docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
services:
  graphhopper:
    image: israelhikingmap/graphhopper
    container_name: graphhopper
    restart: unless-stopped
    ports:
      - "8989:8989"
    volumes:
      - /sharenfs/docker-data/graphhopper/data:/data
      - /sharenfs/docker-data/graphhopper/graph-cache:/graph-cache
      - /sharenfs/docker-data/graphhopper/srtm:/srtm
    entrypoint:
      - /bin/bash
    command:
      - '-c'
      - 'java -Ddw.graphhopper.datareader.file=/data/france.osm.pbf -Ddw.graphhopper.graph.location=/graph-cache -jar *.jar server config.yml'

docker compose logs -f

1
graphhopper  | java.io.FileNotFoundException: File config.yml not found

ls /sharenfs/docker-data/graphhopper

1
config.yml  data  docker-compose.yml  graph-cache  srtm

Le problĂšme est que config.yml n’est pas montĂ© dans le container.

Dans ta commande docker run originale, le fichier config.yml Ă©tait accessible car tu dĂ©marrais depuis le dossier host oĂč il se trouvait (le working directory du container incluait ce fichier).
Dans Docker Compose, tu montes seulement data, graph-cache et srtm, mais pas config.yml, donc le container ne le voit pas.

Solution : monter config.yml dans le container

Modify ton docker-compose.yml pour ajouter le volume config.yml :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
services:
  graphhopper:
    image: israelhikingmap/graphhopper
    container_name: graphhopper
    restart: unless-stopped
    ports:
      - "8989:8989"
    volumes:
      - /sharenfs/docker-data/graphhopper/data:/data
      - /sharenfs/docker-data/graphhopper/graph-cache:/graph-cache
      - /sharenfs/docker-data/graphhopper/srtm:/srtm
      - /sharenfs/docker-data/graphhopper/config.yml:/graphhopper/config.yml
    entrypoint:
      - /bin/bash
    command:
      - '-c'
      - 'java -Ddw.graphhopper.datareader.file=/data/france.osm.pbf -Ddw.graphhopper.graph.location=/graph-cache -jar *.jar server /graphhopper/config.yml'

Changes :

  • Ajout du volume : ```yaml
    • /sharenfs/docker-data/graphhopper/config.yml:/graphhopper/config.yml ```
  • Dans la commande Java, tu passes maintenant le chemin absolu dans le container :
    1
    
    server /graphhopper/config.yml
    

    au lieu de server config.yml.

Appliquer la modification

1
2
3
4
5
6
7
# 1. Sauvegarder le nouveau docker-compose.yml
cd /sharenfs/docker-data/graphhopper/
# 2. Recréer le container avec la nouvelle configuration
docker compose up -d --force-recreate

# 3. Vérifier les logs
docker compose logs -f

Le fichier config.yml sera maintenant accessible dans le container sous /graphhopper/config.yml, et Java le trouvera.

Vérifier fonctionnement

1
2
3
4
5
# Voir le statut
docker compose ps

# Logs
docker compose logs -f

Test le routage (curl)

Passe maintenant au test le plus important : un POST /route avec ton format routing.ts.

Exécuter :

1
2
3
4
5
6
7
8
9
10
11
12
# Tester le routage
curl -sS -X POST http://127.0.0.1:8989/route \
  -H "Content-Type: application/json" \
  -d '{
    "points": [[0.991,47.199],[0.891,47.199]],
    "profile": "foot",
    "elevation": true,
    "points_encoded": false,
    "details": ["road_class","hike_rating","mtb_rating"],
    "custom_model": {}
  }'
echo

Si tu obtiens un JSON (ou au moins pas une erreur HTTP), dis-moi :

  • le code HTTP (si erreur)
  • et le message d’erreur (si ça Ă©choue)

Ensuite je te confirme la modif exacte de routing.ts pour pointer vers https://graphhopper.rnmkcy.eu/route (et si custom_model doit ĂȘtre vide ou non pour que ça marche).

1
{"message":"Cannot find the path details: [surface]","hints":[{"message":"Cannot find the path details: [surface]","details":"java.lang.IllegalArgumentException"}]}

Ça confirme que ton GraphHopper (v12 + ton config/import) n’a pas activĂ©/autorisĂ© la “details” surface (et potentiellement d’autres). Dans ton routing.ts, soit on retire les dĂ©tails non supportĂ©s, soit on les active cĂŽtĂ© serveur.

Le plus simple cÎté code : remplacer graphhopperDetails par une liste sans surface (et garder ce qui marche).

Fais ce changement dans routing.ts :

1
2
- const graphhopperDetails = ['road_class', 'surface', 'hike_rating', 'mtb_rating'];
+ const graphhopperDetails = ['road_class', 'hike_rating', 'mtb_rating'];

Test sans surface

1
2
3
4
5
6
7
8
9
10
curl -sS -X POST http://127.0.0.1:8989/route \
  -H "Content-Type: application/json" \
  -d '{
    "points": [[0.991,47.199],[0.891,47.199]],
    "profile": "foot",
    "elevation": true,
    "points_encoded": false,
    "details": ["road_class","hike_rating","mtb_rating"],
    "custom_model": {}
  }' |jq
ÉlĂ©ment Ton exemple (correct)
points [[lon,lat],[lon,lat]] → [longueur, lat]
profile "foot" (au lieu de "vehicle": "car")
points_encoded false (pour utiliser [lon,lat] explicites)
details ["road_class","hike_rating","mtb_rating"]
custom_model {} explicite
curl flags -sS (silencieux + suivir SSL)
URL http://127.0.0.1:8989/route

Cet appel demande un itinéraire à pied, avec élévation, et retourne les détails de classe de voie, de rating randonnée et MTB.[docs.graphhopper]

Structure

Dossier /sharenfs/docker-data/graphhopper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/sharenfs/docker-data/graphhopper
├── config.yml
├── data
│   ├── default-gh
│   └── france.osm.pbf
├── docker-compose.yml
├── graph-cache
│   ├── edgekv_keys
│   ├── edgekv_vals
│   ├── edges
│   ├── geometry
│   ├── location_index
│   ├── nodes
│   ├── nodes_ch_car
│   ├── properties
│   ├── properties.txt
│   └── shortcuts_car
└── srtm

Nginx (proxy)

Fichier : /etc/nginx/conf.d/graphhopper.rnmkcy.eu.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
server {
  listen 80;
  server_name graphhopper.rnmkcy.eu;
  return 301 https://$host$request_uri;
}

server {
   listen 443 ssl;
   listen [::]:443 ssl;
   http2 on;
  server_name graphhopper.rnmkcy.eu;

  # Certificats SSL
  include /etc/nginx/conf.d/ssl-modern.inc;

  client_max_body_size 20m;

  location / {
    proxy_pass http://127.0.0.1:8989;
    proxy_http_version 1.1;

    proxy_set_header Host              $host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    proxy_buffering off;
    proxy_read_timeout 300;
  }
}

Puis :

1
sudo nginx -t && sudo systemctl reload nginx

Modif routing.ts

Remplace juste la base :

1
2
- fetch('https://graphhopper.gpx.studio/route', {
+ fetch('https://graphhopper.rnmkcy.eu/route', {

Test

1
2
3
4
5
6
7
8
9
10
curl -sS -X POST https://graphhopper.rnmkcy.eu/route \
  -H "Content-Type: application/json" \
  -d '{
    "points": [[0.991,47.199],[0.891,47.199]],
    "profile": "foot",
    "elevation": true,
    "points_encoded": false,
    "details": ["road_class","hike_rating","mtb_rating"],
    "custom_model": {}
  }' |jq

🧭 gpx.studio


https://github.com/gpxstudio

Cloner gpx.studio dans le dossier courant

1
2
# cloner le dernier gpx.studio
git clone https://github.com/gpxstudio/gpx.studio.git

Le code est divisé en deux parties:

  • gpx: une bibliothĂšque de typescript pour parser et manipuler des fichiers GPX,
  • site web: le site lui-mĂȘme, qui est une application SvelteKit.

Vous aurez besoin de Node.js pour construire et exécuter ces deux parties.

Construction bibliothĂšque gpx

Construction bibliothĂšque gpx

1
2
3
cd gpx.studio/gpx
npm install
npm run build

npm website install

Pour pouvoir charger la carte, vous devrez créer votre propre Maptiler API key et la stocker dans un fichier .env dans le répertoire website.

1
2
3
cd ../website
echo PUBLIC_MAPTILER_KEY=nUWxxxxxxxxxxxxxxgb > .env
npm install

Modifications des sources website

website/src/lib/docs/ , en fr uniquement

1
2
3
4
5
6
7
8
# Ne garder que les dossiers en et fr
mkdir -p /tmp/docs
mv website/src/lib/docs/en /tmp/docs/
mv website/src/lib/docs/fr /tmp/docs/
rm -r website/src/lib/docs/
mv /tmp/docs/en website/src/lib/docs/
mv /tmp/docs/fr website/src/lib/docs/
ls website/src/lib/docs/

website/src/locales/, en.json fr.json uniquement

1
find website/src/locales/ -name "*.json" -type f -not \( -name 'en.json' -or -name 'fr.json' \) -delete

website/src/lib/languages.ts

1
2
3
4
export const languages: Record<string, string> = {
    en: 'English',
    fr: 'Français',
};

website/src/lib/components/toolbar/tools/routing/routing.ts

- const graphhopperDetails = ['road_class', 'surface', 'hike_rating', 'mtb_rating']; + const graphhopperDetails = ['road_class', 'hike_rating', 'mtb_rating']; - let response = await fetch('https://graphhopper.gpx.studio/route', { + let response = await fetch('https://graphhopper.rnmkcy.eu/route', {

Supprimer les boutons aide et donate
Fichier website/src/lib/components/Menu.svelte , supprimer les lignes 545 Ă  557

1
2
3
4
5
6
7
8
9
10
11
12
13
            <Button
                variant="ghost"
                href="https://opencollective.com/gpxstudio"
                target="_blank"
                class="cursor-default h-fit rounded-md font-bold text-support hover:text-support px-3 py-0.5"
                aria-label={i18n._('menu.donate')}
            >
                <HeartHandshake size="18" class="md:hidden" />
                <span class="hidden md:flex flex-row items-center">
                    {i18n._('menu.donate')}
                    <Heart size="16" class="ml-1" fill="var(--support)" />
                </span>
            </Button>

Opérations supplémentaires

1
2
3
4
# checher 'en' 
find src/ -name "*" -type f -exec grep -Hn "'en'" {} \;
# Remplacer 'en' par 'fr'
find src/ -name "*" -type f -exec sed -i "s#'en'#'fr'#g" {} \;

website/src/hooks.server.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<     const language = event.params.language ?? 'en';
---
>     //const language = event.params.language ?? 'fr';
>     const language = 'fr';
31,32c32,33
<         "name": "gpx.studio",
<         "url": "https://gpx.studio"
---
>         "name": "gpx.rnmkcy.eu",
>         "url": "https://gpx.rnmkcy.eu"
49c50
<     <link rel="alternate" hreflang="x-default" href="https://gpx.studio${getURLForLanguage('en', path)}" />
---
>     <link rel="alternate" hreflang="x-default" href="https://gpx.studio${getURLForLanguage('fr', path)}" />
54c55
<             headTag += `   <link rel="alternate" hreflang="${lang}" href="https://gpx.studio${getURLForLanguage(lang, path)}" />
---
>             headTag += `   <link rel="alternate" hreflang="${lang}" href="https://gpx.rnmkcy.eu${getURLForLanguage(lang, path)}" />

src/routes/+layout.svelte

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
33c33
<                 folders.indexOf('l') >= 0 ? (folders[folders.indexOf('l') + 1] ?? 'en') : 'en';
---
>                 folders.indexOf('l') >= 0 ? (folders[folders.indexOf('l') + 1] ?? 'fr') : 'fr';
49,50c49,50
<             } else if (i18n.lang !== 'en') {
<                 i18n.lang = 'en';
---
>             } else if (i18n.lang !== 'fr') {
>                 i18n.lang = 'fr';
53c53
<             i18n.lang = 'en';
---
>             i18n.lang = 'fr';
67c67
<     let showNavAndFooter = $derived(page.route.id === null || !appRoutes.includes(page.route.id));
---
> let showNavAndFooter = $derived(page.route.id === null || !appRoutes.includes(page.route.id));
81c81
<             <Footer />
---
>             <!--<Footer />-->

Construction website

On lance le build

1
npm run build

Résultat construction

1
2
3
4
5
6
7
8
9
10
✓ built in 41.58s

Run npm run preview to preview your production build locally.

> Using @sveltejs/adapter-static
  Wrote site to "build"
  ✔ done

> website@0.0.1 postbuild
> npx tsx src/lib/scripts/sitemap.ts

AprĂšs la construction on copie le dossier build

1
2
3
4
5
6
7
# Mise Ă  jour site nginx
sudo rm -rf /var/www/gpx.studio
sudo cp -r /sharenfs/gpx.studio/website/build /var/www/gpx.studio
# les droits
sudo chown www-data:www-data -R /var/www/gpx.studio
# Recharger nginx
sudo systemctl reload nginx

AccĂšs par le lien https://gpx.rnmkcy.eu

cwwk - gpx.rnmkcy.eu

Configuration nginx /etc/nginx/conf.d/gpx.rnmkcy.eu.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name gpx.rnmkcy.eu;

  include /etc/nginx/conf.d/ssl-modern.inc;


  root /var/www/gpx.studio;
  index index.html;

  location / {
    try_files $uri $uri/ /index.html;
  }

  # forcer /app et /app/ vers le bon fichier
  location = /app { return 302 /app.html; }
  location = /app/ { return 302 /app.html; }

  # et les variantes avec langue (ex: /fr/app/)
  location ~ ^/(fr|en|es|de|cs|tr|it|pt-BR|nl|zh|eu)/app$ {
    return 302 /$1/app.html;
  }
  location ~ ^/(fr|en|es|de|cs|tr|it|pt-BR|nl|zh|eu)/app/$ {
    return 302 /$1/app.html;
  }

  # pareil pour embed si besoin
  location = /embed { return 302 /embed.html; }
  location = /embed/ { return 302 /embed.html; }
  location ~ ^/(fr|en|es|de|cs|tr|it|pt-BR|nl|zh|eu)/embed$ {
    return 302 /$1/embed.html;
  }
  location ~ ^/(fr|en|es|de|cs|tr|it|pt-BR|nl|zh|eu)/embed/$ {
    return 302 /$1/embed.html;
  }
}

Vérifier et recharger nginx

1
2
sudo nginx -t
sudo systemctl reload nginx

Crédits

Ce projet utilise les projets open source suivants:

Annexe

Test local

1
npm run preview

Lance le serveur avec une adresse locale

1
2
3
4
5
6
> website@0.0.1 preview
> vite preview

  ➜  Local:   http://localhost:4173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

L’exĂ©cution a lieu sur un serveur, utilisation de SSH tunneling, sur un poste ayant un accĂšs SSH, ouvrir un terminal et exĂ©cuter la commande suivante

1
ssh -L 9500:localhost:4173 yick@192.168.0.205 -p 55205 -i /home/yann/.ssh/yick-ed25519

Puis ouvrir le lien suivant dans un navigateur : localhost:9500

gpx.studio mode dev

1
npm run dev

Le serveur est accessible sur le lien http://localhost:5173/, q pour sortir

1
2
3
4
5
6
7
8
9
10
11
12
13
> website@0.0.1 dev
> vite dev

Forced re-optimization of dependencies

  VITE v5.4.8  ready in 1305 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help
Browserslist: caniuse-lite is outdated. Please run:
  npx update-browserslist-db@latest
  Why you should do it regularly: https://github.com/browserslist/update-db#readme

L’exĂ©cution a lieu sur un serveur, utilisation de SSH tunneling, sur un poste ayant un accĂšs SSH, ouvrir un terminal et exĂ©cuter la commande suivante

1
ssh -L 9500:localhost:4173 yick@192.168.0.205 -p 55205 -i /home/yann/.ssh/yick-ed25519

Puis ouvrir le lien suivant dans un navigateur : localhost:9500

Cet article est sous licence CC BY 4.0 par l'auteur.