Calcul de buffer sous MySQL - 2ème partie - la pratique#
Date de publication initiale : 17 avril 2012
Pour mettre en application le premier article sur le calcul de buffer dans MySQL, nous allons développer une petite démo. Tout d'abord il nous faudra initialiser une base de données qui contiendra pas mal d'enregistrements, puis nous alimenterons cette base avec des points d'intérêts provenant d'OpenStreetMap et enfin nous développerons une carto avec l'API Google Maps pour tester la formule Haversine.
Pour la démo, il nous faut un grand nombre d'enregistrements. Au lieu de générer des faux points, autant s'amuser un peu avec les nouveaux serveurs et services proposés par OpenStreetMap France. Parmi les services nous utiliserons ici l'OverpassAPI qui permet de faire des extractions : il suffit de faire la bonne requête pour extraire ce que nous désirons (par exemple les restaurants dans une France métropolitaine un peu élargie). Nous allons donc utiliser la boîte de requête en ligne pour extraire les POI :
Qui nous renverra un fichier d'environ 10 Mo que nous pousserons dans la base de données. L'OverpassAPI fournit un fichier XML OSMifié. Il suffit donc de parser le fichier et d'écrire un petit script pour alimenter la base.
Pour alimenter la base de données, nous utiliserons ici le PHP. N'importe quel autre langage de scripts fait l'affaire. Voici un extrait de l'export d'OverpassAPI :
<nodeid="29681754"lat="43.6436341"lon="1.5220926"><tagk="amenity"v="restaurant"/><tagk="name"v="La Table de Grand-mere"/><tagk="website"v="http://www.latabledegrandmere.fr/"/></node>
C'est du XML et PHP sait le lire et le transformer en objet. C'est assez pratique !
Bon un peu de code :
On se connecte à la base de données.
On regarde si le fichier existe.
Pour chaque node, on récupère le nom si il existe.
On fait une petite requête d'insertion dans la table.
<?phpdefine('HOST','localhost');define('PORT','5432');define('DB_USER','mylogin');define('DB_PASS','mypass');define('DB_NAME','osmtestpoi');if(file_exists('./resources/osm/osmrestotoulouse.osm')){$link=mysql_connect(HOST,DB_USER,DB_PASS);mysql_select_db(DB_NAME);$xml=simplexml_load_file('./resources/osm/osmrestotoulouse.osm');foreach($xml->nodeas$node){$existname=0;foreach($node->tagas$tag){if((string)$tag['k']=='name'){$name=mysql_real_escape_string($tag['v']);$existname=1;}else{$name="";}}switch($existname){case0:$sql="INSERT INTO poi (id_poi, geom_poi) VALUES (".$node['id'].",GeomFromText('POINT(".$node['lon']." ".$node['lat'].")'))";break;case1:$sql="INSERT INTO poi (id_poi, name_poi, geom_poi) VALUES (".$node['id'].",'".$name."',GeomFromText('POINT(".$node['lon']." ".$node['lat'].")'))";break;}$result=mysql_query($sql);if(!$result){echo"Requête invalide : ".mysql_error()."<br/>";}else{echo$node['id']." => insert<br/>";}}mysql_free_result($result);mysql_close($link);}else{exit('gloubs - il est où le fichier :/');}?>
Ça fait quelques temps que nous n'avons pas développé avec l'API Google Maps ; malgré les désaffections des gros utilisateurs au profit d'OpenStreetMap, ça n'en reste pas moins une très belle librairie.
<!DOCTYPE html><html><head><title>Calcul de buffer sous MySQL</title><metaname="viewport"content="width=device-width, initial-scale=1.0, user-scalable=no"><metacharset="UTF-8"><styletype="text/css">html,body,#map_canvas{margin:0;padding:0;height:100%;}</style><scripttype="text/javascript"src="https://maps.googleapis.com/maps/api/js?sensor=false"></script><scripttype="text/javascript">varmap;functioninitialize(){varmyOptions={zoom:6,center:newgoogle.maps.LatLng(46.89,2.61),mapTypeId:google.maps.MapTypeId.ROADMAP};map=newgoogle.maps.Map(document.getElementById('map_canvas'),myOptions);}google.maps.event.addDomListener(window,'load',initialize);</script></head><body><divid="map_canvas"></div></body></html>
Nous avons besoin d'une connexion Ajax vers le script PHP pour "aller chercher" nos marqueurs dans la base de données. Contrairement à l'API v2 qui proposait un connecteur par défaut, dans la v3 il faut le déclarer. Souvent nous n'avons pas besoin de coder cette fonction avec l'usage intensif de jQuery ou autre. Mais c'est pas plus mal de savoir ce que l'on fait :
Ici pas trop de souci, nous éditons le XML contenant les marqueurs en manipulant le DOM pour gérer la construction du fichier XML : il s'agit dune simple requête SQL - c'est ce script qu'il faudra changer pour prendre en compte le rayon de recherche et le centre.
<?phpheader("Content-type: text/xml");define('HOST','localhost');define('PORT','5432');define('DB_USER','mylogin');define('DB_PASS','mypass');define('DB_NAME','osmtestpoi');$link=mysql_connect(HOST,DB_USER,DB_PASS);mysql_select_db(DB_NAME);$dom=newDOMDocument("1.0");$node=$dom->createElement("markers");$parnode=$dom->appendChild($node);$query="SELECT poi.id_poi, poi.name_poi, x(geom_poi) AS X, y(geom_poi) AS Y FROM poi WHERE 1";$result=mysql_query($query);while($row=@mysql_fetch_assoc($result)){$node=$dom->createElement("marker");$newnode=$parnode->appendChild($node);$newnode->setAttribute("name",$row['name_poi']);$newnode->setAttribute("lat",$row['Y']);$newnode->setAttribute("lng",$row['X']);$newnode->setAttribute("type","restaurant");}echo$dom->saveXML();mysql_free_result($result);mysql_close($link);?>
Exemple sur Toulouse sans buffer - éviter de le faire avec la France - votre navigateur risque de ne pas trop apprécier le chargement ;)
Info
Le serveur hébergeant la démonstration n'étant plus disponible depuis de nombreuses années, la démonstration, autre fois intégrée en iFrame est désactivée. <iframe src="http://88.191.142.86/geotribu/blog/buffermysql/index_toulouse_nobuffer.html" width="640" height="480" frameborder="0"></iframe>
Hum : j'ai testé la carte avec toutes les données contenues en base (un peu plus de 47000 restaurants) ; bah le navigateur a un peu de mal, faut vraiment mettre en place ce système de buffer - d'ailleurs on peut remarquer qu'il y a beaucoup plus de restaurants recensés chez nos voisins :
On cherche dans la base de données les enregistrements qui sont dans la bounding box calculée dans le premier article et qui sont à une distance inférieure au buffer :
<?phpheader("Content-type: text/xml");define('HOST','localhost');define('PORT','5432');define('DB_USER','mylogin');define('DB_PASS','mypass');define('DB_NAME','osmtestpoi');$link=mysql_connect(HOST,DB_USER,DB_PASS);mysql_select_db(DB_NAME);$dom=newDOMDocument("1.0");$node=$dom->createElement("markers");$parnode=$dom->appendChild($node);$query="SELECT poifrance.id_poi, poifrance.name_poi, y(poifrance.geom_poi) AS Y,x(poifrance.geom_poi) AS X,6371 * 2 * asin(sqrt(power(sin((".$_GET['lat']." - abs(y(poifrance.geom_poi))) * pi() / 180 / 2), 2)+ cos(".$_GET['lat']." * pi() / 180) * cos(abs(y(poifrance.geom_poi)) * pi() / 180)* power(sin((".$_GET['lng']." - x(poifrance.geom_poi)) * pi() / 180 / 2), 2) )) AS distanceFROM poifrance WHERE 1 = 1 ";if(isset($_GET['lat'])&&isset($_GET['lng'])&&isset($_GET['buffer'])){$deglat=111.195;$lat=$_GET['lat'];$lng=$_GET['lng'];$buffer=$_GET['buffer'];$query.=" AND y(poifrance.geom_poi) BETWEEN ".$lat." - (".$buffer." / ".$deglat.")AND ".$lat." + (".$buffer." / ".$deglat.") ";$query.=" AND x(poifrance.geom_poi) BETWEEN ".$lng." - ".$buffer." / ABS(COS(RADIANS(".$lat.")) * ".$deglat.")AND ".$lng." + ".$buffer." / ABS(COS(RADIANS(".$lat.")) * ".$deglat.")";$query.=" HAVING distance < ".$_GET['buffer'];}$result=mysql_query($query);while($row=@mysql_fetch_assoc($result)){$node=$dom->createElement("marker");$newnode=$parnode->appendChild($node);$newnode->setAttribute("name",$row['name_poi']);$newnode->setAttribute("lat",$row['Y']);$newnode->setAttribute("lng",$row['X']);$newnode->setAttribute("type","restaurant");}echo$dom->saveXML();mysql_free_result($result);mysql_close($link);?>
Ce qui donne :) Attention le rayon maximum de recherche est de 50 km.
Info
Le serveur hébergeant la démonstration n'étant plus disponible depuis de nombreuses années, la démonstration, autre fois intégrée en iFrame est désactivée. <iframe src="http://88.191.142.86/geotribu/blog/buffermysql/index.html" width="800" height="600" frameborder="0"></iframe>
Ces deux billets nous ont montré comment mettre en place un système de buffer avec une base de données MySQL. En fait ce n'est pas si compliqué que ça, il convient juste de bien cerner le problème : limiter la recherche à une bounding box calculée avec la formule Haversine. Après, les appels à la base de données et l'affichage sont maintenant des choses bien connues. Si vous avez des remarques et / ou des suggestions, vous pouvez sans problème les soumettre dans les commentaires ou dans le ForumSIG.