Le pipeline pas à pas et en images#

Nous présentons ici un exemple simple et complet reposant sur une collection de traces simulées à partir d’un réseau. Le réseau est un extrait de la BDTOPO situé sur un versant de montagne en face de la ville de Chamonix, il représente un cas non complexe de tronçons.

Ce pipeline est exécuté en une seule itération.

Import des librairies#

  • Les deux librairies socles : tracklib et footprint2graph

  • Pour visualiser les résultats : matplotlib

[1]:
import os
import sys

import matplotlib.pyplot as plt

# Ajout dans la variable PATH du système du chemin où est installée la librairie tracklib
module_path = os.path.abspath(os.path.join('../../../../tracklib'))
if module_path not in sys.path:
    sys.path.append(module_path)
# Alias pour tracklib
import tracklib as tkl

# Ajout dans la variable PATH du système du chemin où est installée la librairie footprint2graph
module_path = os.path.abspath(os.path.join('../../..'))
if module_path not in sys.path:
    sys.path.append(module_path)


Import des données : réseau puis traces simulées#

[2]:
# WKT;link_id;source;target;direction;wkt_source;wkt_target
fmt = tkl.NetworkFormat({
       "pos_edge_id": 1,
       "pos_source": 2,
       "pos_target": 3,
       "pos_wkt": 0,
       "srid": "ENU",
       "separator": ";",
       "header": 1})
netpath = os.path.abspath(os.path.join('../../../data/network2.csv'))
network = tkl.NetworkReader.readFromFile(netpath, fmt, verbose=False)

plt.figure(figsize=(8, 6))
network.plot('k-', '', 'g-', 'r-', 0.5, plt)

print ('Number of edges=', len(network.EDGES))
print ('Number of nodes=', len(network.NODES))
print ('')
Number of edges= 7
Number of nodes= 8

../_images/examples_DetailedQuickstart_4_1.png
[3]:
tkl.stochastics.seed(333)
#
noiser = tkl.NoiseProcess(amps=2.5, kernels=tkl.ExponentialKernel(80))

# generate simulated trajectories from the network
collection = tkl.generateTracksOnNetwork(network, N=1000, p_round_trip=0.05, p_cplx_trip=0.10, resolution=1, noiser=noiser)

# add 3 attributes
for idx, track in enumerate(collection):
    track.createAnalyticalFeature('TID', idx+1)

#
plt.figure(figsize=(8, 5))
collection.plot(append=plt)
100% (1000 of 1000) |####################| Elapsed Time: 0:00:18 Time:  0:00:180001
------------------------------------------------------------
877 (87.7 %) tracks generated on network
------------------------------------------------------------
../_images/examples_DetailedQuickstart_5_2.png

Dossier de stockage des résultats#

[4]:
from footprint2graph import prepareEnv, setupEnv, logEnv

RESPATH = r'/home/md_vandamme/7_LIB/footprint2graph/test/resultjn/'

import os
import shutil

# Suppression des répertoires
prepareEnv(RESPATH)

# Création des répertoires
iteration_index = 1
setupEnv(RESPATH, iteration_index)

# Log environment information
logEnv(RESPATH)

#  On définit un format pour le stockage des traces modifiées dans le pipeline
fmt = tkl.TrackFormat({'ext': 'CSV',
                       'srid': 'ENU',
                       'id_E': 1, 'id_N': 0, 'id_U': 3, 'id_T': 2,
                       'time_fmt': '2D/2M/4Y 2h:2m:2s',
                       'separator': ';',
                       'header': 0,
                       'cmt': '#',
                       'read_all': True})

pipeline_idx = 1

Step 1 : segmentation and resampling#

[5]:
from footprint2graph import segmentation_resample

# Paramètre : Nombre de points minimum pour un morceau de trace au moment du découpage
#             si le nombre n'est pas atteint, le morceau de trace est oublié
NB_OBS_MIN           = 10

# Paramètre : Distance en mètres entre 2 points, si supérieure au seuil on coupe la trace
DIST_MAX_2OBS        = 50


RESAMPLE_SIZE_GRID = 1
RESAMPLE_SIZE_FUSION = 5

# =============================================================================
#  On définit un format pour le stockage des traces modifiées dans le pipeline

segmentation_resample(RESPATH, collection, fmt, NB_OBS_MIN, DIST_MAX_2OBS,
                    RESAMPLE_SIZE_GRID, RESAMPLE_SIZE_FUSION)




Starting segmentation and resampling...
Starting segmentation ...
     500 / 877
    Number of tracks after segmentation: 877
Finished saving segmented tracks.
Starting resampling ...
    Number of tracks to resample:  877
    Number of tracks after resampling: 877
    Number of tracks after resampling: 877
Finished saving resampled tracks.
Stage 1 finished: segmentation and resampling.
[6]:
from footprint2graph import report_file

report_file(RESPATH, 'rawdata.json')
=================================================================
                  PIPELINE REPORT INFORMATION

Informations sur les données en entrée
    Fin du traitement : 15/06/2026 22:38:48

--------------------------------------------------
Number of tracks :                      877
Number of traces split :                0
Number of traces after preprocessing :  877

Moyenne des distances :                 309 m
Moyenne du nombre de points :           313

--------------------------------------------------
Emprise spatiale
  XMin = 996522.591, YMin = 6542965.120433569, XMax = 996836.704633088, YMax = 6543144.124

Step 2 : calculs des cartes de densités, de constraste et binaire#

[7]:
from footprint2graph import density_polygonize

# Définition des grilles géométrique et contraste
G1_SIZE              = 2
G2_SIZE              = 30

SEUIL_DENSITE = 25    # pas d'unité
SEUIL_SURFACE = 1000  # m2

cut_factor  = 5

interp_dist = 5
clean_dist  = 0

density_polygonize(RESPATH, G1_SIZE, G2_SIZE, SEUIL_DENSITE, SEUIL_SURFACE,
                       pipeline_idx, cut_factor=cut_factor, interp_dist=interp_dist, clean_dist=clean_dist)

Starting rasterization and vectorization (iteration 1)

    Loading tracks from :  resample_grid
    Number of tracks to load:  877
    Building high-resolution geometry density grid G1 :  2 m ...
    Building low-resolution contextual density grid G2 :  30 m ...
    Assigning track points to the G1 and G2 grids
         500 / 877
    Computing G1 ...
    Computing G2 ...
    Number of neighboring cells to consider: 7
    Building contrast grid :  2 m
    Execution time (seconds): 6.8887412548065186
    Finished heatmap computation.
    Starting morphological closing image ...
100% (291 of 291) |######################| Elapsed Time: 0:00:00 Time:  0:00:00
100% (287 of 287) |######################| Elapsed Time: 0:00:00 Time:  0:00:00
100% (1417 of 1417) |####################| Elapsed Time: 0:00:00 Time:  0:00:00
    Execution time (seconds): 0.23677825927734375
    Finished morphological opening.
Vectorizing cleaned image ...
Extracting road surface vector features ...
    Number of polygonize features:  3
    Number of polygonize features copied:  2
    Execution time (seconds): 0.020345687866210938
    Vectorization completed.
Smoothing polygon to remove stair-step artifacts ...
    Execution time (seconds): 0.07509517669677734
    Road surface smoothing completed.
    Starting centerline computation ...
    Execution time (seconds): 0.18407940864562988
    Centerline computed.
Stage 2 completed: rasterization and vectorization.
[8]:
from footprint2graph.util.PlotRes import plotAFMap, matPlotShapefile, maPlotRasterTiff


plt.figure(figsize=(15, 15))

# ----------------------------------------------------------------------------------------------------------
ax1 = plt.subplot2grid((3, 2), (0, 0))
rasterG1 = tkl.RasterReader.readFromAscFile(RESPATH + 'image/G1_1.asc', name='G1', separator='\t')
mapDensity = rasterG1.getAFMap('G1')
plotAFMap(mapDensity, append=ax1, cmap='jet', vmin=0)

ax2 = plt.subplot2grid((3, 2), (0, 1))
rasterK = tkl.RasterReader.readFromAscFile(RESPATH + 'image/K_1.asc', name='K', separator='\t')
mapContraste = rasterK.getAFMap('K')
plotAFMap(mapContraste, append=ax2, cmap='jet', vmin=0)

# ----------------------------------------------------------------------------------------------------------
ax3 = plt.subplot2grid((3, 2), (1, 0))
rasterB = tkl.RasterReader.readFromAscFile(RESPATH + 'image/B_1.asc', name='B', separator='\t')
mapBinaire = rasterB.getAFMap('B')
plotAFMap(mapBinaire, append=ax3, cmap='Greys', vmin=0)

ax4 = plt.subplot2grid((3, 2), (1, 1))
maPlotRasterTiff(RESPATH, 'image/erosion_1.tif', ax4)
ax4.set_title('Closing')

# ----------------------------------------------------------------------------------------------------------
ax5 = plt.subplot2grid((3, 2), (2, 0))
matPlotShapefile(RESPATH, 'image/road_surface_1.shp', ax5)
ax5.set_title('Road surface')

ax6 = plt.subplot2grid((3, 2), (2, 1))
matPlotShapefile(RESPATH, 'image/road_surface_lissee_1.shp', ax6)
ax6.set_title('Road surface lissée')

[8]:
Text(0.5, 1.0, 'Road surface lissée')
../_images/examples_DetailedQuickstart_13_1.png
[9]:
report_file(RESPATH, 'image1.json')
=================================================================
                  PIPELINE REPORT INFORMATION

Résultats des calculs matriciels
    Itération n° : 1
    Fin du traitement : 15/06/2026 22:38:55

Image Processing Results
--------------------------------------------------
High-resolution grid cell size :                      2 m
Low-resolution grid cell size :                       30 m
Number of neighboring cells to consider :             7
Cell cluster size threshold for filling or removal :  17 m2

Polygonization Results
--------------------------------------------------
Number of polygonize features :                      3
Number of small polygonized features :               0
Average area of small polygons :                     0 m2
Number of polygonized features above threshold :     2

--------------------------------------------------
Number of edges in the skeleton :      1

Step 3 : création de la center line#

[10]:
from footprint2graph import addTopologyToNetwork

SEARCH = 25
h = 5
addTopologyToNetwork(RESPATH, SEARCH, h, NB_OBS_MIN, RESAMPLE_SIZE_FUSION, pipeline_idx)

fmt = tkl.NetworkFormat({
           "pos_edge_id": 0,
           "pos_source": 1,
           "pos_target": 2,
           "pos_wkt": 4,
           "srid": "ENU",
           "separator": ",",
           "header": 1})

networkpath = RESPATH + 'network/reseau_1.csv'
squelette = tkl.NetworkReader.readFromFile(networkpath, fmt, verbose=False)

100% (16 of 16) |########################| Elapsed Time: 0:00:00 Time:  0:00:000:00
100% (16 of 16) |########################| Elapsed Time: 0:00:00 Time:  0:00:00
Starting topology creation for the network
    Number of edges in the skeleton: 279
    Finished loaded skeleton.
    /home/md_vandamme/7_LIB/footprint2graph/test/resultjn/network/tmp_in.csv not exists
    /home/md_vandamme/7_LIB/footprint2graph/test/resultjn/network/tmp_out.csv not exists

    Finished removing hooked parts of the skeleton.
    Finished simplification of the skeleton.
Building [100 x 53] spatial index...
    Number of edges in the skeleton (after snapping): 16
    Edge count difference after snapping :  0
    Number of edges in the simplified skeleton: 15
    Number of nodes: 16
     Shortest edges limit :  50
    Number of edges in the skeleton (after removing the shortest edges): 0
    Conflation cannot be performed for node  11 ; the three incident edges are too long: 33 167 284
    Conflation cannot be performed for node  0 ; the three incident edges are too long: 33 64 122
    Edge count after conflation: 5
Stage 3 completed: adding topology to the skeleton.
[11]:
from footprint2graph.util.PlotRes import plotSqueletteTopo, matPlotShapefile

plt.figure(figsize=(18, 8))

ax1 = plt.subplot2grid((2, 3), (0, 0))
matPlotShapefile(RESPATH, 'image/road_surface_lissee_1.shp', ax1)
ax1.set_title('Road surface lissée')

ax2 = plt.subplot2grid((2, 3), (0, 1))
matPlotShapefile(RESPATH + 'network/', 'squelette_1.shp', ax2)
ax2.set_title('squelette brut')

ax3 = plt.subplot2grid((2, 3), (0, 2))
plotSqueletteTopo(RESPATH, ax3)
ax3.set_title('squelette avec topologie')

ax4 = plt.subplot2grid((2, 3), (1, 0))
network.plot(edges='b-', size=1.0, append=ax4)
ax4.set_title('BDTOPO')

ax5 = plt.subplot2grid((2, 3), (1, 1))
squelette.plot('k-', nodes='ko', size=0.8, append=ax5)
ax5.set_title('squelette simplifié avec topologie')

print ('')

../_images/examples_DetailedQuickstart_17_1.png
[12]:
report_file(RESPATH, 'topology1.json')
=================================================================
                  PIPELINE REPORT INFORMATION

Topologie
    Itération n° : 1
    Fin du traitement : 15/06/2026 22:38:57

--------------------------------------------------
Number of edges in the skeleton :      279
Number of skeleton edges removed for being too short :  0

Step 4 : Construction des géométries agrégées pour chaque arc du squelette#

  1. Attribue les points des traces brutes à chaque arc de la topologie

    • Recalage avec l’algorithme de Newson and Krumm (2009)

  2. Reconstruit les bons morceaux de traces candidats pour chaque arc de la topologie

  3. Agrégation des morceaux de traces

  4. Conflation des traces fusionnées afin d’obtenir un réseau de mobilité

[13]:
from footprint2graph import createNetworkGeom

SEARCH = 25
BUFFER = 20
createNetworkGeom(RESPATH, SEARCH, BUFFER, pipeline_idx)
Starting map-matching, aggregation, and conflation of GNSS trajectories.
    Loading network (1) ...
        Number of edges =  5
        Number of nodes =  6
        Total segment length of the network =  765.9881721625045
    Loading collection of tracks ...
100% (6 of 6) |##########################| Elapsed Time: 0:00:00 Time:  0:00:00
        Number of tracks: 877
        Execution time (seconds): 0.6144757270812988
    Starting map-matching ...
        Index spatial :  [100 x 53] spatial index centered on [996680.2867880978; 6543054.718881102]
Map-matching preparation...
        Parameter search_radius:  25
        Map-matching ended.
        Execution time (seconds): 13.260647058486938
        Prepare map-matching results for candidate segment generation
    Number of map-matched points = 49111 (89.82 %)
    Map-matching results restructuring completed.
        Map-matching results exported.
Starting construction of candidate trajectory segments for each topology edge ...
    265  candidates for edge 15
    571  candidates for edge 12
    264  candidates for edge 16
    189  candidates for edge 17
    235  candidates for edge 21
    Number of processed edges:  5
    Minimum number of candidate tracks per edge:  189
    Maximum number of candidate traces per edge:  571
    Average number of candidate tracks per edge:  305
    Segment construction completed.
        Execution time (seconds): 4.8302083015441895
    Starting track segment aggregation for all network edges ...
        Number of candidate tracks / number of sampled tracks 265 / 30
        Number of candidate tracks / number of sampled tracks 569 / 30
        Number of candidate tracks / number of sampled tracks 264 / 30
        Number of candidate tracks / number of sampled tracks 189 / 30
        Number of candidate tracks / number of sampled tracks 235 / 30
        Number of aggregations: 5
        Number of aggregations with 30 traces: 5
        Number of aggregations with fewer than 30 traces: 0
        Minimum number of traces in aggregation: 189
        Average number of traces in aggregation: 304
        Aggregation process finished.
        Execution time (seconds): 2.6931533813476562
    Starting conflation ...
        Conflation process finished.
        Execution time (seconds): 0.00784158706665039
Stage 4 completed: map-matching, aggregation, and conflation.
[14]:
report_file(RESPATH, 'mapmatch1.json')
=================================================================
                  PIPELINE REPORT INFORMATION

Map Matching
    Itération n° : 1
    Fin du traitement : 15/06/2026 22:39:12

Search radius (m):                25

Results
--------------------------------------------------
Map-matched points:               49111 ( 89.82 % )
Off-track points:                 5564 ( 10.18 % )

Quality
--------------------------------------------------
Root Mean Square Error (RMSE) :   3 m
Maximal displacement :            8.58 m

[15]:
report_file(RESPATH, 'candidate1.json')
=================================================================
                  PIPELINE REPORT INFORMATION

Préparation des traces pour la fusion: segments candidats
    Itération n° : 1
    Fin du traitement : 15/06/2026 22:39:16

Results
--------------------------------------------------
Number of processed edges :                         5
Minimum number of candidate traces per edge :       189
Maximum number of candidate traces per edge :       571
Average number of candidate tracks per edge  :      305

[16]:
report_file(RESPATH, 'aggregate1.json')
=================================================================
                  PIPELINE REPORT INFORMATION

Aggrégation des segments de traces candidats
    Itération n° : 1
    Fin du traitement : 15/06/2026 22:39:19

Results
--------------------------------------------------
Number of aggregations :                            5
Number of aggregations with 30 traces :             5
Number of aggregations with fewer than 30 traces :  0
Minimum number of traces in aggregation :           189
Average number of traces in aggregation :           304

[17]:
report_file(RESPATH, 'conflate1.json')
=================================================================
                  PIPELINE REPORT INFORMATION

Conflation de la géométrie du réseau sur le squelette
    Itération n° : 1
    Fin du traitement : 15/06/2026 22:39:19

Results
--------------------------------------------------
Segments conflated :             5 ( 100.0 % )

Distorsion
--------------------------------------------------
Total distorsion RMSE :          3.268 m
Maximum distorsion RMSE :        16.67 %

Visualisation du recalage sur le réseau#

[18]:
from footprint2graph.util.PlotRes import plotMM

plotMM(RESPATH, squelette)
plt.show()
../_images/examples_DetailedQuickstart_26_0.png

Visualisation des candidats pour l’agrégation#

[19]:
from footprint2graph.util.PlotRes import plotSegmentsConstruction

plt.figure(figsize=(8, 8))

ax1 = plt.subplot2grid((1, 1), (0, 0))
plotSegmentsConstruction(RESPATH, ax1, squelette)
../_images/examples_DetailedQuickstart_28_0.png

Visualisation de l’agrégation et de la conflation#

[20]:
from footprint2graph.util.PlotRes import plotAggregation, plotConflation
import os

plt.figure(figsize=(18, 15))

# ---------------------------------------------------
ax1 = plt.subplot2grid((2, 2), (0, 0))
network.plot('k-', '', 'g-', 'r-', 0.5, ax1)
ax1.set_title('BDTOPO')

# ---------------------------------------------------
ax2 = plt.subplot2grid((2, 2), (0, 1))
squelette.plot('k-', nodes='ko', size=0.8, append=ax2)
ax2.set_title('squelette simplifié avec topologie')

# ---------------------------------------------------
ax3 = plt.subplot2grid((2, 2), (1, 0))
plotAggregation(RESPATH, ax3)
ax3.set_title('Agrégation des morceaux de traces')

# ---------------------------------------------------
ax4 = plt.subplot2grid((2, 2), (1, 1))
plotConflation(RESPATH, ax4)
ax4.set_title('CONFLATION')

[20]:
Text(0.5, 1.0, 'CONFLATION')
../_images/examples_DetailedQuickstart_30_1.png

Regards sur le pipeline de construction#

[21]:
plt.figure(figsize=(15, 15))

# ---------------------------------------------------
ax1 = plt.subplot2grid((3, 3), (0, 0))
collection.plot(append=ax1)

ax2 = plt.subplot2grid((3, 3), (0, 1))
rasterG1 = tkl.RasterReader.readFromAscFile(RESPATH + 'image/G1_1.asc', name='G1', separator='\t')
mapDensity = rasterG1.getAFMap('G1')
plotAFMap(mapDensity, append=ax2, cmap='jet', vmin=0)

ax3 = plt.subplot2grid((3, 3), (0, 2))
maPlotRasterTiff(RESPATH, 'image/erosion_1.tif', ax3)
ax3.set_title('Fermeture')

# ---------------------------------------------------
ax4 = plt.subplot2grid((3, 3), (1, 0))
matPlotShapefile(RESPATH, 'image/road_surface_lissee_1.shp', ax4)
ax4.set_title('Road surface lissée')

ax5 = plt.subplot2grid((3, 3), (1, 1))
matPlotShapefile(RESPATH + 'network/', 'squelette_1.shp', ax5)
ax5.set_title('squelette brut')

ax6 = plt.subplot2grid((3, 3), (1, 2))
squelette.plot('k-', nodes='ko', size=0.8, append=ax5)
ax6.set_title('squelette simplifié avec topologie')

# ---------------------------------------------------
ax7 = plt.subplot2grid((3, 3), (2, 0))
plotSegmentsConstruction(RESPATH, ax7, squelette)
ax7.set_title("candidats pour l'agrégation")

ax8 = plt.subplot2grid((3, 3), (2, 1))
plotAggregation(RESPATH, ax8)
ax8.set_title('Agrégation des morceaux de traces')

ax9 = plt.subplot2grid((3, 3), (2, 2))
plotConflation(RESPATH, ax9)
ax9.set_title('Réseau de mobilité')
[21]:
Text(0.5, 1.0, 'Réseau de mobilité')
../_images/examples_DetailedQuickstart_32_1.png