Tracé de frontières avec gnuplot(1)

Cette page est obsolète, voir plutôt Tracé de frontières avec gnuplot(2)

Dans les données d'OpenStreetMap, les limites administratives des territoires sont des routes comme les autres, composées de noeuds. L'objectif est ici de rassembler en un fichier les coordonnées (latitude et longitude) de ces points, de manière à pouvoir les visualiser avec gnuplot.

Exemple avec la Belgique.

D'abord obtenir le fichier osm de la frontière belge, qu'on peut visualiser sur http://www.openstreetmap.org/browse/relation/52411.

  wget -O 52411.osm http://www.openstreetmap.org/api/0.6/relation/52411

On pourrait obtenir tous les noeuds en un seul fichier en ajoutant /full à cette url, mais je préfère rapatrier les données de chaque membre séparément pour m'assurer qu'ils soient ordonnés1.

  <?xml version="1.0" encoding="UTF-8"?>
  <osm version="0.6" generator="OpenStreetMap server">
    <relation id="52411" visible="true" timestamp="2009-10-28T23:30:32Z" version="295" changeset="2977966" user="Ldp" uid="48796">
      <member type="way" ref="24718735" role="enclave"/>
      <member type="way" ref="25417004" role="enclave"/>
      <member type="way" ref="25418377" role="enclave"/>
  (etc.)
      <member type="way" ref="43312486" role=""/>
      <tag k="admin_level" v="2"/>
      <tag k="boundary" v="administrative"/>
      <tag k="ISO3166-1" v="be"/>
      <tag k="name" v="België - Belgique - Belgien"/>
      <tag k="name:da" v="Belgien"/>
      <tag k="name:de" v="Belgien"/>
      <tag k="name:el" v="Βέλγιο"/>
      <tag k="name:en" v="Belgium"/>
      <tag k="name:es" v="Bélgica"/>
      <tag k="name:fr" v="Belgique"/>
      <tag k="name:nl" v="België"/>
      <tag k="native_name:da" v="Kongeriget Belgien"/>
      <tag k="native_name:de" v="Königreich Belgien"/>
      <tag k="native_name:el" v="Βασίλειο του Βελγίου"/>
      <tag k="native_name:en" v="Kingdom of Belgium"/>
      <tag k="native_name:es" v="Reino de Bélgica"/>
      <tag k="native_name:fr" v="Royaume de Belgique"/>
      <tag k="native_name:nl" v="Koninkrijk België"/>
      <tag k="timezone" v="Europe/Brussels"/>
      <tag k="TMC:cid_58:tabcd_1:Class" v="Area"/>
      <tag k="TMC:cid_58:tabcd_1:LCLversion" v="8"/>
      <tag k="TMC:cid_58:tabcd_1:LocationCode" v="3"/>
      <tag k="type" v="boundary"/>
    </relation>
  </osm>

On va ensuite rapatrier les fichers correspondant à chaque membre mentionné au moyen de ce script Perl. On utilise XML::Simple qui permet de transformer des données xml en une structure arborescente de type hashref2 :

  #! /usr/bin/perl
  use LWP::Simple;
  use XML::Simple;
  
  $filename =  $ARGV[0];
  my $ref = XMLin($filename);
  @members = @{$ref->{'relation'}->{'member'}};
  
  foreach (@members) {
    my $id = $_->{'ref'};
    print "# $id\n";  # l'id de chaque segment sera incorporé dans
                      # le fichier comme un commentaire.
    my $content = get("http://api.openstreetmap.org/api/0.6/way/$id/full");
    my $ref = XMLin($content, ForceArray => 1, KeyAttr => []);
    foreach $node (@{$ref->{node}}) {
      print "  $node->{lat} $node->{lon}\n"
    }
    print "\n\n";     # gnuplot demande une double ligne vide pour séparer
  }                   # les données

Le fait de séparer les données permettra éventuellement d'en tracer une sélection avec gnuplot (avec l'option index n, voir la documentation de gnuplot[1]).

  gv@fantasio:~/travaux/openstreetmap$ ./getways.pl 52411.osm > Belgique.dat

Le fichier obtenu contient bien les données sous cette forme:

 # 24718735
   50.5971521 6.2465063
   50.5968143 6.2480513
   50.5977787 6.2484375
   50.5981764 6.2469527
 # 25417004
   50.5565244 6.2175382
   50.5562626 6.2181133
 (etc.)

On peut déjà les visualiser avec gnuplot

 gnuplot> plot 'Belgique.dat' using 2:1 with lines

La frontière ainsi dessinée ne comprend pas la ligne côtière, mais bien la limite des eaux territoriales.

Pour obtenir cette ligne côtière, je n'ai pas trouvé de relation unique qui la synthétise. Mais en zoomant sur un fragment de la carte et en exportant les données xml, j'ai repéré des routes non visibles dotées de ces attributs:

  <tag k="natural" v="coastline"/>

J'ai donc rapatrié les données de ce type en précisant une BoundingBox correspondant à la côte belge:

  gv@fantasio:~/travaux/openstreetmap$ wget -O coastline.osm
  http://osmxapi.hypercube.telascience.org/api/0.6/way[natural=coastline][bbox=2.48,51.08,3.38,51.38]

et modifié légèrement le premier script pour qu'il rapatrie les noeuds de toutes les routes mentionnées dans ce fichier osm:

  #! /usr/bin/perl
  use LWP::Simple;
  use XML::Simple;
  
  $filename =  $ARGV[0];
  my $ref = XMLin($filename, ForceArray => 1, KeyAttr => []);
  @ways = @{$ref->{'way'}};
  
  foreach (@ways) {
    my $id = $_->{'id'};
    print "# $id\n"; 
    my $content = get("http://api.openstreetmap.org/api/0.6/way/$id/full");
    my $ref = XMLin($content, ForceArray => 1, KeyAttr => []);
  
    foreach $node (@{$ref->{node}}) {
      print "  $node->{lat} $node->{lon}\n"
    }
    print "\n\n";
  }
  gv@fantasio:~/travaux/openstreetmap$ ./coastline.pl coastline.osm > coastline.dat

On peut alors dessiner la carte avec gnuplot3, et l'exporter en format bitmap (png par ex.) ou postscript:

 gnuplot> plot 'Belgique.dat' using 2:1 with lines lt 9
 gnuplot> replot 'coastline.dat' using 2:1 with lines lt 7
 gnuplot> set term postscript eps solid color
 Terminal type set to 'postscript'
 Options are 'eps noenhanced defaultplex \
   leveldefault color colortext \
   solid dashlength 1.0 linewidth 1.0 butt \
   palfuncparam 2000,0.003 \
   "Helvetica" 14 '
 gnuplot> set output 'carte.eps'
 gnuplot> replot

Le résultat est présent à cette adresse, au format eps.

Footnotes:

1. Le fichier xml obtenu avec /full pourrait sans doute être analysé avec un parser plus évolué, de manière à y trouver les routes, qui contiennent les références aux noeuds, puis à extraire les coordonnées de ces noeuds lors d'une deuxième passe. Mais par paresse,je voulais obtenir un résultat rapidement avec XML::Simple (cpan.org[2]).
2. L'option
  ForceArray => 1, KeyAttr => []

est très importante; gràce à cela, les noeuds restent ordonnés dans une liste de type array, en respectant l'ordre existant dans les données osm. Sinon, chacun serait intégré dans une structure de tableau, avec son id comme clé. Or l'ordre des éléments d'un tableau n'est pas controllable. Et par conséquent, en joignant ces noeuds par une ligne, le résultat serait incohérent.

3. Au prix de quelques migraines pour trouver la syntaxe des options souhaitées :-)