Cartographie avec R : Metz un carrefour de voies romaines

Je vous avais promis de m’intéresser cette année un peu plus profondément à la visualisation de données. Comme la technicité des billets précédents ne vous a pas effrayé et que ces billets semblent vous plaire, je vous propose de poursuivre avec la carte ci-dessous :

map-roman-roads-from-divodurum-with-R.png

Il s’agit d’une représentation des voix romaines entre le 1er et le Vème siècle autour de Metz.

Après une analyse sommaire des informations apportées par cette carte, il faudra s’interroger sur les sources de ces données et la validité de cette représentation. Pour finir, vous verrez le code qui permet de générer cette carte.

Le premier intérêt de cette carte est sa représentation topographique. La vallée de la Moselle apparaît clairement. La localisation des citées à proximité des rivières et de plaines et également évidente. Les zones d’influences des peuples gaulois (en rouge) apparaissent également sans avoir besoin de tracer des frontières qui sont de toutes façon mal connues et peut-être anachroniques. On peut encore noter que les futures relations privilégiées entre les trois évêchés de Metz, Toul et Verdun présentent une certaine cohérence géographique et économique même si dans les faits les territoires réellement gérés seront trop morcelés pour créer une unité.

Pour terminer, vous pourrez désormais fortement nuancer l’affirmation traditionnelle que Divodurum était une cité d’appui à la garnison de Strasbourg (Argentorate). Il apparaît sur la carte que l’appui était plus évident pour la garnison de Trèves (Augusta Treverorum). La liaison entre Metz et Trèves se fait sans difficulté le long de la vallée de la Moselle et encore plus rapidement en navigant sur la rivière. Un convoi commercial ne mettait qu’un jour et demi pour rejoindre Trèves depuis Metz alors qu’il fallait cinq jours pour atteindre Strasbourg. Une troupe armée mettait moins d’une journée pour atteindre Trèves et deux jours et demi pour atteindre Strasbourg.

L’application Orbis1 de l’université de Stanford, vous permet de vous amuser à calculer vous même les itinéraires et temps de trajets à travers tous l’empire romain en fonction des types de transports ou la saison.

Notre connaissance des voies romaines provient de l’archéologie mais également de documents comme l’«Itinéraire D’Antonin», un guide de voyage de la fin du IIIème siècle connu par une vingtaine de manuscrits du VIIIème au XVème siècle. Ce guide s’inspire sans doute de la table de Peutinger2 dont on dispose d’une copie du XIIIème siècle. Voici, ci-dessous l’extrait de la table, dans une version moderne dessinée, pour la région qui nous intéresse :

Peutinger-Metz.jpg

Les doubles tours représentent les lieux de passages importants. On reconnait aisément la source et le cours de la Moselle avec “Divo Durimedio Matricorum”. Vous pouvez noter une erreur du copiste. On devrait en réalité avoir “Divo Duri Mediomatricurum”. Medio signifiant “peuple”, comme dans “Mediomatrici”. Le long de la moselle on trouve également “Aug(usta) Treverorum” pour Trèves.

“Tullio. x” indique une distance de 10 lieues entre Tullio (Toul) et Scarponna (Dieulouard). À proximité de Toul, le temple à l’origine d’une source et en général identifié comme le sanctuaire de Grand. Je vous laisse trouver par vous même la localisation de Argentorate sur le fragment III du dessin de l’université de Sciences Appliquées de Augsburg3.

Ces données géographiques ont été numérisées et sont désormais accessibles en ligne. Vous pouvez par exemple utiliser l’application À-la-carte du groupe AWNC de l’université américaine de Caroline du Nord4. Ce groupe réalise également la base Pleiades5 qui géolocalise de façon collaboratives les sites antiques.

Toutes ces données sont accessibles dans des formats documentés, libres et ouvert. Cela va nous permettre de réaliser notre propre carte avec exclusivement les points qui nous intéresse.

Comme pour le billet précédent qui traitait de la célèbre carte de Minard, je vais encore utiliser R. Il s’agit d’un logiciel libre, destiné à l’origine aux statistiques il permet également de traiter tous types de données. Nous allons voir ici ces capacités à traiter des données géographiques. Comme pour d’autres logiciels que j’ai présenté sur ce blog, la possibilité de traiter des données à partir de simple fichiers textes permettra de facilement documenter ou modifier le traitement de données.

Je vais essentiellement me servir de deux types de fichiers. Les fichiers “shape“ contiennent des points géolocalisés comme les rivières, les lieux ou encore les voies romaines. Les fichiers images aux format “tif“ (des “raster“) permettront de récupérer les informations d’élévations et l’ombrage des reliefs pour obtenir un rendu plus réaliste. J’aurai pu traiter directement des raster de cartes mais ceux-ci sont souvent trop volumineux.

J’ai récupéré les fichiers de données depuis le dépôt du groupe AWNC6 parce que ces cartes essayent de rester fidèles ou de reproduire la géographie de l’antiquité. On peut trouver de nombreux autres fichiers shape ou raster libres d’usage sur le site de l’IGN pour la France ou Natural Earth pour le Monde.

Voici le code pour générer l’image en tête de ce billet avec les explications détaillées :

# Chargement des bibliothèques nécessaires
library(raster)
library(rgdal)
library(ggplot2)

# Lecture des données
# ===================
# lecture des rasters
nat.earth <- stack('./Map/mod_elevation.tif')
hillshade <- stack('./Map/mod_hillshade.tif')
# lecture des shape
ne_rivers <- readOGR(".","all_rivers")
ne_roads <- readOGR(".","ba_roads")
ne_places <- readOGR(".","pleiades_places")

Ici, il faut faire une petit pause dans le code. Maintenant que les données sont contenues dans ne_places ou ne_roads, on peut examiner ces données. Vous verrez que vous trouverez beaucoup d’informations complémentaires, comme des périodes, dates, annotation des sources, …

Ici, on observe par exemple que les noms d’emplacements modernes se trouvent dans la colonne GEOCONTEXT et TITLE contient le nom antique.

Voici un exemple de données à exploiter :

> summary(places)
...
      FEATURETYP           GEOCONTEXT        ID          LOCATIONPR 
 settlement:74   GER            :  3   108725 :  1   precise  :125  
 unlocated :15   Altenstadt GER :  1   108732 :  1   rough    : 18  
 river     :11   Altrier, LUX   :  1   108738 :  1   unlocated: 18  
 temple    : 9   Alzette FRA/LUX:  1   108743 :  1                  
 station   : 6   Andrésy?       :  1   108754 :  1                  
 villa     : 6   (Other)        :147   108767 :  1                  
 (Other)   :40   NA's           :  7   (Other):155  
...

Ici, je n’ai choisi de m’intéresser que à la région autour de Metz en ne présentant que quelques cités importantes et les routes.

# Traitement des données
# ======================

# Définition de la zone géographique
# ----------------------------------
# 5 < longitude < 8 et 48.25 < latitude < 50
e = extent(raster(xmn = 5, xmx = 8, ymn = 48.25, ymx = 50))

river.subset <- crop(ne_rivers, e)
roads.subset <- crop(ne_roads, e)
places <- crop(ne_places, e)
nat.crop <- crop(nat.earth, e)
hillshade.crop <- crop(hillshade, e)

# Sélection des données à afficher
# --------------------------------

# Le shape Pleiades et un mélange de points et de données,
# c'est un SpatialPointsDataFrame

df_places <- fortify(places@data) # data frame des données Pleiades

# Sites à afficher, on obtient le nom et la géolocalisation
sites <- c('Metz','Trier GER','Verdun', 'Strasbourg', 
          'Toul', 'Bliesbruck', 'Marsal', 'Grand')
txt <- df_places[df_places$GEOCONTEXT %in% sites, ]

# Sélection des noms de peuples présents avant 100
peuples <- df_places[df_places$TYPE=='unknown' & 
                df_places$MAXDATE >= -100 & 
                df_places$MINDATE <= 100, ]

# Les données de rivières sont des SpatialLinesDataFrame 
# sélection d'un groupe de rivières importantes et secondaires
rivers <- subset(river.subset, awmc_class == 1)
rivers.small <- subset(river.subset, awmc_class == 3)

# Idem pour les routes
roads <- subset(roads.subset, Major_or_M == 1 )
roads.minor <- subset(roads.subset, Major_or_M == 0)

# Préparation de l'affichage
# ==========================
# conversion des valeurs d'élévation et d'ombrage
rast.table <- data.frame(xyFromCell(nat.crop, 1:ncell(nat.crop)),
                         getValues(nat.crop/255))

hill.table <- data.frame(xyFromCell(hillshade.crop, 1:ncell(hillshade.crop)),
                         getValues(hillshade.crop/255))

# ombrage en niveau de gris
hill.table$rgb <- with(hill.table,grey(mod_hillshade))

# séquence de gradient de 20 couleurs
gradient <- seq( min(rast.table$mod_elevation),
                 max(rast.table$mod_elevation), length.out=20)

# Affichage
# ========

p <- ggplot()   # on utilise ggplot2
p <- p + layer(geom="raster", data=rast.table,
    mapping=aes(x,y,fill=mod_elevation))                    # fond d'élévation
p <- p + scale_fill_gradientn(colours = terrain.colors(20), # 20 couleurs "terrain"
    breaks=gradient, guide = FALSE)                         # absence de légende
p <- p+ geom_tile(data=hill.table,
    aes(x,y,alpha=hill.table$rgb), fill = "grey20")         # ombrage en gris
p <- p + scale_alpha_discrete(range=c(1,0),guide = FALSE)   # transparence
p <- p + geom_path(data=rivers, 
    aes(x = long, y = lat, group = group), 
    color = 'blue', size=.6)                                # rivières importantes
p <- p + geom_path(data=rivers.small, 
    aes(x = long, y = lat, group = group), 
    size=.2, color = 'blue')                                # rivières secondaires
p <- p + geom_path(data=roads, 
    aes(x = long, y = lat, group = group), color = 'red')   # routes importantes
p <- p + geom_path(data=roads.minor, 
    aes(x = long, y = lat, group = group), color = 'red', 
   linetype=2)                                             # routes secondaires
p <- p + geom_text(data=txt, 
    aes(x=REPRLONG, y=REPRLAT, 
    label=sprintf("%s\n%s",TITLE,GEOCONTEXT)),             # textes des lieux
    size=4, col="white")
p <- p + geom_text(data=peuples,                           # textes des peuples
    aes(x=REPRLONG, y=REPRLAT, label=TITLE),               # 'jitter' positionne
    size=5, col="red",position="jitter")                   # avec un léger aléa
p <- p + scale_x_continuous(expand=c(0,0)) +
    scale_y_continuous(expand=c(0,0))                      # échelles des axes
p <- p + xlab('') + ylab('')                               # labels des axes

print(p) # Affichage

Voilà, la carte est réalisée et facilement modifiable.

Ceux qui ont suivi attentivement jusqu’ici remarqueront qu’il manque le nom des rivières. En effet, lors du “crop” les points prévus par le shape river se trouvaient hors cadre. On pourrait facilement les ajouter sur l’image avec un logiciel comme Gimp mais nous allons plutôt nous amuser à calculer la position du texte à 1/4 de la longueur de la rivière.

Voici le code :

# on crée un data frame avec les données de
# river qui est pour l'instant un SpatialLinesDataFrame
df_rivers <- fortify(rivers@data)

# idem avec les coordonnées des points
# un bug empêche pour l'instant un simple fortify(rivers@lines)
library(plyr)
df_lines <- ldply(rivers@lines, fortify)

# la fonction R trouve le point proche de 1/4 de la longueur
findloc <- function(rn,col){
  point <- as.integer( nrow(df_lines[df_lines$id == rn, ])/4 )
  return(as.numeric( df_lines[df_lines$id == rn, ][point,col] ) )
}

# pour toutes les rivières on applique la fonction
for(i in rownames(df_rivers)){df_rivers[i,]$lon <- findloc(i,"long")}
for(i in rownames(df_rivers)){df_rivers[i,]$lat <- findloc(i,"lat")}

# il ne reste plus qu'à les dessiner avec un "jitter"
p <- p +  geom_text(data=df_rivers, 
  aes(x=lon, y=lat, label=Name), 
  size=3, col="blue", position="jitter")

Liens utiles et références

1 ORBIS, The Stanford Geospatial Network Model of the Roman World, URL: http://orbis.stanford.edu/#mapping
2 Table de Peutinger, Wikipedia, URL: http://fr.wikipedia.org/wiki/Table_de_Peutinger
3 Tabula Peutingeriana, Bibliotheca Augustana, URL: http://www.hs-augsburg.de/~harsch/Chronologia/Lspost03/Tabula/tab_pe03.html
4 Application «À-la-carte», Ancient World Mapping Center - University of North Carolina, URL: http://awmc.unc.edu/awmc/applications/alacarte/
5 Base de données Pleiades, Ancient World Mapping Center - University of North Carolina, URL: http://pleiades.stoa.org/
6 Fichiers shape et raster mis à disposition par AWMC, URL: http://awmc.unc.edu/awmc/map_data/