5df9b7d2f8963f950dc628bd27d83f766da58643
[lhc/web/www.git] / www / plugins / gis / gis_fonctions.php
1 <?php
2
3 if (!defined('_ECRIRE_INC_VERSION')) {
4 return;
5 }
6
7 include_spip('inc/config');
8 include_spip('inc/json');
9
10 /**
11 * Filtre dec_to_dms, http://www.statemaster.com/encyclopedia/Geographic-coordinate-conversion
12 *
13 * @param decimal $coord
14 * @return string
15 */
16 function dec_to_dms($coord) {
17 return sprintf(
18 '%0.0f° %2.3f',
19 floor(abs($coord)),
20 60*(abs($coord)-floor(abs($coord)))
21 );
22 }
23
24 /**
25 * Filtre dms_to_dec, http://www.statemaster.com/encyclopedia/Geographic-coordinate-conversion
26 *
27 * @param string $ref N, E, S, W
28 * @param int $deg
29 * @param int $min
30 * @param int $sec
31 * @return decimal
32 */
33 function dms_to_dec($ref, $deg, $min, $sec) {
34
35 $arrLatLong = array();
36 $arrLatLong['N'] = 1;
37 $arrLatLong['E'] = 1;
38 $arrLatLong['S'] = -1;
39 $arrLatLong['W'] = -1;
40
41 return ($deg+((($min*60)+($sec))/3600)) * $arrLatLong[$ref];
42 }
43
44 /**
45 * Filtre distance pour renvoyer la distance entre deux points
46 * http://snipplr.com/view/2531/calculate-the-distance-between-two-coordinates-latitude-longitude/
47 * sinon voir ici : http://zone.spip.org/trac/spip-zone/browser/_plugins_/forms/geoforms/inc/gPoint.php
48 *
49 * @param int|array $from
50 * id_gis du point de référence ou tableau de coordonnées
51 * @param int|array $to
52 * id_gis du point distant ou tableau de coordonnées
53 * @param bool $miles
54 * Renvoyer le résultat en miles (kilomètres par défaut)
55 * @return float
56 * Retourne la distance en kilomètre ou en miles
57 */
58 function distance($from, $to, $miles = false) {
59 // On ne travaille que si on a toutes les infos
60 if (((is_array($from) and isset($from['lat']) and isset($from['lon'])) // Le départ est soit un tableau soit un entier
61 or ($from = intval($from) and $from > 0 and $from = sql_fetsel('lat,lon', 'spip_gis', "id_gis=$from")))
62 and ((is_array($to) and isset($to['lat']) and isset($to['lon'])) or ($to = intval($to) and $to > 0 and $to = sql_fetsel('lat,lon', 'spip_gis', "id_gis=$to"))) // Le distant est soit un tableau soit un entier
63 ) {
64 $pi80 = M_PI / 180;
65 $from['lat'] *= $pi80;
66 $from['lon'] *= $pi80;
67 $to['lat'] *= $pi80;
68 $to['lon'] *= $pi80;
69
70 $r = 6372.797; // mean radius of Earth in km
71 $dlat = $to['lat'] - $from['lat'];
72 $dlng = $to['lon'] - $from['lon'];
73 $a = sin($dlat / 2) * sin($dlat / 2) + cos($from['lat']) * cos($to['lat']) * sin($dlng / 2) * sin($dlng / 2);
74 $c = 2 * atan2(sqrt($a), sqrt(1 - $a));
75 $km = $r * $c;
76
77 return ($miles ? ($km * 0.621371192) : $km);
78 }
79
80 return false;
81 }
82
83 /**
84 * Afficher proprement une distance
85 *
86 * @param float $distance
87 * Nombre indiquant une distance
88 * @param int $precision
89 * Précision des décimales du nombre final, par défaut 2
90 * @param string $format_entree
91 * Format de distance donnée en entrée : par défaut en kilomètres, sinon en mètres avec "m"
92 * @return string
93 * Retourne une chaine composée d'un nombre arrondi et d'une unité de mesure de distance
94 **/
95 function distance_en_metres($distance, $precision=2, $format_entree='km') {
96 if ($distance) {
97 // On passe toujours tout en kilomètres pour uniformiser
98 if ($format_entree == 'm') {
99 $distance = $distance / 1000;
100 }
101
102 // Si c'est supérieur à 1, on reste en kilomètres
103 if ($distance > 1) {
104 $unite = 'km';
105 }
106 elseif (($distance = $distance*1000) > 1) {
107 $unite = 'm';
108 }
109
110 $distance = number_format($distance, $precision, ',', '') . ' ' . $unite;
111 }
112
113 return $distance;
114 }
115
116 /**
117 * Compilation du critère {distancefrom}
118 *
119 * Critère {distancefrom} qui permet de ne sélectionner que les objets se trouvant à une distance comparée avec un point de repère.
120 * On doit lui passer 3 paramètres obligatoires :
121 * - le point de repère qui est un tableau avec les clés "lat" et "lon" ou un id_gis
122 * - l'opérateur de comparaison
123 * - la distance à comparer, en kilomètres
124 * Cela donne par exemple :
125 * {distancefrom #ARRAY{lat,#LAT,lon,#LON},<,30}
126 * {distancefrom #ARRAY{lat,#ENV{lat},lon,#ENV{lon}},<=,#ENV{distance}}
127 *
128 * @param unknown $idb
129 * @param unknown &$boucles
130 * @param unknown $crit
131 */
132 function critere_distancefrom_dist($idb, &$boucles, $crit) {
133 $boucle = &$boucles[$idb];
134 $id_table = $boucle->id_table; // articles
135 $primary = $boucle->primary; // id_article
136 $objet = objet_type($id_table); // article
137
138 if (($id_table == 'gis' or isset($boucle->join['gis'])) // Soit depuis une boucle (GIS) soit un autre objet mais avec {gis}
139 and count($crit->param) == 3 // Il faut aussi qu'il y ait 3 critères obligatoires
140 ) {
141 $point_reference = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent);
142 $operateur = calculer_liste($crit->param[1], array(), $boucles, $boucles[$idb]->id_parent);
143 $distance = calculer_liste($crit->param[2], array(), $boucles, $boucles[$idb]->id_parent);
144
145 // Si le point de référence est un entier, on essaye de récupérer les coordonnées du point GIS
146 // Et si on a toujours pas de tableau correct, on met false
147 $boucle->hierarchie .= '$point_reference = '.$point_reference.';';
148 $boucle->hierarchie .= 'if (is_numeric($point_reference)){ $point_reference = sql_fetsel("lat,lon", "spip_gis", "id_gis = ".intval($point_reference)); }';
149 $boucle->hierarchie .= 'if (!is_array($point_reference) or !isset($point_reference["lat"]) or !isset($point_reference["lon"])){ $point_reference = false; }';
150 // L'opérateur doit exister dans une liste précise
151 $boucle->hierarchie .= '$operateur_distance = trim('.$operateur.');';
152 $boucle->hierarchie .= 'if (!in_array($operateur_distance, array("=","<",">","<=",">="))){ $operateur_distance = false; }';
153 $boucle->hierarchie .= '$distance = '.$distance.';';
154
155 $boucle->select[] = '".(!$point_reference ? "\'\' as distance" : "(6371 * acos( cos( radians(".$point_reference["lat"].") ) * cos( radians( gis.lat ) ) * cos( radians( gis.lon ) - radians(".$point_reference["lon"].") ) + sin( radians(".$point_reference["lat"].") ) * sin( radians( gis.lat ) ) ) ) AS distance")."';
156 $boucle->having[] = '((!$point_reference or !$operateur_distance or !$distance) ? "1=1" : "distance $operateur_distance ".sql_quote($distance))';
157 }
158 }
159
160 /**
161 * Compile le critère `{gis}` qui permet de compléter la boucle avec les points GIS
162 *
163 * Usage
164 * - `{gis}` Retourne les objets ayant des points (et ajoute les balises spéciales GIS tel que `#TITRE_GIS`)
165 * - `{!gis}` Retourne les objets sans points
166 * - `{gis distance<XX}`, sur une boucle `GIS`, filtre une liste de points par rapport à la distance du point de l'env
167 *
168 * @param string $idb
169 * @param array $boucles
170 * @param Critere $crit
171 */
172 function critere_gis_dist($idb, &$boucles, $crit) {
173 $boucle = &$boucles[$idb];
174 $id_table = $boucle->id_table; // articles
175 $primary = $boucle->primary; // id_article
176 $objet = objet_type($id_table); // article
177
178 if ($id_table == 'gis') {
179 // exclure l'élément en cours des résultats
180 $id_gis = calculer_argument_precedent($idb, $primary, $boucles);
181 $boucle->where[]= array("'!='", "'$boucle->id_table." . "$primary'", $id_gis);
182
183 // récupérer les paramètres du critère
184 $op='';
185 $params = $crit->param;
186 $type = array_shift($params);
187 $type = $type[0]->texte;
188 if (preg_match(',^(\w+)([<>=]+)([0-9]+)$,', $type, $r)) {
189 $type=$r[1];
190 $op=$r[2];
191 $op_val=$r[3];
192 }
193 if ($op) {
194 $boucle->having[]= array("'".$op."'", "'".$type."'",$op_val);
195 }
196
197 // récupérer lat/lon du point de la boucle englobante
198 $lat = calculer_argument_precedent($idb, 'lat', $boucles);
199 $lon = calculer_argument_precedent($idb, 'lon', $boucles);
200
201 // http://www.awelty.fr/developpement-web/php/
202 // http://www.movable-type.co.uk/scripts/latlong-db.html
203 // http://code.google.com/intl/fr/apis/maps/articles/geospatial.html#geospatial
204 $select = "(6371 * acos( cos( radians(\".$lat.\") ) * cos( radians( gis.lat ) ) * cos( radians( gis.lon ) - radians(\".$lon.\") ) + sin( radians(\".$lat.\") ) * sin( radians( gis.lat ) ) ) ) AS distance";
205 $order = "'distance'";
206
207 $boucle->select[]= $select;
208 $boucle->order[]= $order;
209 } else {
210 /* Recherche d'objets SANS point */
211 if ($crit->not) {
212 $boucle->from['gis_liens'] = 'spip_gis_liens';
213 $boucle->from_type['gis_liens'] = 'LEFT';
214 $boucle->join['gis_liens'] = array("'$id_table'","'id_objet'","'$primary'","'gis_liens.objet='.sql_quote('$objet')");
215 $boucle->where[] = "'gis_liens.id_gis IS NULL'";
216
217 /* Recherche d'objets AVEC point + ajout des champs GIS */
218 } else {
219 // ajouter tous les champs du point au select
220 // et les suffixer pour lever toute ambiguite avec des champs homonymes
221 if (!function_exists('objet_info')) {
222 include_spip('inc/filtres');
223 }
224 $champs = objet_info('gis', 'champs_critere_gis');
225 foreach ($champs as $champ) {
226 $boucle->select[] = $champ;
227 }
228 // jointure sur spip_gis_liens/spip_gis
229 // cf plugin notation
230 // $boucle->join["surnom (as) table de liaison"] = array("surnom de la table a lier", "cle primaire de la table de liaison", "identifiant a lier", "type d'objet de l'identifiant");
231 $boucle->from['gis_liens'] = 'spip_gis_liens';
232 $boucle->join['gis_liens']= array("'$id_table'","'id_objet'","'$primary'","'gis_liens.objet='.sql_quote('$objet')");
233 $boucle->from['gis'] = 'spip_gis';
234 $boucle->join['gis']= array("'gis_liens'","'id_gis'");
235 // bien renvoyer tous les points qui son attachés à l'objet
236 // mais attention, si on trouve en amont un groupement portant sur un champ *de GIS*,
237 // alors cela signifie que la personne veut faire une opération de groupement sur les points donc là on n'ajoute pas id_gis
238 $tous_les_points = true;
239 foreach ($boucle->group as $champ) {
240 if (in_array($champ, array('ville', 'code_postal', 'pays', 'code_pays', 'region','departement'))) {
241 $tous_les_points = false;
242 }
243 }
244 if ($tous_les_points) {
245 $boucle->group[] = 'gis_liens.id_gis';
246 }
247 // ajouter gis aux jointures et spécifier les jointures explicites pour pouvoir utiliser les balises de la table de jointure
248 // permet de passer dans trouver_champ_exterieur() depuis index_tables_en_pile()
249 // cf http://article.gmane.org/gmane.comp.web.spip.zone/6628
250 $boucle->jointures[] = 'gis';
251 if (empty($boucle->jointures_explicites)) {
252 $boucle->jointures_explicites = 'gis_liens gis';
253 } else {
254 $boucle->jointures_explicites .= ' gis_liens gis';
255 }
256 }
257 }
258 }
259 function critere_gis_tout_dist($idb, &$boucles, $crit) {
260 $crit->op = 'gis';
261 $critere_gis = charger_fonction('gis', 'critere/');
262 $critere_gis($idb, $boucles, $crit);
263 $boucle = &$boucles[$idb];
264 $boucle->from_type['gis_liens'] = 'LEFT';
265 $boucle->from_type['gis'] = 'LEFT';
266 }
267
268 /**
269 * Balise #DISTANCE issue du critère {gis distance<XX}
270 * merci marcimant : http://formation.magraine.net/spip.php?article61
271 *
272 * @param unknown_type $p
273 */
274 function balise_distance_dist($p) {
275 return rindex_pile($p, 'distance', 'gis');
276 }
277
278 /**
279 * Balise #TITRE_GIS : retourne le titre du point
280 * Necessite le critere {gis} sur la boucle
281 *
282 * @param unknown_type $p
283 */
284 function balise_titre_gis_dist($p) {
285 return rindex_pile($p, 'titre_gis', 'gis');
286 }
287
288 /**
289 * Balise #DESCRIPTIF_GIS : retourne le descriptif du point
290 * Necessite le critere {gis} sur la boucle
291 *
292 * @param unknown_type $p
293 */
294 function balise_descriptif_gis_dist($p) {
295 return rindex_pile($p, 'descriptif_gis', 'gis');
296 }
297
298 /**
299 * Balise #ADRESSE_GIS : retourne l'adresse du point
300 * Necessite le critere {gis} sur la boucle
301 *
302 * @param unknown_type $p
303 */
304 function balise_adresse_gis_dist($p) {
305 return rindex_pile($p, 'adresse_gis', 'gis');
306 }
307
308 /**
309 * Balise #PAYS_GIS : retourne le pays du point
310 * Necessite le critere {gis} sur la boucle
311 *
312 * @param unknown_type $p
313 */
314 function balise_pays_gis_dist($p) {
315 return rindex_pile($p, 'pays_gis', 'gis');
316 }
317
318 /**
319 * Balise #CODE_PAYS_GIS : retourne le code pays du point
320 * Necessite le critere {gis} sur la boucle
321 *
322 * @param unknown_type $p
323 */
324 function balise_code_pays_gis_dist($p) {
325 return rindex_pile($p, 'code_pays_gis', 'gis');
326 }
327
328 /**
329 * Balise #VILLE_GIS : retourne la ville du point
330 * Necessite le critere {gis} sur la boucle
331 *
332 * @param unknown_type $p
333 */
334 function balise_ville_gis_dist($p) {
335 return rindex_pile($p, 'ville_gis', 'gis');
336 }
337
338 /**
339 * Balise #REGION_GIS : retourne la région du point
340 * Necessite le critere {gis} sur la boucle
341 *
342 * @param unknown_type $p
343 */
344 function balise_region_gis_dist($p) {
345 return rindex_pile($p, 'region_gis', 'gis');
346 }
347
348 /**
349 * Balise #DEPARTEMENT_GIS : censé retourner le département du point
350 * Necessite le critere {gis} sur la boucle
351 *
352 * @param unknown_type $p
353 */
354 function balise_departement_gis_dist($p) {
355 return rindex_pile($p, 'departement_gis', 'gis');
356 }
357
358 /**
359 * Balise #CODE_POSTAL_GIS : retourne le code postal du point
360 * Necessite le critere {gis} sur la boucle
361 *
362 * @param unknown_type $p
363 */
364 function balise_code_postal_gis_dist($p) {
365 return rindex_pile($p, 'code_postal_gis', 'gis');
366 }
367
368 /**
369 * Définition du fond de carte à utiliser par défaut en prenant compte les defines
370 */
371 function gis_layer_defaut() {
372 $defaut = 'openstreetmap_mapnik';
373 if (defined('_GIS_LAYER_DEFAUT_FORCE')) {
374 return _GIS_LAYER_DEFAUT_FORCE;
375 } else {
376 if (defined('_GIS_LAYER_DEFAUT')) {
377 $defaut = _GIS_LAYER_DEFAUT;
378 }
379 $config = lire_config('gis/layer_defaut');
380 return $config ? $config : $defaut;
381 }
382 }
383
384 /**
385 * Recuperer les cles primaires du env pour l'appel a l'url json des points
386 * @param $env
387 * @return array
388 */
389 function gis_modele_url_json_env($env) {
390 $contexte = array();
391 if (is_string($env)) {
392 $env = unserialize($env);
393 }
394 if ($env) {
395 // d'abord toutes les cles primaires connues
396 $tables_sql = lister_tables_objets_sql();
397 foreach (array_keys($tables_sql) as $table) {
398 $primary = id_table_objet($table);
399 if (isset($env[$primary])) {
400 $contexte[$primary] = is_array($env[$primary]) ? $env[$primary] : trim($env[$primary]);
401 }
402 }
403 // puis cas particuliers et ceux ajoutés par le pipeline
404 $keys = pipeline('gis_modele_parametres_autorises', array('objet', 'id_objet', 'id_secteur', 'id_parent', 'media', 'recherche', 'mots', 'pays', 'code_pays', 'region', 'departement', 'ville', 'code_postal', 'adresse'));
405 foreach ($keys as $key) {
406 if (isset($env[$key])) {
407 $contexte[$key] = is_array($env[$key]) ? $env[$key] : trim($env[$key]);
408 }
409 }
410 }
411 return $contexte;
412 }
413
414
415 /**
416 * Transforme un paramètre d'entrée en tableau
417 * s'il n'en est pas déjà un.
418 *
419 * Permet d'utiliser dans l'appel au modèle de carte gis
420 * depuis un texte d'article des paramètres tabulaires,
421 * tel que des identifiants de documents de tracés kml,
422 * tel que `<carte_gis|kml=10,11,12>`
423 *
424 * @example `#ENV{kml}|gis_param_to_array`
425 *
426 * @param string|int|array $param
427 * Le paramètre à transformer en tableau
428 * @param string $sep
429 * Le séparateur utilisé
430 * @return array
431 **/
432 function gis_param_to_array($param, $sep = ',') {
433 if (is_array($param)) {
434 return $param;
435 }
436 // enlever les espaces et exploser
437 $tab = explode($sep, trim((string)$param));
438 // enlever les champs vides, les espaces sur chaques champs.
439 return array_map('trim', array_filter($tab));
440 }
441
442 /**
443 * Transformer le tableau de kml en tableau d'urls :
444 * si numerique c'est un id de document
445 * si chaine c'est une url qu'on rapatrie en local
446 * @param array $kml
447 * @return array
448 */
449 function gis_kml_to_urls($kml) {
450 if ($kml and count($kml)) {
451 include_spip('inc/filtres_mini');
452 include_spip('inc/distant');
453 foreach ($kml as $k => $v) {
454 if (is_numeric($v)) {
455 $kml[$k] = url_absolue(generer_url_entite($v, 'document'));
456 } else {
457 $kml[$k] = _DIR_RACINE.copie_locale($kml[$k], 'modif');
458 }
459 }
460 }
461 return $kml;
462 }
463
464 /**
465 * Retourne les propriétés JSON de l'icône d'un point
466 *
467 * @param string $img
468 * Balise HTML `<img ... />` ou chemin de l'image (qui peut être une URL distante).
469 * @return string
470 * Les propriétés de l'icône
471 **/
472 function gis_icon_properties($img = '') {
473 $props = $icon = '';
474
475 if ($img) {
476 if (largeur($img) >= 44) {
477 $icon = extraire_attribut(filtrer('image_graver', filtrer('image_recadre', filtrer('image_passe_partout', $img, 32, 32), 32, 32, 'center', 'transparent')), 'src');
478 } else {
479 $icon = extraire_attribut($img, 'src') ? extraire_attribut($img, 'src') : $img;
480 }
481 } else {
482 $icon = find_in_path('images/marker_defaut.png');
483 }
484
485 if ($icon) {
486 $props .= ",\n\t\t\t\"icon\": ". json_encode(url_absolue($icon)).',';
487 list($h,$w) = taille_image($icon);
488 $props .= "\n\t\t\t\"icon_size\": ". json_encode(array($w,$h)).',';
489 /**
490 * Si l'icone est carrée, on considère que c'est soit un point soit un carré qui pointe un lieu et non une "goutte"
491 * On centre donc au milieu de l'icone
492 */
493 if ($w == $h) {
494 $props .= "\n\t\t\t\"icon_anchor\": ". json_encode(array($w/2, $h/2)).',';
495 $props .= "\n\t\t\t\"popup_anchor\": ". json_encode(array(0,0));
496 } else {
497 $props .= "\n\t\t\t\"icon_anchor\": ". json_encode(array($w/2, $h)).',';
498 $props .= "\n\t\t\t\"popup_anchor\": ". json_encode(array(1, -round($h/1.2, 2)));
499 }
500 }
501
502 if ($shadow = find_in_path('images/marker_defaut_shadow.png')) {
503 $props .= ",\n\t\t\t\"shadow\": ". json_encode(url_absolue($shadow));
504 list($h,$w) = taille_image($shadow);
505 $props .= ",\n\t\t\t\"shadow_size\": ". json_encode(array($w,$h));
506 }
507
508 return $props;
509 }