Automatisation de POI-OSM

mar, 18/12/2012 - 11:54 -- Remiguel

Enfin, j'ai terminé l'automatisation de la mise à jours des donnés de ma page POI-OSM. Cela à été plus long que prévu. Ma page POI-OSM permet de télécharger des points d'intérêts d'OSM. Ce script Php permet d'actualiser les fichiers de donnés simplement et rapidement, ce qui me permettra de maintenir ma page à jours. Merci à tsuji de http://www.developpez.net, pour son aide et solution pour la sélection de noeud avec un critère sur ses enfants.

 

Que fait mon script Php?

  1. Télécharger sur mon serveur un fichier .osm.bz2 du site Geofabrik.
  2. Lire ligne par ligne, le fichier compressé en bz2.
  3. Garder les donnés compressé en écartant certaines lignes sans enfants.
  4. Traiter le fichier osm pour ne conserver que les noeuds avec enfants des catégories souhaitées.
  5. S'assurer que le nouveau fichier pèse plus que l'antérieur, avant de le sauvegarder.

 

1. Télécharger le fichier.

// download file
$country = "switzerland";
$file = $country.".osm.bz2";

if (file_exists(("filetmp/".$file))) { // erase the file if exits
unlink ("filetmp/".$file);
}

if ($country == "germany") {
$url  = 'ftp5.gwdg.de/pub/misc/openstreetmap/download.geofabrik.de/'.$file;
} else {
$url  = 'http://download.geofabrik.de/openstreetmap/europe/'.$file;
};
    $path = 'filetmp/'.$file;
    $fdow = fopen($path, 'w');
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_FILE, $fdow);
    $data = curl_exec($ch);
    curl_close($ch);
    fclose($fdow);
    unset($fdow);

2. Lire ligne par ligne le fichier bz2. 

$countr_osm = "filetmp/".$country.".osm";

if (file_exists(($countr_osm))) { // erase the file if exits
unlink ($countr_osm);
}
$countr_2xml = $country."2.xml";
if (file_exists(($countr_2xml))) { // erase the file if exits
unlink ($countr_2xml);
}
$kount = 0;
$tempfile = "tmp_file.txt";

if (file_exists($countr_osm)) {
echo "Le fichier OSM existe. Pas nécessaire de décompresser le bz2 \n";
} else {
$fp = fopen ($countr_osm,"w"); // Open a file to save the extracted data

$bz = bzopen("filetmp/$file", "r") or die("Couldn't open $file"); // Open the compressed file

3. Pendant la lecture du bz2 et l'écriture du fichier osm, le code fait un premier nettoyage, ligne par ligne, en laissant de coté toutes les lignes sans enfants du type <nodes>, <nd> y <member>. Cela permet de réduire le fichier décompresser. Sans cela le fichier de la France est supérieur à 60 Go. Avec cette méthode le fichier ne dépasse pas 10 Go. Avec l'utilisation de bzread et fwrite l'occupation de mémoire reste basse.

$decompressed_file = '';
while ($decompressed_file = bzread($bz, 4096)) { // read the compressed file by chunked
$ftmp = fopen ($tempfile,"w+");

if($decompressed_file === FALSE) die('Read problem');
if(bzerror($bz) === NULL) die('Compression Problem');

fwrite($ftmp,$decompressed_file, strlen($decompressed_file)); // save the chunk in a tmp file
fseek($ftmp, 0);

    while (!feof($ftmp)) {
    $line= fgets($ftmp, 1024);
         if ((strpos($line,"<nd ") !== false && strpos($line,"/>") !== false)
            || (strpos($line,"<member ") !== false && strpos($line,"/>") !== false)
            || (strpos($line,"<node ") !== false && strpos($line,"/>") !== false))
         {
         // leave this line out of the output file
         } else {
         fwrite($fp,$line);
         $kount = ++$kount;
         echo $kount."\n";
         } // close if
    } // close while
    fclose($ftmp);
    unlink($tempfile);
} // close while

bzclose($bz);
fclose($fp);
// Delete the object to free memory
unset($bz);
unset($fp);
unset($ftmp);
$kount=0;

echo "Le fichier OSM est prêt \n";
} // close if else

4. À nouveau, pour éviter de saturer la mémoire du serveur, le script lit noeud par noeud, avec cette fois xmlread et on sauvegarde seulement les noeuds qui ont une des catégories souhaitée. La mémoire se vide chaque 2000 itérations. J'ai utilisé un objet DOM qui me permet de garder en mémoire les noeuds, leurs attributs et enfants pour les sauvegarder dans un fichier xml avec xmlwrite. Osm est un fichier xml.

// extract only the data we need (define in array block) and save them in a new file

// the file is read node by node

$block = array('amenity', 'craft', 'emergency', 'historic', 'leisure', 'man_made', 'natural', 'office', 'shop', 'sport', 'tourism', 'aeroway', 'railway');

$xmlWriter = new XMLWriter();

$xmlWriter->openMemory();

$xmlWriter->setIndent(true);

$xmlWriter->startDocument('1.0','UTF-8');

$xmlWriter->startElement('osm');

$xmlWriter->startAttribute('version');

$xmlWriter->text('0.1');

$xmlWriter->startAttribute('generator');

$xmlWriter->text('Remiguel');

if(file_exists($countr_osm)) {

    $xml = new XMLReader(); 

    $xml->open($countr_osm); // input file as source

    $node=null;

    $kflag=false;

    while($xml->read()){ 

        if ($xml->nodeType==XMLReader::ELEMENT && $xml->name=='node') {

            $node=$xml->expand(); // copies the entire node in a DOM object

            $kflag=false;    //reset 

        }

        if ($kflag

            && $xml->nodeType==XMLReader::END_ELEMENT 

            && $xml->name=='node'

        ) {

            // read and write node attributes

            $xmlWriter->startElement('node'); 

            foreach ($node->attributes as $key => $value) {

            $xmlWriter->startAttribute($key);

            $xmlWriter->text($value->value);

            }

            // read and write tag attributes

            $tag = $node->getElementsByTagName("tag"); 

            foreach($tag as $d){

            $xmlWriter->startElement('tag');

            $xmlWriter->startAttribute('k');

            $xmlWriter->text($d->getAttribute('k'));

            $xmlWriter->startAttribute('v');

            $xmlWriter->text($d->getAttribute('v'));

            $xmlWriter->endElement(); // close tag

            }

            $xmlWriter->endElement(); // close node

            // echo_memory_usage();

            echo $kount."\n";

            if ($kount > 2000) {

   file_put_contents($countr_2xml, $xmlWriter->flush(true), FILE_APPEND);

   $kount=0;

   }

            $node=null;

            $kflag=false;

        }

        

        foreach ($block as $cat) {

        if (!$kflag

            && $xml->nodeType==XMLReader::ELEMENT 

            && $xml->name=='tag' 

            && $xml->getAttribute('k')==$cat

            && $xml->getAttribute('v') // maybe this line can be removed

        ) {

            $kflag = true;

            $kount = ++$kount;

        }

        }

    } // close while

} // close if

$xmlWriter->endElement();

$xmlWriter->endDocument();

file_put_contents($countr_2xml, $xmlWriter->flush(true), FILE_APPEND);

unlink ($countr_osm);

unlink ("filetmp/".$file);

// Delete the object to free memory

unset($xmlWriter);

unset($xml);

5. On compare le nouveau fichier xml avec l'ancien. Si il est plus volumineux, on considère que le processus est allé jusqu'au bout, et on écrase l'ancien fichier avec le nouveau:

if (file_exists(($country.".xml"))) {
    if (filesize($countr_2xml) > filesize($country.".xml")) {
    unlink ($country.".xml");
    rename($countr_2xml, $country.".xml");
    echo 'Actualisation de '.$country. ' terminado';
    } else {
    unlink($countr_2xml);
    echo 'Le fichier ' .$country. ' etait deja à jours';
    }
} else {
rename($countr_2xml, $country.".xml");
echo 'Nouveau fichier créé: '.$country;
}
 
Ce script sera complété avec une boucle foreach pour traiter tout les pays sans avoir besoin de l'éditer, avant de l'exécuter mensuellement avec cron.
 
Piece(s) jointe(s):