Por fin está hecho. He terminado la automatización para actualizar los datos de mi página POI-OSM. Ha sido largo, pues no ha sido tan simple como me lo esperaba. Mi página POI-OSM permite descargar PDIs de OSM. Este script permite actualizar los datos fuentes de forma más rápida y con más regularidad.
Gracias a tsuji de http://www.developpez.net, por su ayuda, en la solución de selección de nodos con criterio en un hijo.
¿Que hace mi script Php?
- Bajar un fichero .osm.bz2 de Geofabrik al servidor.
- Leer linea por linea el fichero comprimido.
- Salvar los datos descomprimido, omitiendo las lineas sin hijos.
- Tratar el fichero osm para conservar solo los nodos con hijos con las categorías necesarios para mi pagina.
- Comprobar que el fichero xml tiene un tamaño superior al anterior antes de salvarlo.
1. Bajar un fichero
// 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. Leer linea por linea el fichero bz2 comprimido.
$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 "El fichero OSM existe. No hace falta descomprimir el 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. Durante la lectura del bz2 y la escritura del fichero osm, se aprovecha para hacer una primera limpieza linea por linea, omitiendo las lineas sin hijos de <nodes>, <nd> y <member>. Eso permite reducir los ficheros osm una vez descomprimidos. Sin eso el fichero para Francia pueden alcanzar 60 Gbytes. con esta limpieza, no superan 10 Gbytes. La ocupación de memoria es baja usando bzread combinado a fwrite.
$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 "el fichero OSM esta listo \n";
} // close if else
4. De nuevo para evitar saturar la memoria del servidor se lee nodos por nodos con xmlread y se graba solo los nodos, con categorías elegidas. La memoria se vacía, cada 2000 iteraciones. Uso un elemento DOM para guardar los nodos con sus atributos sus hijos y los grabo en un fichero xml con xmlwrite. Osm y xml tienen el mismo formato.
// 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. Se compara el fichero xml de salida con el fichero anterior, si existe. Si el fichero nuevo tiene mayor tamaño (señal que no se paro el proceso) se sustituye el fichero del servidor por el nuevo: