[PLUGINS] +maj gis4
authorLudovic CHEVALIER <ludovic.chevalier@heureux-cyclage.org>
Sat, 26 Mar 2016 10:20:17 +0000 (11:20 +0100)
committerLudovic CHEVALIER <ludovic.chevalier@heureux-cyclage.org>
Sat, 26 Mar 2016 10:20:17 +0000 (11:20 +0100)
208 files changed:
www/plugins/gis/TODO.txt [deleted file]
www/plugins/gis/action/editer_gis.php
www/plugins/gis/action/gis_geocoder_rechercher.php [new file with mode: 0644]
www/plugins/gis/base/gis.php
www/plugins/gis/crud/gis.php
www/plugins/gis/formulaires/configurer_gis.html
www/plugins/gis/formulaires/configurer_gis.php
www/plugins/gis/formulaires/editer_gis.html
www/plugins/gis/formulaires/rechercher_gis.html
www/plugins/gis/formulaires/rechercher_gis.php
www/plugins/gis/genie/gis_nettoyer_base.php [deleted file]
www/plugins/gis/gis_administrations.php
www/plugins/gis/gis_autoriser.php
www/plugins/gis/gis_fonctions.php
www/plugins/gis/gis_options.php
www/plugins/gis/gis_pipelines.php
www/plugins/gis/images/marker.png [deleted file]
www/plugins/gis/images/openlayers/dark/blank.gif [deleted file]
www/plugins/gis/images/openlayers/dark/cloud-popup-relative.png [deleted file]
www/plugins/gis/images/openlayers/dark/east-mini.png [deleted file]
www/plugins/gis/images/openlayers/dark/layer-switcher-maximize.png [deleted file]
www/plugins/gis/images/openlayers/dark/layer-switcher-minimize.png [deleted file]
www/plugins/gis/images/openlayers/dark/north-mini.png [deleted file]
www/plugins/gis/images/openlayers/dark/slider.png [deleted file]
www/plugins/gis/images/openlayers/dark/south-mini.png [deleted file]
www/plugins/gis/images/openlayers/dark/west-mini.png [deleted file]
www/plugins/gis/images/openlayers/dark/zoom-minus-mini.png [deleted file]
www/plugins/gis/images/openlayers/dark/zoom-panel.png [deleted file]
www/plugins/gis/images/openlayers/dark/zoom-plus-mini.png [deleted file]
www/plugins/gis/images/openlayers/dark/zoom-world-mini.png [deleted file]
www/plugins/gis/images/openlayers/dark/zoombar.png [deleted file]
www/plugins/gis/inc/gis_xmlrpc.php
www/plugins/gis/javascript/gis.js.html
www/plugins/gis/javascript/gis_geocoder.js
www/plugins/gis/javascript/gis_init_map.js [deleted file]
www/plugins/gis/javascript/leaflet.gis.js [new file with mode: 0644]
www/plugins/gis/json/gis.html
www/plugins/gis/json/gis_articles.html
www/plugins/gis/json/gis_articles_branche.html
www/plugins/gis/json/gis_auteurs.html
www/plugins/gis/json/gis_documents.html
www/plugins/gis/json/gis_evenements.html
www/plugins/gis/json/gis_mots.html
www/plugins/gis/json/gis_organisations.html [new file with mode: 0644]
www/plugins/gis/json/gis_point_libre.html
www/plugins/gis/json/gis_rubriques.html
www/plugins/gis/json/gis_sites.html
www/plugins/gis/json/gis_tous_avec_liens_espace_prive.html
www/plugins/gis/lang/gis.xml
www/plugins/gis/lang/gis_de.php [new file with mode: 0644]
www/plugins/gis/lang/gis_en.php
www/plugins/gis/lang/gis_es.php
www/plugins/gis/lang/gis_fr.php
www/plugins/gis/lang/gis_nl.php [new file with mode: 0644]
www/plugins/gis/lang/gis_ru.php
www/plugins/gis/lang/gis_sk.php
www/plugins/gis/lang/paquet-gis.xml
www/plugins/gis/lang/paquet-gis_en.php
www/plugins/gis/lang/paquet-gis_es.php
www/plugins/gis/lang/paquet-gis_fr.php
www/plugins/gis/lang/paquet-gis_nl.php [new file with mode: 0644]
www/plugins/gis/lang/paquet-gis_ru.php
www/plugins/gis/lang/paquet-gis_sk.php
www/plugins/gis/lib/leaflet/LICENSE [new file with mode: 0644]
www/plugins/gis/lib/leaflet/README.md [new file with mode: 0644]
www/plugins/gis/lib/leaflet/dist/images/layers-2x.png [new file with mode: 0644]
www/plugins/gis/lib/leaflet/dist/images/layers.png [new file with mode: 0644]
www/plugins/gis/lib/leaflet/dist/images/marker-icon-2x.png [new file with mode: 0644]
www/plugins/gis/lib/leaflet/dist/images/marker-icon.png [new file with mode: 0644]
www/plugins/gis/lib/leaflet/dist/images/marker-shadow.png [new file with mode: 0644]
www/plugins/gis/lib/leaflet/dist/leaflet-src.js [new file with mode: 0644]
www/plugins/gis/lib/leaflet/dist/leaflet.css [new file with mode: 0644]
www/plugins/gis/lib/leaflet/dist/leaflet.js [new file with mode: 0644]
www/plugins/gis/lib/leaflet/plugins/Bing.js [new file with mode: 0755]
www/plugins/gis/lib/leaflet/plugins/Control.FullScreen.js [new file with mode: 0644]
www/plugins/gis/lib/leaflet/plugins/Control.MiniMap.js [new file with mode: 0644]
www/plugins/gis/lib/leaflet/plugins/GPX.Speed.js [new file with mode: 0755]
www/plugins/gis/lib/leaflet/plugins/GPX.js [new file with mode: 0755]
www/plugins/gis/lib/leaflet/plugins/Google.js [new file with mode: 0755]
www/plugins/gis/lib/leaflet/plugins/KML.js [new file with mode: 0755]
www/plugins/gis/lib/leaflet/plugins/Marker.Rotate.js [new file with mode: 0755]
www/plugins/gis/lib/leaflet/plugins/images/icon-fullscreen-2x.png [new file with mode: 0644]
www/plugins/gis/lib/leaflet/plugins/images/icon-fullscreen.png [new file with mode: 0755]
www/plugins/gis/lib/leaflet/plugins/images/toggle.png [new file with mode: 0755]
www/plugins/gis/lib/leaflet/plugins/leaflet-plugins.css [new file with mode: 0755]
www/plugins/gis/lib/leaflet/plugins/leaflet-providers.js [new file with mode: 0644]
www/plugins/gis/lib/leaflet/plugins/leaflet.markercluster-src.js [new file with mode: 0644]
www/plugins/gis/lib/leaflet/plugins/leaflet.markercluster.css [new file with mode: 0755]
www/plugins/gis/modeles/carte_gis.html
www/plugins/gis/modeles/carte_gis_objet.html [new file with mode: 0644]
www/plugins/gis/modeles/carte_gis_preview.html
www/plugins/gis/paquet.xml
www/plugins/gis/prive/inclure/gis_objet_formulaires.html
www/plugins/gis/prive/objets/contenu/gis.html
www/plugins/gis/prive/objets/infos/gis.html
www/plugins/gis/prive/objets/liste/gis.html
www/plugins/gis/prive/objets/liste/gis_associer.html
www/plugins/gis/prive/objets/liste/gis_lies.html
www/plugins/gis/prive/objets/liste/gis_tous.html [deleted file]
www/plugins/gis/prive/objets/liste/objets_gis.html
www/plugins/gis/prive/objets/liste/objets_gis_simple.html
www/plugins/gis/prive/squelettes/contenu/gis.html
www/plugins/gis/prive/squelettes/contenu/gis_edit.html
www/plugins/gis/prive/squelettes/contenu/gis_tous.html
www/plugins/gis/prive/squelettes/navigation/gis_tous.html [deleted file]
www/plugins/gis/prive/squelettes/top/gis_tous.html [new file with mode: 0644]
www/plugins/gis/prive/style_prive_plugin_gis.html
www/plugins/gis/saisies/carte.html
www/plugins/gis/svn.revision
www/plugins/saisies/action/deplacer_saisie.php
www/plugins/saisies/balise/configurer_saisie.php
www/plugins/saisies/balise/generer_saisies.php
www/plugins/saisies/balise/saisie.php
www/plugins/saisies/balise/voir_saisie.php
www/plugins/saisies/balise/voir_saisies.php
www/plugins/saisies/contenu/page-saisies_cvt.html [deleted file]
www/plugins/saisies/css/formulaires_constructeur.css
www/plugins/saisies/demo/page-saisies_cvt.html [new file with mode: 0644]
www/plugins/saisies/extra-vues/pays.html [deleted file]
www/plugins/saisies/formulaires/construire_formulaire.html
www/plugins/saisies/formulaires/construire_formulaire.php
www/plugins/saisies/formulaires/inc-saisies-cvt.html
www/plugins/saisies/formulaires/saisies_cvt.php
www/plugins/saisies/inc/saisies.php
www/plugins/saisies/inc/saisies_afficher.php
www/plugins/saisies/inc/saisies_lister.php
www/plugins/saisies/inc/saisies_manipuler.php
www/plugins/saisies/inclure/configurer_saisie_fonctions.php
www/plugins/saisies/inclure/fieldset_legend.html [new file with mode: 0644]
www/plugins/saisies/inclure/js_afficher_si.html
www/plugins/saisies/inclure/saisies_aide.html
www/plugins/saisies/javascript/saisies.js
www/plugins/saisies/lang/paquet-saisies.xml
www/plugins/saisies/lang/paquet-saisies_ar.php
www/plugins/saisies/lang/paquet-saisies_de.php
www/plugins/saisies/lang/paquet-saisies_en.php
www/plugins/saisies/lang/paquet-saisies_es.php
www/plugins/saisies/lang/paquet-saisies_fr.php
www/plugins/saisies/lang/paquet-saisies_fr_tu.php
www/plugins/saisies/lang/paquet-saisies_nl.php
www/plugins/saisies/lang/paquet-saisies_pt_br.php [new file with mode: 0644]
www/plugins/saisies/lang/paquet-saisies_ru.php
www/plugins/saisies/lang/paquet-saisies_sk.php
www/plugins/saisies/lang/saisies.xml
www/plugins/saisies/lang/saisies_ca.php
www/plugins/saisies/lang/saisies_de.php
www/plugins/saisies/lang/saisies_en.php
www/plugins/saisies/lang/saisies_es.php
www/plugins/saisies/lang/saisies_fa.php
www/plugins/saisies/lang/saisies_fr.php
www/plugins/saisies/lang/saisies_fr_tu.php
www/plugins/saisies/lang/saisies_it.php
www/plugins/saisies/lang/saisies_nl.php
www/plugins/saisies/lang/saisies_pt_br.php [new file with mode: 0644]
www/plugins/saisies/lang/saisies_ru.php
www/plugins/saisies/lang/saisies_sk.php
www/plugins/saisies/paquet.xml
www/plugins/saisies/plugin.xml [deleted file]
www/plugins/saisies/saisies-vues/_base.html
www/plugins/saisies/saisies-vues/checkbox.html
www/plugins/saisies/saisies-vues/couleur.html [new file with mode: 0644]
www/plugins/saisies/saisies-vues/destinataires.html
www/plugins/saisies/saisies-vues/radio.html
www/plugins/saisies/saisies-vues/selecteur_article_fonctions.php
www/plugins/saisies/saisies-vues/selecteur_document.html
www/plugins/saisies/saisies-vues/selection.html
www/plugins/saisies/saisies-vues/selection_multiple.html
www/plugins/saisies/saisies.css.html
www/plugins/saisies/saisies/_base.html
www/plugins/saisies/saisies/articles_originaux.html
www/plugins/saisies/saisies/auteurs.html
www/plugins/saisies/saisies/case.html
www/plugins/saisies/saisies/case.yaml
www/plugins/saisies/saisies/checkbox.html
www/plugins/saisies/saisies/choisir_objets_edit.html [new file with mode: 0644]
www/plugins/saisies/saisies/date.html
www/plugins/saisies/saisies/date_jour_mois_annee.html
www/plugins/saisies/saisies/destinataires.html
www/plugins/saisies/saisies/explication.html
www/plugins/saisies/saisies/explication.yaml
www/plugins/saisies/saisies/fieldset.html
www/plugins/saisies/saisies/fieldset.yaml
www/plugins/saisies/saisies/groupe_mots.html
www/plugins/saisies/saisies/hidden.html
www/plugins/saisies/saisies/hidden.yaml
www/plugins/saisies/saisies/input.html
www/plugins/saisies/saisies/input.yaml
www/plugins/saisies/saisies/mot.html
www/plugins/saisies/saisies/oui_non.html
www/plugins/saisies/saisies/position_construire_formulaire.html
www/plugins/saisies/saisies/radio.html
www/plugins/saisies/saisies/radio.yaml
www/plugins/saisies/saisies/secteur.html
www/plugins/saisies/saisies/selecteur_document.html
www/plugins/saisies/saisies/selecteur_document.yaml [new file with mode: 0644]
www/plugins/saisies/saisies/selecteur_site.html
www/plugins/saisies/saisies/selection.html
www/plugins/saisies/saisies/selection.yaml
www/plugins/saisies/saisies/selection_multiple.html
www/plugins/saisies/saisies/selection_multiple.yaml
www/plugins/saisies/saisies/statuts_auteurs.html
www/plugins/saisies/saisies/textarea.html
www/plugins/saisies/saisies/textarea.yaml
www/plugins/saisies/saisies/true_false.html
www/plugins/saisies/saisies_fonctions.php
www/plugins/saisies/saisies_options.php
www/plugins/saisies/saisies_pipelines.php
www/plugins/saisies/svn.revision

diff --git a/www/plugins/gis/TODO.txt b/www/plugins/gis/TODO.txt
deleted file mode 100644 (file)
index b51cd70..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-peut être utiliser cette lib php pour le geocoder ? : https://github.com/willdurand/Geocoder
-
-des couches à pomper :
-- http://earthquake.usgs.gov/template/js/classes/usgs/leaflet/layer/
-
-une barre de zoom bien faite à passer en plugin ici : https://github.com/CloudMade/Leaflet/pull/689
-
-Avoir un crayon de modification de point depuis le public
-
-Interface de recherche de point :
-- textuelle (recherche dans le titre + descriptif + pays + bla bla bla)
-- par carte : Ouvre une carte + on clique le point avec un bouton choisir dedans + joint le point en question à un objet
-(utilisation via mediabox possible)
-- Une simple liste des points paginés par date de modif/ d'ajout 
\ No newline at end of file
index 3eb3486..f28a5ca 100644 (file)
@@ -76,9 +76,32 @@ function gis_modifier($id_gis, $set=null) {
                $set
        );
 
+       if(isset($c['lon'])){
+               if($c['lon'] > 180){
+                       while($c['lon'] > 180){
+                               $c['lon'] = $c['lon'] - 360;
+                       }
+               }else if($c['lon'] <= -180){
+                       while($c['lon'] <= -180){
+                               $c['lon'] = $c['lon'] + 360;
+                       }
+               }
+       }
+       if(isset($c['lat'])){
+               if($c['lat'] > 90){
+                       while($c['lat'] > 90){
+                               $c['lat'] = $c['lat'] - 180;
+                       }
+               }else if($c['lat'] <= -90){
+                       while($c['lat'] <= -90){
+                               $c['lat'] = $c['lat'] + 180;
+                       }
+               }
+       }
        if ($err = objet_modifier_champs('gis', $id_gis,
                array(
                        //'nonvide' => array('nom' => _T('info_sans_titre')),
+                       'data' => $set,
                        'invalideur' => "id='gis/$id_gis'",
                ),
                $c))
@@ -117,7 +140,7 @@ function gis_associer($id_gis,$objets, $qualif = null){
 }
 
 /**
- * Dossocier un point géolocalisé des objets listes sous forme
+ * Dissocier un point géolocalisé des objets listes sous forme
  * array($objet=>$id_objets,...)
  * $id_objets peut lui meme etre un scalaire ou un tableau pour une liste d'objets du meme type
  *
diff --git a/www/plugins/gis/action/gis_geocoder_rechercher.php b/www/plugins/gis/action/gis_geocoder_rechercher.php
new file mode 100644 (file)
index 0000000..2419d98
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+if (!defined('_ECRIRE_INC_VERSION')) return;
+
+include_spip("inc/distant");
+
+/**
+ * Proxy vers le service Nomatim d'OpenStreetMap.
+ *
+ * Cette fonction permet de transmettre une requete auprès du service
+ * de recherche d'adresse d'OpenStreetMap (Nomatim).
+ * 
+ * Seuls les arguments spécifiques à Nomatim sont transmis.
+ */
+function action_gis_geocoder_rechercher_dist() {
+       include_spip("inc/modifier");
+
+       $mode = _request("mode");
+       if(!$mode || !in_array($mode, array("search", "reverse")))
+               return;
+
+       /* On filtre les arguments à renvoyer à Nomatim (liste blanche) */
+       $arguments = collecter_requests(array("json_callback", "format", "q", "limit", "addressdetails", "accept-language", "lat", "lon"), array());
+
+       if(!empty($arguments)) {
+               header('Content-Type: application/json; charset=UTF-8');
+               echo recuperer_page("http://nominatim.openstreetmap.org/{$mode}?" . http_build_query($arguments));
+       }
+}
index 5117ea6..89aa1cb 100644 (file)
@@ -3,24 +3,20 @@
 if (!defined("_ECRIRE_INC_VERSION")) return;
 
 function gis_declarer_tables_interfaces($interface){
-       $interface['tables_jointures']['spip_gis'][] = 'gis_liens';
-       $interface['tables_jointures']['spip_gis_liens'][] = 'gis';
-       $interface['tables_jointures']['spip_articles'][] = 'gis_liens';
-       $interface['tables_jointures']['spip_auteurs'][] = 'gis_liens';
-       $interface['tables_jointures']['spip_breves'][] = 'gis_liens';
-       $interface['tables_jointures']['spip_documents'][] = 'gis_liens';
-       $interface['tables_jointures']['spip_groupes_mots'][] = 'gis_liens';
-       $interface['tables_jointures']['spip_mots'][] = 'gis_liens';
-       $interface['tables_jointures']['spip_rubriques'][] = 'gis_liens';
-       $interface['tables_jointures']['spip_syndic'][] = 'gis_liens';
-
        $interface['table_des_tables']['gis'] = 'gis';
        $interface['table_des_tables']['gis_liens'] = 'gis_liens';
 
        // Traitements typo et raccourcis
+       $interface['table_des_traitements']['TITRE_GIS'][] = 'typo(extraire_multi(%s))';
        $interface['table_des_traitements']['DESCRIPTIF_GIS'][] = _TRAITEMENT_RACCOURCIS;
+       $interface['table_des_traitements']['VILLE_GIS'][] = 'typo(extraire_multi(%s))';
+       $interface['table_des_traitements']['PAYS_GIS'][] = 'typo(extraire_multi(%s))';
+       $interface['table_des_traitements']['REGION_GIS'][] = 'typo(extraire_multi(%s))';
+       $interface['table_des_traitements']['DEPARTEMENT_GIS'][] = 'typo(extraire_multi(%s))';
        $interface['table_des_traitements']['VILLE'][] = 'typo(extraire_multi(%s))';
        $interface['table_des_traitements']['PAYS'][] = 'typo(extraire_multi(%s))';
+       $interface['table_des_traitements']['REGION'][] = 'typo(extraire_multi(%s))';
+       $interface['table_des_traitements']['DEPARTEMENT'][] = 'typo(extraire_multi(%s))';
 
        return $interface;
 }
@@ -37,7 +33,7 @@ function gis_declarer_tables_objets_sql($tables){
                /* La table */
                'field' => array(
                        "id_gis" => "bigint(21) NOT NULL",
-                       "titre" => "varchar(255) NOT NULL DEFAULT ''",
+                       "titre" => "text NOT NULL DEFAULT ''",
                        "descriptif" => "text NOT NULL DEFAULT ''",
                        "lat" => "double NULL NULL",
                        "lon" => "double NULL NULL",
@@ -46,11 +42,23 @@ function gis_declarer_tables_objets_sql($tables){
                        "pays" => "text NOT NULL DEFAULT ''",
                        "code_pays" => "varchar(255) NOT NULL DEFAULT ''",
                        "region" => "text NOT NULL DEFAULT ''",
+                       "departement" => "text NOT NULL DEFAULT ''",
                        "ville" => "text NOT NULL DEFAULT ''",
                        "code_postal" => "varchar(255) NOT NULL DEFAULT ''"
                ),
                'key' => array(
                        "PRIMARY KEY" => "id_gis",
+                       'KEY lat' => 'lat',
+                       'KEY lon' => 'lon',
+                       'KEY pays' => 'pays(500)',
+                       'KEY code_pays' => 'code_pays',
+                       'KEY region' => 'region(500)',
+                       'KEY departement' => 'departement(500)',
+                       'KEY ville' => 'ville(500)',
+                       'KEY code_postal' => 'code_postal',
+               ),
+               'join' => array(
+                               "id_gis"=>"id_gis"
                ),
                'principale' => 'oui',
                'modeles' => array('carte_gis', 'carte_gis_preview'),
@@ -59,18 +67,19 @@ function gis_declarer_tables_objets_sql($tables){
                'titre' => "titre, '' AS lang",
 
                /* L'édition, l'affichage et la recherche */
-               'page' => 'gis',
+               'page' => false,
                'url_voir' => 'gis',
                'url_edit' => 'gis_edit',
                'editable' => 'oui',
-               'champs_editables' => array('lat', 'lon', 'zoom', 'titre', 'descriptif', 'adresse', 'code_postal', 'ville', 'region', 'pays'),
-               /*'champs_editables' => array(), */
+               'champs_editables' => array('lat', 'lon', 'zoom', 'titre', 'descriptif', 'adresse', 'code_postal', 'ville', 'region', 'departement', 'pays', 'code_pays'),
+               'champs_versionnes' => array('lat', 'lon', 'zoom', 'titre', 'descriptif', 'adresse', 'code_postal', 'ville', 'region', 'departement', 'pays', 'code_pays'),
                'icone_objet' => 'gis',
                'rechercher_champs' => array(
                        'titre' => 8,
                        'descriptif' => 5,
                        'pays' => 3,
                        'region' => 3,
+                       'departement' => 3,
                        'ville' => 3,
                        'code_postal' => 3,
                ),
@@ -89,18 +98,19 @@ function gis_declarer_tables_objets_sql($tables){
                'texte_logo_objet' => 'gis:libelle_logo_gis',
        );
 
-       $spip_gis_liens = array(
-               "id_gis" => "bigint(21) NOT NULL",
-               "objet" => "VARCHAR (25) DEFAULT '' NOT NULL",
-               "id_objet" => "bigint(21) NOT NULL");
+       $tables[]['tables_jointures'][]= 'gis_liens';
+       $tables[]['champs_versionnes'][] = 'jointure_gis';
 
-       $spip_gis_liens_key = array(
-               "PRIMARY KEY" => "id_gis,id_objet,objet",
-               "KEY id_objet" => "id_gis");
-
-       $tables_auxiliaires['spip_gis_liens'] = array(
-               'field' => &$spip_gis_liens,
-               'key' => &$spip_gis_liens_key);
+       // recherche jointe sur les points gis pour tous les objets
+       $tables[]['rechercher_jointures']['gis'] = array(
+                       'titre' => 3,
+                       'descriptif' => 2,
+                       'pays' => 4,
+                       'region' => 1,
+                       'departement' => 1,
+                       'ville' => 1,
+                       'code_postal' => 1
+               );
 
        return $tables;
 }
@@ -113,7 +123,10 @@ function gis_declarer_tables_auxiliaires($tables_auxiliaires){
 
        $spip_gis_liens_key = array(
                "PRIMARY KEY" => "id_gis,id_objet,objet",
-               "KEY id_objet" => "id_gis");
+               "KEY id_gis" => "id_gis",
+               "KEY id_objet" => "id_objet",
+               "KEY objet" => "objet"
+       );
 
        $tables_auxiliaires['spip_gis_liens'] = array(
                'field' => &$spip_gis_liens,
@@ -122,4 +135,4 @@ function gis_declarer_tables_auxiliaires($tables_auxiliaires){
        return $tables_auxiliaires;
 }
 
-?>
\ No newline at end of file
+?>
index 1b743ad..ab9e955 100644 (file)
@@ -17,13 +17,11 @@ include_spip('action/editer_gis');
  * @return array : un array avec (bool) success, (string) message et (array) result indiquant l'id créé 
  */
 function crud_gis_create_dist($dummy,$set=null){
-       if ($id = insert_gis()){
-               list($e,$ok) = revisions_gis($id,$set);
-       }
-       else{
-               $e = _L('create error');
-       }
-       return array('success'=>$e?false:true,'message'=>$e?$e:$ok,'result'=>array('id'=>$id));
+       if (autoriser('voir','gis') && $id = gis_inserer())
+               $err = gis_modifier($id,$set);
+       else
+               $err = _T('crud:erreur_creation',array('objet'=>'gis'));
+       return array('success'=>($err && strlen($err)>0)?false:true,'message'=>$err,'result'=>array('id'=>$id));
 }
 
 /**
@@ -37,13 +35,13 @@ function crud_gis_create_dist($dummy,$set=null){
 function crud_gis_update_dist($id,$set=null){
        $id_gis = sql_getfetsel('id_gis','spip_gis','id_gis='.intval($id));
        if(!$id_gis){
-               $e = _T('gis:erreur_gis_inconnu',array('id'=>$id));
+               $err = _T('gis:erreur_gis_inconnu',array('id'=>$id));
        }else if(autoriser('modifier','gis',$id)){
-               list($e,$ok) = revisions_gis($id,$set);
+               $err = gis_modifier($id,$set);
        }else{
-               $e = _L('update error');
+               $err = _L('update error');
        }
-       return array('success'=>$e?false:true,'message'=>$e?$e:$ok,'result'=>array('id'=>$id));
+       return array('success'=>($err && strlen($err)>0)?false:true,'message'=>$err,'result'=>array('id'=>$id));
 }
 
 /**
@@ -56,9 +54,9 @@ function crud_gis_update_dist($id,$set=null){
  */
 function crud_gis_delete_dist($id){
        if(autoriser('supprimer','gis',$id)){
-               list($e,$ok) = supprimer_gis($id);
+               $err = gis_supprimer($id);
        }
-       return array('success'=>$e?false:true,'message'=>$e?$e:$ok,'result'=>array('id'=>$id));
+       return array('success'=>is_numeric($err)?true:false,'message'=>$err,'result'=>array('id'=>$id));
 }
 
 ?>
\ No newline at end of file
index d38bdf2..d612571 100755 (executable)
@@ -7,7 +7,12 @@
 
 <form method="post" action="#ENV{action}"><div>
        #ACTION_FORMULAIRE{#ENV{action}}
-       <ul>
+       <[(#VAL{ul}|saisie_balise_structure_formulaire)] class="editer-groupe">
+               <[(#VAL{li}|saisie_balise_structure_formulaire)] class="rechercher_adresse editer editer_map_config">
+                       <label for="champ_map_config_geocoder"><:gis:label_rechercher_address:></label>
+                       <input type="text" class="text" name="champ_map_config_geocoder" id="champ_map_config_geocoder" value="" />
+                       <a id="map_config_rechercher_geocodage"><:info_rechercher:></a>
+               </[(#VAL{li}|saisie_balise_structure_formulaire)]>
                [(#SAISIE{input,lat,
                        label=<:gis:lat:>,
                        defaut=0,
@@ -77,7 +82,7 @@
                        label=<:gis:cfg_lbl_activer_objets:>,
                        exclus=spip_gis})]
 
-       </ul>
+       </[(#VAL{ul}|saisie_balise_structure_formulaire)]>
 
 
        <p class="boutons">
 </div></form>
 
 <script type="text/javascript">
-<!---
+/*<![CDATA[*/
+
 (function($){
+       var map, map_container = 'map_config', geocoder, marker;
        
-       var maj_inputs = function(map,pos) {
-               var zoom = map.getZoom();
-               $("#champ_lat").val(pos.lat);
-               $("#champ_lon").val(pos.lng);
-               $("#champ_zoom").val(zoom);
+       var maj_inputs = function(map,pos,action) {
+               if(action == 'click'){
+                       var zoom = map.getZoom();
+                       $("#champ_lat").val(pos.lat);
+                       $("#champ_lon").val(pos.lng);
+                       $("#champ_zoom").val(zoom);
+                       annuler_geocoder = 1;
+               }else if(annuler_geocoder != 1){
+                       if(pos.point == 'undefined'){
+                               $('#champ_#ENV{champ_lat,lat}').val(pos.lat);
+                               $('#champ_#ENV{champ_lon,lon}').val(pos.lng);
+                               map.panTo(pos);
+                               marker.setLatLng(pos);
+                       }else{
+                               $('#champ_#ENV{champ_lat,lat}').val(pos.point.lat);
+                               $('#champ_#ENV{champ_lon,lon}').val(pos.point.lng);
+                               map.panTo(pos.point);
+                               marker.setLatLng(pos.point);
+                       }
+               }
        }
        
+       function geocode(query) {
+               if(!query.error)
+                       maj_inputs(map,query,'geocoding');
+               else
+                       alert('<:gis:erreur_geocoder:> '+query.search);
+       }
+
        var init_config = function() {
-               var map;
-               var map_container = 'map_config';
                map = new L.Map(map_container);
                
                map.attributionControl.setPrefix('');
                </B_layers>
 
                map.setView(new L.LatLng(#ENV{lat,0},#ENV{lon,0}),#ENV{zoom,0});
-               
-               var marker = new L.Marker(new L.LatLng(#ENV{lat,0},#ENV{lon,0}));
+
+               marker = new L.Marker(new L.LatLng(#ENV{lat,0},#ENV{lon,0}));
                map.addLayer(marker);
                
+               geocoder = new L.Geocoder(geocode,{acceptLanguage:'#ENV{lang}'});
+               
                // mettre a jour les coordonnees quand on clique la carte
                map.on('click', function(e) {
+                       annuler_geocoder = 0;
                        marker.setLatLng(e.latlng);
                        map.panTo(e.latlng);
-                       maj_inputs(map,e.latlng);
+                       maj_inputs(map,e.latlng,'click');
+               });
+               
+               // geocoder si clic...
+               $('a#map_config_rechercher_geocodage').css("cursor","pointer").click(function(){
+                       var address = $("#champ_map_config_geocoder").val();
+                       annuler_geocoder = 0;
+                       geocoder.geocode(address);
+               });
+               
+               // ne pas soumettre le formulaire si on presse Entree depuis le champ de recherche
+               $('#champ_map_config_geocoder').keypress(function(e){
+                       if (e.which == 13) {
+                               $('a#map_config_rechercher_geocodage').trigger("click");
+                               return false;
+                       }
                });
                
                // mettre à jour le zoom quand on le modifie
                map.on('zoomend', function(e) {
                        $("#champ_zoom").val(e.target._zoom);
                });
-
        }
 
        $(function(){
        });
 
 })(jQuery);
--->
+/*]]>*/
 </script>
 </div>
index 40bdc3b..23999bd 100644 (file)
@@ -8,8 +8,12 @@ if (!defined('_ECRIRE_INC_VERSION')) return;
  */
 function formulaires_configurer_gis_verifier_dist(){
        $erreurs = array();
-       
-       if ((_request('layer_defaut') == 'bing_aerial') OR in_array('bing_aerial', _request('layers'))){
+       $layers = _request('layers');
+       if (!is_array($layers)) {
+               $layers = array();
+       }
+
+       if ((_request('layer_defaut') == 'bing_aerial') OR in_array('bing_aerial', $layers)){
                $obligatoire = 'api_key_bing';
                if (!_request($obligatoire)){
                        $erreurs[$obligatoire] = _T('info_obligatoire');
@@ -22,8 +26,8 @@ function formulaires_configurer_gis_verifier_dist(){
                $layer_defaut = lire_config('gis/layer_defaut');
                // Si on change la couche par défaut ou si une couche google est présente dans la conf, le formulaire ne doit pas etre traiter en ajax
                if ((_request('layer_defaut') != $layer_defaut)
-                       OR (count(array_intersect(array('google_roadmap', 'google_satellite', 'google_terrain'), _request('layers'))) > 0)
-                       OR (in_array('bing_aerial', _request('layers'))))
+                       OR (count(array_intersect(array('google_roadmap', 'google_satellite', 'google_terrain'), $layers)) > 0)
+                       OR (in_array('bing_aerial', $layers)))
                        refuser_traiter_formulaire_ajax();
        }
        
index 6ff0371..0f3db59 100755 (executable)
@@ -8,12 +8,12 @@
                [(#REM) declarer les hidden qui declencheront le service du formulaire 
                parametre : url d'action ]
                #ACTION_FORMULAIRE{#ENV{action}}
-               <ul>
+               <[(#VAL{ul}|saisie_balise_structure_formulaire)] class="editer-groupe">
                        [(#SAISIE{hidden,objet})]
                        [(#SAISIE{hidden,id_objet})]
                        [(#SAISIE{carte,editer_gis_#ENV{id_gis},env})]
-                       <li class="fieldset">
-                       <fieldset><ul>
+                       <[(#VAL{li}|saisie_balise_structure_formulaire)] class="fieldset">
+                       <fieldset><[(#VAL{ul}|saisie_balise_structure_formulaire)]>
                                [(#SAISIE{input,lat,
                                        label=<:gis:lat:>,
                                        defaut=#ENV{lat,#CONFIG{gis/lat,0}},
                                [(#SAISIE{textarea,descriptif,
                                        label=<:info_descriptif:>,
                                        rows=5})]
-                       </ul></fieldset>
-                       </li>
-                       <li class="fieldset adresse"[(#CONFIG{gis/adresse}|=={on}|non) style="display: none;"]>
-                       <fieldset><ul>
+                       </[(#VAL{ul}|saisie_balise_structure_formulaire)]></fieldset>
+                       </[(#VAL{li}|saisie_balise_structure_formulaire)]>
+                       <[(#VAL{li}|saisie_balise_structure_formulaire)] class="fieldset adresse"[(#CONFIG{gis/adresse}|=={on}|non) style="display: none;"]>
+                       <fieldset><[(#VAL{ul}|saisie_balise_structure_formulaire)]>
                                [(#SAISIE{input,adresse,
                                        label=<:gis:label_adress:>})]
                                [(#SAISIE{input,code_postal,
                                        label=<:gis:label_code_postal:>})]
                                [(#SAISIE{input,ville,
                                        label=<:gis:label_ville:>})]
+                               [(#SAISIE{input,departement,
+                                       label=<:gis:label_departement:>})]
                                [(#SAISIE{input,region,
                                        label=<:gis:label_region:>})]
                                [(#SAISIE{input,pays,
                                        label=<:gis:label_pays:>})]
-                       </ul></fieldset>
-                       </li>
-               </ul>
+                               [(#SAISIE{input,code_pays,
+                                       label=<:gis:label_code_pays:>})]
+                       </[(#VAL{ul}|saisie_balise_structure_formulaire)]></fieldset>
+                       </[(#VAL{li}|saisie_balise_structure_formulaire)]>
+               </[(#VAL{ul}|saisie_balise_structure_formulaire)]>
                [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ]
                <!--extra-->
                <p class='boutons'[ style="direction: (#LANG_DIR|=={ltr}|?{rtl,ltr})"]>
index 55409e0..032c349 100755 (executable)
@@ -3,7 +3,7 @@
 <BOUCLE_si_recherche(CONDITION){si #ENV{recherche_gis}}>
 <B_recherche>
        <ul class="liste_items">
-       <BOUCLE_recherche(GIS){titre LIKE %(#ENV{recherche_gis})%}{doublons objet}>#SET{id_gis,#ID_GIS}
+       <BOUCLE_recherche(GIS){recherche #ENV{recherche_gis}}{doublons objet}>#SET{id_gis,#ID_GIS}
        #SET{ou, #LISTE{#VILLE, #PAYS}|array_filter|join{", "}}
        [<li class="item">(#TITRE)[ ((#GET{ou}))]
                <div class="actions">
        <BOUCLE_si_editable(CONDITION){si #ENV{editable}}>
        <form method='post' action='#ENV{action}' name='formulaire_rechercher_gis' id='formulaire_rechercher_gis'><div>
                #ACTION_FORMULAIRE{#ENV{action}}
-               <ul>
+               <[(#VAL{ul}|saisie_balise_structure_formulaire)] class="editer-groupe">
                        [(#SAISIE{input,recherche_gis,
                                label=<:gis:label_rechercher_point:>})]
-               </ul>
+               </[(#VAL{ul}|saisie_balise_structure_formulaire)]>
                [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ]
                <!--extra-->
                <p class='boutons'>
index 5e1597a..7d614fe 100755 (executable)
@@ -33,6 +33,7 @@ function formulaires_rechercher_gis_charger_dist($objet='', $id_objet='', $retou
  * @param string $recherche
  */
 function formulaires_rechercher_gis_verifier_dist($objet='', $id_objet='', $retour='', $recherche=''){
+       $erreurs = array();
        return $erreurs;
 }
 
@@ -45,7 +46,8 @@ function formulaires_rechercher_gis_verifier_dist($objet='', $id_objet='', $reto
  * @param string $recherche
  */
 function formulaires_rechercher_gis_traiter_dist($objet='', $id_objet='', $retour='', $recherche=''){
-       return;
+       $res = array();
+       return $res;
 }
 
-?>
\ No newline at end of file
+?>
diff --git a/www/plugins/gis/genie/gis_nettoyer_base.php b/www/plugins/gis/genie/gis_nettoyer_base.php
deleted file mode 100644 (file)
index 09f40f1..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-<?php
-
-if (!defined('_ECRIRE_INC_VERSION')) return;
-
-function genie_gis_nettoyer_base_dist($t){
-
-       $liens = array();
-       
-       # liens vers un article inexistant
-       if ($articles = sql_allfetsel("A.id_article,L.id_gis,L.objet,L.id_objet","spip_gis_liens AS L 
-                       INNER JOIN spip_articles AS A 
-                       ON (A.id_article = L.id_objet AND L.objet='article')",
-                       "A.id_article IS NULL")) {
-               $liens = array_merge($liens, $articles);
-       }
-                       
-       # liens vers une breve inexistante
-       if ($breves = sql_allfetsel("B.id_breve,L.id_gis,L.objet,L.id_objet","spip_gis_liens AS L 
-                       INNER JOIN spip_breves AS B 
-                       ON (B.id_breve = L.id_objet AND L.objet='breve')",
-                       "B.id_breve IS NULL")) {
-               $liens = array_merge($liens, $breves);
-       }
-       
-       foreach ($liens as $row) {
-               sql_delete("spip_gis_liens","id_gis=".$row['id_gis']." AND objet=".$row['objet']." AND id_objet=".$row['id_objet']);
-               spip_log("GIS GENIE : Suppression du lien gis ". $row['id_gis'] ." => ". $row['objet'] ." ". $row['id_objet'],"gis");
-       }
-
-       return 1;
-}
-
-?>
\ No newline at end of file
index b6e2093..6ea3aa0 100644 (file)
@@ -11,13 +11,13 @@ if (!defined('_ECRIRE_INC_VERSION')) return;
  */
 function gis_upgrade($nom_meta_base_version, $version_cible){
        $maj = array();
-       
+
        // Première installation
        $maj['create'] = array(
                array('maj_tables', array('spip_gis')),
                array('maj_tables', array('spip_gis_liens')),
        );
-       
+
        // Mise à jour depuis GIS 1
        $maj['2.0'] = array(
                // On ajoute la nouvelle table
@@ -35,25 +35,54 @@ function gis_upgrade($nom_meta_base_version, $version_cible){
                // Virer la table pour les mots
                array('sql_drop_table', 'spip_gis_mots'),
        );
-       
+
        // Des nouveaux champs
        $maj['2.0.1'] = array(
                array('maj_tables', array('spip_gis')),
        );
-       
+
        // Augmenter la précision des champs de coordonnées
        $maj['2.0.2'] = array(
                array('sql_alter', 'TABLE spip_gis CHANGE lat lat DOUBLE NULL NULL'),
                array('sql_alter', 'TABLE spip_gis CHANGE lon lon DOUBLE NULL NULL'),
        );
-       
+
+       // Ajouter des INDEX sur les champs potentiellement utilisables dans des comparaisons/group by/etc.
+       $maj['2.0.4'] = array(
+               array('sql_alter', 'TABLE spip_gis ADD INDEX (lat)'),
+               array('sql_alter', 'TABLE spip_gis ADD INDEX (lon)'),
+               array('sql_alter', 'TABLE spip_gis ADD INDEX (pays(500))'),
+               array('sql_alter', 'TABLE spip_gis ADD INDEX (code_pays)'),
+               array('sql_alter', 'TABLE spip_gis ADD INDEX (region(500))'),
+               array('sql_alter', 'TABLE spip_gis ADD INDEX (ville(500))'),
+               array('sql_alter', 'TABLE spip_gis ADD INDEX (code_postal)'),
+       );
+
+       // Ajout du département dans les champs de coordonnées
+       $maj['2.0.5'] = array(
+               array('maj_tables',array('spip_gis')),
+       );
+
+       // Transformer les titres des points de varchar(255) à text
+       $maj['2.0.6'] = array(
+               array('sql_alter', 'TABLE spip_gis CHANGE titre titre text NOT NULL'),
+       );
+
+       // Ajouter un index sur toutes les colonnes de la table de liens, pas juste id_gis
+       $maj['2.0.8'] = array(
+               array('sql_alter', 'TABLE spip_gis_liens DROP INDEX id_objet'), // virer l'ancien d'abord (nommé id_objet)
+               array('sql_alter', 'TABLE spip_gis_liens ADD INDEX (id_gis)'),
+               array('sql_alter', 'TABLE spip_gis_liens ADD INDEX (objet)'),
+               array('sql_alter', 'TABLE spip_gis_liens ADD INDEX (id_objet)'),
+       );
+
        include_spip('base/upgrade');
        maj_plugin($nom_meta_base_version, $version_cible, $maj);
 }
 
 function gis_upgrade_2_0(){
        include_spip('action/editer_gis');
-       
+
        // On déplace les liaisons articles et rubriques
        $res = sql_select('*','spip_gis');
        while ($row = sql_fetch($res)) {
@@ -62,7 +91,7 @@ function gis_upgrade_2_0(){
                if($row['id_rubrique'] != 0)
                        lier_gis($row['id_gis'], 'article', $row['id_rubrique']);
        }
-       
+
        // On déplace les liaisons mots
        $res = sql_select('*','spip_gis_mots');
        while ($row = sql_fetch($res)) {
index 2a2c739..8f67652 100644 (file)
@@ -15,7 +15,7 @@ function gis_autoriser(){};
  * @param array $opt Des options
  * @return boolean true/false
  */
-function autoriser_gis_iconifier_dist($faire,$quoi){
+function autoriser_gis_iconifier_dist($faire,$quoi,$id,$qui,$opts){
        return autoriser('modifier','gis',$id,$qui,$opts);
 }
 
@@ -71,7 +71,8 @@ function autoriser_gis_lier_dist($faire,$quoi,$id,$qui,$opts){
 
 /**
  * Autorisation a délier un point d'un objet
- * Un auteur peut délier un point à un autre objet que s'il peut modifier l'objet à lier en question
+ * Un auteur peut délier un point d'un autre objet que s'il peut modifier l'objet en question
+ * Si l'objet lié n'existe plus, on vérifie que l'auteur a le droit de modifier le point
  * 
  * @param string $faire L'action
  * @param string $type Le type d'objet
@@ -81,7 +82,12 @@ function autoriser_gis_lier_dist($faire,$quoi,$id,$qui,$opts){
  * @return boolean true/false
  */
 function autoriser_gis_delier_dist($faire,$quoi,$id,$qui,$opts){
-       return autoriser('lier','gis',$id,$qui,$opts);
+       $table = table_objet_sql($opts['objet']);
+       $_id_objet = id_table_objet($table);
+       if (!sql_getfetsel($_id_objet,$table,"$_id_objet=".intval($opts['id_objet'])))
+               return autoriser('modifier','gis',$id,$qui,$opts);
+       else
+               return autoriser('lier','gis',$id,$qui,$opts);
 }
 
 /**
@@ -104,4 +110,4 @@ function autoriser_gis_supprimer_dist($faire,$quoi,$id,$qui,$opts){
        }
        return autoriser('modifier','gis',$id,$qui,$opts);
 }
-?>
\ No newline at end of file
+?>
index 494d01c..72f98c5 100755 (executable)
@@ -137,11 +137,16 @@ function critere_distancefrom_dist($idb, &$boucles, $crit) {
 }
 
 /**
- * Critere {gis distance<XX} pour filtrer une liste de points par rapport à la distance du point de l'env
+ * Compile le critère `{gis}` qui permet de compléter la boucle avec les points GIS
  *
- * @param unknown_type $idb
- * @param unknown_type $boucles
- * @param unknown_type $crit
+ * Usage
+ * - `{gis}` Retourne les objets ayant des points (et ajoute les balises spéciales GIS tel que `#TITRE_GIS`)
+ * - `{!gis}` Retourne les objets sans points
+ * - `{gis distance<XX}`, sur une boucle `GIS`, filtre une liste de points par rapport à la distance du point de l'env
+ *
+ * @param string $idb
+ * @param array $boucles
+ * @param Critere $crit
  */
 function critere_gis_dist($idb, &$boucles, $crit) {
        $boucle = &$boucles[$idb];
@@ -180,44 +185,56 @@ function critere_gis_dist($idb, &$boucles, $crit) {
                $boucle->select[]= $select;
                $boucle->order[]= $order;
        } else {
-               // ajouter tous les champs du point au select 
-               // et les suffixer pour lever toute ambiguite avec des champs homonymes
-               $boucle->select[]= 'gis.titre AS titre_gis';
-               $boucle->select[]= 'gis.descriptif AS descriptif_gis';
-               $boucle->select[]= 'gis.adresse AS adresse_gis';
-               $boucle->select[]= 'gis.pays AS pays_gis';
-               $boucle->select[]= 'gis.code_pays AS code_pays_gis';
-               $boucle->select[]= 'gis.region AS region_gis';
-               $boucle->select[]= 'gis.ville AS ville_gis';
-               $boucle->select[]= 'gis.code_postal AS code_postal_gis';
-               // jointure sur spip_gis_liens/spip_gis
-               // cf plugin notation
-               // $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");
-               $boucle->from['gis_liens'] = 'spip_gis_liens';
-               $boucle->join['gis_liens']= array("'$id_table'","'id_objet'","'$primary'","'gis_liens.objet='.sql_quote('$objet')");
-               $boucle->from['gis'] = 'spip_gis';
-               $boucle->join['gis']= array("'gis_liens'","'id_gis'");
-               // bien renvoyer tous les points qui son attachés à l'objet
-               // mais attention, si on trouve en amont un groupement portant sur un champ *de GIS*,
-               // alors cela signifie que la personne veut faire une opération de groupement sur les points donc là on n'ajoute pas id_gis
-               $tous_les_points = true;
-               foreach ($boucle->group as $champ){
-                       if (in_array($champ, array('ville', 'code_postal', 'pays', 'code_pays', 'region'))) {
-                               $tous_les_points = false;
+
+               /* Recherche d'objets SANS point */
+               if ($crit->not) {
+                       $boucle->from['gis_liens'] = 'spip_gis_liens';
+                       $boucle->from_type['gis_liens'] = "LEFT";
+                       $boucle->join['gis_liens'] = array("'$id_table'","'id_objet'","'$primary'","'gis_liens.objet='.sql_quote('$objet')");
+                       $boucle->where[] = "'gis_liens.id_gis IS NULL'";
+
+               /* Recherche d'objets AVEC point + ajout des champs GIS */
+               } else {
+                       // ajouter tous les champs du point au select 
+                       // et les suffixer pour lever toute ambiguite avec des champs homonymes
+                       $boucle->select[]= 'gis.titre AS titre_gis';
+                       $boucle->select[]= 'gis.descriptif AS descriptif_gis';
+                       $boucle->select[]= 'gis.adresse AS adresse_gis';
+                       $boucle->select[]= 'gis.pays AS pays_gis';
+                       $boucle->select[]= 'gis.code_pays AS code_pays_gis';
+                       $boucle->select[]= 'gis.region AS region_gis';
+                       $boucle->select[]= 'gis.departement AS departement_gis';
+                       $boucle->select[]= 'gis.ville AS ville_gis';
+                       $boucle->select[]= 'gis.code_postal AS code_postal_gis';
+                       // jointure sur spip_gis_liens/spip_gis
+                       // cf plugin notation
+                       // $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");
+                       $boucle->from['gis_liens'] = 'spip_gis_liens';
+                       $boucle->join['gis_liens']= array("'$id_table'","'id_objet'","'$primary'","'gis_liens.objet='.sql_quote('$objet')");
+                       $boucle->from['gis'] = 'spip_gis';
+                       $boucle->join['gis']= array("'gis_liens'","'id_gis'");
+                       // bien renvoyer tous les points qui son attachés à l'objet
+                       // mais attention, si on trouve en amont un groupement portant sur un champ *de GIS*,
+                       // alors cela signifie que la personne veut faire une opération de groupement sur les points donc là on n'ajoute pas id_gis
+                       $tous_les_points = true;
+                       foreach ($boucle->group as $champ){
+                               if (in_array($champ, array('ville', 'code_postal', 'pays', 'code_pays', 'region','departement'))) {
+                                       $tous_les_points = false;
+                               }
+                       }
+                       if ($tous_les_points) {
+                               $boucle->group[] = 'gis_liens.id_gis';
+                       }
+                       // ajouter gis aux jointures et spécifier les jointures explicites pour pouvoir utiliser les balises de la table de jointure
+                       // permet de passer dans trouver_champ_exterieur() depuis index_tables_en_pile()
+                       // cf http://article.gmane.org/gmane.comp.web.spip.zone/6628
+                       $boucle->jointures[] = 'gis';
+                       if (empty($boucle->jointures_explicites)){
+                               $boucle->jointures_explicites = 'gis_liens gis';
+                       }
+                       else{
+                               $boucle->jointures_explicites .= ' gis_liens gis';
                        }
-               }
-               if ($tous_les_points) {
-                       $boucle->group[] = 'gis_liens.id_gis';
-               }
-               // ajouter gis aux jointures et spécifier les jointures explicites pour pouvoir utiliser les balises de la table de jointure
-               // permet de passer dans trouver_champ_exterieur() depuis index_tables_en_pile()
-               // cf http://article.gmane.org/gmane.comp.web.spip.zone/6628
-               $boucle->jointures[] = 'gis';
-               if (empty($boucle->jointures_explicites)){
-                       $boucle->jointures_explicites = 'gis_liens gis';
-               }
-               else{
-                       $boucle->jointures_explicites .= ' gis_liens gis';
                }
        }
 }
@@ -302,6 +319,16 @@ function balise_region_gis_dist($p) {
        return rindex_pile($p, 'region_gis', 'gis');
 }
 
+/**
+ * Balise #DEPARTEMENT_GIS : censé retourner le département du point
+ * Necessite le critere {gis} sur la boucle
+ *
+ * @param unknown_type $p
+ */
+function balise_departement_gis_dist($p) {
+       return rindex_pile($p, 'departement_gis', 'gis');
+}
+
 /**
  * Balise #CODE_POSTAL_GIS : retourne le code postal du point
  * Necessite le critere {gis} sur la boucle
@@ -342,14 +369,16 @@ function gis_modele_url_json_env($env){
                $tables_sql = lister_tables_objets_sql();
                foreach (array_keys($tables_sql) as $table){
                        $primary = id_table_objet($table);
-                       if (isset($env[$primary]))
-                               $contexte[$primary] = $env[$primary];
+                       if (isset($env[$primary])) {
+                               $contexte[$primary] = is_array($env[$primary]) ? $env[$primary] : trim($env[$primary]);
+                       }
                }
-               // puis cas particuliers
-               $keys = array("id_objet","id_secteur","id_parent","media","recherche","mots");
+               // puis cas particuliers et ceux ajoutés par le pipeline
+               $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"));
                foreach ($keys as $key){
-                       if (isset($env[$key]))
-                               $contexte[$key] = $env[$key];
+                       if (isset($env[$key])) {
+                               $contexte[$key] = is_array($env[$key]) ? $env[$key] : trim($env[$key]);
+                       }
                }
        }
        return $contexte;
@@ -371,9 +400,47 @@ function gis_kml_to_urls($kml){
                                $kml[$k] = url_absolue(generer_url_entite($v,"document"));
                        }
                        else
-                               $kml[$k] = _DIR_RACINE.copie_locale($kml[$k]);
+                               $kml[$k] = _DIR_RACINE.copie_locale($kml[$k],"modif");
                }
        }
        return $kml;
 }
+
+/**
+ * Retourne les propriétés JSON de l'icône d'un point
+ * 
+ * @param string $img
+ *     Balise HTML `<img ... />` ou chemin de l'image (qui peut être une URL distante).
+ * @return string
+ *     Les propriétés de l'icône
+**/
+function gis_icon_properties($img=''){
+       $props = $icon = '';
+       
+       if ($img) {
+               if (largeur($img) >= 44)
+                       $icon = extraire_attribut(filtrer('image_graver',filtrer('image_recadre',filtrer('image_passe_partout',$img,32,32),32,32,'center','transparent')),'src');
+               else
+                       $icon = extraire_attribut($img,'src') ? extraire_attribut($img,'src') : $img;
+       }
+       else
+               $icon = find_in_path('images/marker_defaut.png');
+       
+       if ($icon) {
+               $props .= ",\n\"icon\": ". json_encode(url_absolue($icon)).",";
+               list($h,$w) = taille_image($icon);
+               $props .= "\n\"icon_size\": ". json_encode(array($w,$h)).",";
+               $props .= "\n\"icon_anchor\": ". json_encode(array($w/2,$h)).",";
+               $props .= "\n\"popup_anchor\": ". json_encode(array(1,-round($h/1.2,2)));
+       }
+       
+       if ($shadow = find_in_path('images/marker_defaut_shadow.png')) {
+               $props .= ",\n\"shadow\": ". json_encode(url_absolue($shadow));
+               list($h,$w) = taille_image($shadow);
+               $props .= ",\n\"shadow_size\": ". json_encode(array($w,$h));
+       }
+       
+       return $props;
+}
+
 ?>
index 730774c..ca3b0cd 100755 (executable)
@@ -2,13 +2,14 @@
 
 if (!defined('_ECRIRE_INC_VERSION')) return;
 
-define('_DIR_LIB_GIS','lib/leaflet-gis-4.8.7/');
+// pour compat cf http://zone.spip.org/trac/spip-zone/changeset/79911/
+define('_DIR_LIB_GIS',find_in_path('lib/leaflet/'));
 
 $GLOBALS['logo_libelles']['id_gis'] = _T('gis:libelle_logo_gis');
 
 $config = @unserialize($GLOBALS['meta']['gis']);
 
-$GLOBALS['gis_layers'] = array (
+$gis_layers = array (
        'openstreetmap_mapnik' => array(
                'nom' => 'OpenStreetMap',
                'layer' => 'L.tileLayer.provider("OpenStreetMap")'
@@ -21,12 +22,20 @@ $GLOBALS['gis_layers'] = array (
                'nom' => 'OpenStreetMap DE',
                'layer' => 'L.tileLayer.provider("OpenStreetMap.DE")'
        ),
+       'openstreetmap_fr' => array(
+               'nom' => 'OpenStreetMap FR',
+               'layer' => 'L.tileLayer.provider("OpenStreetMap.France")'
+       ),
+       'openstreetmap_hot' => array(
+               'nom' => 'OpenStreetMap H.O.T.',
+               'layer' => 'L.tileLayer.provider("OpenStreetMap.HOT")'
+       ),
        'google_roadmap' => array(
                'nom' => 'Google Roadmap',
                'layer' => 'L.Google("ROADMAP")'
        ),
        'google_satellite' => array(
-               'nom' => 'Google Satelitte',
+               'nom' => 'Google Satellite',
                'layer' => 'L.Google("SATELLITE")'
        ),
        'google_terrain' => array(
@@ -53,6 +62,18 @@ $GLOBALS['gis_layers'] = array (
                'nom' => 'Thunderforest Outdoors',
                'layer' => 'L.tileLayer.provider("Thunderforest.Outdoors")'
        ),
+       'thunderforest_spinalmap' => array(
+               'nom' => 'Thunderforest SpinalMap',
+               'layer' => 'L.tileLayer.provider("Thunderforest.SpinalMap")'
+       ),
+       'thunderforest_pioneer' => array(
+               'nom' => 'Thunderforest Pioneer',
+               'layer' => 'L.tileLayer.provider("Thunderforest.Pioneer")'
+       ),
+       'opentopomap' => array(
+               'nom' => 'OpenTopoMap',
+               'layer' => 'L.tileLayer.provider("OpenTopoMap")'
+       ),
        'openmapsurfer' => array(
                'nom' => 'OpenMapSurfer',
                'layer' => 'L.tileLayer.provider("OpenMapSurfer")'
@@ -61,6 +82,14 @@ $GLOBALS['gis_layers'] = array (
                'nom' => 'OpenMapSurfer Grayscale',
                'layer' => 'L.tileLayer.provider("OpenMapSurfer.Grayscale")'
        ),
+       'hydda' => array(
+               'nom' => 'Hydda',
+               'layer' => 'L.tileLayer.provider("Hydda")'
+       ),
+       'hydda_base' => array(
+               'nom' => 'Hydda Base',
+               'layer' => 'L.tileLayer.provider("Hydda.Base")'
+       ),
        'mapquestopen_osm' => array(
                'nom' => 'Mapquest Open',
                'layer' => 'L.tileLayer.provider("MapQuestOpen.OSM")'
@@ -113,7 +142,6 @@ $GLOBALS['gis_layers'] = array (
                'nom' => 'Esri WorldPhysical',
                'layer' => 'L.tileLayer.provider("Esri.WorldPhysical")'
        ),
-       
        'esri_oceanbasemap' => array(
                'nom' => 'Esri OceanBasemap',
                'layer' => 'L.tileLayer.provider("Esri.OceanBasemap")'
@@ -126,30 +154,26 @@ $GLOBALS['gis_layers'] = array (
                'nom' => 'Esri WorldGrayCanvas',
                'layer' => 'L.tileLayer.provider("Esri.WorldGrayCanvas")'
        ),
-       'nokia_normalday' => array(
-               'nom' => 'Nokia normalDay',
-               'layer' => 'L.tileLayer.provider("Nokia.normalDay")'
-       ),
-       'nokia_normalgreyday' => array(
-               'nom' => 'Nokia normalGreyDay',
-               'layer' => 'L.tileLayer.provider("Nokia.normalGreyDay")'
-       ),
-       'nokia_satellitenolabelsday' => array(
-               'nom' => 'Nokia satelliteNoLabelsDay',
-               'layer' => 'L.tileLayer.provider("Nokia.satelliteNoLabelsDay")'
-       ),
-       'nokia_satelliteyeslabelsday' => array(
-               'nom' => 'Nokia satelliteYesLabelsDay',
-               'layer' => 'L.tileLayer.provider("Nokia.satelliteYesLabelsDay")'
+       'cartodb_positron' => array(
+               'nom' => 'CartoDB Positron',
+               'layer' => 'L.tileLayer.provider("CartoDB.Positron")'
        ),
-       'nokia_terrainday' => array(
-               'nom' => 'Nokia terrainDay',
-               'layer' => 'L.tileLayer.provider("Nokia.terrainDay")'
+       'cartodb_positron_base' => array(
+               'nom' => 'CartoDB Positron Base',
+               'layer' => 'L.tileLayer.provider("CartoDB.PositronNoLabels")'
        ),
-       'acetate' => array(
-               'nom' => 'Acetate',
-               'layer' => 'L.tileLayer.provider("Acetate.all")'
+       'cartodb_darkmatter' => array(
+               'nom' => 'CartoDB DarkMatter',
+               'layer' => 'L.tileLayer.provider("CartoDB.DarkMatter")'
        ),
+       'cartodb_darkmatter_base' => array(
+               'nom' => 'CartoDB DarkMatter Base',
+               'layer' => 'L.tileLayer.provider("CartoDB.DarkMatterNoLabels")'
+       )
 );
 
-?>
\ No newline at end of file
+if (isset($GLOBALS['gis_layers']) and is_array($GLOBALS['gis_layers'])) {
+       $GLOBALS['gis_layers'] = array_merge($gis_layers,$GLOBALS['gis_layers']);
+} else {
+       $GLOBALS['gis_layers'] = $gis_layers;
+}
index 5259a55..800ea5b 100755 (executable)
@@ -9,11 +9,9 @@ if (!defined('_ECRIRE_INC_VERSION')) return;
  * @return mixed
  */
 function gis_insert_head_css($flux){
-       $flux .="\n".'<link rel="stylesheet" href="'. find_in_path(_DIR_LIB_GIS.'dist/leaflet.css') .'" />';
-       $flux .="\n".'<!--[if lte IE 8]> <link rel="stylesheet" href="'. find_in_path(_DIR_LIB_GIS.'dist/leaflet.ie.css') .'" /> <![endif]-->';
-       $flux .="\n".'<link rel="stylesheet" href="'. find_in_path(_DIR_LIB_GIS.'plugins/leaflet-plugins.css') .'" />';
-       $flux .="\n".'<link rel="stylesheet" href="'. sinon(find_in_path('css/leaflet.markercluster.css'),find_in_path(_DIR_LIB_GIS.'plugins/leaflet.markercluster.css')) .'" />';
-       $flux .="\n".'<!--[if lte IE 8]><link rel="stylesheet" href="'. sinon(find_in_path('css/leaflet.markercluster.ie.css'),find_in_path(_DIR_LIB_GIS.'plugins/leaflet.markercluster.ie.css')) .'" /><![endif]-->';
+       $flux .="\n".'<link rel="stylesheet" href="'. sinon(find_in_path('css/leaflet.css'),find_in_path('lib/leaflet/dist/leaflet.css')) .'" />';
+       $flux .="\n".'<link rel="stylesheet" href="'. sinon(find_in_path('css/leaflet-plugins.css'),find_in_path('lib/leaflet/plugins/leaflet-plugins.css')) .'" />';
+       $flux .="\n".'<link rel="stylesheet" href="'. sinon(find_in_path('css/leaflet.markercluster.css'),find_in_path('lib/leaflet/plugins/leaflet.markercluster.css')) .'" />';
        return $flux;
 }
 
@@ -27,7 +25,7 @@ function gis_insert_head($flux){
        
        // initialisation des valeurs de config
        $config = @unserialize($GLOBALS['meta']['gis']);
-       if (!is_array($config['layers']))
+       if (!isset($config['layers']) || !is_array($config['layers']))
                $config['layers'] = array('openstreetmap_mapnik');
        
        include_spip('gis_fonctions');
@@ -36,7 +34,7 @@ function gis_insert_head($flux){
        
        // insertion des scripts pour google si nécessaire
        if (count(array_intersect(array('google_roadmap', 'google_satellite', 'google_terrain'), $config['layers'])) > 0) {
-               $flux .="\n".'<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false&amp;language='.$GLOBALS['spip_lang'].'"></script>';
+               $flux .="\n".'<script type="text/javascript" src="//maps.google.com/maps/api/js?sensor=false&amp;language='.$GLOBALS['spip_lang'].'"></script>';
        }
        
        return $flux;
@@ -44,6 +42,7 @@ function gis_insert_head($flux){
 
 /**
  * Insertion des scripts et css du plugin dans les pages de l'espace privé
+ *
  * @param $flux
  * @return mixed
  */
@@ -53,6 +52,12 @@ function gis_header_prive($flux){
        return $flux;
 }
 
+/**
+ * Insertion du bloc GIS dans les pages des objets de l'espace privé
+ *
+ * @param $flux
+ * @return mixed
+ */
 function gis_afficher_contenu_objet($flux){
        if ($objet = $flux['args']['type']
                and include_spip('inc/config')
@@ -60,9 +65,6 @@ function gis_afficher_contenu_objet($flux){
                and ($id = intval($flux['args']['id_objet']))
                
        ){
-               // TODO : seulement si la conf permet de geolocaliser cet objet
-               // -> ajouter un element a la array suivante (qqch comme ca - voir les mots):
-               //   'editable'=>autoriser('associergis',$type,$id)?'oui':'non'
                $texte = recuperer_fond(
                        'prive/contenu/gis_objet',
                        array(
@@ -77,14 +79,21 @@ function gis_afficher_contenu_objet($flux){
        return $flux;
 }
 
+/**
+ * Si la geolocalisation des documents est activée dans la config,
+ * création/suppression d'un point à partir des métadonnées du document ajouté (jpg, kml et kmz)
+ *
+ * @param $flux
+ * @return mixed
+ */
 function gis_post_edition($flux){
-       
-       if (($flux['args']['operation'] == 'ajouter_document') 
+       if (is_array($flux) && isset($flux['args']['operation']) && ($flux['args']['operation'] == 'ajouter_document') 
                AND ($document = sql_fetsel("*","spip_documents","id_document=".intval($flux['args']['id_objet'])))
+               AND (in_array(table_objet_sql('document'), lire_config('gis/gis_objets', array())))
        ) {
-               if(in_array($document['extension'],array('jpg','kml','kmz'))){
+               if (in_array($document['extension'],array('jpg','kml','kmz'))) {
                        $config = @unserialize($GLOBALS['meta']['gis']);
-                       if(!is_array($config))
+                       if (!is_array($config))
                                $config = array();
                        include_spip('inc/documents');
                        $fichier = get_spip_doc($document['fichier']);
@@ -92,70 +101,64 @@ function gis_post_edition($flux){
                }
                if ($document['extension'] == 'jpg') {
                        // on recupere les coords definies dans les exif du document s'il y en a
-                       if ($exifs =  @exif_read_data($fichier,'GPS')) {
-                               if(!function_exists('dms_to_dec'))
+                       if (function_exists('exif_read_data') AND $exifs =  @exif_read_data($fichier,'GPS')) {
+                               if (!function_exists('dms_to_dec'))
                                        include_spip('gis_fonctions');
                                spip_log("GIS EXIFS : Récuperation des coordonnees du fichier $fichier","gis");
                                
                                $LatDeg = explode("/",$exifs["GPSLatitude"][0]);
-                               if(is_numeric($LatDeg[1]) > 0)
+                               if (is_numeric($LatDeg[1]) > 0)
                                        $intLatDeg = $LatDeg[0]/$LatDeg[1];
 
                                $LatMin = explode("/",$exifs["GPSLatitude"][1]);
-                               if(is_numeric($LatMin[1]) > 0)
+                               if (is_numeric($LatMin[1]) > 0)
                                        $intLatMin = $LatMin[0]/$LatMin[1];
 
                                $LatSec = explode("/",$exifs["GPSLatitude"][2]);
-                               if(is_numeric($LatSec[1]) > 0)
+                               if (is_numeric($LatSec[1]) > 0)
                                        $intLatSec = $LatSec[0]/$LatSec[1];
 
                                $LongDeg = explode("/",$exifs["GPSLongitude"][0]);
-                               if(is_numeric($LongDeg[1]) > 0)
+                               if (is_numeric($LongDeg[1]) > 0)
                                        $intLongDeg = $LongDeg[0]/$LongDeg[1];
 
                                $LongMin = explode("/",$exifs["GPSLongitude"][1]);
-                               if(is_numeric($LongMin[1]) > 0)
+                               if (is_numeric($LongMin[1]) > 0)
                                        $intLongMin = $LongMin[0]/$LongMin[1];
 
                                $LongSec = explode("/",$exifs["GPSLongitude"][2]);
-                               if(is_numeric($LongSec[1]) > 0)
+                               if (is_numeric($LongSec[1]) > 0)
                                        $intLongSec = $LongSec[0]/$LongSec[1];
 
                                // round to 5 = approximately 1 meter accuracy
-                               if(is_numeric($intLatDeg) && is_numeric($intLatMin) && is_numeric($intLatSec))
+                               if (is_numeric($intLatDeg) && is_numeric($intLatMin) && is_numeric($intLatSec))
                                        $latitude = round(dms_to_dec($exifs["GPSLatitudeRef"],
                                                $intLatDeg,$intLatMin,$intLatSec),5);
 
-                               if(is_numeric($intLongDeg) && is_numeric($intLongMin) && is_numeric($intLongSec))
+                               if (is_numeric($intLongDeg) && is_numeric($intLongMin) && is_numeric($intLongSec))
                                        $longitude =  round(dms_to_dec($exifs["GPSLongitudeRef"],
                                                $intLongDeg,$intLongMin,$intLongSec), 5);
-                               if($config['geocoder'] == 'on'){
-                                       include_spip('inc/xml');
-                                       $url_geocoder = 'http://maps.googleapis.com/maps/api/geocode/xml?latlng='.urlencode($latitude).','.urlencode($longitude).'&sensor=true';
-                                       $geocoder = spip_xml_load($url_geocoder);
-                                       spip_xml_match_nodes(',result,',$geocoder,$matches_adress);
-                                       if(is_array($matches_adress['result'])){
-                                               foreach($matches_adress['result'] as $component){
-                                                       if(in_array('country',$component['type'])){
-                                                               $pays = $component['address_component'][0]['long_name'][0];
-                                                               $code_pays = $component['address_component'][0]['short_name'][0];
-                                                       }
-                                                       if(in_array('administrative_area_level_1',$component['type'])){
-                                                               $region = $component['address_component'][0]['long_name'][0];
-                                                       }
-                                                       if(in_array('locality',$component['type'])){
-                                                               $ville = $component['address_component'][0]['long_name'][0];
-                                                       }
-                                                       if(in_array('postal_code',$component['type'])){
-                                                               $code_postal = $component['address_component'][0]['long_name'][0];
-                                                       }
-                                                       if(in_array('route',$component['type'])){
-                                                               $adresse = $component['address_component'][0]['long_name'][0];
-                                                       }
+                               if ($config['geocoder'] == 'on') {
+                                       include_spip('inc/distant');
+                                       $url_geocoder = 'http://nominatim.openstreetmap.org/reverse/?format=xml&addressdetails=1&accept-language='.urlencode($GLOBALS['meta']['langue_site']).'&lat='.urlencode($latitude).'&lon='.urlencode($longitude);
+                                       $json = recuperer_page($url_geocoder);
+                                       $geocoder = json_decode($json,true);
+                                       if (is_array($geocoder)) {
+                                               $pays = $geocoder['address']['country'];
+                                               $code_pays = $geocoder['address']['country_code'];
+                                               $region = $geocoder['address']['state'];
+                                               if ($geocoder['address']['city']) {
+                                                       $ville = $geocoder['address']['city'];
+                                               } else if ($geocoder['address']['town']) {
+                                                       $ville = $geocoder['address']['town'];
+                                               } else if ($geocoder['address']['village']) {
+                                                       $ville = $geocoder['address']['village'];
                                                }
+                                               $code_postal = $geocoder['address']['postcode'];
+                                               $adresse = $geocoder['address']['road'];
                                        }
                                }
-                       }else if(file_exists($fichier)){
+                       } else if (file_exists($fichier)) {
                                include_spip("inc/iptc");
 
                                $er = new class_IPTC($fichier);
@@ -163,44 +166,39 @@ function gis_post_edition($flux){
                                $codesiptc = $er->h_codesIptc;
                                $string_recherche = '';
                                
-                               if($iptc['city']){
+                               if ($iptc['city']) {
                                        $string_recherche .= $iptc['city'].', ';
                                }
-                               if($iptc['provinceState']){
+                               if ($iptc['provinceState']) {
                                        $string_recherche .= $iptc['provinceState'].', ';
                                }
-                               if($iptc['country']){
+                               if ($iptc['country']) {
                                        $string_recherche .= $iptc['country'];
                                }
-                               if(strlen($string_recherche)){
-                                       include_spip('inc/xml');
-                                       $url_geocoder = 'http://maps.googleapis.com/maps/api/geocode/xml?address='.urlencode($string_recherche).'&sensor=true';
-                                       $geocoder = spip_xml_load($url_geocoder);
-                                       if(is_array($geocoder)){
-                                               spip_xml_match_nodes(',location,',$geocoder,$matches);
-                                               $latitude = $matches['location']['0']['lat']['0'];
-                                               $longitude = $matches['location']['0']['lng']['0'];
-                                               if($config['adresse'] == 'on'){
-                                                       spip_xml_match_nodes(',address_component,',$geocoder,$matches_adress);
-                                                       if(is_array($matches_adress['address_component'])){
-                                                               foreach($matches_adress['address_component'] as $component){
-                                                                       if(in_array('country',$component['type'])){
-                                                                               $pays = $component['long_name'][0];
-                                                                               $code_pays = $component['short_name'][0];
-                                                                       }
-                                                                       if(in_array('administrative_area_level_1',$component['type'])){
-                                                                               $region = $component['long_name'][0];
-                                                                       }
-                                                                       if(in_array('locality',$component['type'])){
-                                                                               $ville = $component['long_name'][0];
-                                                                       }
-                                                               }
+                               if (strlen($string_recherche)) {
+                                       include_spip('inc/distant');
+                                       $url_geocoder = 'http://nominatim.openstreetmap.org/search/?format=json&addressdetails=1&limit=1&accept-language='.urlencode($GLOBALS['meta']['langue_site']).'&q='.urlencode($string_recherche);
+                                       $json = recuperer_page($url_geocoder);
+                                       $geocoder = json_decode($json,true);
+                                       if (is_array($geocoder[0])) {
+                                               $latitude = $geocoder[0]['lat'];
+                                               $longitude = $geocoder[0]['lon'];
+                                               if ($config['adresse'] == 'on') {
+                                                       $pays = $geocoder[0]['address']['country'];
+                                                       $code_pays = $geocoder[0]['address']['country_code'];
+                                                       $region = $geocoder[0]['address']['state'];
+                                                       if ($geocoder[0]['address']['city']) {
+                                                               $ville = $geocoder[0]['address']['city'];
+                                                       } else if ($geocoder[0]['address']['town']) {
+                                                               $ville = $geocoder[0]['address']['town'];
+                                                       } else if ($geocoder[0]['address']['village']) {
+                                                               $ville = $geocoder[0]['address']['village'];
                                                        }
                                                }
                                        }
                                }
                        }
-                       if(is_numeric($latitude) && is_numeric($longitude)){
+                       if (is_numeric($latitude) && is_numeric($longitude)) {
                                $c = array(
                                        'titre' => basename($fichier),
                                        'lat'=> $latitude,
@@ -210,6 +208,7 @@ function gis_post_edition($flux){
                                        'code_postal' => $code_postal,
                                        'ville' => $ville,
                                        'region' => $region,
+                                       'departement' => $departement,
                                        'pays' => $pays,
                                        'code_pays' => $code_pays
                                );
@@ -221,12 +220,11 @@ function gis_post_edition($flux){
                                
                                include_spip('action/editer_gis');
        
-                               if($id_gis = sql_getfetsel("G.id_gis","spip_gis AS G LEFT  JOIN spip_gis_liens AS T ON T.id_gis=G.id_gis ","T.id_objet=" . intval($id_document) . " AND T.objet='document'")){
+                               if ($id_gis = sql_getfetsel("G.id_gis","spip_gis AS G LEFT  JOIN spip_gis_liens AS T ON T.id_gis=G.id_gis ","T.id_objet=" . intval($id_document) . " AND T.objet='document'")) {
                                        // Des coordonnées sont déjà définies pour ce document => on les update
                                        revisions_gis($id_gis,$c);
                                        spip_log("GIS EXIFS : Update des coordonnées depuis EXIFS pour le document $id_document => id_gis = $id_gis","gis");
-                               }
-                               else{
+                               } else {
                                        // Aucune coordonnée n'est définie pour ce document  => on les crées
                                        $id_gis = insert_gis();
                                        revisions_gis($id_gis,$c);
@@ -234,11 +232,11 @@ function gis_post_edition($flux){
                                        spip_log("GIS EXIFS : Création des coordonnées depuis EXIFS pour le document $id_document => id_gis = $id_gis","gis");
                                }
                        }
-               }elseif(in_array($document['extension'],array('kml','kmz','gpx'))){
+               } elseif (in_array($document['extension'],array('kml','kmz','gpx'))) {
                        $recuperer_info = charger_fonction('kml_infos','inc');
                        $infos = $recuperer_info($document['id_document']);
-                       if($infos){
-                               if(is_numeric($latitude = $infos['latitude']) && is_numeric($longitude = $infos['longitude'])){
+                       if ($infos) {
+                               if (is_numeric($latitude = $infos['latitude']) && is_numeric($longitude = $infos['longitude'])) {
                                        $c = array(
                                                'titre' => $infos['titre'] ? $infos['titre'] : basename($fichier),
                                                'descriptif' => $infos['descriptif'],
@@ -249,12 +247,11 @@ function gis_post_edition($flux){
                        
                                        include_spip('action/editer_gis');
                
-                                       if($id_gis = sql_getfetsel("G.id_gis","spip_gis AS G LEFT  JOIN spip_gis_liens AS T ON T.id_gis=G.id_gis ","T.id_objet=" . intval($id_document) . " AND T.objet='document'")){
+                                       if ($id_gis = sql_getfetsel("G.id_gis","spip_gis AS G LEFT  JOIN spip_gis_liens AS T ON T.id_gis=G.id_gis ","T.id_objet=" . intval($id_document) . " AND T.objet='document'")) {
                                                // Des coordonnées sont déjà définies pour ce document => on les update
                                                revisions_gis($id_gis,$c);
                                                spip_log("GIS EXIFS : Update des coordonnées depuis EXIFS pour le document $id_document => id_gis = $id_gis","gis");
-                                       }
-                                       else{
+                                       } else {
                                                // Aucune coordonnée n'est définie pour ce document  => on les crées
                                                $id_gis = insert_gis();
                                                revisions_gis($id_gis,$c);
@@ -264,14 +261,14 @@ function gis_post_edition($flux){
                                }
                                unset($infos['longitude']);
                                unset($infos['latitude']);
-                               if(count($infos) > 0){
+                               if (count($infos) > 0) {
                                        include_spip('action/editer_document');
                                        document_modifier($id_document, $infos);
                                }
                        }
                }
        }
-       if (($flux['args']['operation'] == 'supprimer_document') 
+       if (is_array($flux) && isset($flux['args']['operation']) && ($flux['args']['operation'] == 'supprimer_document') 
                AND ($id_document = intval($flux['args']['id_objet'])
                AND ($id_gis = sql_getfetsel("G.id_gis","spip_gis AS G LEFT  JOIN spip_gis_liens AS T ON T.id_gis=G.id_gis ","T.id_objet=" . intval($id_document) . " AND T.objet='document'")))
        ) {
@@ -283,9 +280,22 @@ function gis_post_edition($flux){
        return $flux;
 }
 
-function gis_taches_generales_cron($taches_generales){
-       $taches_generales['gis_nettoyer_base'] = 3600*48;
-       return $taches_generales;
+
+/**
+ * Optimiser la base de données en supprimant les liens orphelins
+ *
+ * @param array $flux
+ * @return array
+ */
+function gis_optimiser_base_disparus($flux){
+
+       include_spip('action/editer_liens');
+       // optimiser les liens morts :
+       // entre gis vers des objets effaces
+       // depuis des gis effaces
+       $flux['data'] += objet_optimiser_liens(array('gis'=>'*'),'*');
+
+       return $flux;
 }
 
 function gis_saisies_autonomes($flux){
@@ -317,4 +327,24 @@ function gis_xmlrpc_server_class($flux){
        return $flux;
 }
 
+/**
+ * Insertion dans le traitement du formulaire de configuration
+ * 
+ * Purger le répertoire js si on a une carte google dans les layers pour recalculer le js statique
+ * Peut être à améliorer
+ * 
+ * @param array $flux
+ *             Le contexte du pipeline
+ * @return array $flux
+ */
+function gis_formulaire_traiter($flux){
+       if($flux['args']['form'] == 'configurer_gis'){
+               if (count(array_intersect(array('google_roadmap', 'google_satellite', 'google_terrain'), _request('layers'))) > 0){
+                       include_spip('inc/invalideur');
+                       purger_repertoire(_DIR_VAR.'cache-js');
+                       suivre_invalideur(1);
+               }
+       }
+       return $flux;
+}
 ?>
diff --git a/www/plugins/gis/images/marker.png b/www/plugins/gis/images/marker.png
deleted file mode 100644 (file)
index c05d02c..0000000
Binary files a/www/plugins/gis/images/marker.png and /dev/null differ
diff --git a/www/plugins/gis/images/openlayers/dark/blank.gif b/www/plugins/gis/images/openlayers/dark/blank.gif
deleted file mode 100644 (file)
index 2799b45..0000000
Binary files a/www/plugins/gis/images/openlayers/dark/blank.gif and /dev/null differ
diff --git a/www/plugins/gis/images/openlayers/dark/cloud-popup-relative.png b/www/plugins/gis/images/openlayers/dark/cloud-popup-relative.png
deleted file mode 100644 (file)
index 9db138a..0000000
Binary files a/www/plugins/gis/images/openlayers/dark/cloud-popup-relative.png and /dev/null differ
diff --git a/www/plugins/gis/images/openlayers/dark/east-mini.png b/www/plugins/gis/images/openlayers/dark/east-mini.png
deleted file mode 100644 (file)
index 3f1f3d8..0000000
Binary files a/www/plugins/gis/images/openlayers/dark/east-mini.png and /dev/null differ
diff --git a/www/plugins/gis/images/openlayers/dark/layer-switcher-maximize.png b/www/plugins/gis/images/openlayers/dark/layer-switcher-maximize.png
deleted file mode 100644 (file)
index 20ad0b9..0000000
Binary files a/www/plugins/gis/images/openlayers/dark/layer-switcher-maximize.png and /dev/null differ
diff --git a/www/plugins/gis/images/openlayers/dark/layer-switcher-minimize.png b/www/plugins/gis/images/openlayers/dark/layer-switcher-minimize.png
deleted file mode 100644 (file)
index 3d27de3..0000000
Binary files a/www/plugins/gis/images/openlayers/dark/layer-switcher-minimize.png and /dev/null differ
diff --git a/www/plugins/gis/images/openlayers/dark/north-mini.png b/www/plugins/gis/images/openlayers/dark/north-mini.png
deleted file mode 100644 (file)
index cfb9b7d..0000000
Binary files a/www/plugins/gis/images/openlayers/dark/north-mini.png and /dev/null differ
diff --git a/www/plugins/gis/images/openlayers/dark/slider.png b/www/plugins/gis/images/openlayers/dark/slider.png
deleted file mode 100644 (file)
index 45622bc..0000000
Binary files a/www/plugins/gis/images/openlayers/dark/slider.png and /dev/null differ
diff --git a/www/plugins/gis/images/openlayers/dark/south-mini.png b/www/plugins/gis/images/openlayers/dark/south-mini.png
deleted file mode 100644 (file)
index f8cac08..0000000
Binary files a/www/plugins/gis/images/openlayers/dark/south-mini.png and /dev/null differ
diff --git a/www/plugins/gis/images/openlayers/dark/west-mini.png b/www/plugins/gis/images/openlayers/dark/west-mini.png
deleted file mode 100644 (file)
index 34e9776..0000000
Binary files a/www/plugins/gis/images/openlayers/dark/west-mini.png and /dev/null differ
diff --git a/www/plugins/gis/images/openlayers/dark/zoom-minus-mini.png b/www/plugins/gis/images/openlayers/dark/zoom-minus-mini.png
deleted file mode 100644 (file)
index abc125e..0000000
Binary files a/www/plugins/gis/images/openlayers/dark/zoom-minus-mini.png and /dev/null differ
diff --git a/www/plugins/gis/images/openlayers/dark/zoom-panel.png b/www/plugins/gis/images/openlayers/dark/zoom-panel.png
deleted file mode 100644 (file)
index f94fbb9..0000000
Binary files a/www/plugins/gis/images/openlayers/dark/zoom-panel.png and /dev/null differ
diff --git a/www/plugins/gis/images/openlayers/dark/zoom-plus-mini.png b/www/plugins/gis/images/openlayers/dark/zoom-plus-mini.png
deleted file mode 100644 (file)
index f7a717c..0000000
Binary files a/www/plugins/gis/images/openlayers/dark/zoom-plus-mini.png and /dev/null differ
diff --git a/www/plugins/gis/images/openlayers/dark/zoom-world-mini.png b/www/plugins/gis/images/openlayers/dark/zoom-world-mini.png
deleted file mode 100644 (file)
index 0dee7ee..0000000
Binary files a/www/plugins/gis/images/openlayers/dark/zoom-world-mini.png and /dev/null differ
diff --git a/www/plugins/gis/images/openlayers/dark/zoombar.png b/www/plugins/gis/images/openlayers/dark/zoombar.png
deleted file mode 100644 (file)
index 4a13fe0..0000000
Binary files a/www/plugins/gis/images/openlayers/dark/zoombar.png and /dev/null differ
index fdc5291..e2867fc 100644 (file)
@@ -116,7 +116,14 @@ function spip_lire_gis($args){
        $logo = quete_logo('id_gis','on', $res['result'][0]['id_gis'], '', false);
        if(is_array($logo))
                $res['result'][0]['logo'] = url_absolue($logo[0]);
-       
+
+       if(defined('_DIR_PLUGIN_GISGEOM')){
+               if(isset($res['result'][0]['geo'])){
+                       include_spip('gisgeom_fonctions');
+                       $res['result'][0]['geo'] = wkt_to_json($wkt);
+               }
+       }
+
        $gis_struct = $res['result'][0];
        $gis_struct = array_map('texte_backend',$gis_struct);
        return $gis_struct;
index 6746daa..8537b6a 100644 (file)
@@ -1,24 +1,39 @@
 #HTTP_HEADER{Content-type:text/javascript}
 
-[(#INCLURE{[(#VAL{[(#EVAL{_DIR_LIB_GIS})dist/[(#CONFIG{auto_compress_js}|=={oui}|?{'leaflet','leaflet-src'})].js]})]})]
+[(#INCLURE{lib/leaflet/dist/[(#CONFIG{auto_compress_js}|=={oui}|?{'leaflet','leaflet-src'})].js})]
 
-[L.Icon.Default.imagePath = "(#CHEMIN{#EVAL{_DIR_LIB_GIS}dist/images}|url_absolue)";]
+[L.Icon.Default.imagePath = "(#CHEMIN{lib/leaflet/dist/images}|url_absolue)";]
+
+(function() {
+
+L.gisConfig = {
+       "gis_layers":#EVAL{json_encode($GLOBALS['gis_layers'])},
+       "default_layer":"[(#REM|gis_layer_defaut)]",
+       "affiche_layers":[(#CONFIG{gis/layers,#ARRAY}|json_encode)]
+};
+
+L.geocoderConfig = {
+       "forwardUrl":"[(#VAL{gis_geocoder_rechercher}|generer_url_action{mode=search}|html_entity_decode)]",
+       "reverseUrl":"[(#VAL{gis_geocoder_rechercher}|generer_url_action{mode=reverse}|html_entity_decode)]"
+};
+
+})();
 
 [(#INCLURE{javascript/gis_geocoder.js})]
 
 [(#INCLURE{javascript/gis_utils.js})]
 
-[(#INCLURE{[(#VAL{[(#EVAL{_DIR_LIB_GIS})plugins/KML.js]})]})]
+[(#INCLURE{lib/leaflet/plugins/KML.js})]
 
-[(#INCLURE{[(#VAL{[(#EVAL{_DIR_LIB_GIS})plugins/GPX.js]})]})]
+[(#INCLURE{lib/leaflet/plugins/GPX.js})]
 
-[(#INCLURE{[(#VAL{[(#EVAL{_DIR_LIB_GIS})plugins/leaflet-providers.js]})]})]
+[(#INCLURE{lib/leaflet/plugins/leaflet-providers.js})]
 
-[(#INCLURE{[(#VAL{[(#EVAL{_DIR_LIB_GIS})plugins/Control.FullScreen.js]})]})]
+[(#INCLURE{lib/leaflet/plugins/Control.FullScreen.js})]
 
-[(#INCLURE{[(#VAL{[(#EVAL{_DIR_LIB_GIS})plugins/Control.MiniMap.js]})]})]
+[(#INCLURE{lib/leaflet/plugins/Control.MiniMap.js})]
 
-[(#REM) Tester suivant la config pour ces deux scripts ]
+[(#REM) Scripts de google et bing si besoin ]
 
 #SET{layers,#CONFIG{gis/layers,#ARRAY{0,openstreetmap_mapnik}}}
 [(#VAL|gis_layer_defaut|in_array{#GET{layers}}|non)
 ]
 
 [(#LISTE{google_roadmap,google_satellite,google_terrain}|array_intersect{#GET{layers}}|count|>{0}|oui)
-[(#INCLURE{[(#VAL{[(#EVAL{_DIR_LIB_GIS})plugins/Google.js]})]})]
+[(#INCLURE{lib/leaflet/plugins/Google.js})]
 ]
 
 [(#VAL{bing_aerial}|in_array{#GET{layers}}|oui)
-[(#INCLURE{[(#VAL{[(#EVAL{_DIR_LIB_GIS})plugins/Bing.js]})]})]
+[(#INCLURE{lib/leaflet/plugins/Bing.js})]
 ]
 
-#INCLURE{javascript/gis_init_map.js}
+[(#INCLURE{lib/leaflet/plugins/leaflet.markercluster-src.js})]
 
-[(#REM)
-Lib clustering si besoin
-]
-[(#ENV{cluster}|=={oui}|oui)
-[(#INCLURE{[(#VAL{[(#EVAL{_DIR_LIB_GIS})plugins/leaflet.markercluster-src.js]})]})]
-]
+[(#INCLURE{javascript/leaflet.gis.js})]
 
 [(#CONFIG{auto_compress_js}|=={oui}|oui)
 #FILTRE{compacte}
index 825b07d..1494d32 100644 (file)
@@ -7,9 +7,10 @@ L.Geocoder = L.Class.extend({
        includes: L.Mixin.Events,
 
        options: {
-               forwardUrl: 'http://open.mapquestapi.com/nominatim/v1/search',
-               reverseUrl: 'http://open.mapquestapi.com/nominatim/v1/reverse',
+               forwardUrl: L.geocoderConfig.forwardUrl,
+               reverseUrl: L.geocoderConfig.reverseUrl,
                limit: 1,
+               acceptLanguage:'fr',
                addressdetails: 1
        },
 
@@ -22,6 +23,7 @@ L.Geocoder = L.Class.extend({
                if (L.LatLng && (data instanceof L.LatLng)) {
                        this._reverse_geocode(data);
                } else if (typeof(data) == 'string') {
+                       this.options.search = data;
                        this._geocode(data);
                }
        },
@@ -33,7 +35,8 @@ L.Geocoder = L.Class.extend({
                                format: 'json',
                                q: text,
                                limit: this.options.limit,
-                               addressdetails: this.options.addressdetails
+                               addressdetails: this.options.addressdetails,
+                               "accept-language":this.options.acceptLanguage
                        }
                );
        },
@@ -50,7 +53,7 @@ L.Geocoder = L.Class.extend({
        },
 
        _request: function (url, data) {
-               $.ajax({
+               jQuery.ajax({
                        cache: true,
                        context: this,
                        data: data,
@@ -61,55 +64,63 @@ L.Geocoder = L.Class.extend({
                });
        },
        
-       _callback: function (response) {
+       _callback: function (response,textStatus,jqXHR) {
                var return_location = {};
-               if (response instanceof Array && !response.length) {
-                       return false;
+               if(this.options.search)
+                       return_location.search = this.options.search;
+               if (((response instanceof Array) && (!response.length)) || ((response instanceof Object) && (response.error))) {
+                       return_location.error = 'not found';
                } else {
-                       return_location.street = '';
-                       return_location.postcode = '';
-                       return_location.locality = '';
-                       return_location.region = '';
-                       return_location.country = '';
-                       
-                       if (response.length > 0) {
+                       return_location.street = return_location.postcode = return_location.postcode = 
+                       return_location.locality = return_location.region = return_location.country  = '';
+
+                       if (response.length > 0)
                                place = response[0];
-                       else {
+                       else {
                                place = response;
                        }
-                       
+
                        var street_components = [];
-                       
+
                        if (place.address.country) {
                                return_location.country = place.address.country;
                        }
+                       if (place.address.country_code) {
+                               return_location.country_code = place.address.country_code;
+                       }
                        if (place.address.state) {
                                return_location.region = place.address.state;
                        }
+                       /* un jour peut-être...
+                       if (place.address.county) {
+                               return_location.departement = place.address.county;
+                       }
+                       */
                        if (place.address.city) {
                                return_location.locality = place.address.city;
-                       }else if(place.address.county){
-                               street_components.push(place.address.pedestrian);
+                       } else if (place.address.town) {
+                               return_location.locality = place.address.town;
+                       } else if (place.address.village) {
+                               return_location.locality = place.address.village;
+                       } else if (place.address.county) {
+                               street_components.push(place.address.county);
                        }
                        if (place.address.postcode) {
                                return_location.postcode = place.address.postcode;
                        }
                        if (place.address.road) {
                                street_components.push(place.address.road);
-                       }else if(place.address.pedestrian){
+                       } else if (place.address.pedestrian) {
                                street_components.push(place.address.pedestrian);
                        }
                        if (place.address.house_number) {
                                street_components.unshift(place.address.house_number);
                        }
-                       
                        if (return_location.street === '' && street_components.length > 0) {
                                return_location.street = street_components.join(' ');
                        }
-                       
                        return_location.point = new L.LatLng(place.lat, place.lon);
-                       
-                       this._user_callback(return_location);
                }
+               this._user_callback(return_location);
        }
-});
\ No newline at end of file
+});
diff --git a/www/plugins/gis/javascript/gis_init_map.js b/www/plugins/gis/javascript/gis_init_map.js
deleted file mode 100644 (file)
index e99e7e1..0000000
+++ /dev/null
@@ -1,252 +0,0 @@
-var gis_init_map = function(mapcfg) {
-       var map_container = mapcfg["mapid"];
-
-       // Création de la carte Leafleat
-       var map = new L.Map(map_container,{
-               scrollWheelZoom: mapcfg["scrollWheelZoom"],
-               zoomControl: mapcfg["zoomControl"],
-               maxZoom: mapcfg["maxZoom"]
-       });
-       // affecter sur la globale homonyme a mapid/map_container (compat ascendante)
-       eval(map_container+"=map;");
-       // affecter sur l'objet du DOM
-       jQuery("#"+map_container).get(0).map=map;
-
-       // Appeler l'éventuelle fonction de callback et trigger "load"
-       map.on('load',function(e){
-               if (mapcfg["callback"] && typeof(mapcfg["callback"]) === "function") {
-                       var callback = mapcfg["callback"];
-                       callback(e.target);
-               }
-               jQuery("#"+map_container).trigger('load',e.target);
-       });
-
-       // Déterminer la position initiale de la carte
-       if (!mapcfg['utiliser_bb']){
-               map.setView(new L.LatLng(mapcfg['lat'], mapcfg['lon']), mapcfg['zoom']);
-       }
-       else {
-               map.fitBounds(
-                       new L.LatLngBounds(
-                               new L.LatLng(mapcfg['sw_lat'], mapcfg['sw_lon']),
-                               new L.LatLng(mapcfg['ne_lat'], mapcfg['ne_lon'])
-                       )
-               );
-       }
-
-       var get_layer=function(name){
-               var layer;
-               if (typeof mapcfg['layers'][name]!=="undefined")
-               eval("layer=new "+ mapcfg['layers'][name]["layer"]+";");
-               return layer;
-       }
-
-       // Fond de carte par défaut (layer)
-       var default_layer = get_layer(mapcfg['default_layer']);
-       map.addLayer(default_layer);
-
-       if (mapcfg['control_type'] && !mapcfg['no_control'] && mapcfg['affiche_layers'].length>1){
-               var layers_control = new L.Control.Layers();
-               layers_control.addBaseLayer(default_layer,mapcfg['layers'][mapcfg['default_layer']]["nom"]);
-               for(var l in mapcfg['affiche_layers']){
-                       if (mapcfg['affiche_layers'][l]!==mapcfg['default_layer']){
-                               var layer = get_layer(mapcfg['affiche_layers'][l]);
-                               if (typeof layer!=="undefined")
-                                       layers_control.addBaseLayer(layer,mapcfg['layers'][mapcfg['affiche_layers'][l]]["nom"]);
-                       }
-               }
-               map.addControl(layers_control);
-               // ajouter l'objet du controle de layers à la carte pour permettre d'y accéder depuis le callback
-               map.layersControl = layers_control;
-               // classe noajax sur le layer_control pour éviter l'ajout de hidden par SPIP
-               jQuery(layers_control._form).addClass('noajax');
-       }
-
-
-       map.attributionControl.setPrefix('');
-
-       // Ajout des contrôles de la carte
-       if (!mapcfg['no_control']){
-               if (mapcfg['scale'])
-                       map.addControl(new L.Control.Scale());
-               if (mapcfg['fullscreen'])
-                       map.addControl(new L.Control.FullScreen());
-               if (mapcfg['overview']){
-                       var minimap_layer = get_layer(mapcfg['default_layer']);
-                       var miniMap = new L.Control.MiniMap(minimap_layer,{width: 100,height: 100, toggleDisplay: true}).addTo(map);
-               }
-       }
-
-       // API setGeoJsonFeatureIcon : Pour Ajouter l'icone d'un point (feature = item d'un GeoJson)
-       map.setGeoJsonFeatureIcon = function (feature, layer) {
-               // Déclarer l'icone du points, si défini
-               if (feature.properties && feature.properties.icon){
-                       icon_options = {
-                               'iconUrl': feature.properties.icon,
-                               'iconSize': new L.Point( feature.properties.icon_size[0], feature.properties.icon_size[1] ),
-                               'iconAnchor': new L.Point( feature.properties.icon_anchor[0], feature.properties.icon_anchor[1] ),
-                               'popupAnchor': new L.Point( feature.properties.popup_anchor[0], feature.properties.popup_anchor[1] )
-                       };
-                       if (feature.properties.shadow)
-                               icon_options.shadowUrl = feature.properties.shadow;
-                       if (feature.properties.shadow_size)
-                               icon_options.shadowSize = new L.Point( feature.properties.shadow_size[0], feature.properties.shadow_size[1] );
-                       layer.setIcon(new L.Icon(icon_options));
-               }
-       }
-
-       // API setGeoJsonFeaturePopup : Pour Ajouter le texte de popup d'un point (feature = item d'un GeoJson)
-       map.setGeoJsonFeaturePopup = function (feature, layer) {
-               // Déclarer le contenu de la popup s'il y en a
-               if (feature.properties && (feature.properties.title || feature.properties.description)){
-                       var popupContent = '';
-                       var popupOptions = '';
-                       if (feature.properties.title)
-                               popupContent = '<strong class="title">' + feature.properties.title + '</strong>';
-                       if (feature.properties.description)
-                               popupContent = popupContent + feature.properties.description;
-                       if (feature.properties.popup_options)
-                               popupOptions = feature.properties.popup_options;
-                       layer.bindPopup(popupContent,popupOptions);
-               }
-       }
-
-       /*
-               Il y a pour le moment 2 façons d'analyser le GeoJson calculé
-               en fonction de si on veut faire du clustering (regrouper les points proches)
-               ou non. Il y a certainement moyen de regrouper en un seul élément
-               la plupart du code, en se passant du js L.geoJson même hors clustering.
-               À réfléchir.
-       */
-       // API parseGeoJson
-       if (!mapcfg['cluster']){
-               // Analyse des points et déclaration (sans regroupement des points en cluster)
-               map.parseGeoJson = function(data) {
-                       if (data.features.length > 0) {
-                               var geojson = new L.geoJson('', {
-                                       style: mapcfg['path_styles'],
-                                       onEachFeature: function (feature, layer) {
-                                               // Déclarer l'icone du point
-                                               map.setGeoJsonFeatureIcon(feature, layer);
-                                               // Déclarer le contenu de la popup s'il y en a
-                                               map.setGeoJsonFeaturePopup(feature, layer);
-                                       }
-                               }).addTo(map);
-                               geojson.addData(data);
-                               if (mapcfg['autocenterandzoom'])
-                                       map.fitBounds(geojson.getBounds());
-                               if (mapcfg['open_id'].length)
-                                       gis_focus_marker(mapcfg['open_id'],map_container.substring(3));
-
-                               if (typeof map.geojsons=="undefined") map.geojsons = [];
-                               map.geojsons.push(geojson);
-                       }
-               }
-       }
-       else {
-               // Analyse des points et déclaration (en regroupant les points en cluster)
-               map.parseGeoJson = function(data) {
-                       var options = {
-                               showCoverageOnHover:false
-                       };
-                       if (mapcfg["clusterMaxZoom"])
-                               options.disableClusteringAtZoom = parseInt(mapcfg["clusterMaxZoom"]);
-                       if (mapcfg["clusterShowCoverageOnHover"])
-                               options.showCoverageOnHover = Boolean(mapcfg["clusterShowCoverageOnHover"]);
-
-                       map.markers = new L.MarkerClusterGroup(options);
-
-                       /* Pour chaque points présents, on crée un marqueur */
-                       $.each(data.features, function(i, feature) {
-                               if (feature.geometry.coordinates[0]) {
-                                       var latlng = new L.LatLng(feature.geometry.coordinates[1], feature.geometry.coordinates[0]);
-                                       var marker = new L.Marker(latlng);
-
-                                       // Déclarer l'icone du point
-                                       map.setGeoJsonFeatureIcon(feature, marker);
-                                       // Déclarer le contenu de la popup s'il y en a
-                                       map.setGeoJsonFeaturePopup(feature, marker);
-
-                                       marker.id = feature.id;
-                                       map.markers.addLayer(marker);
-                               }
-                       });
-
-                       map.addLayer(map.markers);
-
-                       if (mapcfg['autocenterandzoom'])
-                               map.fitBounds(map.markers.getBounds());
-               }
-       }
-
-       // API Compat Gis3 : addJSON et removeAllMarkers
-       map.addJSON = map.parseGeoJson
-       map.removeAllMarkers = function(){
-               if (typeof map.geojsons=="undefined") map.geojsons = [];
-               for(i in map.geojsons){
-                       map.geojsons[i].clearLayers();
-                       map.removeLayer(map.geojsons[i]);
-               }
-               map.geojsons = [];
-       }
-
-       if (mapcfg['affiche_points']
-               && typeof(mapcfg['json_points'])!=="undefined"
-               && mapcfg['json_points']['url'].length){
-               // Récupération des points à mettre sur la carte, via json externe
-               var args = {};
-               jQuery.extend(true, args, mapcfg['json_points']['env']);
-               if (typeof mapcfg['json_points']['objets']!=="undefined"){
-                       args["objets"] = mapcfg['json_points']['objets'];
-                       if (args["objets"]=="point_libre"){
-                               args["lat"]=mapcfg['lat'];
-                               args["lon"]=mapcfg['lon'];
-                               if (typeof mapcfg['json_points']['titre']!=="undefined")
-                                       args["titre"]= mapcfg['json_points']['titre'];
-                               if (typeof mapcfg['json_points']['description']!=="undefined")
-                                       args["description"]=mapcfg['json_points']['description'];
-                               if (typeof mapcfg['json_points']['icone']!=="undefined")
-                                       args["icone"]=mapcfg['json_points']['icone'];
-                       }
-               }
-               if (typeof mapcfg['json_points']['limit']!=="undefined")
-                       args["limit"] = mapcfg['json_points']['limit'];
-               jQuery.getJSON(mapcfg['json_points']['url'],args,
-                       function(data) {
-                               if (data){
-                                       // Charger le json (data) et déclarer les points
-                                       map.parseGeoJson(data);
-                                       jQuery("#"+map_container).trigger('ready',map);
-                               }
-                       }
-               );
-       }
-
-       if (mapcfg['kml'] && mapcfg['kml'].length){
-               map.kml = {};
-               for(var i in mapcfg['kml']){
-                       map.kml[i] = new L.KML(mapcfg['kml'][i], {async: true});
-                       if (mapcfg['centrer_fichier']) {
-                               map.kml[i].on("loaded", function(e) { map.fitBounds(e.target.getBounds()); });
-                       }
-                       map.addLayer(map.kml[i]);
-               }
-       }
-       if (mapcfg['gpx'] && mapcfg['gpx'].length){
-               map.gpx = {};
-               for(var i in mapcfg['gpx']){
-                       map.gpx[i] = new L.GPX(mapcfg['gpx'][i], {async: true});
-                       if (mapcfg['centrer_fichier']) {
-                               map.gpx[i].on("loaded", function(e) { map.fitBounds(e.target.getBounds()); });
-                       }
-                       map.addLayer(map.gpx[i]);
-               }
-       }
-
-       if (mapcfg['localize_visitor'])
-               map.locate({setView: true, maxZoom: mapcfg['zoom']});
-
-       // si pas de points trigger ici
-       if (!mapcfg['affiche_points'] || !mapcfg['json_points'].length)
-               jQuery("#"+map_container).trigger('ready',map);
-}
diff --git a/www/plugins/gis/javascript/leaflet.gis.js b/www/plugins/gis/javascript/leaflet.gis.js
new file mode 100644 (file)
index 0000000..f55cd4a
--- /dev/null
@@ -0,0 +1,314 @@
+(function() {
+// Plugin Leaflet L.Map.Gis
+L.Map.Gis = L.Map.extend({
+       
+       includes: L.Mixin.Events,
+       
+       options:{
+               mapId: 'map_gis',
+               utiliser_bb: false,
+               sw_lat: 0,
+               ne_lat: 0,
+               sw_lon: 0,
+               ne_lon: 0,
+               gis_layers: L.gisConfig.gis_layers,
+               default_layer: L.gisConfig.default_layer,
+               affiche_layers: L.gisConfig.affiche_layers,
+               scaleControl: false,
+               overviewControl: false,
+               layersControl: false,
+               layersControlOptions: {},
+               noControl: false,
+               cluster: false,
+               clusterOptions: {
+                       disableClusteringAtZoom: 0,
+                       showCoverageOnHover: false,
+                       maxClusterRadius: 80,
+                       spiderfyOnMaxZoom: false
+               },
+               pathStyles: null,
+               autocenterandzoom: false,
+               openId: false,
+               affiche_points: true,
+               json_points: {
+                       url: "",
+                       objets: "",
+                       limit: 500,
+                       env: [],
+                       titre: "",
+                       description: "",
+                       icone: ""
+               },
+               localize_visitor: false,
+               localize_visitor_zoom: 0,
+               centrer_fichier: true,
+               kml: false,
+               gpx: false,
+               geojson: false
+       },
+       
+       initialize: function (id,options) {
+               L.Util.setOptions(this, options);
+               
+               this.on('load',function(e){
+                       // Affecter sur l'objet du DOM
+                       jQuery("#"+this._container.id).get(0).map = this;
+                       // Appeler l'éventuelle fonction de callback
+                       if (this.options.callback && typeof(this.options.callback) === "function")
+                               this.options.callback(this);
+                       // trigger load sur l'objet du DOM
+                       jQuery("#"+this._container.id).trigger('load',this);
+               });
+               
+               L.Map.prototype.initialize.call(this, id, options);
+               
+               if (this.options.utiliser_bb){
+                       this.fitBounds(
+                               L.latLngBounds(
+                                       [this.options.sw_lat, this.options.sw_lon],
+                                       [this.options.ne_lat, this.options.ne_lon]
+                               )
+                       );
+               }
+               
+               this.populateTileLayers(this.options.affiche_layers);
+               
+               this.initControls();
+               
+               this.loadData();
+               
+               this.addOverlays();
+               
+               if (this.options.localize_visitor){
+                       var maxZoom = this.options.localize_visitor_zoom;
+                       this.on('locationerror',function(e){
+                               maxZoom = this.options.zoom;
+                               alert(e.message);
+                       });
+                       this.locate({setView: true, maxZoom: maxZoom});
+               }
+               
+               // Si pas de points affichés trigger ready ici
+               if (!this.options.affiche_points || !this.options.json_points.length)
+                       jQuery("#"+this._container.id).trigger('ready',this);
+       },
+
+       populateTileLayers: function (tilelayers) {
+               // Fond de carte par défaut
+               var default_layer = this.createTileLayer(this.options.default_layer);
+               this.addLayer(default_layer);
+               // Fonds de carte supplémentaires
+               if (this.options.layersControl && !this.options.noControl && this.options.affiche_layers.length>1){
+                       var layers_control = L.control.layers('','',this.options.layersControlOptions);
+                       layers_control.addBaseLayer(default_layer,this.options.gis_layers[this.options.default_layer].nom);
+                       for(var l in this.options.affiche_layers){
+                               if (this.options.affiche_layers[l]!==this.options.default_layer){
+                                       var layer = this.createTileLayer(this.options.affiche_layers[l]);
+                                       if (typeof layer!=="undefined")
+                                               layers_control.addBaseLayer(layer,this.options.gis_layers[this.options.affiche_layers[l]].nom);
+                               }
+                       }
+                       this.addControl(layers_control);
+                       // Ajouter l'objet du controle de layers à la carte pour permettre d'y accéder depuis le callback
+                       this.layersControl = layers_control;
+                       // Classe noajax sur le layer_control pour éviter l'ajout de hidden par SPIP
+                       L.DomUtil.addClass(layers_control._form,'noajax');
+               }
+       },
+
+       initControls: function () {
+               this.attributionControl.setPrefix('');
+               if (this.options.scaleControl)
+                       L.control.scale().addTo(this);
+               if (this.options.overviewControl){
+                       // todo ajouter une option pour permettre de choisir la couche à afficher dans la minimap
+                       var minimap_layer = this.createTileLayer(this.options.default_layer);
+                       L.control.minimap(minimap_layer,{width: 100,height: 100, toggleDisplay: true}).addTo(this);
+               }
+       },
+       
+       createTileLayer: function (name) {
+               var layer;
+               if (typeof this.options.gis_layers[name]!=="undefined")
+                       eval("layer=new "+ this.options.gis_layers[name].layer +";");
+               return layer;
+       },
+
+       // API setGeoJsonFeatureIcon : Pour Ajouter l'icone d'un point (feature = item d'un GeoJson)
+       setGeoJsonFeatureIcon: function (feature, layer) {
+               // Déclarer l'icone du points, si défini
+               if (feature.properties && feature.properties.icon){
+                       icon_options = {
+                               'iconUrl': feature.properties.icon,
+                               'iconSize': [feature.properties.icon_size[0], feature.properties.icon_size[1]],
+                               'iconAnchor': [feature.properties.icon_anchor[0], feature.properties.icon_anchor[1]]
+                       };
+                       if (feature.properties.popup_anchor)
+                               icon_options.popupAnchor = [feature.properties.popup_anchor[0], feature.properties.popup_anchor[1]];
+                       if (feature.properties.shadow)
+                               icon_options.shadowUrl = feature.properties.shadow;
+                       if (feature.properties.shadow_size)
+                               icon_options.shadowSize = [feature.properties.shadow_size[0], feature.properties.shadow_size[1]];
+                       layer.setIcon(L.icon(icon_options));
+               }
+       },
+       
+       // API setGeoJsonFeaturePopup : Pour Ajouter le texte de popup d'un point (feature = item d'un GeoJson)
+       setGeoJsonFeaturePopup: function (feature, layer) {
+               // Déclarer le contenu de la popup s'il y en a
+               if (feature.properties && (feature.properties.title || feature.properties.description)){
+                       var popupContent = '';
+                       var popupOptions = '';
+                       if (feature.properties.title)
+                               popupContent = '<strong class="title">' + feature.properties.title + '</strong>';
+                       if (feature.properties.description)
+                               popupContent = popupContent + feature.properties.description;
+                       if (feature.properties.popup_options)
+                               popupOptions = feature.properties.popup_options;
+                       layer.bindPopup(popupContent,popupOptions);
+               }
+       },
+       
+       // API parseGeoJson
+       parseGeoJson: function(data) {
+               var map = this;
+               // Analyse des points et déclaration (sans regroupement des points en cluster)
+               if (!map.options.cluster){
+                       if (data.features.length > 0){
+                               var geojson = L.geoJson('', {
+                                       style: this.options.pathStyles,
+                                       onEachFeature: function (feature, layer) {
+                                               // Déclarer l'icone du point
+                                               map.setGeoJsonFeatureIcon(feature, layer);
+                                               // Déclarer le contenu de la popup s'il y en a
+                                               map.setGeoJsonFeaturePopup(feature, layer);
+                                       }
+                               }).addData(data).addTo(map);
+                               
+                               if (map.options.autocenterandzoom){
+                                       if (data.features.length == 1 && data.features[0].geometry.type == 'Point')
+                                               map.setView(geojson.getBounds().getCenter(), map.options.zoom);
+                                       else
+                                               map.fitBounds(geojson.getBounds());
+                               }
+                               if (map.options.openId)
+                                       gis_focus_marker(map.options.openId,map.options.mapId);
+
+                               if (typeof map.geojsons=="undefined") map.geojsons = [];
+                               map.geojsons.push(geojson);
+                       }
+               } else {
+                       map.markers = L.markerClusterGroup(map.options.clusterOptions);
+
+                       /* Pour chaque points présents, on crée un marqueur */
+                       jQuery.each(data.features, function(i, feature){
+                               if (feature.geometry.coordinates[0]){
+                                       var marker = L.marker([feature.geometry.coordinates[1], feature.geometry.coordinates[0]]);
+
+                                       // Déclarer l'icone du point
+                                       map.setGeoJsonFeatureIcon(feature, marker);
+                                       // Déclarer le contenu de la popup s'il y en a
+                                       map.setGeoJsonFeaturePopup(feature, marker);
+
+                                       marker.id = feature.id;
+                                       map.markers.addLayer(marker);
+                               }
+                       });
+
+                       map.addLayer(map.markers);
+
+                       if (map.options.autocenterandzoom){
+                               if (data.features.length > 1)
+                                       map.fitBounds(map.markers.getBounds());
+                               else
+                                       map.setView(map.markers.getBounds().getCenter(), map.options.zoom);
+                       }
+               }
+       },
+       
+       // API Compat GIS3
+       addJSON: function(data) {
+               return this.parseGeoJson(data);
+       },
+       
+       // API Compat GIS3
+       removeAllMarkers: function(){
+               if (typeof this.geojsons=="undefined") this.geojsons = [];
+               for(var i in this.geojsons){
+                       this.geojsons[i].clearLayers();
+                       this.removeLayer(this.geojsons[i]);
+               }
+               this.geojsons = [];
+       },
+       
+       loadData: function () {
+               var map = this;
+               if (map.options.affiche_points
+                       && typeof(map.options.json_points) !== "undefined"
+                       && map.options.json_points.url.length){
+                       // Récupération des points à mettre sur la carte, via json externe
+                       var args = {};
+                       jQuery.extend(true, args, map.options.json_points.env);
+                       if (typeof map.options.json_points.objets !== "undefined"){
+                               args.objets = map.options.json_points.objets;
+                               if (args.objets == "point_libre"){
+                                       args.lat = map.options.center[0];
+                                       args.lon = map.options.center[1];
+                                       if (typeof map.options.json_points.titre !== "undefined")
+                                               args.titre = map.options.json_points.titre;
+                                       if (typeof map.options.json_points.description !== "undefined")
+                                               args.description = map.options.json_points.description;
+                                       if (typeof map.options.json_points.icone !== "undefined")
+                                               args.icone = map.options.json_points.icone;
+                               }
+                       }
+                       if (typeof map.options.json_points.limit !== "undefined")
+                               args.limit = map.options.json_points.limit;
+                       jQuery.getJSON(map.options.json_points.url,args,
+                               function(data) {
+                                       if (data){
+                                               // Charger le json (data) et déclarer les points
+                                               map.parseGeoJson(data);
+                                               jQuery("#"+map._container.id).trigger('ready',map);
+                                       }
+                               }
+                       );
+               }
+       },
+       
+       addOverlays: function () {
+               var map = this;
+               if (map.options.kml && map.options.kml.length){
+                       map.kml = {};
+                       for(var i in map.options.kml){
+                               map.kml[i] = new L.KML(map.options.kml[i], {async: true});
+                               if (map.options.centrer_fichier)
+                                       map.kml[i].on("loaded", function(e) { map.fitBounds(e.target.getBounds()); });
+                               map.addLayer(map.kml[i]);
+                       }
+               }
+               if (map.options.gpx && map.options.gpx.length){
+                       map.gpx = {};
+                       for(var i in map.options.gpx){
+                               map.gpx[i] = new L.GPX(map.options.gpx[i], {async: true});
+                               if (map.options.centrer_fichier)
+                                       map.gpx[i].on("loaded", function(e) { map.fitBounds(e.target.getBounds()); });
+                               map.addLayer(map.gpx[i]);
+                       }
+               }
+               if (map.options.geojson && map.options.geojson.length){
+                       for(var i in map.options.geojson){
+                               jQuery.getJSON(map.options.geojson[i], function(data){
+                                       if (data)
+                                               map.parseGeoJson(data);
+                               });
+                       }
+               }
+       }
+});
+
+L.map.gis = function (id, options) {
+       return new L.Map.Gis(id, options);
+};
+
+})();
\ No newline at end of file
index 6ad03a8..01a1967 100644 (file)
@@ -1,21 +1,9 @@
-               <BOUCLE_gis(GIS){id_gis ?}{id_rubrique ?}{id_article ?}{id_breve ?}{id_document ?}{id_mot ?}{id_auteur ?}{id_syndic ?}{recherche ?}{0, #ENV{limit}}{","}>
+               <BOUCLE_gis(GIS){id_gis ?}{objet ?}{id_objet ?}{id_rubrique ?}{id_article ?}{id_breve ?}{id_document ?}{id_mot ?}{id_auteur ?}{id_syndic ?}{recherche ?}{0, #ENV{limit}}{","}>
                {"type": "Feature",
-               "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},
+               ["geometry": (#GEOMETRY|appliquer_filtre{wkt_to_json}|sinon{{"type": "Point", "coordinates": \[#LON, #LAT\]}}),]
                "id":"#ID_GIS",
                "properties": {
-                       "title":[(#TITRE*|supprimer_numero|json_encode)],
-                       "description":[(#DESCRIPTIF|json_encode)][(#SET{logo_doc,''})]
-                       [(#LOGO_GIS|oui)
-                       [(#SET{logo_doc,#LOGO_GIS|image_passe_partout{32,32}|image_recadre{32,32}})]]
-                       [(#LOGO_GIS|non)
-                       [(#CHEMIN{images/marker_defaut.png}|oui)[
-                               (#SET{logo_doc,#CHEMIN{images/marker_defaut.png}|image_passe_partout{32,32}|image_recadre{32,32}})]
-                       ]]
-                       [(#GET{logo_doc}|oui)
-                       #SET{icon_w,#GET{logo_doc}|extraire_attribut{src}|largeur}
-                       #SET{icon_h,#GET{logo_doc}|extraire_attribut{src}|hauteur}
-                       [,"icon": (#GET{logo_doc}|extraire_attribut{src}|url_absolue|json_encode)],
-                       "icon_size": \[[(#GET{icon_w})],[(#GET{icon_h})]\],
-                       "icon_anchor": \[[(#GET{icon_w}|div{2})],[(#GET{icon_h})]\],
-                       "popup_anchor": \[1,[-(#GET{icon_h}|div{1.2})]\]]
-               }}</BOUCLE_gis>
\ No newline at end of file
+                       "title":[(#TITRE*|extraire_multi|supprimer_numero|json_encode)],
+                       "description":[(#DESCRIPTIF|json_encode)][
+                       (#LOGO_GIS|gis_icon_properties)]
+               }}</BOUCLE_gis>
index 52a5ac5..a50f389 100644 (file)
@@ -1,21 +1,9 @@
                <BOUCLE_art(ARTICLES){gis}{id_article ?}{id_rubrique ?}{id_secteur ?}{id_mot ?}{id_groupe ?}{id_auteur ?}{recherche ?}{0, #ENV{limit}}{","}>
                {"type": "Feature",
-               "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},
+               ["geometry": (#GEOMETRY|appliquer_filtre{wkt_to_json}|sinon{{"type": "Point", "coordinates": \[#LON, #LAT\]}}),]
                "id":"#ID_GIS",
                "properties": {
-                       "title":[(#TITRE_GIS*|sinon{#TITRE*}|supprimer_numero|json_encode)],
-                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][(#SET{logo_doc,''})]
-                       [(#LOGO_GIS|oui)
-                       [(#SET{logo_doc,#LOGO_GIS|image_passe_partout{32,32}|image_recadre{32,32}})]]
-                       [(#LOGO_GIS|non)
-                       [(#CHEMIN{images/marker_defaut.png}|oui)
-                               [(#SET{logo_doc,#CHEMIN{images/marker_defaut.png}|image_passe_partout{32,32}|image_recadre{32,32}})]
-                       ]]
-                       [(#GET{logo_doc}|oui)
-                       #SET{icon_w,#GET{logo_doc}|extraire_attribut{src}|largeur}
-                       #SET{icon_h,#GET{logo_doc}|extraire_attribut{src}|hauteur}
-                       [,"icon": (#GET{logo_doc}|extraire_attribut{src}|url_absolue|json_encode)],
-                       "icon_size": \[[(#GET{icon_w})],[(#GET{icon_h})]\],
-                       "icon_anchor": \[[(#GET{icon_w}|div{2})],[(#GET{icon_h})]\],
-                       "popup_anchor": \[1,[-(#GET{icon_h}|div{1.2})]\]]
+                       "title":[(#URL_ARTICLE|lien_ou_expose{[(#TITRE_GIS*|sinon{#TITRE*}|extraire_multi|supprimer_numero)]}|json_encode)],
+                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][
+                       (#LOGO_GIS|gis_icon_properties)]
                }}</BOUCLE_art>
\ No newline at end of file
index 71fcbc8..f988b03 100644 (file)
@@ -1,21 +1,9 @@
                <BOUCLE_art(ARTICLES){gis}{id_article ?}{branche ?}{id_mot ?}{id_groupe ?}{id_auteur ?}{recherche ?}{0, #ENV{limit}}{","}>
                {"type": "Feature",
-               "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},
+               ["geometry": (#GEOMETRY|appliquer_filtre{wkt_to_json}|sinon{{"type": "Point", "coordinates": \[#LON, #LAT\]}}),]
                "id":"#ID_GIS",
                "properties": {
-                       "title":[(#TITRE_GIS*|sinon{#TITRE*}|supprimer_numero|json_encode)],
-                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][(#SET{logo_doc,''})]
-                       [(#LOGO_GIS|oui)
-                       [(#SET{logo_doc,#LOGO_GIS|image_passe_partout{32,32}|image_recadre{32,32}})]]
-                       [(#LOGO_GIS|non)
-                       [(#CHEMIN{images/marker_defaut.png}|oui)
-                               [(#SET{logo_doc,#CHEMIN{images/marker_defaut.png}|image_passe_partout{32,32}|image_recadre{32,32}})]
-                       ]]
-                       [(#GET{logo_doc}|oui)
-                       #SET{icon_w,#GET{logo_doc}|extraire_attribut{src}|largeur}
-                       #SET{icon_h,#GET{logo_doc}|extraire_attribut{src}|hauteur}
-                       [,"icon": (#GET{logo_doc}|extraire_attribut{src}|url_absolue|json_encode)],
-                       "icon_size": \[[(#GET{icon_w})],[(#GET{icon_h})]\],
-                       "icon_anchor": \[[(#GET{icon_w}|div{2})],[(#GET{icon_h})]\],
-                       "popup_anchor": \[1,[-(#GET{icon_h}|div{1.2})]\]]
+                       "title":[(#URL_ARTICLE|lien_ou_expose{[(#TITRE_GIS*|sinon{#TITRE*}|extraire_multi|supprimer_numero)]}|json_encode)],
+                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][
+                       (#LOGO_GIS|gis_icon_properties)]
                }}</BOUCLE_art>
\ No newline at end of file
index c1c2edc..5a23c09 100644 (file)
@@ -1,21 +1,9 @@
-               <BOUCLE_auteurs(AUTEURS){gis}{id_article ?}{id_auteur ?}{recherche ?}{0, #ENV{limit}}{","}>
+               <BOUCLE_auteurs(AUTEURS){gis}{id_article ?}{id_auteur ?}{id_mot ?}{recherche ?}{0, #ENV{limit}}{","}>
                {"type": "Feature",
-               "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},
+               ["geometry": (#GEOMETRY|appliquer_filtre{wkt_to_json}|sinon{{"type": "Point", "coordinates": \[#LON, #LAT\]}}),]
                "id":"#ID_GIS",
                "properties": {
-                       "title":[(#TITRE_GIS*|sinon{#NOM*}|supprimer_numero|json_encode)],
-                       "description":[(#DESCRIPTIF_GIS|sinon{#BIO}|json_encode)][(#SET{logo_doc,''})]
-                       [(#LOGO_GIS|oui)
-                       [(#SET{logo_doc,#LOGO_GIS|image_passe_partout{32,32}|image_recadre{32,32}})]]
-                       [(#LOGO_GIS|non)
-                       [(#CHEMIN{images/marker_defaut.png}|oui)
-                               [(#SET{logo_doc,#CHEMIN{images/marker_defaut.png}|image_passe_partout{32,32}|image_recadre{32,32}})]
-                       ]]
-                       [(#GET{logo_doc}|oui)
-                       #SET{icon_w,#GET{logo_doc}|extraire_attribut{src}|largeur}
-                       #SET{icon_h,#GET{logo_doc}|extraire_attribut{src}|hauteur}
-                       [,"icon": (#GET{logo_doc}|extraire_attribut{src}|url_absolue|json_encode)],
-                       "icon_size": \[[(#GET{icon_w})],[(#GET{icon_h})]\],
-                       "icon_anchor": \[[(#GET{icon_w}|div{2})],[(#GET{icon_h})]\],
-                       "popup_anchor": \[1,[-(#GET{icon_h}|div{1.2})]\]]
+                       "title":[(#URL_AUTEUR|lien_ou_expose{[(#TITRE_GIS*|sinon{#NOM*}|extraire_multi|supprimer_numero)]}|json_encode)],
+                       "description":[(#DESCRIPTIF_GIS|sinon{#BIO}|json_encode)][
+                       (#LOGO_GIS|gis_icon_properties)]
                }}</BOUCLE_auteurs>
\ No newline at end of file
index 7c738d0..b2215c9 100644 (file)
@@ -1,21 +1,9 @@
                <BOUCLE_doc(DOCUMENTS){gis}{id_article ?}{id_rubrique ?}{id_secteur ?}{id_mot ?}{recherche ?}{media ?}{0, #ENV{limit}}{","}>
                {"type": "Feature",
-               "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},
+               ["geometry": (#GEOMETRY|appliquer_filtre{wkt_to_json}|sinon{{"type": "Point", "coordinates": \[#LON, #LAT\]}}),]
                "id":"#ID_GIS",
                "properties": {
-                       "title":[(#TITRE_GIS*|sinon{#TITRE*}|supprimer_numero|json_encode)],
-                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][(#SET{logo_doc,''})]
-                       [(#LOGO_GIS|oui)
-                       [(#SET{logo_doc,#LOGO_GIS|image_passe_partout{32,32}|image_recadre{32,32}})]]
-                       [(#LOGO_GIS|non)
-                       [(#CHEMIN{images/marker_defaut.png}|oui)
-                               [(#SET{logo_doc,#CHEMIN{images/marker_defaut.png}|image_passe_partout{32,32}|image_recadre{32,32}})]
-                       ]]
-                       [(#GET{logo_doc}|oui)
-                       #SET{icon_w,#GET{logo_doc}|extraire_attribut{src}|largeur}
-                       #SET{icon_h,#GET{logo_doc}|extraire_attribut{src}|hauteur}
-                       [,"icon": (#GET{logo_doc}|extraire_attribut{src}|url_absolue|json_encode)],
-                       "icon_size": \[[(#GET{icon_w})],[(#GET{icon_h})]\],
-                       "icon_anchor": \[[(#GET{icon_w}|div{2})],[(#GET{icon_h})]\],
-                       "popup_anchor": \[1,[-(#GET{icon_h}|div{1.2})]\]]
+                       "title":[(#URL_DOCUMENT|lien_ou_expose{[(#TITRE_GIS*|sinon{#TITRE*}|extraire_multi|supprimer_numero)]}|json_encode)],
+                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][
+                       (#LOGO_GIS|gis_icon_properties)]
                }}</BOUCLE_doc>
\ No newline at end of file
index ff10c5b..f058949 100644 (file)
@@ -1,21 +1,9 @@
                <BOUCLE_events(EVENEMENTS){gis}{id_evenement ?}{id_article ?}{id_rubrique ?}{id_secteur ?}{id_mot ?}{id_auteur ?}{recherche ?}{0, #ENV{limit}}{","}>
                {"type": "Feature",
-               "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},
+               ["geometry": (#GEOMETRY|appliquer_filtre{wkt_to_json}|sinon{{"type": "Point", "coordinates": \[#LON, #LAT\]}}),]
                "id":"#ID_GIS",
                "properties": {
-                       "title":[(#TITRE_GIS*|sinon{#TITRE*}|supprimer_numero|json_encode)],
-                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][(#SET{logo_doc,''})]
-                       [(#LOGO_GIS|oui)
-                       [(#SET{logo_doc,#LOGO_GIS|image_passe_partout{32,32}|image_recadre{32,32}})]]
-                       [(#LOGO_GIS|non)
-                       [(#CHEMIN{images/marker_defaut.png}|oui)
-                               [(#SET{logo_doc,#CHEMIN{images/marker_defaut.png}|image_passe_partout{32,32}|image_recadre{32,32}})]
-                       ]]
-                       [(#GET{logo_doc}|oui)
-                       #SET{icon_w,#GET{logo_doc}|extraire_attribut{src}|largeur}
-                       #SET{icon_h,#GET{logo_doc}|extraire_attribut{src}|hauteur}
-                       [,"icon": (#GET{logo_doc}|extraire_attribut{src}|url_absolue|json_encode)],
-                       "icon_size": \[[(#GET{icon_w})],[(#GET{icon_h})]\],
-                       "icon_anchor": \[[(#GET{icon_w}|div{2})],[(#GET{icon_h})]\],
-                       "popup_anchor": \[1,[-(#GET{icon_h}|div{1.2})]\]]
+                       "title":[(#URL_EVENEMENT|extraire_multi|lien_ou_expose{[(#TITRE_GIS*|sinon{#TITRE*}|supprimer_numero)]}|json_encode)],
+                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][
+                       (#LOGO_GIS|gis_icon_properties)]
                }}</BOUCLE_events>
index 18b308b..e1c9861 100644 (file)
@@ -1,21 +1,9 @@
                <BOUCLE_mots(MOTS){gis}{id_mot ?}{id_groupe ?}{id_article ?}{id_rubrique ?}{id_breve ?}{id_syndic ?}{id_forum ?}{recherche ?}{0, #ENV{limit}}{","}>
                {"type": "Feature",
-               "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},
+               ["geometry": (#GEOMETRY|appliquer_filtre{wkt_to_json}|sinon{{"type": "Point", "coordinates": \[#LON, #LAT\]}}),]
                "id":"#ID_GIS",
                "properties": {
-                       "title":[(#TITRE_GIS*|sinon{#TITRE*}|supprimer_numero|json_encode)],
-                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][(#SET{logo_doc,''})]
-                       [(#LOGO_GIS|oui)
-                       [(#SET{logo_doc,#LOGO_GIS|image_passe_partout{32,32}|image_recadre{32,32}})]]
-                       [(#LOGO_GIS|non)
-                       [(#CHEMIN{images/marker_defaut.png}|oui)
-                               [(#SET{logo_doc,#CHEMIN{images/marker_defaut.png}|image_passe_partout{32,32}|image_recadre{32,32}})]
-                       ]]
-                       [(#GET{logo_doc}|oui)
-                       #SET{icon_w,#GET{logo_doc}|extraire_attribut{src}|largeur}
-                       #SET{icon_h,#GET{logo_doc}|extraire_attribut{src}|hauteur}
-                       [,"icon": (#GET{logo_doc}|extraire_attribut{src}|url_absolue|json_encode)],
-                       "icon_size": \[[(#GET{icon_w})],[(#GET{icon_h})]\],
-                       "icon_anchor": \[[(#GET{icon_w}|div{2})],[(#GET{icon_h})]\],
-                       "popup_anchor": \[1,[-(#GET{icon_h}|div{1.2})]\]]
+                       "title":[(#URL_MOT|lien_ou_expose{[(#TITRE_GIS*|sinon{#TITRE*}|extraire_multi|supprimer_numero)]}|json_encode)],
+                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][
+                       (#LOGO_GIS|gis_icon_properties)]
                }}</BOUCLE_mots>
\ No newline at end of file
diff --git a/www/plugins/gis/json/gis_organisations.html b/www/plugins/gis/json/gis_organisations.html
new file mode 100644 (file)
index 0000000..d0dbbd9
--- /dev/null
@@ -0,0 +1,9 @@
+               <BOUCLE_organisations(ORGANISATIONS){gis}{id_organisation ?}{id_parent ?}{id_mot ?}{recherche ?}{0, #ENV{limit}}{","}>
+               {"type": "Feature",
+               ["geometry": (#GEOMETRY|appliquer_filtre{wkt_to_json}|sinon{{"type": "Point", "coordinates": \[#LON, #LAT\]}}),]
+               "id":"#ID_GIS",
+               "properties": {
+                       "title":[(#TITRE_GIS*|sinon{#NOM*}|extraire_multi|supprimer_numero|json_encode)],
+                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][
+                       (#LOGO_GIS|gis_icon_properties)]
+               }}</BOUCLE_organisations>
index 96edf86..8e5c3fa 100644 (file)
@@ -3,17 +3,8 @@
        "geometry": {"type": "Point", "coordinates": [[(#ENV{lon})], [(#ENV{lat})]]},
        "id":"1",
        "properties": {
-               "title":[(#ENV{titre}|json_encode)],
-               "description":[(#ENV{description}|wrap{<p>}|json_encode)]
-               #SET{icone, #CHEMIN_IMAGE{#ENV*{icone,0}}|sinon{#CHEMIN{#ENV*{icone,0}}}}
-               #SET{icone_defaut, #CHEMIN{images/marker_defaut.png}}
-               #SET{logo_doc,#GET{icone}|sinon{#GET{icone_defaut}}|image_passe_partout{32,32}|image_recadre{32,32,center}|extraire_attribut{src}|url_absolue}
-               [(#GET{logo_doc}|oui)
-               #SET{icon_w,#GET{logo_doc}|extraire_attribut{src}|largeur}
-               #SET{icon_h,#GET{logo_doc}|extraire_attribut{src}|hauteur}
-               [,"icon": (#GET{logo_doc}|extraire_attribut{src}|url_absolue|json_encode)],
-               "icon_size": \[[(#GET{icon_w})],[(#GET{icon_h})]\],
-               "icon_anchor": \[[(#GET{icon_w}|div{2})],[(#GET{icon_h})]\],
-               "popup_anchor": \[1,[-(#GET{icon_h}|div{1.2})]\]]
+               "title":[(#ENV{titre}|?{#ENV{titre},''}|json_encode)],
+               "description":[(#ENV{description}|?{#ENV{description}|wrap{<p>},''}|json_encode)][
+               (#CHEMIN_IMAGE{#ENV*{icone,0}}|sinon{#CHEMIN{#ENV*{icone,0}}}|gis_icon_properties)]
        }
 }
index 0c88fd1..dc14b2d 100644 (file)
@@ -1,21 +1,9 @@
                <BOUCLE_rub(RUBRIQUES){gis}{id_rubrique ?}{id_secteur ?}{id_parent?=#ENV{id_parent}}{id_mot ?}{recherche ?}{0, #ENV{limit}}{","}>
                {"type": "Feature",
-               "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},
+               ["geometry": (#GEOMETRY|appliquer_filtre{wkt_to_json}|sinon{{"type": "Point", "coordinates": \[#LON, #LAT\]}}),]
                "id":"#ID_GIS",
                "properties": {
-                       "title":[(#TITRE_GIS*|sinon{#TITRE*}|supprimer_numero|json_encode)],
-                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][(#SET{logo_doc,''})]
-                       [(#LOGO_GIS|oui)
-                       [(#SET{logo_doc,#LOGO_GIS|image_passe_partout{32,32}|image_recadre{32,32}})]]
-                       [(#LOGO_GIS|non)
-                       [(#CHEMIN{images/marker_defaut.png}|oui)
-                               [(#SET{logo_doc,#CHEMIN{images/marker_defaut.png}|image_passe_partout{32,32}|image_recadre{32,32}})]
-                       ]]
-                       [(#GET{logo_doc}|oui)
-                       #SET{icon_w,#GET{logo_doc}|extraire_attribut{src}|largeur}
-                       #SET{icon_h,#GET{logo_doc}|extraire_attribut{src}|hauteur}
-                       [,"icon": (#GET{logo_doc}|extraire_attribut{src}|url_absolue|json_encode)],
-                       "icon_size": \[[(#GET{icon_w})],[(#GET{icon_h})]\],
-                       "icon_anchor": \[[(#GET{icon_w}|div{2})],[(#GET{icon_h})]\],
-                       "popup_anchor": \[1,[-(#GET{icon_h}|div{1.2})]\]]
+                       "title":[(#URL_RUBRIQUE|extraire_multi|lien_ou_expose{[(#TITRE_GIS*|sinon{#TITRE*}|supprimer_numero)]}|json_encode)],
+                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][
+                       (#LOGO_GIS|gis_icon_properties)]
                }}</BOUCLE_rub>
\ No newline at end of file
index 0de784f..a43b62f 100644 (file)
@@ -1,21 +1,9 @@
                <BOUCLE_sit(SITES){gis}{id_syndic ?}{id_rubrique ?}{id_secteur ?}{id_mot ?}{recherche ?}{0, #ENV{limit}}{","}>
                {"type": "Feature",
-               "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},
+               ["geometry": (#GEOMETRY|appliquer_filtre{wkt_to_json}|sinon{{"type": "Point", "coordinates": \[#LON, #LAT\]}}),]
                "id":"#ID_GIS",
                "properties": {
-                       "title":[(#TITRE_GIS*|sinon{#NOM_SITE*}|supprimer_numero|json_encode)],
-                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][(#SET{logo_doc,''})]
-                       [(#LOGO_GIS|oui)
-                       [(#SET{logo_doc,#LOGO_GIS|image_passe_partout{32,32}|image_recadre{32,32}})]]
-                       [(#LOGO_GIS|non)
-                       [(#CHEMIN{images/marker_defaut.png}|oui)
-                               [(#SET{logo_doc,#CHEMIN{images/marker_defaut.png}|image_passe_partout{32,32}|image_recadre{32,32}})]
-                       ]]
-                       [(#GET{logo_doc}|oui)
-                       #SET{icon_w,#GET{logo_doc}|extraire_attribut{src}|largeur}
-                       #SET{icon_h,#GET{logo_doc}|extraire_attribut{src}|hauteur}
-                       [,"icon": (#GET{logo_doc}|extraire_attribut{src}|url_absolue|json_encode)],
-                       "icon_size": \[[(#GET{icon_w})],[(#GET{icon_h})]\],
-                       "icon_anchor": \[[(#GET{icon_w}|div{2})],[(#GET{icon_h})]\],
-                       "popup_anchor": \[1,[-(#GET{icon_h}|div{1.2})]\]]
+                       "title":[(#ID_SYNDIC|generer_url_entite{site}|lien_ou_expose{[(#TITRE_GIS*|sinon{#NOM_SITE*}|extraire_multi|supprimer_numero)]}|json_encode)],
+                       "description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)][
+                       (#LOGO_GIS|gis_icon_properties)]
                }}</BOUCLE_sit>
\ No newline at end of file
index b9c3d50..bceae48 100644 (file)
@@ -1,21 +1,9 @@
                <BOUCLE_gis(GIS){id_gis ?}{id_rubrique ?}{id_article ?}{id_breve ?}{id_document ?}{id_mot ?}{id_auteur ?}{id_syndic ?}{recherche ?}{0, #ENV{limit}}{","}>
                {"type": "Feature",
-               "geometry": {"type": "Point", "coordinates": [#LON, #LAT]},
+               ["geometry": (#GEOMETRY|appliquer_filtre{wkt_to_json}|sinon{{"type": "Point", "coordinates": \[#LON, #LAT\]}}),]
                "id":"#ID_GIS",
                "properties": {
-                       "title":[(#VAL{<a href='[(#ID_GIS|generer_url_entite{gis,'','',0})]'>[(#TITRE*|supprimer_numero|sinon{----})]</a>}|json_encode)],
-                       "description":[(#DESCRIPTIF|json_encode)][(#SET{logo_doc,''})]
-                       [(#LOGO_GIS|oui)
-                       [(#SET{logo_doc,#LOGO_GIS|image_passe_partout{32,32}|image_recadre{32,32}})]]
-                       [(#LOGO_GIS|non)
-                       [(#CHEMIN{images/marker_defaut.png}|oui)[
-                               (#SET{logo_doc,#CHEMIN{images/marker_defaut.png}|image_passe_partout{32,32}|image_recadre{32,32}})]
-                       ]]
-                       [(#GET{logo_doc}|oui)
-                       #SET{icon_w,#GET{logo_doc}|extraire_attribut{src}|largeur}
-                       #SET{icon_h,#GET{logo_doc}|extraire_attribut{src}|hauteur}
-                       [,"icon": (#GET{logo_doc}|extraire_attribut{src}|url_absolue|json_encode)],
-                       "icon_size": \[[(#GET{icon_w})],[(#GET{icon_h})]\],
-                       "icon_anchor": \[[(#GET{icon_w}|div{2})],[(#GET{icon_h})]\],
-                       "popup_anchor": \[1,[-(#GET{icon_h}|div{1.2})]\]]
+                       "title":[(#VAL{<a href='[(#ID_GIS|generer_url_entite{gis,'','',0})]'>[(#TITRE*|extraire_multi|supprimer_numero|sinon{----})]</a>}|json_encode)],
+                       "description":[(#DESCRIPTIF|json_encode)][(#SET{logo_doc,''})][
+                       (#LOGO_GIS|gis_icon_properties)]
                }}</BOUCLE_gis>
index 5ab9a60..42f4723 100644 (file)
@@ -1,16 +1,30 @@
 <traduction module="gis" gestionnaire="salvatore" url="http://trad.spip.net" source="svn://zone.spip.org/spip-zone/_plugins_/gis/trunk/lang/" reference="fr">
-       <langue code="en" url="http://trad.spip.net/tradlang_module/gis?lang_cible=en">
+       <langue code="de" url="http://trad.spip.net/tradlang_module/gis?lang_cible=de" total="140" traduits="140" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Torsten Willmann" lien="http://trad.spip.net/auteur/torsten-willmann" />
+       </langue>
+       <langue code="en" url="http://trad.spip.net/tradlang_module/gis?lang_cible=en" total="140" traduits="140" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="b_b" lien="http://trad.spip.net/auteur/b_b" />
                <traducteur nom="Benitron" lien="http://trad.spip.net/auteur/benitron" />
+               <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
                <traducteur nom="kent1" lien="http://trad.spip.net/auteur/kent1" />
        </langue>
-       <langue code="es" url="http://trad.spip.net/tradlang_module/gis?lang_cible=es">
-               <traducteur nom="Raquel S. Bujaldón" lien="http://trad.spip.net/auteur/raquel" />
+       <langue code="es" url="http://trad.spip.net/tradlang_module/gis?lang_cible=es" total="140" traduits="140" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="alf" lien="http://trad.spip.net/auteur/alf" />
+               <traducteur nom="b_b" lien="http://trad.spip.net/auteur/b_b" />
+               <traducteur nom="Raquel S. Bujaldón" lien="http://trad.spip.net/auteur/raquel-s-bujaldon" />
+               <traducteur nom="severo" lien="http://trad.spip.net/auteur/severo" />
+       </langue>
+       <langue code="fr" url="http://trad.spip.net/tradlang_module/gis?lang_cible=fr" total="140" traduits="140" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       </langue>
+       <langue code="nl" url="http://trad.spip.net/tradlang_module/gis?lang_cible=nl" total="140" traduits="140" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="b_b" lien="http://trad.spip.net/auteur/b_b" />
+               <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
        </langue>
-       <langue code="fr" />
-       <langue code="ru" url="http://trad.spip.net/tradlang_module/gis?lang_cible=ru">
+       <langue code="ru" url="http://trad.spip.net/tradlang_module/gis?lang_cible=ru" total="140" traduits="122" relire="0" modifs="2" nouveaux="16" pourcent="87.14">
                <traducteur nom="Serge Markitanenko" lien="http://trad.spip.net/auteur/serge-markitanenko" />
        </langue>
-       <langue code="sk" url="http://trad.spip.net/tradlang_module/gis?lang_cible=sk">
+       <langue code="sk" url="http://trad.spip.net/tradlang_module/gis?lang_cible=sk" total="140" traduits="139" relire="0" modifs="1" nouveaux="0" pourcent="99.29">
+               <traducteur nom="b_b" lien="http://trad.spip.net/auteur/b_b" />
                <traducteur nom="jaro" lien="http://trad.spip.net/auteur/jaro" />
        </langue>
 </traduction>
diff --git a/www/plugins/gis/lang/gis_de.php b/www/plugins/gis/lang/gis_de.php
new file mode 100644 (file)
index 0000000..73e67fc
--- /dev/null
@@ -0,0 +1,173 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/gis?lang_cible=de
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucun_gis' => 'Kein Punkt',
+       'aucun_objet' => 'Kein Objekt',
+
+       // B
+       'bouton_lier' => 'Diesen Punkt verknüpfen',
+       'bouton_supprimer_gis' => 'Diesen Punkt endgültig löschen',
+       'bouton_supprimer_lien' => 'Diese Verknüpfung löschen',
+
+       // C
+       'cfg_descr_gis' => 'Geoinformationssystem<br /><a href="http://contrib.spip.net/4189" class="spip_out">zur Dokumentation</a>.',
+       'cfg_inf_adresse' => 'Adressfelder anzeigen (Land, Satdt, Region, Adresse ...)',
+       'cfg_inf_bing' => 'Der Layer Bing Aerial benötigt einen Schlüssel zu erzeugen auf der  <a href=\'@url@\' class="spip_out">Bing Website</a>.',
+       'cfg_inf_cloudmade' => 'Diese API benötigt einen Schlüssel. Zu erzeugen auf der <a href=\'@url@\' class="spip_out">CloudMade Website</a>.',
+       'cfg_inf_geocoder' => 'Geocoding aktivieren (Suche nach einer Adresse, Anzeige von Adressen zu den Koordinaten).',
+       'cfg_inf_geolocaliser_user_html5' => 'Wenn es der Browser des Nutzers erlaubt, wird sein ungefährer Standort als Voreinstellung für die Position von Punkten genommen.',
+       'cfg_inf_google' => 'Diese API benötigt einen Schlüssel. Zu erzeugen auf der <a href=\'@url@\' class="spip_out">le GoogleMaps Seite </a>.',
+       'cfg_inf_yandex' => 'Diese API benötigt einen Schlüssel. Zu erzeugen auf der <a href=\'@url@\' class="spip_out">Yandex Website</a>.',
+       'cfg_lbl_activer_objets' => 'Geotargeting für folgende Inhalte aktivieren:',
+       'cfg_lbl_adresse' => 'Adressfelder anzeigen',
+       'cfg_lbl_api' => 'Karten-API ',
+       'cfg_lbl_api_cloudmade' => 'CloudMade',
+       'cfg_lbl_api_google' => 'Google Maps v2',
+       'cfg_lbl_api_googlev3' => 'Google Maps v3',
+       'cfg_lbl_api_key_bing' => 'Bing Schlüssel',
+       'cfg_lbl_api_key_cloudmade' => 'CloudMade Schlüssel',
+       'cfg_lbl_api_key_google' => 'GoogleMaps Schlüssel',
+       'cfg_lbl_api_key_yandex' => 'Yandex Schlüssel',
+       'cfg_lbl_api_mapquest' => 'MapQuest',
+       'cfg_lbl_api_microsoft' => 'Microsoft Bing',
+       'cfg_lbl_api_openlayers' => 'OpenLayers',
+       'cfg_lbl_api_ovi' => 'Ovi Nokia',
+       'cfg_lbl_api_yandex' => 'Yandex',
+       'cfg_lbl_geocoder' => 'Geocoder',
+       'cfg_lbl_geolocaliser_user_html5' => 'Karte beim Anlegen auf den Standort des Nutzers zentrieren.',
+       'cfg_lbl_layer_defaut' => 'Standardlayer',
+       'cfg_lbl_layers' => 'Vorgeschlagene Layer',
+       'cfg_lbl_maptype' => 'Kartentyp',
+       'cfg_lbl_maptype_carte' => 'Karte',
+       'cfg_lbl_maptype_hybride' => 'Hybrid',
+       'cfg_lbl_maptype_relief' => 'Relief',
+       'cfg_lbl_maptype_satellite' => 'Satellit',
+       'cfg_titre_gis' => 'GIS Einstellungen',
+
+       // E
+       'editer_gis_editer' => 'Punkt bearbeiten',
+       'editer_gis_nouveau' => 'Neuen Punkt anlegen',
+       'editer_gis_titre' => 'GIS Punkte',
+       'erreur_geocoder' => 'Ihre Suche ergab kein Ergebnis:',
+       'erreur_recherche_pas_resultats' => 'Kein Punkt passt zu Ihrer Suche.',
+       'erreur_xmlrpc_lat_lon' => 'Länge und Breite müssen als Argumente übergeben werden.',
+       'explication_api_forcee' => 'Die API wird durch ein Plugin oder Skelett erzwungen.',
+       'explication_import' => 'Eine Datei im GPX oder KML - Format importieren.',
+       'explication_layer_forcee' => 'Der Layer wird durch ein anderes Plugin oder Skelett erzwungen.',
+       'explication_maptype_force' => 'Der Kartentyp wird durch ein anderes Plugin oder Skelett erzwungen.',
+
+       // F
+       'formulaire_creer_gis' => 'GIS-Punkt anlegen:',
+       'formulaire_modifier_gis' => 'Diesen GIS-Punkt bearbeiten:',
+
+       // G
+       'gis_pluriel' => 'GIS-Punkte',
+       'gis_singulier' => 'GIS Punkt',
+
+       // I
+       'icone_gis_tous' => 'GIS Punkte',
+       'info_1_gis' => 'Ein GIS Punkt',
+       'info_1_objet_gis' => '1 verknüpftes Objekt zu diesem Punkt',
+       'info_aucun_gis' => 'Kein GIS Punkt',
+       'info_aucun_objet_gis' => 'Kein verknüpftes Objekt zu diesem Punkt',
+       'info_geolocalisation' => 'Geolokalisation',
+       'info_id_objet' => 'Nr',
+       'info_liste_gis' => 'GIS Punkte',
+       'info_nb_gis' => '@nb@ GIS Punkte',
+       'info_nb_objets_gis' => '@nb@ verknüpfte Objekte zu diesem Punkt',
+       'info_numero_gis' => 'Punkt Nummer',
+       'info_objet' => 'Objekt',
+       'info_recherche_gis_zero' => 'Kein Resultat für « @cherche_gis@ ».',
+       'info_supprimer_lien' => 'Entfernen',
+       'info_supprimer_liens' => 'Alle Punkte entfernen',
+       'info_voir_fiche_objet' => 'Seite anzeigen',
+
+       // L
+       'label_adress' => 'Adresse',
+       'label_code_pays' => 'Länderkürzel',
+       'label_code_postal' => 'Postleitzahl',
+       'label_departement' => 'Bundesland',
+       'label_import' => 'Importieren',
+       'label_inserer_modele_articles' => 'verknüpft mit Artikeln',
+       'label_inserer_modele_articles_sites' => 'verknüpft mit Artikeln und Websites',
+       'label_inserer_modele_auteurs' => 'verknüpft mit Autoren',
+       'label_inserer_modele_centrer_auto' => 'Nicht automatisch Zentrieren',
+       'label_inserer_modele_centrer_fichier' => 'Karte nicht auf KLM/GPX-Dateien zentrieren',
+       'label_inserer_modele_controle' => 'Bedienelemente verbergen',
+       'label_inserer_modele_controle_type' => 'Typen verbergen',
+       'label_inserer_modele_description' => 'Beschreibung',
+       'label_inserer_modele_documents' => 'verknüpft mit Dokumenten',
+       'label_inserer_modele_echelle' => 'Maßstab',
+       'label_inserer_modele_fullscreen' => 'Gesamter Bildschirm Button',
+       'label_inserer_modele_gpx' => 'GPX Datei Overlay',
+       'label_inserer_modele_hauteur_carte' => 'Höhe der Karte',
+       'label_inserer_modele_identifiant' => 'ID',
+       'label_inserer_modele_identifiant_opt' => 'ID (optional)',
+       'label_inserer_modele_identifiant_placeholder' => 'id_gis',
+       'label_inserer_modele_kml' => 'KML-Datei als Overlay',
+       'label_inserer_modele_kml_gpx' => 'id_document oder URL',
+       'label_inserer_modele_largeur_carte' => 'Breite der Karte',
+       'label_inserer_modele_limite' => 'Maximale Anzahl Punkte',
+       'label_inserer_modele_localiser_visiteur' => 'Auf den Besucher zentrieren',
+       'label_inserer_modele_mini_carte' => 'Mini-Karte der Lage',
+       'label_inserer_modele_molette' => 'Scrollrad desaktovieren',
+       'label_inserer_modele_mots' => 'verknüpft mit Schlagwörtern',
+       'label_inserer_modele_objets' => 'Punktekategorie',
+       'label_inserer_modele_point_gis' => 'Einzelpunkt angelegt',
+       'label_inserer_modele_point_libre' => 'Einzelpunkt',
+       'label_inserer_modele_points' => 'Punkte verbergen',
+       'label_inserer_modele_rubriques' => 'verknüpft mit Rubriken',
+       'label_inserer_modele_sites' => 'verknüpft mit Websites',
+       'label_inserer_modele_titre_carte' => 'Titel der Karte',
+       'label_pays' => 'Land',
+       'label_rechercher_address' => 'Adresse suchen',
+       'label_rechercher_point' => 'Punkt suchen',
+       'label_region' => 'Region',
+       'label_ville' => 'Stadt',
+       'lat' => 'Breite',
+       'libelle_logo_gis' => 'LOGO DES PUNKTS',
+       'lien_ajouter_gis' => 'Deisen Punkt hinzufügen',
+       'lon' => 'Länge',
+
+       // T
+       'telecharger_gis' => 'Im Format @format@ herunterladen',
+       'texte_ajouter_gis' => 'GIS Punkt hinzufügen',
+       'texte_creer_associer_gis' => 'GIS Punkt anlegen und hinzufügen',
+       'texte_creer_gis' => 'GIS Punkt anlegen',
+       'texte_modifier_gis' => 'Diesen GIS Punkt bearbeiten',
+       'texte_voir_gis' => 'GIS Punkt anzeigen',
+       'titre_bloc_creer_point' => 'Neuen Punkt verknüpfen',
+       'titre_bloc_points_lies' => 'Verknüpfte Punkte',
+       'titre_bloc_rechercher_point' => 'Punkt suchen',
+       'titre_nombre_utilisation' => 'Eine Verwendung',
+       'titre_nombre_utilisations' => '@nb@ Verwendungen',
+       'titre_nouveau_point' => 'Neuer Punkt',
+       'titre_objet' => 'Ttiel',
+       'toolbar_actions_title' => 'Zeichnen abbrechen',
+       'toolbar_buttons_marker' => 'Einen Punkt zeichnen',
+       'toolbar_buttons_polygon' => 'Ein Polygon zeichnen',
+       'toolbar_buttons_polyline' => 'Eine Linie zeichnen',
+       'toolbar_handlers_marker_tooltip_start' => 'Klicken um Marker zu setzen',
+       'toolbar_handlers_polygon_tooltip_cont' => 'Klicken um das Polygonzeichnen   fortzusetzen',
+       'toolbar_handlers_polygon_tooltip_end' => 'Auf den ersten Punkt klicken um das Polygon abzuschliessen',
+       'toolbar_handlers_polygon_tooltip_start' => 'Klicken um mit dem Polygon zu beginnen',
+       'toolbar_handlers_polyline_tooltip_cont' => 'Klicken um die Linie fortzusetzen',
+       'toolbar_handlers_polyline_tooltip_end' => 'Letzten Punkt klicken um Linie abzuschließen',
+       'toolbar_handlers_polyline_tooltip_start' => 'Klicken um Linie zu beginnen',
+       'toolbar_undo_text' => 'Letzten Punkt löschen',
+       'toolbar_undo_title' => 'Letzten Punkt löschen',
+
+       // Z
+       'zoom' => 'Zoom'
+);
+
+?>
index 71c3699..448ca41 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/gis?lang_cible=en
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
@@ -17,12 +19,12 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'bouton_supprimer_lien' => 'Remove this link',
 
        // C
-       'cfg_descr_gis' => 'Geographic Information System.<br /><a href="http://www.spip-contrib.net/3887" class="spip_out">Link to the documentation</a>.',
+       'cfg_descr_gis' => 'Geographic Information System.<br /><a href="http://contrib.spip.net/4189" class="spip_out">Link to the documentation</a>.',
        'cfg_inf_adresse' => 'Displays additional address fields (country, city, state, address ...)',
        'cfg_inf_bing' => 'The Bing Aerial layer needs a key you can create on <a href=\'@url@\' class="spip_out">the Bing website</a>.',
        'cfg_inf_cloudmade' => 'This API needs a key you can create on <a href=\'@url@\' class="spip_out">the CloudMade website</a>.',
        'cfg_inf_geocoder' => 'Enable geocoder functions (search from an address, recovery of the address from the coordinates).',
-       'cfg_inf_geolocaliser_user_html5' => 'If the user\'s browser allows it, its approximate geographic location is retrieved to give the default position when creating a new point.',
+       'cfg_inf_geolocaliser_user_html5' => 'If the users browser allows it, its approximate geographic location is retrieved to give the default position when creating a new point.',
        'cfg_inf_google' => 'This API needs a key you can create on <a href=\'@url@\' class="spip_out">the GoogleMaps website</a>.',
        'cfg_inf_yandex' => 'This API needs a key you can create on <a href=\'@url@\' class="spip_out">the yandex website</a>.',
        'cfg_lbl_activer_objets' => 'Enable geotagging of content:',
@@ -49,13 +51,13 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'cfg_lbl_maptype_hybride' => 'Hybrid',
        'cfg_lbl_maptype_relief' => 'Relief',
        'cfg_lbl_maptype_satellite' => 'Satellite',
-       'cfg_titre_gis' => 'GIS',
+       'cfg_titre_gis' => 'GIS configuration',
 
        // E
        'editer_gis_editer' => 'Edit this point',
-       'editer_gis_explication' => 'This page lists the whole location-based points of the website.',
        'editer_gis_nouveau' => 'Create a new point',
        'editer_gis_titre' => 'The location-based points',
+       'erreur_geocoder' => 'No results for your search:',
        'erreur_recherche_pas_resultats' => 'No point corresponds to the searched text.',
        'erreur_xmlrpc_lat_lon' => 'Latitude and longitude should be set as arguments',
        'explication_api_forcee' => 'The is imposed by another plugin or skeleton.',
@@ -64,8 +66,8 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'explication_maptype_force' => 'The base map is imposed by another plugin or skeleton.',
 
        // F
-       'formulaire_creer_gis' => 'Create a new location-based point :',
-       'formulaire_modifier_gis' => 'Modify the location-based point :',
+       'formulaire_creer_gis' => 'Create a new location-based point:',
+       'formulaire_modifier_gis' => 'Modify the location-based point:',
 
        // G
        'gis_pluriel' => 'Location-based points',
@@ -91,7 +93,9 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
 
        // L
        'label_adress' => 'Address',
+       'label_code_pays' => 'Country code',
        'label_code_postal' => 'Postal code',
+       'label_departement' => 'Department',
        'label_import' => 'Import',
        'label_inserer_modele_articles' => 'linked to articles',
        'label_inserer_modele_articles_sites' => 'linked to articles + websites',
@@ -130,7 +134,7 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'label_region' => 'Region',
        'label_ville' => 'Town',
        'lat' => 'Latitude',
-       'libelle_logo_gis' => 'POINT\\\'S LOGO',
+       'libelle_logo_gis' => 'POINT\\S LOGO',
        'lien_ajouter_gis' => 'Add this point',
        'lon' => 'Longitude',
 
@@ -148,6 +152,19 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'titre_nombre_utilisations' => '@nb@ uses',
        'titre_nouveau_point' => 'New point',
        'titre_objet' => 'Title',
+       'toolbar_actions_title' => 'Cancel the drawing',
+       'toolbar_buttons_marker' => 'Plot a point',
+       'toolbar_buttons_polygon' => 'Draw a polygon',
+       'toolbar_buttons_polyline' => 'Draw a line',
+       'toolbar_handlers_marker_tooltip_start' => 'Click to set marker',
+       'toolbar_handlers_polygon_tooltip_cont' => 'Click to continue drawing the polygon',
+       'toolbar_handlers_polygon_tooltip_end' => 'Click the first point to close the polygon',
+       'toolbar_handlers_polygon_tooltip_start' => 'Click to start drawing the polygon',
+       'toolbar_handlers_polyline_tooltip_cont' => 'Click to continue to draw the line',
+       'toolbar_handlers_polyline_tooltip_end' => 'Click the last point to end line',
+       'toolbar_handlers_polyline_tooltip_start' => 'Click to start drawing the line',
+       'toolbar_undo_text' => 'Delete last point',
+       'toolbar_undo_title' => 'Delete last point drawn',
 
        // Z
        'zoom' => 'Zoom'
index 020bc3d..cbdfaa1 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/gis?lang_cible=es
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
@@ -17,7 +19,7 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'bouton_supprimer_lien' => 'Eliminar este enlace',
 
        // C
-       'cfg_descr_gis' => 'Sistema de Información Geográfica.<br /><a href="http://www.spip-contrib.net/3887">Ir a la documentación</a>.',
+       'cfg_descr_gis' => 'Sistema de Información Geográfica.<br /><a href="http://contrib.spip.net/4189" class="spip_out">Ir a la documentación</a>.',
        'cfg_inf_adresse' => 'Mostrar campos adicionales de dirección (país, ciudad, región, dirección...)',
        'cfg_inf_bing' => 'La capa Bing Aerial necesita crear una clave <a href=\'@url@\' class="spip_out">el sitio de Bing</a>.',
        'cfg_inf_cloudmade' => 'Esta API necesita crear una clave en <a href=\'@url@\'>el sitio de CloudMade</a>.',
@@ -49,13 +51,13 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'cfg_lbl_maptype_hybride' => 'Híbrido',
        'cfg_lbl_maptype_relief' => 'Relieve',
        'cfg_lbl_maptype_satellite' => 'Satélite',
-       'cfg_titre_gis' => 'GIS',
+       'cfg_titre_gis' => 'configuración de GIS',
 
        // E
        'editer_gis_editer' => 'Modificar este punto',
-       'editer_gis_explication' => 'Esta página lista todos los puntos geolocalizados del sitio.',
        'editer_gis_nouveau' => 'Crear un nuevo punto',
        'editer_gis_titre' => 'Puntos geolocalizados',
+       'erreur_geocoder' => 'Ningún resultado para su búsqueda:',
        'erreur_recherche_pas_resultats' => 'Ningún punto corresponde a la búsqueda.',
        'erreur_xmlrpc_lat_lon' => 'La latitud y la longitud deben indicarse como parámetros',
        'explication_api_forcee' => 'La API esta impuesta por otro plugin o esqueleto.',
@@ -91,7 +93,9 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
 
        // L
        'label_adress' => 'Dirección',
+       'label_code_pays' => 'Código país',
        'label_code_postal' => 'Código postal',
+       'label_departement' => 'Departamento',
        'label_import' => 'Importar',
        'label_inserer_modele_articles' => 'asociados a los artículos',
        'label_inserer_modele_articles_sites' => 'asociados a los artículos + sitios',
@@ -148,6 +152,19 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'titre_nombre_utilisations' => '@nb@ utilizaciones',
        'titre_nouveau_point' => 'Nuevo punto',
        'titre_objet' => 'Título',
+       'toolbar_actions_title' => 'Cancelar el trazado',
+       'toolbar_buttons_marker' => 'Trazar un punto',
+       'toolbar_buttons_polygon' => 'Trazar un polígono',
+       'toolbar_buttons_polyline' => 'Trazar una línea',
+       'toolbar_handlers_marker_tooltip_start' => 'Haga clic para situar el marcador',
+       'toolbar_handlers_polygon_tooltip_cont' => 'Haga clic para continuar trazando el polígono',
+       'toolbar_handlers_polygon_tooltip_end' => 'Haga clic sobre el primer punto para cerrar el polígono',
+       'toolbar_handlers_polygon_tooltip_start' => 'Haga clic para comenzar a trazar el polígono',
+       'toolbar_handlers_polyline_tooltip_cont' => 'Haga clic para continuar trazando la línea',
+       'toolbar_handlers_polyline_tooltip_end' => 'Haga clic sobre el último punto para terminar la línea',
+       'toolbar_handlers_polyline_tooltip_start' => 'Haga clic para comenzar a trazar la línea',
+       'toolbar_undo_text' => 'Borrar el última punto',
+       'toolbar_undo_title' => 'Borrar el último punto dibujado',
 
        // Z
        'zoom' => 'Zoom'
index 70e25c0..dbc04ec 100644 (file)
@@ -1,7 +1,9 @@
 <?php
 // This is a SPIP language file  --  Ceci est un fichier langue de SPIP
 // Fichier source, a modifier dans svn://zone.spip.org/spip-zone/_plugins_/gis/trunk/lang/
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
@@ -15,16 +17,16 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'bouton_supprimer_lien' => 'Supprimer ce lien',
 
        // C
-       'cfg_descr_gis' => 'Système d\'Information Géographique.<br /><a href="http://www.spip-contrib.net/3887" class="spip_out">Accéder la documentation</a>.',
-       'cfg_inf_adresse' => 'Affiche des champs supplémentaires d\'adresse (pays, ville, région, adresse...)',
+       'cfg_descr_gis' => 'Système d’Information Géographique.<br /><a href="http://contrib.spip.net/4189" class="spip_out">Accéder la documentation</a>.',
+       'cfg_inf_adresse' => 'Affiche des champs supplémentaires dadresse (pays, ville, région, adresse...)',
        'cfg_inf_bing' => 'La couche Bing Aerial nécessite une clé à créer sur <a href=\'@url@\' class="spip_out">le site de Bing</a>.',
        'cfg_inf_cloudmade' => 'Cette API nécessite une clé à créer sur <a href=\'@url@\' class="spip_out">le site de CloudMade</a>.',
-       'cfg_inf_geocoder' => 'Activer les fonctions du geocoder (recherche à partir d\'une adresse, récupération de l\'adresse à partir des coordonnées).',
-       'cfg_inf_geolocaliser_user_html5' => 'Si le navigateur de l\'utilisateur le permet, son emplacement géographique approximatif est récupéré pour donner la position par défaut lors de la création d\'un point.',
+       'cfg_inf_geocoder' => 'Activer les fonctions du geocoder (recherche à partir d’une adresse, récupération de l’adresse à partir des coordonnées).',
+       'cfg_inf_geolocaliser_user_html5' => 'Si le navigateur de l’utilisateur le permet, son emplacement géographique approximatif est récupéré pour donner la position par défaut lors de la création d’un point.',
        'cfg_inf_google' => 'Cette API nécessite une clé à créer sur <a href=\'@url@\' class="spip_out">le site de GoogleMaps</a>.',
        'cfg_inf_yandex' => 'Cette API nécessite une clé à créer sur <a href=\'@url@\' class="spip_out">le site de Yandex</a>.',
-       'cfg_lbl_activer_objets' => 'Activer la géolocalisation sur les contenus :',
-       'cfg_lbl_adresse' => 'Afficher les champs d\'adresse',
+       'cfg_lbl_activer_objets' => 'Activer la géolocalisation sur les contenus :',
+       'cfg_lbl_adresse' => 'Afficher les champs dadresse',
        'cfg_lbl_api' => 'API de cartographie',
        'cfg_lbl_api_cloudmade' => 'CloudMade',
        'cfg_lbl_api_google' => 'Google Maps v2',
@@ -39,7 +41,7 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'cfg_lbl_api_ovi' => 'Ovi Nokia',
        'cfg_lbl_api_yandex' => 'Yandex',
        'cfg_lbl_geocoder' => 'Geocoder',
-       'cfg_lbl_geolocaliser_user_html5' => 'Centrer la carte sur l\'emplacement de l\'utilisateur à la création',
+       'cfg_lbl_geolocaliser_user_html5' => 'Centrer la carte sur l’emplacement de l’utilisateur à la création',
        'cfg_lbl_layer_defaut' => 'Couche par défaut',
        'cfg_lbl_layers' => 'Couches proposées',
        'cfg_lbl_maptype' => 'Fond cartographique',
@@ -47,23 +49,23 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'cfg_lbl_maptype_hybride' => 'Hybride',
        'cfg_lbl_maptype_relief' => 'Relief',
        'cfg_lbl_maptype_satellite' => 'Satellite',
-       'cfg_titre_gis' => 'GIS',
+       'cfg_titre_gis' => 'Configuration de GIS',
 
        // E
        'editer_gis_editer' => 'Modifier ce point',
-       'editer_gis_explication' => 'Cette page liste l\'ensemble des points géolocalisés du site.',
        'editer_gis_nouveau' => 'Créer un nouveau point',
        'editer_gis_titre' => 'Les points géolocalisés',
+       'erreur_geocoder' => 'Aucun résultat pour votre recherche :',
        'erreur_recherche_pas_resultats' => 'Aucun point ne correspond à la recherche.',
        'erreur_xmlrpc_lat_lon' => 'La latitude et la longitude doivent être passées en argument',
-       'explication_api_forcee' => 'L\'API est imposée par un autre plugin ou squelette.',
+       'explication_api_forcee' => 'LAPI est imposée par un autre plugin ou squelette.',
        'explication_import' => 'Importer un fichier au format GPX ou KML.',
        'explication_layer_forcee' => 'La couche est imposée par un autre plugin ou un squelette.',
        'explication_maptype_force' => 'Le fond cartographique est imposé par un autre plugin ou squelette.',
 
        // F
-       'formulaire_creer_gis' => 'Créer un point géolocalisé :',
-       'formulaire_modifier_gis' => 'Modifier le point géolocalisé :',
+       'formulaire_creer_gis' => 'Créer un point géolocalisé :',
+       'formulaire_modifier_gis' => 'Modifier le point géolocalisé :',
 
        // G
        'gis_pluriel' => 'Points géolocalisés',
@@ -89,7 +91,9 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
 
        // L
        'label_adress' => 'Adresse',
+       'label_code_pays' => 'Code pays',
        'label_code_postal' => 'Code postal',
+       'label_departement' => 'Département',
        'label_import' => 'Importer',
        'label_inserer_modele_articles' => 'liés aux articles',
        'label_inserer_modele_articles_sites' => 'liés aux articles + sites',
@@ -146,6 +150,19 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'titre_nombre_utilisations' => '@nb@ utilisations',
        'titre_nouveau_point' => 'Nouveau point',
        'titre_objet' => 'Titre',
+       'toolbar_actions_title' => 'Annuler le tracé',
+       'toolbar_buttons_marker' => 'Tracer un point',
+       'toolbar_buttons_polygon' => 'Tracer un polygone',
+       'toolbar_buttons_polyline' => 'Tracer une ligne',
+       'toolbar_handlers_marker_tooltip_start' => 'Cliquez pour placer le marqueur',
+       'toolbar_handlers_polygon_tooltip_cont' => 'Cliquez pour continuer à tracer le polygone',
+       'toolbar_handlers_polygon_tooltip_end' => 'Cliquez sur le premier point pour fermer le polygone',
+       'toolbar_handlers_polygon_tooltip_start' => 'Cliquez pour commencer à tracer le polygone',
+       'toolbar_handlers_polyline_tooltip_cont' => 'Cliquez pour continuer à tracer la ligne',
+       'toolbar_handlers_polyline_tooltip_end' => 'Cliquez sur le dernier point pour terminer la ligne',
+       'toolbar_handlers_polyline_tooltip_start' => 'Cliquez pour commencer à tracer la ligne',
+       'toolbar_undo_text' => 'Effacer le dernier point',
+       'toolbar_undo_title' => 'Effacer le dernier point tracé',
 
        // Z
        'zoom' => 'Zoom'
diff --git a/www/plugins/gis/lang/gis_nl.php b/www/plugins/gis/lang/gis_nl.php
new file mode 100644 (file)
index 0000000..8ac51ec
--- /dev/null
@@ -0,0 +1,173 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/gis?lang_cible=nl
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // A
+       'aucun_gis' => 'Geen enkel punt',
+       'aucun_objet' => 'Geen object',
+
+       // B
+       'bouton_lier' => 'Koppel dit punt',
+       'bouton_supprimer_gis' => 'Verwijder dit punt permanent',
+       'bouton_supprimer_lien' => 'Verwijder deze link',
+
+       // C
+       'cfg_descr_gis' => 'Geografisch Informatie Systeem.<br /><a href="http://www.spip-contrib.net/4189" class="spip_out">Naar de documentatie</a>.',
+       'cfg_inf_adresse' => 'Toon extra addresvelden (land, stad, staat, adres ...)',
+       'cfg_inf_bing' => 'De Bing Aerial layer heeft een sleutel nodig die je kunt aanmaken op <a href=\'@url@\' class="spip_out">de Bing website</a>.',
+       'cfg_inf_cloudmade' => 'Deze API heeft een sleutel nodig die je kunt aanmaken op <a href=\'@url@\' class="spip_out">de CloudMade website</a>.',
+       'cfg_inf_geocoder' => 'Maak geocoder functies mogelijk (adres zoeken, adres vinden aan de hand van coördinaten).',
+       'cfg_inf_geolocaliser_user_html5' => 'Als de browser het toestaat, wordt de geografische locatie gebruikt als default positie bij het aanmaken van een nieuw punt.',
+       'cfg_inf_google' => 'Deze API heeft een sleutel nodig die je kunt aanmaken op <a href=\'@url@\' class="spip_out">de GoogleMaps website</a>.',
+       'cfg_inf_yandex' => 'Deze API heeft een sleutel nodig die je kunt aanmaken op <a href=\'@url@\' class="spip_out">de Yandex website</a>.',
+       'cfg_lbl_activer_objets' => 'Maak geotagging mogelijk van inhoud:',
+       'cfg_lbl_adresse' => 'Toon adresvelden',
+       'cfg_lbl_api' => 'Geolocatie API',
+       'cfg_lbl_api_cloudmade' => 'CloudMade',
+       'cfg_lbl_api_google' => 'Google Maps v2',
+       'cfg_lbl_api_googlev3' => 'Google Maps v3',
+       'cfg_lbl_api_key_bing' => 'Bing key',
+       'cfg_lbl_api_key_cloudmade' => 'CloudMade API key',
+       'cfg_lbl_api_key_google' => 'GoogleMaps API key',
+       'cfg_lbl_api_key_yandex' => 'Yandex API key',
+       'cfg_lbl_api_mapquest' => 'MapQuest',
+       'cfg_lbl_api_microsoft' => 'Microsoft Bing',
+       'cfg_lbl_api_openlayers' => 'OpenLayers',
+       'cfg_lbl_api_ovi' => 'Ovi Nokia',
+       'cfg_lbl_api_yandex' => 'Yandex',
+       'cfg_lbl_geocoder' => 'Geocoder',
+       'cfg_lbl_geolocaliser_user_html5' => 'Centreer de map op de locatie van de gebruiker tijdens het aanmaken',
+       'cfg_lbl_layer_defaut' => 'Standaard layer',
+       'cfg_lbl_layers' => 'Voorgestelde layers',
+       'cfg_lbl_maptype' => 'Basiskaart',
+       'cfg_lbl_maptype_carte' => 'Kaart',
+       'cfg_lbl_maptype_hybride' => 'Hybrid',
+       'cfg_lbl_maptype_relief' => 'Relief',
+       'cfg_lbl_maptype_satellite' => 'Satelliet',
+       'cfg_titre_gis' => 'Configuratie van GIS',
+
+       // E
+       'editer_gis_editer' => 'Pas dit punt aan',
+       'editer_gis_nouveau' => 'Maak een nieuw punt',
+       'editer_gis_titre' => 'Locatiepunten',
+       'erreur_geocoder' => 'Geen resultaat voor zoekopdracht:',
+       'erreur_recherche_pas_resultats' => 'Geen enkel punt correspondeert met de gezochte tekst.',
+       'erreur_xmlrpc_lat_lon' => 'Latitude en longitude moeten worden ingevuld',
+       'explication_api_forcee' => 'Dit is vereist door een andere plugin or een ander model.',
+       'explication_import' => 'Importeer een bestand in GPX of KML formaat.',
+       'explication_layer_forcee' => 'De layer is vereist door een andere plugin of een ander model.',
+       'explication_maptype_force' => 'De basiskaart is vereist door een andere plugin of een ander model.',
+
+       // F
+       'formulaire_creer_gis' => 'Maak een nieuw locatiepunt:',
+       'formulaire_modifier_gis' => 'Pas het locatiepunt aan:',
+
+       // G
+       'gis_pluriel' => 'Locatiepunten',
+       'gis_singulier' => 'Locatiepunt',
+
+       // I
+       'icone_gis_tous' => 'Locatiepunten',
+       'info_1_gis' => 'Een locatiepunt',
+       'info_1_objet_gis' => '1 object gekoppeld aan dat punt',
+       'info_aucun_gis' => 'Geen locatie punt',
+       'info_aucun_objet_gis' => 'Geen object gekoppeld aan dat punt',
+       'info_geolocalisation' => 'Geolocatie',
+       'info_id_objet' => 'Nr',
+       'info_liste_gis' => 'Locatiepunten',
+       'info_nb_gis' => '@nb@ location-based punts',
+       'info_nb_objets_gis' => '@nb@ objecten gekoppeld aan dat punt',
+       'info_numero_gis' => 'Punt nummer',
+       'info_objet' => 'Object',
+       'info_recherche_gis_zero' => 'Geen resultaat voor « @cherche_gis@ ».',
+       'info_supprimer_lien' => 'Ontkoppel',
+       'info_supprimer_liens' => 'Ontkoppel alle punten',
+       'info_voir_fiche_objet' => 'Ga naar bladzijde',
+
+       // L
+       'label_adress' => 'Adres',
+       'label_code_pays' => 'Landcode',
+       'label_code_postal' => 'Postcode',
+       'label_departement' => 'Departement',
+       'label_import' => 'Importeer',
+       'label_inserer_modele_articles' => 'gekoppeld aan artikelen',
+       'label_inserer_modele_articles_sites' => 'gekoppeld aan artikelen + websites',
+       'label_inserer_modele_auteurs' => 'gekoppeld aan auteurs',
+       'label_inserer_modele_centrer_auto' => 'Geen automatische centrering',
+       'label_inserer_modele_centrer_fichier' => 'Centreer de kaart niet op KLM/GPX bestanden',
+       'label_inserer_modele_controle' => 'Verberg bedieningsknoppen',
+       'label_inserer_modele_controle_type' => 'Verberg types',
+       'label_inserer_modele_description' => 'Omschrijving',
+       'label_inserer_modele_documents' => 'gekoppeld aan documenten',
+       'label_inserer_modele_echelle' => 'Schaal',
+       'label_inserer_modele_fullscreen' => 'Knop volledig scherm',
+       'label_inserer_modele_gpx' => 'GPX bestand naar overlay',
+       'label_inserer_modele_hauteur_carte' => 'Kaarthoogte',
+       'label_inserer_modele_identifiant' => 'ID',
+       'label_inserer_modele_identifiant_opt' => 'ID (optioneel)',
+       'label_inserer_modele_identifiant_placeholder' => 'id_gis',
+       'label_inserer_modele_kml' => 'KML bestanden naar overlay',
+       'label_inserer_modele_kml_gpx' => 'id_document of url',
+       'label_inserer_modele_largeur_carte' => 'Kaartbreedte',
+       'label_inserer_modele_limite' => 'Maximum aantal punten',
+       'label_inserer_modele_localiser_visiteur' => 'Centreer op lokatie bezoeker',
+       'label_inserer_modele_mini_carte' => 'Mini situatiekaart',
+       'label_inserer_modele_molette' => 'Schakel het scroll-wiel uit',
+       'label_inserer_modele_mots' => 'Gekoppeld aan woorden',
+       'label_inserer_modele_objets' => 'Categorie van punt(en)',
+       'label_inserer_modele_point_gis' => 'enkel punt geregistreerd',
+       'label_inserer_modele_point_libre' => 'enkel vrij punt',
+       'label_inserer_modele_points' => 'Verberg punten',
+       'label_inserer_modele_rubriques' => 'gekoppeld aan rubrieken',
+       'label_inserer_modele_sites' => 'gekoppeld aan websites',
+       'label_inserer_modele_titre_carte' => 'Kaarttitel',
+       'label_pays' => 'Land',
+       'label_rechercher_address' => 'Zoek een adres',
+       'label_rechercher_point' => 'Zoek een punt',
+       'label_region' => 'Regio',
+       'label_ville' => 'Stad',
+       'lat' => 'Breedtegraad',
+       'libelle_logo_gis' => 'LOGO VAN HET PUNT',
+       'lien_ajouter_gis' => 'Voeg dit punt toe',
+       'lon' => 'Lengtegraad',
+
+       // T
+       'telecharger_gis' => 'Download in @format@ formaat',
+       'texte_ajouter_gis' => 'Voeg een locatiepunt toe',
+       'texte_creer_associer_gis' => 'Maak en link een locatiepunt',
+       'texte_creer_gis' => 'Maak een locatiepunt',
+       'texte_modifier_gis' => 'Pas het locatiepunt aan',
+       'texte_voir_gis' => 'Toon het locatiepunt',
+       'titre_bloc_creer_point' => 'Koppel een nieuw punt',
+       'titre_bloc_points_lies' => 'Gekoppelde punten',
+       'titre_bloc_rechercher_point' => 'Zoek een punt',
+       'titre_nombre_utilisation' => 'Eén toepassing',
+       'titre_nombre_utilisations' => '@nb@ toepassingen',
+       'titre_nouveau_point' => 'Nieuw punt',
+       'titre_objet' => 'Titel',
+       'toolbar_actions_title' => 'Annuleer de tekening',
+       'toolbar_buttons_marker' => 'Plot een punt',
+       'toolbar_buttons_polygon' => 'Teken een polygoon',
+       'toolbar_buttons_polyline' => 'Teken een lijn',
+       'toolbar_handlers_marker_tooltip_start' => 'Klik om de marker in te stellen',
+       'toolbar_handlers_polygon_tooltip_cont' => 'Klik om de polygoon verder te tekenen',
+       'toolbar_handlers_polygon_tooltip_end' => 'Klik op het eerste punt om de polygoon te sluiten',
+       'toolbar_handlers_polygon_tooltip_start' => 'Klik om een polygoon te gaan tekenen',
+       'toolbar_handlers_polyline_tooltip_cont' => 'Klik om de lijn verder te tekenen',
+       'toolbar_handlers_polyline_tooltip_end' => 'Klik het laatste punt voor het einde van de lijn',
+       'toolbar_handlers_polyline_tooltip_start' => 'Klik om een lijn te gaan tekenen',
+       'toolbar_undo_text' => 'Wis het laatste punt',
+       'toolbar_undo_title' => 'Wis het laatst uitgestippelde punt',
+
+       // Z
+       'zoom' => 'Zoom'
+);
+
+?>
index 0a3c096..0e4a80b 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/gis?lang_cible=ru
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
@@ -17,10 +19,10 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'bouton_supprimer_lien' => 'Удалить связь',
 
        // C
-       'cfg_descr_gis' => 'Географическая Информационная Система (GIS).<br /><a href="http://www.spip-contrib.net/3887" class="spip_out">Документация</a>.',
+       'cfg_descr_gis' => 'Географическая Информационная Система (GIS).<br /><a href="http://contrib.spip.net/3887" class="spip_out">Документация</a>.', # MODIF
        'cfg_inf_adresse' => 'Показываются дополнительные поля для ввода адреса (страна, город, область, адрес...)',
        'cfg_inf_bing' => 'Для использования карты Bing Aerial необходимо создать ключ  <a href=\'@url@\' class="spip_out">на сайте Bing</a>.',
-       'cfg_inf_cloudmade' => 'Для использования карты необходимо создать ключ  <a href=\'@url@\' class="spip_out">на сайте CloudMade</a>.',
+       'cfg_inf_cloudmade' => 'Для использования карты необходимо создать ключ <a href=\'@url@\' class="spip_out">на сайте CloudMade</a>.',
        'cfg_inf_geocoder' => 'Включить функцию геопоиска (поиск точки на карте по адресу).',
        'cfg_inf_geolocaliser_user_html5' => 'Новая карта центрируется по расположению пользователя ( если позволяет его браузер).',
        'cfg_inf_google' => 'Для работы с картой необходим API ключ, который  можно создать на <a href=\'@url@\' class="spip_out">сайте  GoogleMaps</a>.',
@@ -49,11 +51,10 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'cfg_lbl_maptype_hybride' => 'Гибрид',
        'cfg_lbl_maptype_relief' => 'Рельеф',
        'cfg_lbl_maptype_satellite' => 'Спутник',
-       'cfg_titre_gis' => 'GIS',
+       'cfg_titre_gis' => 'GIS', # MODIF
 
        // E
        'editer_gis_editer' => 'Изменить точку',
-       'editer_gis_explication' => 'Список всех точек, используемых на вашем сайте.',
        'editer_gis_nouveau' => 'Создать точку',
        'editer_gis_titre' => 'Точки на карте',
        'erreur_recherche_pas_resultats' => 'Нет точек, соответствующих поисковому запросу.',
index c3b4b4c..b6756a9 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/gis?lang_cible=sk
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
@@ -17,7 +19,7 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'bouton_supprimer_lien' => 'Odstrániť tento odkaz',
 
        // C
-       'cfg_descr_gis' => 'Geografický informačný systém.<br /><a href="http://www.spip-contrib.net/3887" class="spip_out">Prejsť na dokumentáciu.</a>',
+       'cfg_descr_gis' => 'Geografický informačný systém.<br /><a href="http://contrib.spip.net/4189" class="spip_out">Prejsť na dokumentáciu.</a>',
        'cfg_inf_adresse' => 'Zobrazí ďalšie polia adresy (krajinu, mesto, štát, adresu a pod.)',
        'cfg_inf_bing' => 'Vrstva Bing Aerial si vyžaduje, aby ste <a href=\'@url@\' class="spip_out">na stránke vyhľadávača Bing</a> vytvorili kľúč.',
        'cfg_inf_cloudmade' => 'Táto aplikácia potrebuje kľúč na vytvorenie <a href=\'@url@\' class="spip_out">stránky v Cloude.</a>',
@@ -49,13 +51,13 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'cfg_lbl_maptype_hybride' => 'Zmiešaná',
        'cfg_lbl_maptype_relief' => 'Reliéf',
        'cfg_lbl_maptype_satellite' => 'Satelitná',
-       'cfg_titre_gis' => 'GIS',
+       'cfg_titre_gis' => 'GIS', # MODIF
 
        // E
        'editer_gis_editer' => 'Upraviť tento bod',
-       'editer_gis_explication' => 'Táto stránka uvádza všetky geolokalizované body na webe.',
        'editer_gis_nouveau' => 'Vytvoriť nový bod',
        'editer_gis_titre' => 'Geolokalizované body',
+       'erreur_geocoder' => 'Žiaden výsledok k vášmu vyhľadávaniu:',
        'erreur_recherche_pas_resultats' => 'Vyhľadávania sa netýka žiaden bod.',
        'erreur_xmlrpc_lat_lon' => 'Zemepisná šírka a dĺžka musia byť odovzdané ako parameter',
        'explication_api_forcee' => 'Túto aplikáciu používa iný zásuvný modul alebo iná šablóna.',
@@ -91,39 +93,41 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
 
        // L
        'label_adress' => 'Adresa',
+       'label_code_pays' => 'Kód krajiny',
        'label_code_postal' => 'PSČ',
+       'label_departement' => 'Kraj',
        'label_import' => 'Nahrať',
-       'label_inserer_modele_articles' => 'liés aux articles', # NEW
-       'label_inserer_modele_articles_sites' => 'liés aux articles + sites', # NEW
-       'label_inserer_modele_auteurs' => 'liés aux auteurs', # NEW
-       'label_inserer_modele_centrer_auto' => 'Pas de centrage auto', # NEW
-       'label_inserer_modele_centrer_fichier' => 'Ne pas centrer la carte sur les fichiers KLM/GPX', # NEW
-       'label_inserer_modele_controle' => 'Cacher les contrôles', # NEW
-       'label_inserer_modele_controle_type' => 'Cacher les types', # NEW
-       'label_inserer_modele_description' => 'Description', # NEW
-       'label_inserer_modele_documents' => 'liés aux documents', # NEW
-       'label_inserer_modele_echelle' => 'Echelle', # NEW
-       'label_inserer_modele_fullscreen' => 'Bouton plein écran', # NEW
-       'label_inserer_modele_gpx' => 'Fichier GPX à superposer', # NEW
-       'label_inserer_modele_hauteur_carte' => 'Hauteur de la carte', # NEW
-       'label_inserer_modele_identifiant' => 'Identifiant', # NEW
-       'label_inserer_modele_identifiant_opt' => 'Identifiant (optionnel)', # NEW
-       'label_inserer_modele_identifiant_placeholder' => 'id_gis', # NEW
-       'label_inserer_modele_kml' => 'Fichier KML à superposer', # NEW
-       'label_inserer_modele_kml_gpx' => 'id_document ou url', # NEW
-       'label_inserer_modele_largeur_carte' => 'Largeur de la carte', # NEW
-       'label_inserer_modele_limite' => 'Nombre de points maximum', # NEW
-       'label_inserer_modele_localiser_visiteur' => 'Centrer sur le visiteur', # NEW
-       'label_inserer_modele_mini_carte' => 'Mini carte de situation', # NEW
-       'label_inserer_modele_molette' => 'Désactiver la molette', # NEW
-       'label_inserer_modele_mots' => 'liés aux mots', # NEW
-       'label_inserer_modele_objets' => 'Type de point(s)', # NEW
-       'label_inserer_modele_point_gis' => 'point unique enregistré', # NEW
-       'label_inserer_modele_point_libre' => 'point unique libre', # NEW
-       'label_inserer_modele_points' => 'Cacher les points', # NEW
-       'label_inserer_modele_rubriques' => 'liés aux rubriques', # NEW
-       'label_inserer_modele_sites' => 'liés aux sites', # NEW
-       'label_inserer_modele_titre_carte' => 'Titre de la carte', # NEW
+       'label_inserer_modele_articles' => 'prepojené s článkami',
+       'label_inserer_modele_articles_sites' => 'prepojené s článkami a stránkami',
+       'label_inserer_modele_auteurs' => 'prepojené s autormi',
+       'label_inserer_modele_centrer_auto' => 'Nevystreďovať automaticky',
+       'label_inserer_modele_centrer_fichier' => 'Nedávať mapu na súbory KLM alebo GPX',
+       'label_inserer_modele_controle' => 'Schovať ovládacie prvky',
+       'label_inserer_modele_controle_type' => 'Skryť typy',
+       'label_inserer_modele_description' => 'Opis',
+       'label_inserer_modele_documents' => 'prepojené s dokumentmi',
+       'label_inserer_modele_echelle' => 'Mierka',
+       'label_inserer_modele_fullscreen' => 'Tlačidlo "Na celú obrazovka"',
+       'label_inserer_modele_gpx' => 'Prekryť súborom GPX',
+       'label_inserer_modele_hauteur_carte' => 'Výška mapy',
+       'label_inserer_modele_identifiant' => 'Identifikátor',
+       'label_inserer_modele_identifiant_opt' => 'Identifikátor (nepovinné)',
+       'label_inserer_modele_identifiant_placeholder' => 'id_gis',
+       'label_inserer_modele_kml' => 'Prekryť súborom KML',
+       'label_inserer_modele_kml_gpx' => 'id_document alebo url',
+       'label_inserer_modele_largeur_carte' => 'Šírka mapy',
+       'label_inserer_modele_limite' => 'Maximálny počet bodov',
+       'label_inserer_modele_localiser_visiteur' => 'Zacieliť na návštevníka',
+       'label_inserer_modele_mini_carte' => 'Malá situačná mapa',
+       'label_inserer_modele_molette' => 'Vypnúť koliesko',
+       'label_inserer_modele_mots' => 'prepojené so slovami',
+       'label_inserer_modele_objets' => 'Typ bodov',
+       'label_inserer_modele_point_gis' => 'jeden zaregistrovaný bod',
+       'label_inserer_modele_point_libre' => 'jeden voľný bod',
+       'label_inserer_modele_points' => 'Schovať body',
+       'label_inserer_modele_rubriques' => 'prepojené s rubrikami',
+       'label_inserer_modele_sites' => 'prepojené so stránkami',
+       'label_inserer_modele_titre_carte' => 'Názov mapy',
        'label_pays' => 'Krajina',
        'label_rechercher_address' => 'Vyhľadať adresu',
        'label_rechercher_point' => 'Vyhľadať bod',
@@ -148,6 +152,19 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'titre_nombre_utilisations' => '@nb@ použití',
        'titre_nouveau_point' => 'Nový bod',
        'titre_objet' => 'Názov',
+       'toolbar_actions_title' => 'Zrušiť trasu',
+       'toolbar_buttons_marker' => 'Nakresliť bod',
+       'toolbar_buttons_polygon' => 'Nakresliť mnohouholník',
+       'toolbar_buttons_polyline' => 'Nakresliť čiaru',
+       'toolbar_handlers_marker_tooltip_start' => 'Ak chcete vložiť značku, kliknite sem',
+       'toolbar_handlers_polygon_tooltip_cont' => 'Kliknite, ak chcete pokračovať v kreslení mnohouholníka',
+       'toolbar_handlers_polygon_tooltip_end' => 'Kliknite na prvý bod, aby bol mnohouholník uzavretý',
+       'toolbar_handlers_polygon_tooltip_start' => 'Ak chcete začať kresliť mnohouholník, kliknite sem',
+       'toolbar_handlers_polyline_tooltip_cont' => 'Ak chcete pokračovať v kreslení čiary, kliknite sem',
+       'toolbar_handlers_polyline_tooltip_end' => 'Kliknite na posledný bod, aby bola čiara ukončená',
+       'toolbar_handlers_polyline_tooltip_start' => 'Ak chcete začať kresliť čiaru, kliknite sem',
+       'toolbar_undo_text' => 'Vymazať posledný bod',
+       'toolbar_undo_title' => 'Vymazať posledný nakreslený bod',
 
        // Z
        'zoom' => 'Lupa'
index 10e313f..12799ff 100644 (file)
@@ -1,15 +1,19 @@
 <traduction module="paquet-gis" gestionnaire="salvatore" url="http://trad.spip.net" source="svn://zone.spip.org/spip-zone/_plugins_/gis/trunk/lang/" reference="fr">
-       <langue code="en" url="http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=en">
+       <langue code="en" url="http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=en" total="2" traduits="2" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
                <traducteur nom="kent1" lien="http://trad.spip.net/auteur/kent1" />
        </langue>
-       <langue code="es" url="http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=es">
-               <traducteur nom="Raquel S. Bujaldón" lien="http://trad.spip.net/auteur/raquel" />
+       <langue code="es" url="http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=es" total="2" traduits="2" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Raquel S. Bujaldón" lien="http://trad.spip.net/auteur/raquel-s-bujaldon" />
        </langue>
-       <langue code="fr" />
-       <langue code="ru" url="http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=ru">
+       <langue code="fr" url="http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=fr" total="2" traduits="2" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       </langue>
+       <langue code="nl" url="http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=nl" total="2" traduits="2" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
+       </langue>
+       <langue code="ru" url="http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=ru" total="2" traduits="2" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
                <traducteur nom="Serge Markitanenko" lien="http://trad.spip.net/auteur/serge-markitanenko" />
        </langue>
-       <langue code="sk" url="http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=sk">
+       <langue code="sk" url="http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=sk" total="2" traduits="2" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
                <traducteur nom="jaro" lien="http://trad.spip.net/auteur/jaro" />
        </langue>
 </traduction>
index 9a87053..7ec83af 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=en
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
index c433e2f..c50b6fd 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=es
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
index 8440ef5..a24c1fe 100644 (file)
@@ -1,13 +1,15 @@
 <?php
 // This is a SPIP language file  --  Ceci est un fichier langue de SPIP
 // Fichier source, a modifier dans svn://zone.spip.org/spip-zone/_plugins_/gis/trunk/lang/
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
        // G
        'gis_description' => 'Ce plugin permet de créer des points géolocalisés qui peuvent être attachés aux objets de SPIP afin de les afficher sur des cartes dans les pages de votre site. Ces dernières peuvent utiliser les tuiles de différents fournisseurs grâce à la librairie Leaflet.',
-       'gis_slogan' => 'Système d\'information géographique'
+       'gis_slogan' => 'Système dinformation géographique'
 );
 
 ?>
diff --git a/www/plugins/gis/lang/paquet-gis_nl.php b/www/plugins/gis/lang/paquet-gis_nl.php
new file mode 100644 (file)
index 0000000..b4a0593
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=nl
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // G
+       'gis_description' => 'Deze plugin laat je locatiepunten maken die aan SPIP-objecten kunnen worden gekoppeld om een kaart te tonen. Verschillende soorten kaarten zijn mogelijk dankzij de Leaflet bibliotheek.',
+       'gis_slogan' => 'Geografisch Informatie Systeem'
+);
+
+?>
index 5a85d3b..ec329f0 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=ru
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
index 50cfd09..bd404e5 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-gis?lang_cible=sk
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
diff --git a/www/plugins/gis/lib/leaflet/LICENSE b/www/plugins/gis/lib/leaflet/LICENSE
new file mode 100644 (file)
index 0000000..46cd121
--- /dev/null
@@ -0,0 +1,23 @@
+Copyright (c) 2010-2013, Vladimir Agafonkin\r
+Copyright (c) 2010-2011, CloudMade\r
+All rights reserved.\r
+\r
+Redistribution and use in source and binary forms, with or without modification, are\r
+permitted provided that the following conditions are met:\r
+\r
+   1. Redistributions of source code must retain the above copyright notice, this list of\r
+      conditions and the following disclaimer.\r
+\r
+   2. Redistributions in binary form must reproduce the above copyright notice, this list\r
+      of conditions and the following disclaimer in the documentation and/or other materials\r
+      provided with the distribution.\r
+\r
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY\r
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\r
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\r
+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\r
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\r
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
diff --git a/www/plugins/gis/lib/leaflet/README.md b/www/plugins/gis/lib/leaflet/README.md
new file mode 100644 (file)
index 0000000..add1f0e
--- /dev/null
@@ -0,0 +1,33 @@
+Sources utilisées dans la lib :\r
+\r
+* dist/\r
+       * http://leafletjs.com/download.html\r
+* plugins/\r
+       * Bing.js\r
+               * https://github.com/shramov/leaflet-plugins/blob/master/layer/tile/Bing.js\r
+       * Control.FullScreen.js\r
+               * https://github.com/brunob/leaflet.fullscreen/blob/master/Control.FullScreen.js\r
+       * Control.MiniMap.js\r
+               * https://github.com/Norkart/Leaflet-MiniMap/blob/master/src/Control.MiniMap.js\r
+       * Google.js\r
+               * https://github.com/shramov/leaflet-plugins/blob/master/layer/tile/Google.js\r
+       * GPX.js\r
+               * https://github.com/shramov/leaflet-plugins/blob/master/layer/vector/GPX.js\r
+       * GPX.Speed.js\r
+               * https://github.com/shramov/leaflet-plugins/blob/master/layer/vector/GPX.Speed.js\r
+       * images\r
+               * mixed from all sources\r
+       * KML.js\r
+               * https://github.com/shramov/leaflet-plugins/blob/master/layer/vector/KML.js\r
+       * leaflet.markercluster.css\r
+               * https://github.com/Leaflet/Leaflet.markercluster/blob/master/dist/MarkerCluster.css\r
+               * https://github.com/Leaflet/Leaflet.markercluster/blob/master/dist/MarkerCluster.Default.css\r
+       * leaflet.markercluster-src.js\r
+               * https://github.com/Leaflet/Leaflet.markercluster/blob/master/dist/leaflet.markercluster-src.js\r
+       * leaflet-plugins.css\r
+               * https://github.com/brunob/leaflet.fullscreen/blob/master/Control.FullScreen.css\r
+               * https://github.com/Norkart/Leaflet-MiniMap/blob/master/src/Control.MiniMap.css\r
+       * leaflet-providers.js\r
+               * https://github.com/leaflet-extras/leaflet-providers/blob/master/leaflet-providers.js\r
+       * Marker.Rotate.js\r
+               * https://github.com/shramov/leaflet-plugins/blob/master/layer/Marker.Rotate.js\r
diff --git a/www/plugins/gis/lib/leaflet/dist/images/layers-2x.png b/www/plugins/gis/lib/leaflet/dist/images/layers-2x.png
new file mode 100644 (file)
index 0000000..a2cf7f9
Binary files /dev/null and b/www/plugins/gis/lib/leaflet/dist/images/layers-2x.png differ
diff --git a/www/plugins/gis/lib/leaflet/dist/images/layers.png b/www/plugins/gis/lib/leaflet/dist/images/layers.png
new file mode 100644 (file)
index 0000000..bca0a0e
Binary files /dev/null and b/www/plugins/gis/lib/leaflet/dist/images/layers.png differ
diff --git a/www/plugins/gis/lib/leaflet/dist/images/marker-icon-2x.png b/www/plugins/gis/lib/leaflet/dist/images/marker-icon-2x.png
new file mode 100644 (file)
index 0000000..0015b64
Binary files /dev/null and b/www/plugins/gis/lib/leaflet/dist/images/marker-icon-2x.png differ
diff --git a/www/plugins/gis/lib/leaflet/dist/images/marker-icon.png b/www/plugins/gis/lib/leaflet/dist/images/marker-icon.png
new file mode 100644 (file)
index 0000000..e2e9f75
Binary files /dev/null and b/www/plugins/gis/lib/leaflet/dist/images/marker-icon.png differ
diff --git a/www/plugins/gis/lib/leaflet/dist/images/marker-shadow.png b/www/plugins/gis/lib/leaflet/dist/images/marker-shadow.png
new file mode 100644 (file)
index 0000000..d1e773c
Binary files /dev/null and b/www/plugins/gis/lib/leaflet/dist/images/marker-shadow.png differ
diff --git a/www/plugins/gis/lib/leaflet/dist/leaflet-src.js b/www/plugins/gis/lib/leaflet/dist/leaflet-src.js
new file mode 100644 (file)
index 0000000..721ddfb
--- /dev/null
@@ -0,0 +1,9168 @@
+/*
+ Leaflet, a JavaScript library for mobile-friendly interactive maps. http://leafletjs.com
+ (c) 2010-2013, Vladimir Agafonkin
+ (c) 2010-2011, CloudMade
+*/
+(function (window, document, undefined) {\r
+var oldL = window.L,\r
+    L = {};\r
+\r
+L.version = '0.7.7';\r
+\r
+// define Leaflet for Node module pattern loaders, including Browserify\r
+if (typeof module === 'object' && typeof module.exports === 'object') {\r
+       module.exports = L;\r
+\r
+// define Leaflet as an AMD module\r
+} else if (typeof define === 'function' && define.amd) {\r
+       define(L);\r
+}\r
+\r
+// define Leaflet as a global L variable, saving the original L to restore later if needed\r
+\r
+L.noConflict = function () {\r
+       window.L = oldL;\r
+       return this;\r
+};\r
+\r
+window.L = L;\r
+
+
+/*\r
+ * L.Util contains various utility functions used throughout Leaflet code.\r
+ */\r
+\r
+L.Util = {\r
+       extend: function (dest) { // (Object[, Object, ...]) ->\r
+               var sources = Array.prototype.slice.call(arguments, 1),\r
+                   i, j, len, src;\r
+\r
+               for (j = 0, len = sources.length; j < len; j++) {\r
+                       src = sources[j] || {};\r
+                       for (i in src) {\r
+                               if (src.hasOwnProperty(i)) {\r
+                                       dest[i] = src[i];\r
+                               }\r
+                       }\r
+               }\r
+               return dest;\r
+       },\r
+\r
+       bind: function (fn, obj) { // (Function, Object) -> Function\r
+               var args = arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null;\r
+               return function () {\r
+                       return fn.apply(obj, args || arguments);\r
+               };\r
+       },\r
+\r
+       stamp: (function () {\r
+               var lastId = 0,\r
+                   key = '_leaflet_id';\r
+               return function (obj) {\r
+                       obj[key] = obj[key] || ++lastId;\r
+                       return obj[key];\r
+               };\r
+       }()),\r
+\r
+       invokeEach: function (obj, method, context) {\r
+               var i, args;\r
+\r
+               if (typeof obj === 'object') {\r
+                       args = Array.prototype.slice.call(arguments, 3);\r
+\r
+                       for (i in obj) {\r
+                               method.apply(context, [i, obj[i]].concat(args));\r
+                       }\r
+                       return true;\r
+               }\r
+\r
+               return false;\r
+       },\r
+\r
+       limitExecByInterval: function (fn, time, context) {\r
+               var lock, execOnUnlock;\r
+\r
+               return function wrapperFn() {\r
+                       var args = arguments;\r
+\r
+                       if (lock) {\r
+                               execOnUnlock = true;\r
+                               return;\r
+                       }\r
+\r
+                       lock = true;\r
+\r
+                       setTimeout(function () {\r
+                               lock = false;\r
+\r
+                               if (execOnUnlock) {\r
+                                       wrapperFn.apply(context, args);\r
+                                       execOnUnlock = false;\r
+                               }\r
+                       }, time);\r
+\r
+                       fn.apply(context, args);\r
+               };\r
+       },\r
+\r
+       falseFn: function () {\r
+               return false;\r
+       },\r
+\r
+       formatNum: function (num, digits) {\r
+               var pow = Math.pow(10, digits || 5);\r
+               return Math.round(num * pow) / pow;\r
+       },\r
+\r
+       trim: function (str) {\r
+               return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');\r
+       },\r
+\r
+       splitWords: function (str) {\r
+               return L.Util.trim(str).split(/\s+/);\r
+       },\r
+\r
+       setOptions: function (obj, options) {\r
+               obj.options = L.extend({}, obj.options, options);\r
+               return obj.options;\r
+       },\r
+\r
+       getParamString: function (obj, existingUrl, uppercase) {\r
+               var params = [];\r
+               for (var i in obj) {\r
+                       params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));\r
+               }\r
+               return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');\r
+       },\r
+       template: function (str, data) {\r
+               return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {\r
+                       var value = data[key];\r
+                       if (value === undefined) {\r
+                               throw new Error('No value provided for variable ' + str);\r
+                       } else if (typeof value === 'function') {\r
+                               value = value(data);\r
+                       }\r
+                       return value;\r
+               });\r
+       },\r
+\r
+       isArray: Array.isArray || function (obj) {\r
+               return (Object.prototype.toString.call(obj) === '[object Array]');\r
+       },\r
+\r
+       emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='\r
+};\r
+\r
+(function () {\r
+\r
+       // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/\r
+\r
+       function getPrefixed(name) {\r
+               var i, fn,\r
+                   prefixes = ['webkit', 'moz', 'o', 'ms'];\r
+\r
+               for (i = 0; i < prefixes.length && !fn; i++) {\r
+                       fn = window[prefixes[i] + name];\r
+               }\r
+\r
+               return fn;\r
+       }\r
+\r
+       var lastTime = 0;\r
+\r
+       function timeoutDefer(fn) {\r
+               var time = +new Date(),\r
+                   timeToCall = Math.max(0, 16 - (time - lastTime));\r
+\r
+               lastTime = time + timeToCall;\r
+               return window.setTimeout(fn, timeToCall);\r
+       }\r
+\r
+       var requestFn = window.requestAnimationFrame ||\r
+               getPrefixed('RequestAnimationFrame') || timeoutDefer;\r
+\r
+       var cancelFn = window.cancelAnimationFrame ||\r
+               getPrefixed('CancelAnimationFrame') ||\r
+               getPrefixed('CancelRequestAnimationFrame') ||\r
+               function (id) { window.clearTimeout(id); };\r
+\r
+\r
+       L.Util.requestAnimFrame = function (fn, context, immediate, element) {\r
+               fn = L.bind(fn, context);\r
+\r
+               if (immediate && requestFn === timeoutDefer) {\r
+                       fn();\r
+               } else {\r
+                       return requestFn.call(window, fn, element);\r
+               }\r
+       };\r
+\r
+       L.Util.cancelAnimFrame = function (id) {\r
+               if (id) {\r
+                       cancelFn.call(window, id);\r
+               }\r
+       };\r
+\r
+}());\r
+\r
+// shortcuts for most used utility functions\r
+L.extend = L.Util.extend;\r
+L.bind = L.Util.bind;\r
+L.stamp = L.Util.stamp;\r
+L.setOptions = L.Util.setOptions;\r
+
+
+/*\r
+ * L.Class powers the OOP facilities of the library.\r
+ * Thanks to John Resig and Dean Edwards for inspiration!\r
+ */\r
+\r
+L.Class = function () {};\r
+\r
+L.Class.extend = function (props) {\r
+\r
+       // extended class with the new prototype\r
+       var NewClass = function () {\r
+\r
+               // call the constructor\r
+               if (this.initialize) {\r
+                       this.initialize.apply(this, arguments);\r
+               }\r
+\r
+               // call all constructor hooks\r
+               if (this._initHooks) {\r
+                       this.callInitHooks();\r
+               }\r
+       };\r
+\r
+       // instantiate class without calling constructor\r
+       var F = function () {};\r
+       F.prototype = this.prototype;\r
+\r
+       var proto = new F();\r
+       proto.constructor = NewClass;\r
+\r
+       NewClass.prototype = proto;\r
+\r
+       //inherit parent's statics\r
+       for (var i in this) {\r
+               if (this.hasOwnProperty(i) && i !== 'prototype') {\r
+                       NewClass[i] = this[i];\r
+               }\r
+       }\r
+\r
+       // mix static properties into the class\r
+       if (props.statics) {\r
+               L.extend(NewClass, props.statics);\r
+               delete props.statics;\r
+       }\r
+\r
+       // mix includes into the prototype\r
+       if (props.includes) {\r
+               L.Util.extend.apply(null, [proto].concat(props.includes));\r
+               delete props.includes;\r
+       }\r
+\r
+       // merge options\r
+       if (props.options && proto.options) {\r
+               props.options = L.extend({}, proto.options, props.options);\r
+       }\r
+\r
+       // mix given properties into the prototype\r
+       L.extend(proto, props);\r
+\r
+       proto._initHooks = [];\r
+\r
+       var parent = this;\r
+       // jshint camelcase: false\r
+       NewClass.__super__ = parent.prototype;\r
+\r
+       // add method for calling all hooks\r
+       proto.callInitHooks = function () {\r
+\r
+               if (this._initHooksCalled) { return; }\r
+\r
+               if (parent.prototype.callInitHooks) {\r
+                       parent.prototype.callInitHooks.call(this);\r
+               }\r
+\r
+               this._initHooksCalled = true;\r
+\r
+               for (var i = 0, len = proto._initHooks.length; i < len; i++) {\r
+                       proto._initHooks[i].call(this);\r
+               }\r
+       };\r
+\r
+       return NewClass;\r
+};\r
+\r
+\r
+// method for adding properties to prototype\r
+L.Class.include = function (props) {\r
+       L.extend(this.prototype, props);\r
+};\r
+\r
+// merge new default options to the Class\r
+L.Class.mergeOptions = function (options) {\r
+       L.extend(this.prototype.options, options);\r
+};\r
+\r
+// add a constructor hook\r
+L.Class.addInitHook = function (fn) { // (Function) || (String, args...)\r
+       var args = Array.prototype.slice.call(arguments, 1);\r
+\r
+       var init = typeof fn === 'function' ? fn : function () {\r
+               this[fn].apply(this, args);\r
+       };\r
+\r
+       this.prototype._initHooks = this.prototype._initHooks || [];\r
+       this.prototype._initHooks.push(init);\r
+};\r
+
+
+/*\r
+ * L.Mixin.Events is used to add custom events functionality to Leaflet classes.\r
+ */\r
+\r
+var eventsKey = '_leaflet_events';\r
+\r
+L.Mixin = {};\r
+\r
+L.Mixin.Events = {\r
+\r
+       addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object])\r
+\r
+               // types can be a map of types/handlers\r
+               if (L.Util.invokeEach(types, this.addEventListener, this, fn, context)) { return this; }\r
+\r
+               var events = this[eventsKey] = this[eventsKey] || {},\r
+                   contextId = context && context !== this && L.stamp(context),\r
+                   i, len, event, type, indexKey, indexLenKey, typeIndex;\r
+\r
+               // types can be a string of space-separated words\r
+               types = L.Util.splitWords(types);\r
+\r
+               for (i = 0, len = types.length; i < len; i++) {\r
+                       event = {\r
+                               action: fn,\r
+                               context: context || this\r
+                       };\r
+                       type = types[i];\r
+\r
+                       if (contextId) {\r
+                               // store listeners of a particular context in a separate hash (if it has an id)\r
+                               // gives a major performance boost when removing thousands of map layers\r
+\r
+                               indexKey = type + '_idx';\r
+                               indexLenKey = indexKey + '_len';\r
+\r
+                               typeIndex = events[indexKey] = events[indexKey] || {};\r
+\r
+                               if (!typeIndex[contextId]) {\r
+                                       typeIndex[contextId] = [];\r
+\r
+                                       // keep track of the number of keys in the index to quickly check if it's empty\r
+                                       events[indexLenKey] = (events[indexLenKey] || 0) + 1;\r
+                               }\r
+\r
+                               typeIndex[contextId].push(event);\r
+\r
+\r
+                       } else {\r
+                               events[type] = events[type] || [];\r
+                               events[type].push(event);\r
+                       }\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       hasEventListeners: function (type) { // (String) -> Boolean\r
+               var events = this[eventsKey];\r
+               return !!events && ((type in events && events[type].length > 0) ||\r
+                                   (type + '_idx' in events && events[type + '_idx_len'] > 0));\r
+       },\r
+\r
+       removeEventListener: function (types, fn, context) { // ([String, Function, Object]) or (Object[, Object])\r
+\r
+               if (!this[eventsKey]) {\r
+                       return this;\r
+               }\r
+\r
+               if (!types) {\r
+                       return this.clearAllEventListeners();\r
+               }\r
+\r
+               if (L.Util.invokeEach(types, this.removeEventListener, this, fn, context)) { return this; }\r
+\r
+               var events = this[eventsKey],\r
+                   contextId = context && context !== this && L.stamp(context),\r
+                   i, len, type, listeners, j, indexKey, indexLenKey, typeIndex, removed;\r
+\r
+               types = L.Util.splitWords(types);\r
+\r
+               for (i = 0, len = types.length; i < len; i++) {\r
+                       type = types[i];\r
+                       indexKey = type + '_idx';\r
+                       indexLenKey = indexKey + '_len';\r
+\r
+                       typeIndex = events[indexKey];\r
+\r
+                       if (!fn) {\r
+                               // clear all listeners for a type if function isn't specified\r
+                               delete events[type];\r
+                               delete events[indexKey];\r
+                               delete events[indexLenKey];\r
+\r
+                       } else {\r
+                               listeners = contextId && typeIndex ? typeIndex[contextId] : events[type];\r
+\r
+                               if (listeners) {\r
+                                       for (j = listeners.length - 1; j >= 0; j--) {\r
+                                               if ((listeners[j].action === fn) && (!context || (listeners[j].context === context))) {\r
+                                                       removed = listeners.splice(j, 1);\r
+                                                       // set the old action to a no-op, because it is possible\r
+                                                       // that the listener is being iterated over as part of a dispatch\r
+                                                       removed[0].action = L.Util.falseFn;\r
+                                               }\r
+                                       }\r
+\r
+                                       if (context && typeIndex && (listeners.length === 0)) {\r
+                                               delete typeIndex[contextId];\r
+                                               events[indexLenKey]--;\r
+                                       }\r
+                               }\r
+                       }\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       clearAllEventListeners: function () {\r
+               delete this[eventsKey];\r
+               return this;\r
+       },\r
+\r
+       fireEvent: function (type, data) { // (String[, Object])\r
+               if (!this.hasEventListeners(type)) {\r
+                       return this;\r
+               }\r
+\r
+               var event = L.Util.extend({}, data, { type: type, target: this });\r
+\r
+               var events = this[eventsKey],\r
+                   listeners, i, len, typeIndex, contextId;\r
+\r
+               if (events[type]) {\r
+                       // make sure adding/removing listeners inside other listeners won't cause infinite loop\r
+                       listeners = events[type].slice();\r
+\r
+                       for (i = 0, len = listeners.length; i < len; i++) {\r
+                               listeners[i].action.call(listeners[i].context, event);\r
+                       }\r
+               }\r
+\r
+               // fire event for the context-indexed listeners as well\r
+               typeIndex = events[type + '_idx'];\r
+\r
+               for (contextId in typeIndex) {\r
+                       listeners = typeIndex[contextId].slice();\r
+\r
+                       if (listeners) {\r
+                               for (i = 0, len = listeners.length; i < len; i++) {\r
+                                       listeners[i].action.call(listeners[i].context, event);\r
+                               }\r
+                       }\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       addOneTimeEventListener: function (types, fn, context) {\r
+\r
+               if (L.Util.invokeEach(types, this.addOneTimeEventListener, this, fn, context)) { return this; }\r
+\r
+               var handler = L.bind(function () {\r
+                       this\r
+                           .removeEventListener(types, fn, context)\r
+                           .removeEventListener(types, handler, context);\r
+               }, this);\r
+\r
+               return this\r
+                   .addEventListener(types, fn, context)\r
+                   .addEventListener(types, handler, context);\r
+       }\r
+};\r
+\r
+L.Mixin.Events.on = L.Mixin.Events.addEventListener;\r
+L.Mixin.Events.off = L.Mixin.Events.removeEventListener;\r
+L.Mixin.Events.once = L.Mixin.Events.addOneTimeEventListener;\r
+L.Mixin.Events.fire = L.Mixin.Events.fireEvent;\r
+
+
+/*\r
+ * L.Browser handles different browser and feature detections for internal Leaflet use.\r
+ */\r
+\r
+(function () {\r
+\r
+       var ie = 'ActiveXObject' in window,\r
+               ielt9 = ie && !document.addEventListener,\r
+\r
+           // terrible browser detection to work around Safari / iOS / Android browser bugs\r
+           ua = navigator.userAgent.toLowerCase(),\r
+           webkit = ua.indexOf('webkit') !== -1,\r
+           chrome = ua.indexOf('chrome') !== -1,\r
+           phantomjs = ua.indexOf('phantom') !== -1,\r
+           android = ua.indexOf('android') !== -1,\r
+           android23 = ua.search('android [23]') !== -1,\r
+               gecko = ua.indexOf('gecko') !== -1,\r
+\r
+           mobile = typeof orientation !== undefined + '',\r
+           msPointer = !window.PointerEvent && window.MSPointerEvent,\r
+               pointer = (window.PointerEvent && window.navigator.pointerEnabled) ||\r
+                                 msPointer,\r
+           retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) ||\r
+                    ('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') &&\r
+                     window.matchMedia('(min-resolution:144dpi)').matches),\r
+\r
+           doc = document.documentElement,\r
+           ie3d = ie && ('transition' in doc.style),\r
+           webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,\r
+           gecko3d = 'MozPerspective' in doc.style,\r
+           opera3d = 'OTransition' in doc.style,\r
+           any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d) && !phantomjs;\r
+\r
+       var touch = !window.L_NO_TOUCH && !phantomjs && (pointer || 'ontouchstart' in window ||\r
+               (window.DocumentTouch && document instanceof window.DocumentTouch));\r
+\r
+       L.Browser = {\r
+               ie: ie,\r
+               ielt9: ielt9,\r
+               webkit: webkit,\r
+               gecko: gecko && !webkit && !window.opera && !ie,\r
+\r
+               android: android,\r
+               android23: android23,\r
+\r
+               chrome: chrome,\r
+\r
+               ie3d: ie3d,\r
+               webkit3d: webkit3d,\r
+               gecko3d: gecko3d,\r
+               opera3d: opera3d,\r
+               any3d: any3d,\r
+\r
+               mobile: mobile,\r
+               mobileWebkit: mobile && webkit,\r
+               mobileWebkit3d: mobile && webkit3d,\r
+               mobileOpera: mobile && window.opera,\r
+\r
+               touch: touch,\r
+               msPointer: msPointer,\r
+               pointer: pointer,\r
+\r
+               retina: retina\r
+       };\r
+\r
+}());\r
+
+
+/*\r
+ * L.Point represents a point with x and y coordinates.\r
+ */\r
+\r
+L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) {\r
+       this.x = (round ? Math.round(x) : x);\r
+       this.y = (round ? Math.round(y) : y);\r
+};\r
+\r
+L.Point.prototype = {\r
+\r
+       clone: function () {\r
+               return new L.Point(this.x, this.y);\r
+       },\r
+\r
+       // non-destructive, returns a new point\r
+       add: function (point) {\r
+               return this.clone()._add(L.point(point));\r
+       },\r
+\r
+       // destructive, used directly for performance in situations where it's safe to modify existing point\r
+       _add: function (point) {\r
+               this.x += point.x;\r
+               this.y += point.y;\r
+               return this;\r
+       },\r
+\r
+       subtract: function (point) {\r
+               return this.clone()._subtract(L.point(point));\r
+       },\r
+\r
+       _subtract: function (point) {\r
+               this.x -= point.x;\r
+               this.y -= point.y;\r
+               return this;\r
+       },\r
+\r
+       divideBy: function (num) {\r
+               return this.clone()._divideBy(num);\r
+       },\r
+\r
+       _divideBy: function (num) {\r
+               this.x /= num;\r
+               this.y /= num;\r
+               return this;\r
+       },\r
+\r
+       multiplyBy: function (num) {\r
+               return this.clone()._multiplyBy(num);\r
+       },\r
+\r
+       _multiplyBy: function (num) {\r
+               this.x *= num;\r
+               this.y *= num;\r
+               return this;\r
+       },\r
+\r
+       round: function () {\r
+               return this.clone()._round();\r
+       },\r
+\r
+       _round: function () {\r
+               this.x = Math.round(this.x);\r
+               this.y = Math.round(this.y);\r
+               return this;\r
+       },\r
+\r
+       floor: function () {\r
+               return this.clone()._floor();\r
+       },\r
+\r
+       _floor: function () {\r
+               this.x = Math.floor(this.x);\r
+               this.y = Math.floor(this.y);\r
+               return this;\r
+       },\r
+\r
+       distanceTo: function (point) {\r
+               point = L.point(point);\r
+\r
+               var x = point.x - this.x,\r
+                   y = point.y - this.y;\r
+\r
+               return Math.sqrt(x * x + y * y);\r
+       },\r
+\r
+       equals: function (point) {\r
+               point = L.point(point);\r
+\r
+               return point.x === this.x &&\r
+                      point.y === this.y;\r
+       },\r
+\r
+       contains: function (point) {\r
+               point = L.point(point);\r
+\r
+               return Math.abs(point.x) <= Math.abs(this.x) &&\r
+                      Math.abs(point.y) <= Math.abs(this.y);\r
+       },\r
+\r
+       toString: function () {\r
+               return 'Point(' +\r
+                       L.Util.formatNum(this.x) + ', ' +\r
+                       L.Util.formatNum(this.y) + ')';\r
+       }\r
+};\r
+\r
+L.point = function (x, y, round) {\r
+       if (x instanceof L.Point) {\r
+               return x;\r
+       }\r
+       if (L.Util.isArray(x)) {\r
+               return new L.Point(x[0], x[1]);\r
+       }\r
+       if (x === undefined || x === null) {\r
+               return x;\r
+       }\r
+       return new L.Point(x, y, round);\r
+};\r
+
+
+/*\r
+ * L.Bounds represents a rectangular area on the screen in pixel coordinates.\r
+ */\r
+\r
+L.Bounds = function (a, b) { //(Point, Point) or Point[]\r
+       if (!a) { return; }\r
+\r
+       var points = b ? [a, b] : a;\r
+\r
+       for (var i = 0, len = points.length; i < len; i++) {\r
+               this.extend(points[i]);\r
+       }\r
+};\r
+\r
+L.Bounds.prototype = {\r
+       // extend the bounds to contain the given point\r
+       extend: function (point) { // (Point)\r
+               point = L.point(point);\r
+\r
+               if (!this.min && !this.max) {\r
+                       this.min = point.clone();\r
+                       this.max = point.clone();\r
+               } else {\r
+                       this.min.x = Math.min(point.x, this.min.x);\r
+                       this.max.x = Math.max(point.x, this.max.x);\r
+                       this.min.y = Math.min(point.y, this.min.y);\r
+                       this.max.y = Math.max(point.y, this.max.y);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       getCenter: function (round) { // (Boolean) -> Point\r
+               return new L.Point(\r
+                       (this.min.x + this.max.x) / 2,\r
+                       (this.min.y + this.max.y) / 2, round);\r
+       },\r
+\r
+       getBottomLeft: function () { // -> Point\r
+               return new L.Point(this.min.x, this.max.y);\r
+       },\r
+\r
+       getTopRight: function () { // -> Point\r
+               return new L.Point(this.max.x, this.min.y);\r
+       },\r
+\r
+       getSize: function () {\r
+               return this.max.subtract(this.min);\r
+       },\r
+\r
+       contains: function (obj) { // (Bounds) or (Point) -> Boolean\r
+               var min, max;\r
+\r
+               if (typeof obj[0] === 'number' || obj instanceof L.Point) {\r
+                       obj = L.point(obj);\r
+               } else {\r
+                       obj = L.bounds(obj);\r
+               }\r
+\r
+               if (obj instanceof L.Bounds) {\r
+                       min = obj.min;\r
+                       max = obj.max;\r
+               } else {\r
+                       min = max = obj;\r
+               }\r
+\r
+               return (min.x >= this.min.x) &&\r
+                      (max.x <= this.max.x) &&\r
+                      (min.y >= this.min.y) &&\r
+                      (max.y <= this.max.y);\r
+       },\r
+\r
+       intersects: function (bounds) { // (Bounds) -> Boolean\r
+               bounds = L.bounds(bounds);\r
+\r
+               var min = this.min,\r
+                   max = this.max,\r
+                   min2 = bounds.min,\r
+                   max2 = bounds.max,\r
+                   xIntersects = (max2.x >= min.x) && (min2.x <= max.x),\r
+                   yIntersects = (max2.y >= min.y) && (min2.y <= max.y);\r
+\r
+               return xIntersects && yIntersects;\r
+       },\r
+\r
+       isValid: function () {\r
+               return !!(this.min && this.max);\r
+       }\r
+};\r
+\r
+L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[])\r
+       if (!a || a instanceof L.Bounds) {\r
+               return a;\r
+       }\r
+       return new L.Bounds(a, b);\r
+};\r
+
+
+/*\r
+ * L.Transformation is an utility class to perform simple point transformations through a 2d-matrix.\r
+ */\r
+\r
+L.Transformation = function (a, b, c, d) {\r
+       this._a = a;\r
+       this._b = b;\r
+       this._c = c;\r
+       this._d = d;\r
+};\r
+\r
+L.Transformation.prototype = {\r
+       transform: function (point, scale) { // (Point, Number) -> Point\r
+               return this._transform(point.clone(), scale);\r
+       },\r
+\r
+       // destructive transform (faster)\r
+       _transform: function (point, scale) {\r
+               scale = scale || 1;\r
+               point.x = scale * (this._a * point.x + this._b);\r
+               point.y = scale * (this._c * point.y + this._d);\r
+               return point;\r
+       },\r
+\r
+       untransform: function (point, scale) {\r
+               scale = scale || 1;\r
+               return new L.Point(\r
+                       (point.x / scale - this._b) / this._a,\r
+                       (point.y / scale - this._d) / this._c);\r
+       }\r
+};\r
+
+
+/*\r
+ * L.DomUtil contains various utility functions for working with DOM.\r
+ */\r
+\r
+L.DomUtil = {\r
+       get: function (id) {\r
+               return (typeof id === 'string' ? document.getElementById(id) : id);\r
+       },\r
+\r
+       getStyle: function (el, style) {\r
+\r
+               var value = el.style[style];\r
+\r
+               if (!value && el.currentStyle) {\r
+                       value = el.currentStyle[style];\r
+               }\r
+\r
+               if ((!value || value === 'auto') && document.defaultView) {\r
+                       var css = document.defaultView.getComputedStyle(el, null);\r
+                       value = css ? css[style] : null;\r
+               }\r
+\r
+               return value === 'auto' ? null : value;\r
+       },\r
+\r
+       getViewportOffset: function (element) {\r
+\r
+               var top = 0,\r
+                   left = 0,\r
+                   el = element,\r
+                   docBody = document.body,\r
+                   docEl = document.documentElement,\r
+                   pos;\r
+\r
+               do {\r
+                       top  += el.offsetTop  || 0;\r
+                       left += el.offsetLeft || 0;\r
+\r
+                       //add borders\r
+                       top += parseInt(L.DomUtil.getStyle(el, 'borderTopWidth'), 10) || 0;\r
+                       left += parseInt(L.DomUtil.getStyle(el, 'borderLeftWidth'), 10) || 0;\r
+\r
+                       pos = L.DomUtil.getStyle(el, 'position');\r
+\r
+                       if (el.offsetParent === docBody && pos === 'absolute') { break; }\r
+\r
+                       if (pos === 'fixed') {\r
+                               top  += docBody.scrollTop  || docEl.scrollTop  || 0;\r
+                               left += docBody.scrollLeft || docEl.scrollLeft || 0;\r
+                               break;\r
+                       }\r
+\r
+                       if (pos === 'relative' && !el.offsetLeft) {\r
+                               var width = L.DomUtil.getStyle(el, 'width'),\r
+                                   maxWidth = L.DomUtil.getStyle(el, 'max-width'),\r
+                                   r = el.getBoundingClientRect();\r
+\r
+                               if (width !== 'none' || maxWidth !== 'none') {\r
+                                       left += r.left + el.clientLeft;\r
+                               }\r
+\r
+                               //calculate full y offset since we're breaking out of the loop\r
+                               top += r.top + (docBody.scrollTop  || docEl.scrollTop  || 0);\r
+\r
+                               break;\r
+                       }\r
+\r
+                       el = el.offsetParent;\r
+\r
+               } while (el);\r
+\r
+               el = element;\r
+\r
+               do {\r
+                       if (el === docBody) { break; }\r
+\r
+                       top  -= el.scrollTop  || 0;\r
+                       left -= el.scrollLeft || 0;\r
+\r
+                       el = el.parentNode;\r
+               } while (el);\r
+\r
+               return new L.Point(left, top);\r
+       },\r
+\r
+       documentIsLtr: function () {\r
+               if (!L.DomUtil._docIsLtrCached) {\r
+                       L.DomUtil._docIsLtrCached = true;\r
+                       L.DomUtil._docIsLtr = L.DomUtil.getStyle(document.body, 'direction') === 'ltr';\r
+               }\r
+               return L.DomUtil._docIsLtr;\r
+       },\r
+\r
+       create: function (tagName, className, container) {\r
+\r
+               var el = document.createElement(tagName);\r
+               el.className = className;\r
+\r
+               if (container) {\r
+                       container.appendChild(el);\r
+               }\r
+\r
+               return el;\r
+       },\r
+\r
+       hasClass: function (el, name) {\r
+               if (el.classList !== undefined) {\r
+                       return el.classList.contains(name);\r
+               }\r
+               var className = L.DomUtil._getClass(el);\r
+               return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);\r
+       },\r
+\r
+       addClass: function (el, name) {\r
+               if (el.classList !== undefined) {\r
+                       var classes = L.Util.splitWords(name);\r
+                       for (var i = 0, len = classes.length; i < len; i++) {\r
+                               el.classList.add(classes[i]);\r
+                       }\r
+               } else if (!L.DomUtil.hasClass(el, name)) {\r
+                       var className = L.DomUtil._getClass(el);\r
+                       L.DomUtil._setClass(el, (className ? className + ' ' : '') + name);\r
+               }\r
+       },\r
+\r
+       removeClass: function (el, name) {\r
+               if (el.classList !== undefined) {\r
+                       el.classList.remove(name);\r
+               } else {\r
+                       L.DomUtil._setClass(el, L.Util.trim((' ' + L.DomUtil._getClass(el) + ' ').replace(' ' + name + ' ', ' ')));\r
+               }\r
+       },\r
+\r
+       _setClass: function (el, name) {\r
+               if (el.className.baseVal === undefined) {\r
+                       el.className = name;\r
+               } else {\r
+                       // in case of SVG element\r
+                       el.className.baseVal = name;\r
+               }\r
+       },\r
+\r
+       _getClass: function (el) {\r
+               return el.className.baseVal === undefined ? el.className : el.className.baseVal;\r
+       },\r
+\r
+       setOpacity: function (el, value) {\r
+\r
+               if ('opacity' in el.style) {\r
+                       el.style.opacity = value;\r
+\r
+               } else if ('filter' in el.style) {\r
+\r
+                       var filter = false,\r
+                           filterName = 'DXImageTransform.Microsoft.Alpha';\r
+\r
+                       // filters collection throws an error if we try to retrieve a filter that doesn't exist\r
+                       try {\r
+                               filter = el.filters.item(filterName);\r
+                       } catch (e) {\r
+                               // don't set opacity to 1 if we haven't already set an opacity,\r
+                               // it isn't needed and breaks transparent pngs.\r
+                               if (value === 1) { return; }\r
+                       }\r
+\r
+                       value = Math.round(value * 100);\r
+\r
+                       if (filter) {\r
+                               filter.Enabled = (value !== 100);\r
+                               filter.Opacity = value;\r
+                       } else {\r
+                               el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';\r
+                       }\r
+               }\r
+       },\r
+\r
+       testProp: function (props) {\r
+\r
+               var style = document.documentElement.style;\r
+\r
+               for (var i = 0; i < props.length; i++) {\r
+                       if (props[i] in style) {\r
+                               return props[i];\r
+                       }\r
+               }\r
+               return false;\r
+       },\r
+\r
+       getTranslateString: function (point) {\r
+               // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate\r
+               // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care\r
+               // (same speed either way), Opera 12 doesn't support translate3d\r
+\r
+               var is3d = L.Browser.webkit3d,\r
+                   open = 'translate' + (is3d ? '3d' : '') + '(',\r
+                   close = (is3d ? ',0' : '') + ')';\r
+\r
+               return open + point.x + 'px,' + point.y + 'px' + close;\r
+       },\r
+\r
+       getScaleString: function (scale, origin) {\r
+\r
+               var preTranslateStr = L.DomUtil.getTranslateString(origin.add(origin.multiplyBy(-1 * scale))),\r
+                   scaleStr = ' scale(' + scale + ') ';\r
+\r
+               return preTranslateStr + scaleStr;\r
+       },\r
+\r
+       setPosition: function (el, point, disable3D) { // (HTMLElement, Point[, Boolean])\r
+\r
+               // jshint camelcase: false\r
+               el._leaflet_pos = point;\r
+\r
+               if (!disable3D && L.Browser.any3d) {\r
+                       el.style[L.DomUtil.TRANSFORM] =  L.DomUtil.getTranslateString(point);\r
+               } else {\r
+                       el.style.left = point.x + 'px';\r
+                       el.style.top = point.y + 'px';\r
+               }\r
+       },\r
+\r
+       getPosition: function (el) {\r
+               // this method is only used for elements previously positioned using setPosition,\r
+               // so it's safe to cache the position for performance\r
+\r
+               // jshint camelcase: false\r
+               return el._leaflet_pos;\r
+       }\r
+};\r
+\r
+\r
+// prefix style property names\r
+\r
+L.DomUtil.TRANSFORM = L.DomUtil.testProp(\r
+        ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);\r
+\r
+// webkitTransition comes first because some browser versions that drop vendor prefix don't do\r
+// the same for the transitionend event, in particular the Android 4.1 stock browser\r
+\r
+L.DomUtil.TRANSITION = L.DomUtil.testProp(\r
+        ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);\r
+\r
+L.DomUtil.TRANSITION_END =\r
+        L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ?\r
+        L.DomUtil.TRANSITION + 'End' : 'transitionend';\r
+\r
+(function () {\r
+    if ('onselectstart' in document) {\r
+        L.extend(L.DomUtil, {\r
+            disableTextSelection: function () {\r
+                L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);\r
+            },\r
+\r
+            enableTextSelection: function () {\r
+                L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);\r
+            }\r
+        });\r
+    } else {\r
+        var userSelectProperty = L.DomUtil.testProp(\r
+            ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);\r
+\r
+        L.extend(L.DomUtil, {\r
+            disableTextSelection: function () {\r
+                if (userSelectProperty) {\r
+                    var style = document.documentElement.style;\r
+                    this._userSelect = style[userSelectProperty];\r
+                    style[userSelectProperty] = 'none';\r
+                }\r
+            },\r
+\r
+            enableTextSelection: function () {\r
+                if (userSelectProperty) {\r
+                    document.documentElement.style[userSelectProperty] = this._userSelect;\r
+                    delete this._userSelect;\r
+                }\r
+            }\r
+        });\r
+    }\r
+\r
+       L.extend(L.DomUtil, {\r
+               disableImageDrag: function () {\r
+                       L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);\r
+               },\r
+\r
+               enableImageDrag: function () {\r
+                       L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);\r
+               }\r
+       });\r
+})();\r
+
+
+/*\r
+ * L.LatLng represents a geographical point with latitude and longitude coordinates.\r
+ */\r
+\r
+L.LatLng = function (lat, lng, alt) { // (Number, Number, Number)\r
+       lat = parseFloat(lat);\r
+       lng = parseFloat(lng);\r
+\r
+       if (isNaN(lat) || isNaN(lng)) {\r
+               throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');\r
+       }\r
+\r
+       this.lat = lat;\r
+       this.lng = lng;\r
+\r
+       if (alt !== undefined) {\r
+               this.alt = parseFloat(alt);\r
+       }\r
+};\r
+\r
+L.extend(L.LatLng, {\r
+       DEG_TO_RAD: Math.PI / 180,\r
+       RAD_TO_DEG: 180 / Math.PI,\r
+       MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check\r
+});\r
+\r
+L.LatLng.prototype = {\r
+       equals: function (obj) { // (LatLng) -> Boolean\r
+               if (!obj) { return false; }\r
+\r
+               obj = L.latLng(obj);\r
+\r
+               var margin = Math.max(\r
+                       Math.abs(this.lat - obj.lat),\r
+                       Math.abs(this.lng - obj.lng));\r
+\r
+               return margin <= L.LatLng.MAX_MARGIN;\r
+       },\r
+\r
+       toString: function (precision) { // (Number) -> String\r
+               return 'LatLng(' +\r
+                       L.Util.formatNum(this.lat, precision) + ', ' +\r
+                       L.Util.formatNum(this.lng, precision) + ')';\r
+       },\r
+\r
+       // Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula\r
+       // TODO move to projection code, LatLng shouldn't know about Earth\r
+       distanceTo: function (other) { // (LatLng) -> Number\r
+               other = L.latLng(other);\r
+\r
+               var R = 6378137, // earth radius in meters\r
+                   d2r = L.LatLng.DEG_TO_RAD,\r
+                   dLat = (other.lat - this.lat) * d2r,\r
+                   dLon = (other.lng - this.lng) * d2r,\r
+                   lat1 = this.lat * d2r,\r
+                   lat2 = other.lat * d2r,\r
+                   sin1 = Math.sin(dLat / 2),\r
+                   sin2 = Math.sin(dLon / 2);\r
+\r
+               var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);\r
+\r
+               return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\r
+       },\r
+\r
+       wrap: function (a, b) { // (Number, Number) -> LatLng\r
+               var lng = this.lng;\r
+\r
+               a = a || -180;\r
+               b = b ||  180;\r
+\r
+               lng = (lng + b) % (b - a) + (lng < a || lng === b ? b : a);\r
+\r
+               return new L.LatLng(this.lat, lng);\r
+       }\r
+};\r
+\r
+L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Number)\r
+       if (a instanceof L.LatLng) {\r
+               return a;\r
+       }\r
+       if (L.Util.isArray(a)) {\r
+               if (typeof a[0] === 'number' || typeof a[0] === 'string') {\r
+                       return new L.LatLng(a[0], a[1], a[2]);\r
+               } else {\r
+                       return null;\r
+               }\r
+       }\r
+       if (a === undefined || a === null) {\r
+               return a;\r
+       }\r
+       if (typeof a === 'object' && 'lat' in a) {\r
+               return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon);\r
+       }\r
+       if (b === undefined) {\r
+               return null;\r
+       }\r
+       return new L.LatLng(a, b);\r
+};\r
+\r
+
+
+/*\r
+ * L.LatLngBounds represents a rectangular area on the map in geographical coordinates.\r
+ */\r
+\r
+L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])\r
+       if (!southWest) { return; }\r
+\r
+       var latlngs = northEast ? [southWest, northEast] : southWest;\r
+\r
+       for (var i = 0, len = latlngs.length; i < len; i++) {\r
+               this.extend(latlngs[i]);\r
+       }\r
+};\r
+\r
+L.LatLngBounds.prototype = {\r
+       // extend the bounds to contain the given point or bounds\r
+       extend: function (obj) { // (LatLng) or (LatLngBounds)\r
+               if (!obj) { return this; }\r
+\r
+               var latLng = L.latLng(obj);\r
+               if (latLng !== null) {\r
+                       obj = latLng;\r
+               } else {\r
+                       obj = L.latLngBounds(obj);\r
+               }\r
+\r
+               if (obj instanceof L.LatLng) {\r
+                       if (!this._southWest && !this._northEast) {\r
+                               this._southWest = new L.LatLng(obj.lat, obj.lng);\r
+                               this._northEast = new L.LatLng(obj.lat, obj.lng);\r
+                       } else {\r
+                               this._southWest.lat = Math.min(obj.lat, this._southWest.lat);\r
+                               this._southWest.lng = Math.min(obj.lng, this._southWest.lng);\r
+\r
+                               this._northEast.lat = Math.max(obj.lat, this._northEast.lat);\r
+                               this._northEast.lng = Math.max(obj.lng, this._northEast.lng);\r
+                       }\r
+               } else if (obj instanceof L.LatLngBounds) {\r
+                       this.extend(obj._southWest);\r
+                       this.extend(obj._northEast);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       // extend the bounds by a percentage\r
+       pad: function (bufferRatio) { // (Number) -> LatLngBounds\r
+               var sw = this._southWest,\r
+                   ne = this._northEast,\r
+                   heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,\r
+                   widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;\r
+\r
+               return new L.LatLngBounds(\r
+                       new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),\r
+                       new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));\r
+       },\r
+\r
+       getCenter: function () { // -> LatLng\r
+               return new L.LatLng(\r
+                       (this._southWest.lat + this._northEast.lat) / 2,\r
+                       (this._southWest.lng + this._northEast.lng) / 2);\r
+       },\r
+\r
+       getSouthWest: function () {\r
+               return this._southWest;\r
+       },\r
+\r
+       getNorthEast: function () {\r
+               return this._northEast;\r
+       },\r
+\r
+       getNorthWest: function () {\r
+               return new L.LatLng(this.getNorth(), this.getWest());\r
+       },\r
+\r
+       getSouthEast: function () {\r
+               return new L.LatLng(this.getSouth(), this.getEast());\r
+       },\r
+\r
+       getWest: function () {\r
+               return this._southWest.lng;\r
+       },\r
+\r
+       getSouth: function () {\r
+               return this._southWest.lat;\r
+       },\r
+\r
+       getEast: function () {\r
+               return this._northEast.lng;\r
+       },\r
+\r
+       getNorth: function () {\r
+               return this._northEast.lat;\r
+       },\r
+\r
+       contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean\r
+               if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {\r
+                       obj = L.latLng(obj);\r
+               } else {\r
+                       obj = L.latLngBounds(obj);\r
+               }\r
+\r
+               var sw = this._southWest,\r
+                   ne = this._northEast,\r
+                   sw2, ne2;\r
+\r
+               if (obj instanceof L.LatLngBounds) {\r
+                       sw2 = obj.getSouthWest();\r
+                       ne2 = obj.getNorthEast();\r
+               } else {\r
+                       sw2 = ne2 = obj;\r
+               }\r
+\r
+               return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&\r
+                      (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);\r
+       },\r
+\r
+       intersects: function (bounds) { // (LatLngBounds)\r
+               bounds = L.latLngBounds(bounds);\r
+\r
+               var sw = this._southWest,\r
+                   ne = this._northEast,\r
+                   sw2 = bounds.getSouthWest(),\r
+                   ne2 = bounds.getNorthEast(),\r
+\r
+                   latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),\r
+                   lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);\r
+\r
+               return latIntersects && lngIntersects;\r
+       },\r
+\r
+       toBBoxString: function () {\r
+               return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');\r
+       },\r
+\r
+       equals: function (bounds) { // (LatLngBounds)\r
+               if (!bounds) { return false; }\r
+\r
+               bounds = L.latLngBounds(bounds);\r
+\r
+               return this._southWest.equals(bounds.getSouthWest()) &&\r
+                      this._northEast.equals(bounds.getNorthEast());\r
+       },\r
+\r
+       isValid: function () {\r
+               return !!(this._southWest && this._northEast);\r
+       }\r
+};\r
+\r
+//TODO International date line?\r
+\r
+L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng)\r
+       if (!a || a instanceof L.LatLngBounds) {\r
+               return a;\r
+       }\r
+       return new L.LatLngBounds(a, b);\r
+};\r
+
+
+/*\r
+ * L.Projection contains various geographical projections used by CRS classes.\r
+ */\r
+\r
+L.Projection = {};\r
+
+
+/*\r
+ * Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default.\r
+ */\r
+\r
+L.Projection.SphericalMercator = {\r
+       MAX_LATITUDE: 85.0511287798,\r
+\r
+       project: function (latlng) { // (LatLng) -> Point\r
+               var d = L.LatLng.DEG_TO_RAD,\r
+                   max = this.MAX_LATITUDE,\r
+                   lat = Math.max(Math.min(max, latlng.lat), -max),\r
+                   x = latlng.lng * d,\r
+                   y = lat * d;\r
+\r
+               y = Math.log(Math.tan((Math.PI / 4) + (y / 2)));\r
+\r
+               return new L.Point(x, y);\r
+       },\r
+\r
+       unproject: function (point) { // (Point, Boolean) -> LatLng\r
+               var d = L.LatLng.RAD_TO_DEG,\r
+                   lng = point.x * d,\r
+                   lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d;\r
+\r
+               return new L.LatLng(lat, lng);\r
+       }\r
+};\r
+
+
+/*\r
+ * Simple equirectangular (Plate Carree) projection, used by CRS like EPSG:4326 and Simple.\r
+ */\r
+\r
+L.Projection.LonLat = {\r
+       project: function (latlng) {\r
+               return new L.Point(latlng.lng, latlng.lat);\r
+       },\r
+\r
+       unproject: function (point) {\r
+               return new L.LatLng(point.y, point.x);\r
+       }\r
+};\r
+
+
+/*\r
+ * L.CRS is a base object for all defined CRS (Coordinate Reference Systems) in Leaflet.\r
+ */\r
+\r
+L.CRS = {\r
+       latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point\r
+               var projectedPoint = this.projection.project(latlng),\r
+                   scale = this.scale(zoom);\r
+\r
+               return this.transformation._transform(projectedPoint, scale);\r
+       },\r
+\r
+       pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng\r
+               var scale = this.scale(zoom),\r
+                   untransformedPoint = this.transformation.untransform(point, scale);\r
+\r
+               return this.projection.unproject(untransformedPoint);\r
+       },\r
+\r
+       project: function (latlng) {\r
+               return this.projection.project(latlng);\r
+       },\r
+\r
+       scale: function (zoom) {\r
+               return 256 * Math.pow(2, zoom);\r
+       },\r
+\r
+       getSize: function (zoom) {\r
+               var s = this.scale(zoom);\r
+               return L.point(s, s);\r
+       }\r
+};\r
+
+
+/*
+ * A simple CRS that can be used for flat non-Earth maps like panoramas or game maps.
+ */
+
+L.CRS.Simple = L.extend({}, L.CRS, {
+       projection: L.Projection.LonLat,
+       transformation: new L.Transformation(1, 0, -1, 0),
+
+       scale: function (zoom) {
+               return Math.pow(2, zoom);
+       }
+});
+
+
+/*\r
+ * L.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping\r
+ * and is used by Leaflet by default.\r
+ */\r
+\r
+L.CRS.EPSG3857 = L.extend({}, L.CRS, {\r
+       code: 'EPSG:3857',\r
+\r
+       projection: L.Projection.SphericalMercator,\r
+       transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5),\r
+\r
+       project: function (latlng) { // (LatLng) -> Point\r
+               var projectedPoint = this.projection.project(latlng),\r
+                   earthRadius = 6378137;\r
+               return projectedPoint.multiplyBy(earthRadius);\r
+       }\r
+});\r
+\r
+L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {\r
+       code: 'EPSG:900913'\r
+});\r
+
+
+/*\r
+ * L.CRS.EPSG4326 is a CRS popular among advanced GIS specialists.\r
+ */\r
+\r
+L.CRS.EPSG4326 = L.extend({}, L.CRS, {\r
+       code: 'EPSG:4326',\r
+\r
+       projection: L.Projection.LonLat,\r
+       transformation: new L.Transformation(1 / 360, 0.5, -1 / 360, 0.5)\r
+});\r
+
+
+/*\r
+ * L.Map is the central class of the API - it is used to create a map.\r
+ */\r
+\r
+L.Map = L.Class.extend({\r
+\r
+       includes: L.Mixin.Events,\r
+\r
+       options: {\r
+               crs: L.CRS.EPSG3857,\r
+\r
+               /*\r
+               center: LatLng,\r
+               zoom: Number,\r
+               layers: Array,\r
+               */\r
+\r
+               fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23,\r
+               trackResize: true,\r
+               markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d\r
+       },\r
+\r
+       initialize: function (id, options) { // (HTMLElement or String, Object)\r
+               options = L.setOptions(this, options);\r
+\r
+\r
+               this._initContainer(id);\r
+               this._initLayout();\r
+\r
+               // hack for https://github.com/Leaflet/Leaflet/issues/1980\r
+               this._onResize = L.bind(this._onResize, this);\r
+\r
+               this._initEvents();\r
+\r
+               if (options.maxBounds) {\r
+                       this.setMaxBounds(options.maxBounds);\r
+               }\r
+\r
+               if (options.center && options.zoom !== undefined) {\r
+                       this.setView(L.latLng(options.center), options.zoom, {reset: true});\r
+               }\r
+\r
+               this._handlers = [];\r
+\r
+               this._layers = {};\r
+               this._zoomBoundLayers = {};\r
+               this._tileLayersNum = 0;\r
+\r
+               this.callInitHooks();\r
+\r
+               this._addLayers(options.layers);\r
+       },\r
+\r
+\r
+       // public methods that modify map state\r
+\r
+       // replaced by animation-powered implementation in Map.PanAnimation.js\r
+       setView: function (center, zoom) {\r
+               zoom = zoom === undefined ? this.getZoom() : zoom;\r
+               this._resetView(L.latLng(center), this._limitZoom(zoom));\r
+               return this;\r
+       },\r
+\r
+       setZoom: function (zoom, options) {\r
+               if (!this._loaded) {\r
+                       this._zoom = this._limitZoom(zoom);\r
+                       return this;\r
+               }\r
+               return this.setView(this.getCenter(), zoom, {zoom: options});\r
+       },\r
+\r
+       zoomIn: function (delta, options) {\r
+               return this.setZoom(this._zoom + (delta || 1), options);\r
+       },\r
+\r
+       zoomOut: function (delta, options) {\r
+               return this.setZoom(this._zoom - (delta || 1), options);\r
+       },\r
+\r
+       setZoomAround: function (latlng, zoom, options) {\r
+               var scale = this.getZoomScale(zoom),\r
+                   viewHalf = this.getSize().divideBy(2),\r
+                   containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),\r
+\r
+                   centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),\r
+                   newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));\r
+\r
+               return this.setView(newCenter, zoom, {zoom: options});\r
+       },\r
+\r
+       fitBounds: function (bounds, options) {\r
+\r
+               options = options || {};\r
+               bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);\r
+\r
+               var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),\r
+                   paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),\r
+\r
+                   zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));\r
+\r
+               zoom = (options.maxZoom) ? Math.min(options.maxZoom, zoom) : zoom;\r
+\r
+               var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),\r
+\r
+                   swPoint = this.project(bounds.getSouthWest(), zoom),\r
+                   nePoint = this.project(bounds.getNorthEast(), zoom),\r
+                   center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);\r
+\r
+               return this.setView(center, zoom, options);\r
+       },\r
+\r
+       fitWorld: function (options) {\r
+               return this.fitBounds([[-90, -180], [90, 180]], options);\r
+       },\r
+\r
+       panTo: function (center, options) { // (LatLng)\r
+               return this.setView(center, this._zoom, {pan: options});\r
+       },\r
+\r
+       panBy: function (offset) { // (Point)\r
+               // replaced with animated panBy in Map.PanAnimation.js\r
+               this.fire('movestart');\r
+\r
+               this._rawPanBy(L.point(offset));\r
+\r
+               this.fire('move');\r
+               return this.fire('moveend');\r
+       },\r
+\r
+       setMaxBounds: function (bounds) {\r
+               bounds = L.latLngBounds(bounds);\r
+\r
+               this.options.maxBounds = bounds;\r
+\r
+               if (!bounds) {\r
+                       return this.off('moveend', this._panInsideMaxBounds, this);\r
+               }\r
+\r
+               if (this._loaded) {\r
+                       this._panInsideMaxBounds();\r
+               }\r
+\r
+               return this.on('moveend', this._panInsideMaxBounds, this);\r
+       },\r
+\r
+       panInsideBounds: function (bounds, options) {\r
+               var center = this.getCenter(),\r
+                       newCenter = this._limitCenter(center, this._zoom, bounds);\r
+\r
+               if (center.equals(newCenter)) { return this; }\r
+\r
+               return this.panTo(newCenter, options);\r
+       },\r
+\r
+       addLayer: function (layer) {\r
+               // TODO method is too big, refactor\r
+\r
+               var id = L.stamp(layer);\r
+\r
+               if (this._layers[id]) { return this; }\r
+\r
+               this._layers[id] = layer;\r
+\r
+               // TODO getMaxZoom, getMinZoom in ILayer (instead of options)\r
+               if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) {\r
+                       this._zoomBoundLayers[id] = layer;\r
+                       this._updateZoomLevels();\r
+               }\r
+\r
+               // TODO looks ugly, refactor!!!\r
+               if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {\r
+                       this._tileLayersNum++;\r
+                       this._tileLayersToLoad++;\r
+                       layer.on('load', this._onTileLayerLoad, this);\r
+               }\r
+\r
+               if (this._loaded) {\r
+                       this._layerAdd(layer);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       removeLayer: function (layer) {\r
+               var id = L.stamp(layer);\r
+\r
+               if (!this._layers[id]) { return this; }\r
+\r
+               if (this._loaded) {\r
+                       layer.onRemove(this);\r
+               }\r
+\r
+               delete this._layers[id];\r
+\r
+               if (this._loaded) {\r
+                       this.fire('layerremove', {layer: layer});\r
+               }\r
+\r
+               if (this._zoomBoundLayers[id]) {\r
+                       delete this._zoomBoundLayers[id];\r
+                       this._updateZoomLevels();\r
+               }\r
+\r
+               // TODO looks ugly, refactor\r
+               if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {\r
+                       this._tileLayersNum--;\r
+                       this._tileLayersToLoad--;\r
+                       layer.off('load', this._onTileLayerLoad, this);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       hasLayer: function (layer) {\r
+               if (!layer) { return false; }\r
+\r
+               return (L.stamp(layer) in this._layers);\r
+       },\r
+\r
+       eachLayer: function (method, context) {\r
+               for (var i in this._layers) {\r
+                       method.call(context, this._layers[i]);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       invalidateSize: function (options) {\r
+               if (!this._loaded) { return this; }\r
+\r
+               options = L.extend({\r
+                       animate: false,\r
+                       pan: true\r
+               }, options === true ? {animate: true} : options);\r
+\r
+               var oldSize = this.getSize();\r
+               this._sizeChanged = true;\r
+               this._initialCenter = null;\r
+\r
+               var newSize = this.getSize(),\r
+                   oldCenter = oldSize.divideBy(2).round(),\r
+                   newCenter = newSize.divideBy(2).round(),\r
+                   offset = oldCenter.subtract(newCenter);\r
+\r
+               if (!offset.x && !offset.y) { return this; }\r
+\r
+               if (options.animate && options.pan) {\r
+                       this.panBy(offset);\r
+\r
+               } else {\r
+                       if (options.pan) {\r
+                               this._rawPanBy(offset);\r
+                       }\r
+\r
+                       this.fire('move');\r
+\r
+                       if (options.debounceMoveend) {\r
+                               clearTimeout(this._sizeTimer);\r
+                               this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);\r
+                       } else {\r
+                               this.fire('moveend');\r
+                       }\r
+               }\r
+\r
+               return this.fire('resize', {\r
+                       oldSize: oldSize,\r
+                       newSize: newSize\r
+               });\r
+       },\r
+\r
+       // TODO handler.addTo\r
+       addHandler: function (name, HandlerClass) {\r
+               if (!HandlerClass) { return this; }\r
+\r
+               var handler = this[name] = new HandlerClass(this);\r
+\r
+               this._handlers.push(handler);\r
+\r
+               if (this.options[name]) {\r
+                       handler.enable();\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       remove: function () {\r
+               if (this._loaded) {\r
+                       this.fire('unload');\r
+               }\r
+\r
+               this._initEvents('off');\r
+\r
+               try {\r
+                       // throws error in IE6-8\r
+                       delete this._container._leaflet;\r
+               } catch (e) {\r
+                       this._container._leaflet = undefined;\r
+               }\r
+\r
+               this._clearPanes();\r
+               if (this._clearControlPos) {\r
+                       this._clearControlPos();\r
+               }\r
+\r
+               this._clearHandlers();\r
+\r
+               return this;\r
+       },\r
+\r
+\r
+       // public methods for getting map state\r
+\r
+       getCenter: function () { // (Boolean) -> LatLng\r
+               this._checkIfLoaded();\r
+\r
+               if (this._initialCenter && !this._moved()) {\r
+                       return this._initialCenter;\r
+               }\r
+               return this.layerPointToLatLng(this._getCenterLayerPoint());\r
+       },\r
+\r
+       getZoom: function () {\r
+               return this._zoom;\r
+       },\r
+\r
+       getBounds: function () {\r
+               var bounds = this.getPixelBounds(),\r
+                   sw = this.unproject(bounds.getBottomLeft()),\r
+                   ne = this.unproject(bounds.getTopRight());\r
+\r
+               return new L.LatLngBounds(sw, ne);\r
+       },\r
+\r
+       getMinZoom: function () {\r
+               return this.options.minZoom === undefined ?\r
+                       (this._layersMinZoom === undefined ? 0 : this._layersMinZoom) :\r
+                       this.options.minZoom;\r
+       },\r
+\r
+       getMaxZoom: function () {\r
+               return this.options.maxZoom === undefined ?\r
+                       (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :\r
+                       this.options.maxZoom;\r
+       },\r
+\r
+       getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number\r
+               bounds = L.latLngBounds(bounds);\r
+\r
+               var zoom = this.getMinZoom() - (inside ? 1 : 0),\r
+                   maxZoom = this.getMaxZoom(),\r
+                   size = this.getSize(),\r
+\r
+                   nw = bounds.getNorthWest(),\r
+                   se = bounds.getSouthEast(),\r
+\r
+                   zoomNotFound = true,\r
+                   boundsSize;\r
+\r
+               padding = L.point(padding || [0, 0]);\r
+\r
+               do {\r
+                       zoom++;\r
+                       boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)).add(padding);\r
+                       zoomNotFound = !inside ? size.contains(boundsSize) : boundsSize.x < size.x || boundsSize.y < size.y;\r
+\r
+               } while (zoomNotFound && zoom <= maxZoom);\r
+\r
+               if (zoomNotFound && inside) {\r
+                       return null;\r
+               }\r
+\r
+               return inside ? zoom : zoom - 1;\r
+       },\r
+\r
+       getSize: function () {\r
+               if (!this._size || this._sizeChanged) {\r
+                       this._size = new L.Point(\r
+                               this._container.clientWidth,\r
+                               this._container.clientHeight);\r
+\r
+                       this._sizeChanged = false;\r
+               }\r
+               return this._size.clone();\r
+       },\r
+\r
+       getPixelBounds: function () {\r
+               var topLeftPoint = this._getTopLeftPoint();\r
+               return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));\r
+       },\r
+\r
+       getPixelOrigin: function () {\r
+               this._checkIfLoaded();\r
+               return this._initialTopLeftPoint;\r
+       },\r
+\r
+       getPanes: function () {\r
+               return this._panes;\r
+       },\r
+\r
+       getContainer: function () {\r
+               return this._container;\r
+       },\r
+\r
+\r
+       // TODO replace with universal implementation after refactoring projections\r
+\r
+       getZoomScale: function (toZoom) {\r
+               var crs = this.options.crs;\r
+               return crs.scale(toZoom) / crs.scale(this._zoom);\r
+       },\r
+\r
+       getScaleZoom: function (scale) {\r
+               return this._zoom + (Math.log(scale) / Math.LN2);\r
+       },\r
+\r
+\r
+       // conversion methods\r
+\r
+       project: function (latlng, zoom) { // (LatLng[, Number]) -> Point\r
+               zoom = zoom === undefined ? this._zoom : zoom;\r
+               return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);\r
+       },\r
+\r
+       unproject: function (point, zoom) { // (Point[, Number]) -> LatLng\r
+               zoom = zoom === undefined ? this._zoom : zoom;\r
+               return this.options.crs.pointToLatLng(L.point(point), zoom);\r
+       },\r
+\r
+       layerPointToLatLng: function (point) { // (Point)\r
+               var projectedPoint = L.point(point).add(this.getPixelOrigin());\r
+               return this.unproject(projectedPoint);\r
+       },\r
+\r
+       latLngToLayerPoint: function (latlng) { // (LatLng)\r
+               var projectedPoint = this.project(L.latLng(latlng))._round();\r
+               return projectedPoint._subtract(this.getPixelOrigin());\r
+       },\r
+\r
+       containerPointToLayerPoint: function (point) { // (Point)\r
+               return L.point(point).subtract(this._getMapPanePos());\r
+       },\r
+\r
+       layerPointToContainerPoint: function (point) { // (Point)\r
+               return L.point(point).add(this._getMapPanePos());\r
+       },\r
+\r
+       containerPointToLatLng: function (point) {\r
+               var layerPoint = this.containerPointToLayerPoint(L.point(point));\r
+               return this.layerPointToLatLng(layerPoint);\r
+       },\r
+\r
+       latLngToContainerPoint: function (latlng) {\r
+               return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));\r
+       },\r
+\r
+       mouseEventToContainerPoint: function (e) { // (MouseEvent)\r
+               return L.DomEvent.getMousePosition(e, this._container);\r
+       },\r
+\r
+       mouseEventToLayerPoint: function (e) { // (MouseEvent)\r
+               return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));\r
+       },\r
+\r
+       mouseEventToLatLng: function (e) { // (MouseEvent)\r
+               return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));\r
+       },\r
+\r
+\r
+       // map initialization methods\r
+\r
+       _initContainer: function (id) {\r
+               var container = this._container = L.DomUtil.get(id);\r
+\r
+               if (!container) {\r
+                       throw new Error('Map container not found.');\r
+               } else if (container._leaflet) {\r
+                       throw new Error('Map container is already initialized.');\r
+               }\r
+\r
+               container._leaflet = true;\r
+       },\r
+\r
+       _initLayout: function () {\r
+               var container = this._container;\r
+\r
+               L.DomUtil.addClass(container, 'leaflet-container' +\r
+                       (L.Browser.touch ? ' leaflet-touch' : '') +\r
+                       (L.Browser.retina ? ' leaflet-retina' : '') +\r
+                       (L.Browser.ielt9 ? ' leaflet-oldie' : '') +\r
+                       (this.options.fadeAnimation ? ' leaflet-fade-anim' : ''));\r
+\r
+               var position = L.DomUtil.getStyle(container, 'position');\r
+\r
+               if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {\r
+                       container.style.position = 'relative';\r
+               }\r
+\r
+               this._initPanes();\r
+\r
+               if (this._initControlPos) {\r
+                       this._initControlPos();\r
+               }\r
+       },\r
+\r
+       _initPanes: function () {\r
+               var panes = this._panes = {};\r
+\r
+               this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container);\r
+\r
+               this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane);\r
+               panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane);\r
+               panes.shadowPane = this._createPane('leaflet-shadow-pane');\r
+               panes.overlayPane = this._createPane('leaflet-overlay-pane');\r
+               panes.markerPane = this._createPane('leaflet-marker-pane');\r
+               panes.popupPane = this._createPane('leaflet-popup-pane');\r
+\r
+               var zoomHide = ' leaflet-zoom-hide';\r
+\r
+               if (!this.options.markerZoomAnimation) {\r
+                       L.DomUtil.addClass(panes.markerPane, zoomHide);\r
+                       L.DomUtil.addClass(panes.shadowPane, zoomHide);\r
+                       L.DomUtil.addClass(panes.popupPane, zoomHide);\r
+               }\r
+       },\r
+\r
+       _createPane: function (className, container) {\r
+               return L.DomUtil.create('div', className, container || this._panes.objectsPane);\r
+       },\r
+\r
+       _clearPanes: function () {\r
+               this._container.removeChild(this._mapPane);\r
+       },\r
+\r
+       _addLayers: function (layers) {\r
+               layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];\r
+\r
+               for (var i = 0, len = layers.length; i < len; i++) {\r
+                       this.addLayer(layers[i]);\r
+               }\r
+       },\r
+\r
+\r
+       // private methods that modify map state\r
+\r
+       _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) {\r
+\r
+               var zoomChanged = (this._zoom !== zoom);\r
+\r
+               if (!afterZoomAnim) {\r
+                       this.fire('movestart');\r
+\r
+                       if (zoomChanged) {\r
+                               this.fire('zoomstart');\r
+                       }\r
+               }\r
+\r
+               this._zoom = zoom;\r
+               this._initialCenter = center;\r
+\r
+               this._initialTopLeftPoint = this._getNewTopLeftPoint(center);\r
+\r
+               if (!preserveMapOffset) {\r
+                       L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));\r
+               } else {\r
+                       this._initialTopLeftPoint._add(this._getMapPanePos());\r
+               }\r
+\r
+               this._tileLayersToLoad = this._tileLayersNum;\r
+\r
+               var loading = !this._loaded;\r
+               this._loaded = true;\r
+\r
+               this.fire('viewreset', {hard: !preserveMapOffset});\r
+\r
+               if (loading) {\r
+                       this.fire('load');\r
+                       this.eachLayer(this._layerAdd, this);\r
+               }\r
+\r
+               this.fire('move');\r
+\r
+               if (zoomChanged || afterZoomAnim) {\r
+                       this.fire('zoomend');\r
+               }\r
+\r
+               this.fire('moveend', {hard: !preserveMapOffset});\r
+       },\r
+\r
+       _rawPanBy: function (offset) {\r
+               L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));\r
+       },\r
+\r
+       _getZoomSpan: function () {\r
+               return this.getMaxZoom() - this.getMinZoom();\r
+       },\r
+\r
+       _updateZoomLevels: function () {\r
+               var i,\r
+                       minZoom = Infinity,\r
+                       maxZoom = -Infinity,\r
+                       oldZoomSpan = this._getZoomSpan();\r
+\r
+               for (i in this._zoomBoundLayers) {\r
+                       var layer = this._zoomBoundLayers[i];\r
+                       if (!isNaN(layer.options.minZoom)) {\r
+                               minZoom = Math.min(minZoom, layer.options.minZoom);\r
+                       }\r
+                       if (!isNaN(layer.options.maxZoom)) {\r
+                               maxZoom = Math.max(maxZoom, layer.options.maxZoom);\r
+                       }\r
+               }\r
+\r
+               if (i === undefined) { // we have no tilelayers\r
+                       this._layersMaxZoom = this._layersMinZoom = undefined;\r
+               } else {\r
+                       this._layersMaxZoom = maxZoom;\r
+                       this._layersMinZoom = minZoom;\r
+               }\r
+\r
+               if (oldZoomSpan !== this._getZoomSpan()) {\r
+                       this.fire('zoomlevelschange');\r
+               }\r
+       },\r
+\r
+       _panInsideMaxBounds: function () {\r
+               this.panInsideBounds(this.options.maxBounds);\r
+       },\r
+\r
+       _checkIfLoaded: function () {\r
+               if (!this._loaded) {\r
+                       throw new Error('Set map center and zoom first.');\r
+               }\r
+       },\r
+\r
+       // map events\r
+\r
+       _initEvents: function (onOff) {\r
+               if (!L.DomEvent) { return; }\r
+\r
+               onOff = onOff || 'on';\r
+\r
+               L.DomEvent[onOff](this._container, 'click', this._onMouseClick, this);\r
+\r
+               var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter',\r
+                             'mouseleave', 'mousemove', 'contextmenu'],\r
+                   i, len;\r
+\r
+               for (i = 0, len = events.length; i < len; i++) {\r
+                       L.DomEvent[onOff](this._container, events[i], this._fireMouseEvent, this);\r
+               }\r
+\r
+               if (this.options.trackResize) {\r
+                       L.DomEvent[onOff](window, 'resize', this._onResize, this);\r
+               }\r
+       },\r
+\r
+       _onResize: function () {\r
+               L.Util.cancelAnimFrame(this._resizeRequest);\r
+               this._resizeRequest = L.Util.requestAnimFrame(\r
+                       function () { this.invalidateSize({debounceMoveend: true}); }, this, false, this._container);\r
+       },\r
+\r
+       _onMouseClick: function (e) {\r
+               if (!this._loaded || (!e._simulated &&\r
+                       ((this.dragging && this.dragging.moved()) ||\r
+                        (this.boxZoom  && this.boxZoom.moved()))) ||\r
+                           L.DomEvent._skipped(e)) { return; }\r
+\r
+               this.fire('preclick');\r
+               this._fireMouseEvent(e);\r
+       },\r
+\r
+       _fireMouseEvent: function (e) {\r
+               if (!this._loaded || L.DomEvent._skipped(e)) { return; }\r
+\r
+               var type = e.type;\r
+\r
+               type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type));\r
+\r
+               if (!this.hasEventListeners(type)) { return; }\r
+\r
+               if (type === 'contextmenu') {\r
+                       L.DomEvent.preventDefault(e);\r
+               }\r
+\r
+               var containerPoint = this.mouseEventToContainerPoint(e),\r
+                   layerPoint = this.containerPointToLayerPoint(containerPoint),\r
+                   latlng = this.layerPointToLatLng(layerPoint);\r
+\r
+               this.fire(type, {\r
+                       latlng: latlng,\r
+                       layerPoint: layerPoint,\r
+                       containerPoint: containerPoint,\r
+                       originalEvent: e\r
+               });\r
+       },\r
+\r
+       _onTileLayerLoad: function () {\r
+               this._tileLayersToLoad--;\r
+               if (this._tileLayersNum && !this._tileLayersToLoad) {\r
+                       this.fire('tilelayersload');\r
+               }\r
+       },\r
+\r
+       _clearHandlers: function () {\r
+               for (var i = 0, len = this._handlers.length; i < len; i++) {\r
+                       this._handlers[i].disable();\r
+               }\r
+       },\r
+\r
+       whenReady: function (callback, context) {\r
+               if (this._loaded) {\r
+                       callback.call(context || this, this);\r
+               } else {\r
+                       this.on('load', callback, context);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       _layerAdd: function (layer) {\r
+               layer.onAdd(this);\r
+               this.fire('layeradd', {layer: layer});\r
+       },\r
+\r
+\r
+       // private methods for getting map state\r
+\r
+       _getMapPanePos: function () {\r
+               return L.DomUtil.getPosition(this._mapPane);\r
+       },\r
+\r
+       _moved: function () {\r
+               var pos = this._getMapPanePos();\r
+               return pos && !pos.equals([0, 0]);\r
+       },\r
+\r
+       _getTopLeftPoint: function () {\r
+               return this.getPixelOrigin().subtract(this._getMapPanePos());\r
+       },\r
+\r
+       _getNewTopLeftPoint: function (center, zoom) {\r
+               var viewHalf = this.getSize()._divideBy(2);\r
+               // TODO round on display, not calculation to increase precision?\r
+               return this.project(center, zoom)._subtract(viewHalf)._round();\r
+       },\r
+\r
+       _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) {\r
+               var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos());\r
+               return this.project(latlng, newZoom)._subtract(topLeft);\r
+       },\r
+\r
+       // layer point of the current center\r
+       _getCenterLayerPoint: function () {\r
+               return this.containerPointToLayerPoint(this.getSize()._divideBy(2));\r
+       },\r
+\r
+       // offset of the specified place to the current center in pixels\r
+       _getCenterOffset: function (latlng) {\r
+               return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());\r
+       },\r
+\r
+       // adjust center for view to get inside bounds\r
+       _limitCenter: function (center, zoom, bounds) {\r
+\r
+               if (!bounds) { return center; }\r
+\r
+               var centerPoint = this.project(center, zoom),\r
+                   viewHalf = this.getSize().divideBy(2),\r
+                   viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),\r
+                   offset = this._getBoundsOffset(viewBounds, bounds, zoom);\r
+\r
+               return this.unproject(centerPoint.add(offset), zoom);\r
+       },\r
+\r
+       // adjust offset for view to get inside bounds\r
+       _limitOffset: function (offset, bounds) {\r
+               if (!bounds) { return offset; }\r
+\r
+               var viewBounds = this.getPixelBounds(),\r
+                   newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));\r
+\r
+               return offset.add(this._getBoundsOffset(newBounds, bounds));\r
+       },\r
+\r
+       // returns offset needed for pxBounds to get inside maxBounds at a specified zoom\r
+       _getBoundsOffset: function (pxBounds, maxBounds, zoom) {\r
+               var nwOffset = this.project(maxBounds.getNorthWest(), zoom).subtract(pxBounds.min),\r
+                   seOffset = this.project(maxBounds.getSouthEast(), zoom).subtract(pxBounds.max),\r
+\r
+                   dx = this._rebound(nwOffset.x, -seOffset.x),\r
+                   dy = this._rebound(nwOffset.y, -seOffset.y);\r
+\r
+               return new L.Point(dx, dy);\r
+       },\r
+\r
+       _rebound: function (left, right) {\r
+               return left + right > 0 ?\r
+                       Math.round(left - right) / 2 :\r
+                       Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));\r
+       },\r
+\r
+       _limitZoom: function (zoom) {\r
+               var min = this.getMinZoom(),\r
+                   max = this.getMaxZoom();\r
+\r
+               return Math.max(min, Math.min(max, zoom));\r
+       }\r
+});\r
+\r
+L.map = function (id, options) {\r
+       return new L.Map(id, options);\r
+};\r
+
+
+/*\r
+ * Mercator projection that takes into account that the Earth is not a perfect sphere.\r
+ * Less popular than spherical mercator; used by projections like EPSG:3395.\r
+ */\r
+\r
+L.Projection.Mercator = {\r
+       MAX_LATITUDE: 85.0840591556,\r
+\r
+       R_MINOR: 6356752.314245179,\r
+       R_MAJOR: 6378137,\r
+\r
+       project: function (latlng) { // (LatLng) -> Point\r
+               var d = L.LatLng.DEG_TO_RAD,\r
+                   max = this.MAX_LATITUDE,\r
+                   lat = Math.max(Math.min(max, latlng.lat), -max),\r
+                   r = this.R_MAJOR,\r
+                   r2 = this.R_MINOR,\r
+                   x = latlng.lng * d * r,\r
+                   y = lat * d,\r
+                   tmp = r2 / r,\r
+                   eccent = Math.sqrt(1.0 - tmp * tmp),\r
+                   con = eccent * Math.sin(y);\r
+\r
+               con = Math.pow((1 - con) / (1 + con), eccent * 0.5);\r
+\r
+               var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con;\r
+               y = -r * Math.log(ts);\r
+\r
+               return new L.Point(x, y);\r
+       },\r
+\r
+       unproject: function (point) { // (Point, Boolean) -> LatLng\r
+               var d = L.LatLng.RAD_TO_DEG,\r
+                   r = this.R_MAJOR,\r
+                   r2 = this.R_MINOR,\r
+                   lng = point.x * d / r,\r
+                   tmp = r2 / r,\r
+                   eccent = Math.sqrt(1 - (tmp * tmp)),\r
+                   ts = Math.exp(- point.y / r),\r
+                   phi = (Math.PI / 2) - 2 * Math.atan(ts),\r
+                   numIter = 15,\r
+                   tol = 1e-7,\r
+                   i = numIter,\r
+                   dphi = 0.1,\r
+                   con;\r
+\r
+               while ((Math.abs(dphi) > tol) && (--i > 0)) {\r
+                       con = eccent * Math.sin(phi);\r
+                       dphi = (Math.PI / 2) - 2 * Math.atan(ts *\r
+                                   Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi;\r
+                       phi += dphi;\r
+               }\r
+\r
+               return new L.LatLng(phi * d, lng);\r
+       }\r
+};\r
+
+
+\r
+L.CRS.EPSG3395 = L.extend({}, L.CRS, {\r
+       code: 'EPSG:3395',\r
+\r
+       projection: L.Projection.Mercator,\r
+\r
+       transformation: (function () {\r
+               var m = L.Projection.Mercator,\r
+                   r = m.R_MAJOR,\r
+                   scale = 0.5 / (Math.PI * r);\r
+\r
+               return new L.Transformation(scale, 0.5, -scale, 0.5);\r
+       }())\r
+});\r
+
+
+/*\r
+ * L.TileLayer is used for standard xyz-numbered tile layers.\r
+ */\r
+\r
+L.TileLayer = L.Class.extend({\r
+       includes: L.Mixin.Events,\r
+\r
+       options: {\r
+               minZoom: 0,\r
+               maxZoom: 18,\r
+               tileSize: 256,\r
+               subdomains: 'abc',\r
+               errorTileUrl: '',\r
+               attribution: '',\r
+               zoomOffset: 0,\r
+               opacity: 1,\r
+               /*\r
+               maxNativeZoom: null,\r
+               zIndex: null,\r
+               tms: false,\r
+               continuousWorld: false,\r
+               noWrap: false,\r
+               zoomReverse: false,\r
+               detectRetina: false,\r
+               reuseTiles: false,\r
+               bounds: false,\r
+               */\r
+               unloadInvisibleTiles: L.Browser.mobile,\r
+               updateWhenIdle: L.Browser.mobile\r
+       },\r
+\r
+       initialize: function (url, options) {\r
+               options = L.setOptions(this, options);\r
+\r
+               // detecting retina displays, adjusting tileSize and zoom levels\r
+               if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {\r
+\r
+                       options.tileSize = Math.floor(options.tileSize / 2);\r
+                       options.zoomOffset++;\r
+\r
+                       if (options.minZoom > 0) {\r
+                               options.minZoom--;\r
+                       }\r
+                       this.options.maxZoom--;\r
+               }\r
+\r
+               if (options.bounds) {\r
+                       options.bounds = L.latLngBounds(options.bounds);\r
+               }\r
+\r
+               this._url = url;\r
+\r
+               var subdomains = this.options.subdomains;\r
+\r
+               if (typeof subdomains === 'string') {\r
+                       this.options.subdomains = subdomains.split('');\r
+               }\r
+       },\r
+\r
+       onAdd: function (map) {\r
+               this._map = map;\r
+               this._animated = map._zoomAnimated;\r
+\r
+               // create a container div for tiles\r
+               this._initContainer();\r
+\r
+               // set up events\r
+               map.on({\r
+                       'viewreset': this._reset,\r
+                       'moveend': this._update\r
+               }, this);\r
+\r
+               if (this._animated) {\r
+                       map.on({\r
+                               'zoomanim': this._animateZoom,\r
+                               'zoomend': this._endZoomAnim\r
+                       }, this);\r
+               }\r
+\r
+               if (!this.options.updateWhenIdle) {\r
+                       this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);\r
+                       map.on('move', this._limitedUpdate, this);\r
+               }\r
+\r
+               this._reset();\r
+               this._update();\r
+       },\r
+\r
+       addTo: function (map) {\r
+               map.addLayer(this);\r
+               return this;\r
+       },\r
+\r
+       onRemove: function (map) {\r
+               this._container.parentNode.removeChild(this._container);\r
+\r
+               map.off({\r
+                       'viewreset': this._reset,\r
+                       'moveend': this._update\r
+               }, this);\r
+\r
+               if (this._animated) {\r
+                       map.off({\r
+                               'zoomanim': this._animateZoom,\r
+                               'zoomend': this._endZoomAnim\r
+                       }, this);\r
+               }\r
+\r
+               if (!this.options.updateWhenIdle) {\r
+                       map.off('move', this._limitedUpdate, this);\r
+               }\r
+\r
+               this._container = null;\r
+               this._map = null;\r
+       },\r
+\r
+       bringToFront: function () {\r
+               var pane = this._map._panes.tilePane;\r
+\r
+               if (this._container) {\r
+                       pane.appendChild(this._container);\r
+                       this._setAutoZIndex(pane, Math.max);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       bringToBack: function () {\r
+               var pane = this._map._panes.tilePane;\r
+\r
+               if (this._container) {\r
+                       pane.insertBefore(this._container, pane.firstChild);\r
+                       this._setAutoZIndex(pane, Math.min);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       getAttribution: function () {\r
+               return this.options.attribution;\r
+       },\r
+\r
+       getContainer: function () {\r
+               return this._container;\r
+       },\r
+\r
+       setOpacity: function (opacity) {\r
+               this.options.opacity = opacity;\r
+\r
+               if (this._map) {\r
+                       this._updateOpacity();\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       setZIndex: function (zIndex) {\r
+               this.options.zIndex = zIndex;\r
+               this._updateZIndex();\r
+\r
+               return this;\r
+       },\r
+\r
+       setUrl: function (url, noRedraw) {\r
+               this._url = url;\r
+\r
+               if (!noRedraw) {\r
+                       this.redraw();\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       redraw: function () {\r
+               if (this._map) {\r
+                       this._reset({hard: true});\r
+                       this._update();\r
+               }\r
+               return this;\r
+       },\r
+\r
+       _updateZIndex: function () {\r
+               if (this._container && this.options.zIndex !== undefined) {\r
+                       this._container.style.zIndex = this.options.zIndex;\r
+               }\r
+       },\r
+\r
+       _setAutoZIndex: function (pane, compare) {\r
+\r
+               var layers = pane.children,\r
+                   edgeZIndex = -compare(Infinity, -Infinity), // -Infinity for max, Infinity for min\r
+                   zIndex, i, len;\r
+\r
+               for (i = 0, len = layers.length; i < len; i++) {\r
+\r
+                       if (layers[i] !== this._container) {\r
+                               zIndex = parseInt(layers[i].style.zIndex, 10);\r
+\r
+                               if (!isNaN(zIndex)) {\r
+                                       edgeZIndex = compare(edgeZIndex, zIndex);\r
+                               }\r
+                       }\r
+               }\r
+\r
+               this.options.zIndex = this._container.style.zIndex =\r
+                       (isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1);\r
+       },\r
+\r
+       _updateOpacity: function () {\r
+               var i,\r
+                   tiles = this._tiles;\r
+\r
+               if (L.Browser.ielt9) {\r
+                       for (i in tiles) {\r
+                               L.DomUtil.setOpacity(tiles[i], this.options.opacity);\r
+                       }\r
+               } else {\r
+                       L.DomUtil.setOpacity(this._container, this.options.opacity);\r
+               }\r
+       },\r
+\r
+       _initContainer: function () {\r
+               var tilePane = this._map._panes.tilePane;\r
+\r
+               if (!this._container) {\r
+                       this._container = L.DomUtil.create('div', 'leaflet-layer');\r
+\r
+                       this._updateZIndex();\r
+\r
+                       if (this._animated) {\r
+                               var className = 'leaflet-tile-container';\r
+\r
+                               this._bgBuffer = L.DomUtil.create('div', className, this._container);\r
+                               this._tileContainer = L.DomUtil.create('div', className, this._container);\r
+\r
+                       } else {\r
+                               this._tileContainer = this._container;\r
+                       }\r
+\r
+                       tilePane.appendChild(this._container);\r
+\r
+                       if (this.options.opacity < 1) {\r
+                               this._updateOpacity();\r
+                       }\r
+               }\r
+       },\r
+\r
+       _reset: function (e) {\r
+               for (var key in this._tiles) {\r
+                       this.fire('tileunload', {tile: this._tiles[key]});\r
+               }\r
+\r
+               this._tiles = {};\r
+               this._tilesToLoad = 0;\r
+\r
+               if (this.options.reuseTiles) {\r
+                       this._unusedTiles = [];\r
+               }\r
+\r
+               this._tileContainer.innerHTML = '';\r
+\r
+               if (this._animated && e && e.hard) {\r
+                       this._clearBgBuffer();\r
+               }\r
+\r
+               this._initContainer();\r
+       },\r
+\r
+       _getTileSize: function () {\r
+               var map = this._map,\r
+                   zoom = map.getZoom() + this.options.zoomOffset,\r
+                   zoomN = this.options.maxNativeZoom,\r
+                   tileSize = this.options.tileSize;\r
+\r
+               if (zoomN && zoom > zoomN) {\r
+                       tileSize = Math.round(map.getZoomScale(zoom) / map.getZoomScale(zoomN) * tileSize);\r
+               }\r
+\r
+               return tileSize;\r
+       },\r
+\r
+       _update: function () {\r
+\r
+               if (!this._map) { return; }\r
+\r
+               var map = this._map,\r
+                   bounds = map.getPixelBounds(),\r
+                   zoom = map.getZoom(),\r
+                   tileSize = this._getTileSize();\r
+\r
+               if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {\r
+                       return;\r
+               }\r
+\r
+               var tileBounds = L.bounds(\r
+                       bounds.min.divideBy(tileSize)._floor(),\r
+                       bounds.max.divideBy(tileSize)._floor());\r
+\r
+               this._addTilesFromCenterOut(tileBounds);\r
+\r
+               if (this.options.unloadInvisibleTiles || this.options.reuseTiles) {\r
+                       this._removeOtherTiles(tileBounds);\r
+               }\r
+       },\r
+\r
+       _addTilesFromCenterOut: function (bounds) {\r
+               var queue = [],\r
+                   center = bounds.getCenter();\r
+\r
+               var j, i, point;\r
+\r
+               for (j = bounds.min.y; j <= bounds.max.y; j++) {\r
+                       for (i = bounds.min.x; i <= bounds.max.x; i++) {\r
+                               point = new L.Point(i, j);\r
+\r
+                               if (this._tileShouldBeLoaded(point)) {\r
+                                       queue.push(point);\r
+                               }\r
+                       }\r
+               }\r
+\r
+               var tilesToLoad = queue.length;\r
+\r
+               if (tilesToLoad === 0) { return; }\r
+\r
+               // load tiles in order of their distance to center\r
+               queue.sort(function (a, b) {\r
+                       return a.distanceTo(center) - b.distanceTo(center);\r
+               });\r
+\r
+               var fragment = document.createDocumentFragment();\r
+\r
+               // if its the first batch of tiles to load\r
+               if (!this._tilesToLoad) {\r
+                       this.fire('loading');\r
+               }\r
+\r
+               this._tilesToLoad += tilesToLoad;\r
+\r
+               for (i = 0; i < tilesToLoad; i++) {\r
+                       this._addTile(queue[i], fragment);\r
+               }\r
+\r
+               this._tileContainer.appendChild(fragment);\r
+       },\r
+\r
+       _tileShouldBeLoaded: function (tilePoint) {\r
+               if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) {\r
+                       return false; // already loaded\r
+               }\r
+\r
+               var options = this.options;\r
+\r
+               if (!options.continuousWorld) {\r
+                       var limit = this._getWrapTileNum();\r
+\r
+                       // don't load if exceeds world bounds\r
+                       if ((options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit.x)) ||\r
+                               tilePoint.y < 0 || tilePoint.y >= limit.y) { return false; }\r
+               }\r
+\r
+               if (options.bounds) {\r
+                       var tileSize = this._getTileSize(),\r
+                           nwPoint = tilePoint.multiplyBy(tileSize),\r
+                           sePoint = nwPoint.add([tileSize, tileSize]),\r
+                           nw = this._map.unproject(nwPoint),\r
+                           se = this._map.unproject(sePoint);\r
+\r
+                       // TODO temporary hack, will be removed after refactoring projections\r
+                       // https://github.com/Leaflet/Leaflet/issues/1618\r
+                       if (!options.continuousWorld && !options.noWrap) {\r
+                               nw = nw.wrap();\r
+                               se = se.wrap();\r
+                       }\r
+\r
+                       if (!options.bounds.intersects([nw, se])) { return false; }\r
+               }\r
+\r
+               return true;\r
+       },\r
+\r
+       _removeOtherTiles: function (bounds) {\r
+               var kArr, x, y, key;\r
+\r
+               for (key in this._tiles) {\r
+                       kArr = key.split(':');\r
+                       x = parseInt(kArr[0], 10);\r
+                       y = parseInt(kArr[1], 10);\r
+\r
+                       // remove tile if it's out of bounds\r
+                       if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {\r
+                               this._removeTile(key);\r
+                       }\r
+               }\r
+       },\r
+\r
+       _removeTile: function (key) {\r
+               var tile = this._tiles[key];\r
+\r
+               this.fire('tileunload', {tile: tile, url: tile.src});\r
+\r
+               if (this.options.reuseTiles) {\r
+                       L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');\r
+                       this._unusedTiles.push(tile);\r
+\r
+               } else if (tile.parentNode === this._tileContainer) {\r
+                       this._tileContainer.removeChild(tile);\r
+               }\r
+\r
+               // for https://github.com/CloudMade/Leaflet/issues/137\r
+               if (!L.Browser.android) {\r
+                       tile.onload = null;\r
+                       tile.src = L.Util.emptyImageUrl;\r
+               }\r
+\r
+               delete this._tiles[key];\r
+       },\r
+\r
+       _addTile: function (tilePoint, container) {\r
+               var tilePos = this._getTilePos(tilePoint);\r
+\r
+               // get unused tile - or create a new tile\r
+               var tile = this._getTile();\r
+\r
+               /*\r
+               Chrome 20 layouts much faster with top/left (verify with timeline, frames)\r
+               Android 4 browser has display issues with top/left and requires transform instead\r
+               (other browsers don't currently care) - see debug/hacks/jitter.html for an example\r
+               */\r
+               L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome);\r
+\r
+               this._tiles[tilePoint.x + ':' + tilePoint.y] = tile;\r
+\r
+               this._loadTile(tile, tilePoint);\r
+\r
+               if (tile.parentNode !== this._tileContainer) {\r
+                       container.appendChild(tile);\r
+               }\r
+       },\r
+\r
+       _getZoomForUrl: function () {\r
+\r
+               var options = this.options,\r
+                   zoom = this._map.getZoom();\r
+\r
+               if (options.zoomReverse) {\r
+                       zoom = options.maxZoom - zoom;\r
+               }\r
+\r
+               zoom += options.zoomOffset;\r
+\r
+               return options.maxNativeZoom ? Math.min(zoom, options.maxNativeZoom) : zoom;\r
+       },\r
+\r
+       _getTilePos: function (tilePoint) {\r
+               var origin = this._map.getPixelOrigin(),\r
+                   tileSize = this._getTileSize();\r
+\r
+               return tilePoint.multiplyBy(tileSize).subtract(origin);\r
+       },\r
+\r
+       // image-specific code (override to implement e.g. Canvas or SVG tile layer)\r
+\r
+       getTileUrl: function (tilePoint) {\r
+               return L.Util.template(this._url, L.extend({\r
+                       s: this._getSubdomain(tilePoint),\r
+                       z: tilePoint.z,\r
+                       x: tilePoint.x,\r
+                       y: tilePoint.y\r
+               }, this.options));\r
+       },\r
+\r
+       _getWrapTileNum: function () {\r
+               var crs = this._map.options.crs,\r
+                   size = crs.getSize(this._map.getZoom());\r
+               return size.divideBy(this._getTileSize())._floor();\r
+       },\r
+\r
+       _adjustTilePoint: function (tilePoint) {\r
+\r
+               var limit = this._getWrapTileNum();\r
+\r
+               // wrap tile coordinates\r
+               if (!this.options.continuousWorld && !this.options.noWrap) {\r
+                       tilePoint.x = ((tilePoint.x % limit.x) + limit.x) % limit.x;\r
+               }\r
+\r
+               if (this.options.tms) {\r
+                       tilePoint.y = limit.y - tilePoint.y - 1;\r
+               }\r
+\r
+               tilePoint.z = this._getZoomForUrl();\r
+       },\r
+\r
+       _getSubdomain: function (tilePoint) {\r
+               var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;\r
+               return this.options.subdomains[index];\r
+       },\r
+\r
+       _getTile: function () {\r
+               if (this.options.reuseTiles && this._unusedTiles.length > 0) {\r
+                       var tile = this._unusedTiles.pop();\r
+                       this._resetTile(tile);\r
+                       return tile;\r
+               }\r
+               return this._createTile();\r
+       },\r
+\r
+       // Override if data stored on a tile needs to be cleaned up before reuse\r
+       _resetTile: function (/*tile*/) {},\r
+\r
+       _createTile: function () {\r
+               var tile = L.DomUtil.create('img', 'leaflet-tile');\r
+               tile.style.width = tile.style.height = this._getTileSize() + 'px';\r
+               tile.galleryimg = 'no';\r
+\r
+               tile.onselectstart = tile.onmousemove = L.Util.falseFn;\r
+\r
+               if (L.Browser.ielt9 && this.options.opacity !== undefined) {\r
+                       L.DomUtil.setOpacity(tile, this.options.opacity);\r
+               }\r
+               // without this hack, tiles disappear after zoom on Chrome for Android\r
+               // https://github.com/Leaflet/Leaflet/issues/2078\r
+               if (L.Browser.mobileWebkit3d) {\r
+                       tile.style.WebkitBackfaceVisibility = 'hidden';\r
+               }\r
+               return tile;\r
+       },\r
+\r
+       _loadTile: function (tile, tilePoint) {\r
+               tile._layer  = this;\r
+               tile.onload  = this._tileOnLoad;\r
+               tile.onerror = this._tileOnError;\r
+\r
+               this._adjustTilePoint(tilePoint);\r
+               tile.src     = this.getTileUrl(tilePoint);\r
+\r
+               this.fire('tileloadstart', {\r
+                       tile: tile,\r
+                       url: tile.src\r
+               });\r
+       },\r
+\r
+       _tileLoaded: function () {\r
+               this._tilesToLoad--;\r
+\r
+               if (this._animated) {\r
+                       L.DomUtil.addClass(this._tileContainer, 'leaflet-zoom-animated');\r
+               }\r
+\r
+               if (!this._tilesToLoad) {\r
+                       this.fire('load');\r
+\r
+                       if (this._animated) {\r
+                               // clear scaled tiles after all new tiles are loaded (for performance)\r
+                               clearTimeout(this._clearBgBufferTimer);\r
+                               this._clearBgBufferTimer = setTimeout(L.bind(this._clearBgBuffer, this), 500);\r
+                       }\r
+               }\r
+       },\r
+\r
+       _tileOnLoad: function () {\r
+               var layer = this._layer;\r
+\r
+               //Only if we are loading an actual image\r
+               if (this.src !== L.Util.emptyImageUrl) {\r
+                       L.DomUtil.addClass(this, 'leaflet-tile-loaded');\r
+\r
+                       layer.fire('tileload', {\r
+                               tile: this,\r
+                               url: this.src\r
+                       });\r
+               }\r
+\r
+               layer._tileLoaded();\r
+       },\r
+\r
+       _tileOnError: function () {\r
+               var layer = this._layer;\r
+\r
+               layer.fire('tileerror', {\r
+                       tile: this,\r
+                       url: this.src\r
+               });\r
+\r
+               var newUrl = layer.options.errorTileUrl;\r
+               if (newUrl) {\r
+                       this.src = newUrl;\r
+               }\r
+\r
+               layer._tileLoaded();\r
+       }\r
+});\r
+\r
+L.tileLayer = function (url, options) {\r
+       return new L.TileLayer(url, options);\r
+};\r
+
+
+/*\r
+ * L.TileLayer.WMS is used for putting WMS tile layers on the map.\r
+ */\r
+\r
+L.TileLayer.WMS = L.TileLayer.extend({\r
+\r
+       defaultWmsParams: {\r
+               service: 'WMS',\r
+               request: 'GetMap',\r
+               version: '1.1.1',\r
+               layers: '',\r
+               styles: '',\r
+               format: 'image/jpeg',\r
+               transparent: false\r
+       },\r
+\r
+       initialize: function (url, options) { // (String, Object)\r
+\r
+               this._url = url;\r
+\r
+               var wmsParams = L.extend({}, this.defaultWmsParams),\r
+                   tileSize = options.tileSize || this.options.tileSize;\r
+\r
+               if (options.detectRetina && L.Browser.retina) {\r
+                       wmsParams.width = wmsParams.height = tileSize * 2;\r
+               } else {\r
+                       wmsParams.width = wmsParams.height = tileSize;\r
+               }\r
+\r
+               for (var i in options) {\r
+                       // all keys that are not TileLayer options go to WMS params\r
+                       if (!this.options.hasOwnProperty(i) && i !== 'crs') {\r
+                               wmsParams[i] = options[i];\r
+                       }\r
+               }\r
+\r
+               this.wmsParams = wmsParams;\r
+\r
+               L.setOptions(this, options);\r
+       },\r
+\r
+       onAdd: function (map) {\r
+\r
+               this._crs = this.options.crs || map.options.crs;\r
+\r
+               this._wmsVersion = parseFloat(this.wmsParams.version);\r
+\r
+               var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';\r
+               this.wmsParams[projectionKey] = this._crs.code;\r
+\r
+               L.TileLayer.prototype.onAdd.call(this, map);\r
+       },\r
+\r
+       getTileUrl: function (tilePoint) { // (Point, Number) -> String\r
+\r
+               var map = this._map,\r
+                   tileSize = this.options.tileSize,\r
+\r
+                   nwPoint = tilePoint.multiplyBy(tileSize),\r
+                   sePoint = nwPoint.add([tileSize, tileSize]),\r
+\r
+                   nw = this._crs.project(map.unproject(nwPoint, tilePoint.z)),\r
+                   se = this._crs.project(map.unproject(sePoint, tilePoint.z)),\r
+                   bbox = this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ?\r
+                       [se.y, nw.x, nw.y, se.x].join(',') :\r
+                       [nw.x, se.y, se.x, nw.y].join(','),\r
+\r
+                   url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});\r
+\r
+               return url + L.Util.getParamString(this.wmsParams, url, true) + '&BBOX=' + bbox;\r
+       },\r
+\r
+       setParams: function (params, noRedraw) {\r
+\r
+               L.extend(this.wmsParams, params);\r
+\r
+               if (!noRedraw) {\r
+                       this.redraw();\r
+               }\r
+\r
+               return this;\r
+       }\r
+});\r
+\r
+L.tileLayer.wms = function (url, options) {\r
+       return new L.TileLayer.WMS(url, options);\r
+};\r
+
+
+/*\r
+ * L.TileLayer.Canvas is a class that you can use as a base for creating\r
+ * dynamically drawn Canvas-based tile layers.\r
+ */\r
+\r
+L.TileLayer.Canvas = L.TileLayer.extend({\r
+       options: {\r
+               async: false\r
+       },\r
+\r
+       initialize: function (options) {\r
+               L.setOptions(this, options);\r
+       },\r
+\r
+       redraw: function () {\r
+               if (this._map) {\r
+                       this._reset({hard: true});\r
+                       this._update();\r
+               }\r
+\r
+               for (var i in this._tiles) {\r
+                       this._redrawTile(this._tiles[i]);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       _redrawTile: function (tile) {\r
+               this.drawTile(tile, tile._tilePoint, this._map._zoom);\r
+       },\r
+\r
+       _createTile: function () {\r
+               var tile = L.DomUtil.create('canvas', 'leaflet-tile');\r
+               tile.width = tile.height = this.options.tileSize;\r
+               tile.onselectstart = tile.onmousemove = L.Util.falseFn;\r
+               return tile;\r
+       },\r
+\r
+       _loadTile: function (tile, tilePoint) {\r
+               tile._layer = this;\r
+               tile._tilePoint = tilePoint;\r
+\r
+               this._redrawTile(tile);\r
+\r
+               if (!this.options.async) {\r
+                       this.tileDrawn(tile);\r
+               }\r
+       },\r
+\r
+       drawTile: function (/*tile, tilePoint*/) {\r
+               // override with rendering code\r
+       },\r
+\r
+       tileDrawn: function (tile) {\r
+               this._tileOnLoad.call(tile);\r
+       }\r
+});\r
+\r
+\r
+L.tileLayer.canvas = function (options) {\r
+       return new L.TileLayer.Canvas(options);\r
+};\r
+
+
+/*\r
+ * L.ImageOverlay is used to overlay images over the map (to specific geographical bounds).\r
+ */\r
+\r
+L.ImageOverlay = L.Class.extend({\r
+       includes: L.Mixin.Events,\r
+\r
+       options: {\r
+               opacity: 1\r
+       },\r
+\r
+       initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)\r
+               this._url = url;\r
+               this._bounds = L.latLngBounds(bounds);\r
+\r
+               L.setOptions(this, options);\r
+       },\r
+\r
+       onAdd: function (map) {\r
+               this._map = map;\r
+\r
+               if (!this._image) {\r
+                       this._initImage();\r
+               }\r
+\r
+               map._panes.overlayPane.appendChild(this._image);\r
+\r
+               map.on('viewreset', this._reset, this);\r
+\r
+               if (map.options.zoomAnimation && L.Browser.any3d) {\r
+                       map.on('zoomanim', this._animateZoom, this);\r
+               }\r
+\r
+               this._reset();\r
+       },\r
+\r
+       onRemove: function (map) {\r
+               map.getPanes().overlayPane.removeChild(this._image);\r
+\r
+               map.off('viewreset', this._reset, this);\r
+\r
+               if (map.options.zoomAnimation) {\r
+                       map.off('zoomanim', this._animateZoom, this);\r
+               }\r
+       },\r
+\r
+       addTo: function (map) {\r
+               map.addLayer(this);\r
+               return this;\r
+       },\r
+\r
+       setOpacity: function (opacity) {\r
+               this.options.opacity = opacity;\r
+               this._updateOpacity();\r
+               return this;\r
+       },\r
+\r
+       // TODO remove bringToFront/bringToBack duplication from TileLayer/Path\r
+       bringToFront: function () {\r
+               if (this._image) {\r
+                       this._map._panes.overlayPane.appendChild(this._image);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       bringToBack: function () {\r
+               var pane = this._map._panes.overlayPane;\r
+               if (this._image) {\r
+                       pane.insertBefore(this._image, pane.firstChild);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       setUrl: function (url) {\r
+               this._url = url;\r
+               this._image.src = this._url;\r
+       },\r
+\r
+       getAttribution: function () {\r
+               return this.options.attribution;\r
+       },\r
+\r
+       _initImage: function () {\r
+               this._image = L.DomUtil.create('img', 'leaflet-image-layer');\r
+\r
+               if (this._map.options.zoomAnimation && L.Browser.any3d) {\r
+                       L.DomUtil.addClass(this._image, 'leaflet-zoom-animated');\r
+               } else {\r
+                       L.DomUtil.addClass(this._image, 'leaflet-zoom-hide');\r
+               }\r
+\r
+               this._updateOpacity();\r
+\r
+               //TODO createImage util method to remove duplication\r
+               L.extend(this._image, {\r
+                       galleryimg: 'no',\r
+                       onselectstart: L.Util.falseFn,\r
+                       onmousemove: L.Util.falseFn,\r
+                       onload: L.bind(this._onImageLoad, this),\r
+                       src: this._url\r
+               });\r
+       },\r
+\r
+       _animateZoom: function (e) {\r
+               var map = this._map,\r
+                   image = this._image,\r
+                   scale = map.getZoomScale(e.zoom),\r
+                   nw = this._bounds.getNorthWest(),\r
+                   se = this._bounds.getSouthEast(),\r
+\r
+                   topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),\r
+                   size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft),\r
+                   origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale)));\r
+\r
+               image.style[L.DomUtil.TRANSFORM] =\r
+                       L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';\r
+       },\r
+\r
+       _reset: function () {\r
+               var image   = this._image,\r
+                   topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),\r
+                   size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft);\r
+\r
+               L.DomUtil.setPosition(image, topLeft);\r
+\r
+               image.style.width  = size.x + 'px';\r
+               image.style.height = size.y + 'px';\r
+       },\r
+\r
+       _onImageLoad: function () {\r
+               this.fire('load');\r
+       },\r
+\r
+       _updateOpacity: function () {\r
+               L.DomUtil.setOpacity(this._image, this.options.opacity);\r
+       }\r
+});\r
+\r
+L.imageOverlay = function (url, bounds, options) {\r
+       return new L.ImageOverlay(url, bounds, options);\r
+};\r
+
+
+/*\r
+ * L.Icon is an image-based icon class that you can use with L.Marker for custom markers.\r
+ */\r
+\r
+L.Icon = L.Class.extend({\r
+       options: {\r
+               /*\r
+               iconUrl: (String) (required)\r
+               iconRetinaUrl: (String) (optional, used for retina devices if detected)\r
+               iconSize: (Point) (can be set through CSS)\r
+               iconAnchor: (Point) (centered by default, can be set in CSS with negative margins)\r
+               popupAnchor: (Point) (if not specified, popup opens in the anchor point)\r
+               shadowUrl: (String) (no shadow by default)\r
+               shadowRetinaUrl: (String) (optional, used for retina devices if detected)\r
+               shadowSize: (Point)\r
+               shadowAnchor: (Point)\r
+               */\r
+               className: ''\r
+       },\r
+\r
+       initialize: function (options) {\r
+               L.setOptions(this, options);\r
+       },\r
+\r
+       createIcon: function (oldIcon) {\r
+               return this._createIcon('icon', oldIcon);\r
+       },\r
+\r
+       createShadow: function (oldIcon) {\r
+               return this._createIcon('shadow', oldIcon);\r
+       },\r
+\r
+       _createIcon: function (name, oldIcon) {\r
+               var src = this._getIconUrl(name);\r
+\r
+               if (!src) {\r
+                       if (name === 'icon') {\r
+                               throw new Error('iconUrl not set in Icon options (see the docs).');\r
+                       }\r
+                       return null;\r
+               }\r
+\r
+               var img;\r
+               if (!oldIcon || oldIcon.tagName !== 'IMG') {\r
+                       img = this._createImg(src);\r
+               } else {\r
+                       img = this._createImg(src, oldIcon);\r
+               }\r
+               this._setIconStyles(img, name);\r
+\r
+               return img;\r
+       },\r
+\r
+       _setIconStyles: function (img, name) {\r
+               var options = this.options,\r
+                   size = L.point(options[name + 'Size']),\r
+                   anchor;\r
+\r
+               if (name === 'shadow') {\r
+                       anchor = L.point(options.shadowAnchor || options.iconAnchor);\r
+               } else {\r
+                       anchor = L.point(options.iconAnchor);\r
+               }\r
+\r
+               if (!anchor && size) {\r
+                       anchor = size.divideBy(2, true);\r
+               }\r
+\r
+               img.className = 'leaflet-marker-' + name + ' ' + options.className;\r
+\r
+               if (anchor) {\r
+                       img.style.marginLeft = (-anchor.x) + 'px';\r
+                       img.style.marginTop  = (-anchor.y) + 'px';\r
+               }\r
+\r
+               if (size) {\r
+                       img.style.width  = size.x + 'px';\r
+                       img.style.height = size.y + 'px';\r
+               }\r
+       },\r
+\r
+       _createImg: function (src, el) {\r
+               el = el || document.createElement('img');\r
+               el.src = src;\r
+               return el;\r
+       },\r
+\r
+       _getIconUrl: function (name) {\r
+               if (L.Browser.retina && this.options[name + 'RetinaUrl']) {\r
+                       return this.options[name + 'RetinaUrl'];\r
+               }\r
+               return this.options[name + 'Url'];\r
+       }\r
+});\r
+\r
+L.icon = function (options) {\r
+       return new L.Icon(options);\r
+};\r
+
+
+/*
+ * L.Icon.Default is the blue marker icon used by default in Leaflet.
+ */
+
+L.Icon.Default = L.Icon.extend({
+
+       options: {
+               iconSize: [25, 41],
+               iconAnchor: [12, 41],
+               popupAnchor: [1, -34],
+
+               shadowSize: [41, 41]
+       },
+
+       _getIconUrl: function (name) {
+               var key = name + 'Url';
+
+               if (this.options[key]) {
+                       return this.options[key];
+               }
+
+               if (L.Browser.retina && name === 'icon') {
+                       name += '-2x';
+               }
+
+               var path = L.Icon.Default.imagePath;
+
+               if (!path) {
+                       throw new Error('Couldn\'t autodetect L.Icon.Default.imagePath, set it manually.');
+               }
+
+               return path + '/marker-' + name + '.png';
+       }
+});
+
+L.Icon.Default.imagePath = (function () {
+       var scripts = document.getElementsByTagName('script'),
+           leafletRe = /[\/^]leaflet[\-\._]?([\w\-\._]*)\.js\??/;
+
+       var i, len, src, matches, path;
+
+       for (i = 0, len = scripts.length; i < len; i++) {
+               src = scripts[i].src;
+               matches = src.match(leafletRe);
+
+               if (matches) {
+                       path = src.split(leafletRe)[0];
+                       return (path ? path + '/' : '') + 'images';
+               }
+       }
+}());
+
+
+/*\r
+ * L.Marker is used to display clickable/draggable icons on the map.\r
+ */\r
+\r
+L.Marker = L.Class.extend({\r
+\r
+       includes: L.Mixin.Events,\r
+\r
+       options: {\r
+               icon: new L.Icon.Default(),\r
+               title: '',\r
+               alt: '',\r
+               clickable: true,\r
+               draggable: false,\r
+               keyboard: true,\r
+               zIndexOffset: 0,\r
+               opacity: 1,\r
+               riseOnHover: false,\r
+               riseOffset: 250\r
+       },\r
+\r
+       initialize: function (latlng, options) {\r
+               L.setOptions(this, options);\r
+               this._latlng = L.latLng(latlng);\r
+       },\r
+\r
+       onAdd: function (map) {\r
+               this._map = map;\r
+\r
+               map.on('viewreset', this.update, this);\r
+\r
+               this._initIcon();\r
+               this.update();\r
+               this.fire('add');\r
+\r
+               if (map.options.zoomAnimation && map.options.markerZoomAnimation) {\r
+                       map.on('zoomanim', this._animateZoom, this);\r
+               }\r
+       },\r
+\r
+       addTo: function (map) {\r
+               map.addLayer(this);\r
+               return this;\r
+       },\r
+\r
+       onRemove: function (map) {\r
+               if (this.dragging) {\r
+                       this.dragging.disable();\r
+               }\r
+\r
+               this._removeIcon();\r
+               this._removeShadow();\r
+\r
+               this.fire('remove');\r
+\r
+               map.off({\r
+                       'viewreset': this.update,\r
+                       'zoomanim': this._animateZoom\r
+               }, this);\r
+\r
+               this._map = null;\r
+       },\r
+\r
+       getLatLng: function () {\r
+               return this._latlng;\r
+       },\r
+\r
+       setLatLng: function (latlng) {\r
+               this._latlng = L.latLng(latlng);\r
+\r
+               this.update();\r
+\r
+               return this.fire('move', { latlng: this._latlng });\r
+       },\r
+\r
+       setZIndexOffset: function (offset) {\r
+               this.options.zIndexOffset = offset;\r
+               this.update();\r
+\r
+               return this;\r
+       },\r
+\r
+       setIcon: function (icon) {\r
+\r
+               this.options.icon = icon;\r
+\r
+               if (this._map) {\r
+                       this._initIcon();\r
+                       this.update();\r
+               }\r
+\r
+               if (this._popup) {\r
+                       this.bindPopup(this._popup);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       update: function () {\r
+               if (this._icon) {\r
+                       this._setPos(this._map.latLngToLayerPoint(this._latlng).round());\r
+               }\r
+               return this;\r
+       },\r
+\r
+       _initIcon: function () {\r
+               var options = this.options,\r
+                   map = this._map,\r
+                   animation = (map.options.zoomAnimation && map.options.markerZoomAnimation),\r
+                   classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide';\r
+\r
+               var icon = options.icon.createIcon(this._icon),\r
+                       addIcon = false;\r
+\r
+               // if we're not reusing the icon, remove the old one and init new one\r
+               if (icon !== this._icon) {\r
+                       if (this._icon) {\r
+                               this._removeIcon();\r
+                       }\r
+                       addIcon = true;\r
+\r
+                       if (options.title) {\r
+                               icon.title = options.title;\r
+                       }\r
+\r
+                       if (options.alt) {\r
+                               icon.alt = options.alt;\r
+                       }\r
+               }\r
+\r
+               L.DomUtil.addClass(icon, classToAdd);\r
+\r
+               if (options.keyboard) {\r
+                       icon.tabIndex = '0';\r
+               }\r
+\r
+               this._icon = icon;\r
+\r
+               this._initInteraction();\r
+\r
+               if (options.riseOnHover) {\r
+                       L.DomEvent\r
+                               .on(icon, 'mouseover', this._bringToFront, this)\r
+                               .on(icon, 'mouseout', this._resetZIndex, this);\r
+               }\r
+\r
+               var newShadow = options.icon.createShadow(this._shadow),\r
+                       addShadow = false;\r
+\r
+               if (newShadow !== this._shadow) {\r
+                       this._removeShadow();\r
+                       addShadow = true;\r
+               }\r
+\r
+               if (newShadow) {\r
+                       L.DomUtil.addClass(newShadow, classToAdd);\r
+               }\r
+               this._shadow = newShadow;\r
+\r
+\r
+               if (options.opacity < 1) {\r
+                       this._updateOpacity();\r
+               }\r
+\r
+\r
+               var panes = this._map._panes;\r
+\r
+               if (addIcon) {\r
+                       panes.markerPane.appendChild(this._icon);\r
+               }\r
+\r
+               if (newShadow && addShadow) {\r
+                       panes.shadowPane.appendChild(this._shadow);\r
+               }\r
+       },\r
+\r
+       _removeIcon: function () {\r
+               if (this.options.riseOnHover) {\r
+                       L.DomEvent\r
+                           .off(this._icon, 'mouseover', this._bringToFront)\r
+                           .off(this._icon, 'mouseout', this._resetZIndex);\r
+               }\r
+\r
+               this._map._panes.markerPane.removeChild(this._icon);\r
+\r
+               this._icon = null;\r
+       },\r
+\r
+       _removeShadow: function () {\r
+               if (this._shadow) {\r
+                       this._map._panes.shadowPane.removeChild(this._shadow);\r
+               }\r
+               this._shadow = null;\r
+       },\r
+\r
+       _setPos: function (pos) {\r
+               L.DomUtil.setPosition(this._icon, pos);\r
+\r
+               if (this._shadow) {\r
+                       L.DomUtil.setPosition(this._shadow, pos);\r
+               }\r
+\r
+               this._zIndex = pos.y + this.options.zIndexOffset;\r
+\r
+               this._resetZIndex();\r
+       },\r
+\r
+       _updateZIndex: function (offset) {\r
+               this._icon.style.zIndex = this._zIndex + offset;\r
+       },\r
+\r
+       _animateZoom: function (opt) {\r
+               var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();\r
+\r
+               this._setPos(pos);\r
+       },\r
+\r
+       _initInteraction: function () {\r
+\r
+               if (!this.options.clickable) { return; }\r
+\r
+               // TODO refactor into something shared with Map/Path/etc. to DRY it up\r
+\r
+               var icon = this._icon,\r
+                   events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu'];\r
+\r
+               L.DomUtil.addClass(icon, 'leaflet-clickable');\r
+               L.DomEvent.on(icon, 'click', this._onMouseClick, this);\r
+               L.DomEvent.on(icon, 'keypress', this._onKeyPress, this);\r
+\r
+               for (var i = 0; i < events.length; i++) {\r
+                       L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);\r
+               }\r
+\r
+               if (L.Handler.MarkerDrag) {\r
+                       this.dragging = new L.Handler.MarkerDrag(this);\r
+\r
+                       if (this.options.draggable) {\r
+                               this.dragging.enable();\r
+                       }\r
+               }\r
+       },\r
+\r
+       _onMouseClick: function (e) {\r
+               var wasDragged = this.dragging && this.dragging.moved();\r
+\r
+               if (this.hasEventListeners(e.type) || wasDragged) {\r
+                       L.DomEvent.stopPropagation(e);\r
+               }\r
+\r
+               if (wasDragged) { return; }\r
+\r
+               if ((!this.dragging || !this.dragging._enabled) && this._map.dragging && this._map.dragging.moved()) { return; }\r
+\r
+               this.fire(e.type, {\r
+                       originalEvent: e,\r
+                       latlng: this._latlng\r
+               });\r
+       },\r
+\r
+       _onKeyPress: function (e) {\r
+               if (e.keyCode === 13) {\r
+                       this.fire('click', {\r
+                               originalEvent: e,\r
+                               latlng: this._latlng\r
+                       });\r
+               }\r
+       },\r
+\r
+       _fireMouseEvent: function (e) {\r
+\r
+               this.fire(e.type, {\r
+                       originalEvent: e,\r
+                       latlng: this._latlng\r
+               });\r
+\r
+               // TODO proper custom event propagation\r
+               // this line will always be called if marker is in a FeatureGroup\r
+               if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) {\r
+                       L.DomEvent.preventDefault(e);\r
+               }\r
+               if (e.type !== 'mousedown') {\r
+                       L.DomEvent.stopPropagation(e);\r
+               } else {\r
+                       L.DomEvent.preventDefault(e);\r
+               }\r
+       },\r
+\r
+       setOpacity: function (opacity) {\r
+               this.options.opacity = opacity;\r
+               if (this._map) {\r
+                       this._updateOpacity();\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       _updateOpacity: function () {\r
+               L.DomUtil.setOpacity(this._icon, this.options.opacity);\r
+               if (this._shadow) {\r
+                       L.DomUtil.setOpacity(this._shadow, this.options.opacity);\r
+               }\r
+       },\r
+\r
+       _bringToFront: function () {\r
+               this._updateZIndex(this.options.riseOffset);\r
+       },\r
+\r
+       _resetZIndex: function () {\r
+               this._updateZIndex(0);\r
+       }\r
+});\r
+\r
+L.marker = function (latlng, options) {\r
+       return new L.Marker(latlng, options);\r
+};\r
+
+
+/*
+ * L.DivIcon is a lightweight HTML-based icon class (as opposed to the image-based L.Icon)
+ * to use with L.Marker.
+ */
+
+L.DivIcon = L.Icon.extend({
+       options: {
+               iconSize: [12, 12], // also can be set through CSS
+               /*
+               iconAnchor: (Point)
+               popupAnchor: (Point)
+               html: (String)
+               bgPos: (Point)
+               */
+               className: 'leaflet-div-icon',
+               html: false
+       },
+
+       createIcon: function (oldIcon) {
+               var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
+                   options = this.options;
+
+               if (options.html !== false) {
+                       div.innerHTML = options.html;
+               } else {
+                       div.innerHTML = '';
+               }
+
+               if (options.bgPos) {
+                       div.style.backgroundPosition =
+                               (-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px';
+               }
+
+               this._setIconStyles(div, 'icon');
+               return div;
+       },
+
+       createShadow: function () {
+               return null;
+       }
+});
+
+L.divIcon = function (options) {
+       return new L.DivIcon(options);
+};
+
+
+/*\r
+ * L.Popup is used for displaying popups on the map.\r
+ */\r
+\r
+L.Map.mergeOptions({\r
+       closePopupOnClick: true\r
+});\r
+\r
+L.Popup = L.Class.extend({\r
+       includes: L.Mixin.Events,\r
+\r
+       options: {\r
+               minWidth: 50,\r
+               maxWidth: 300,\r
+               // maxHeight: null,\r
+               autoPan: true,\r
+               closeButton: true,\r
+               offset: [0, 7],\r
+               autoPanPadding: [5, 5],\r
+               // autoPanPaddingTopLeft: null,\r
+               // autoPanPaddingBottomRight: null,\r
+               keepInView: false,\r
+               className: '',\r
+               zoomAnimation: true\r
+       },\r
+\r
+       initialize: function (options, source) {\r
+               L.setOptions(this, options);\r
+\r
+               this._source = source;\r
+               this._animated = L.Browser.any3d && this.options.zoomAnimation;\r
+               this._isOpen = false;\r
+       },\r
+\r
+       onAdd: function (map) {\r
+               this._map = map;\r
+\r
+               if (!this._container) {\r
+                       this._initLayout();\r
+               }\r
+\r
+               var animFade = map.options.fadeAnimation;\r
+\r
+               if (animFade) {\r
+                       L.DomUtil.setOpacity(this._container, 0);\r
+               }\r
+               map._panes.popupPane.appendChild(this._container);\r
+\r
+               map.on(this._getEvents(), this);\r
+\r
+               this.update();\r
+\r
+               if (animFade) {\r
+                       L.DomUtil.setOpacity(this._container, 1);\r
+               }\r
+\r
+               this.fire('open');\r
+\r
+               map.fire('popupopen', {popup: this});\r
+\r
+               if (this._source) {\r
+                       this._source.fire('popupopen', {popup: this});\r
+               }\r
+       },\r
+\r
+       addTo: function (map) {\r
+               map.addLayer(this);\r
+               return this;\r
+       },\r
+\r
+       openOn: function (map) {\r
+               map.openPopup(this);\r
+               return this;\r
+       },\r
+\r
+       onRemove: function (map) {\r
+               map._panes.popupPane.removeChild(this._container);\r
+\r
+               L.Util.falseFn(this._container.offsetWidth); // force reflow\r
+\r
+               map.off(this._getEvents(), this);\r
+\r
+               if (map.options.fadeAnimation) {\r
+                       L.DomUtil.setOpacity(this._container, 0);\r
+               }\r
+\r
+               this._map = null;\r
+\r
+               this.fire('close');\r
+\r
+               map.fire('popupclose', {popup: this});\r
+\r
+               if (this._source) {\r
+                       this._source.fire('popupclose', {popup: this});\r
+               }\r
+       },\r
+\r
+       getLatLng: function () {\r
+               return this._latlng;\r
+       },\r
+\r
+       setLatLng: function (latlng) {\r
+               this._latlng = L.latLng(latlng);\r
+               if (this._map) {\r
+                       this._updatePosition();\r
+                       this._adjustPan();\r
+               }\r
+               return this;\r
+       },\r
+\r
+       getContent: function () {\r
+               return this._content;\r
+       },\r
+\r
+       setContent: function (content) {\r
+               this._content = content;\r
+               this.update();\r
+               return this;\r
+       },\r
+\r
+       update: function () {\r
+               if (!this._map) { return; }\r
+\r
+               this._container.style.visibility = 'hidden';\r
+\r
+               this._updateContent();\r
+               this._updateLayout();\r
+               this._updatePosition();\r
+\r
+               this._container.style.visibility = '';\r
+\r
+               this._adjustPan();\r
+       },\r
+\r
+       _getEvents: function () {\r
+               var events = {\r
+                       viewreset: this._updatePosition\r
+               };\r
+\r
+               if (this._animated) {\r
+                       events.zoomanim = this._zoomAnimation;\r
+               }\r
+               if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {\r
+                       events.preclick = this._close;\r
+               }\r
+               if (this.options.keepInView) {\r
+                       events.moveend = this._adjustPan;\r
+               }\r
+\r
+               return events;\r
+       },\r
+\r
+       _close: function () {\r
+               if (this._map) {\r
+                       this._map.closePopup(this);\r
+               }\r
+       },\r
+\r
+       _initLayout: function () {\r
+               var prefix = 'leaflet-popup',\r
+                       containerClass = prefix + ' ' + this.options.className + ' leaflet-zoom-' +\r
+                               (this._animated ? 'animated' : 'hide'),\r
+                       container = this._container = L.DomUtil.create('div', containerClass),\r
+                       closeButton;\r
+\r
+               if (this.options.closeButton) {\r
+                       closeButton = this._closeButton =\r
+                               L.DomUtil.create('a', prefix + '-close-button', container);\r
+                       closeButton.href = '#close';\r
+                       closeButton.innerHTML = '&#215;';\r
+                       L.DomEvent.disableClickPropagation(closeButton);\r
+\r
+                       L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);\r
+               }\r
+\r
+               var wrapper = this._wrapper =\r
+                       L.DomUtil.create('div', prefix + '-content-wrapper', container);\r
+               L.DomEvent.disableClickPropagation(wrapper);\r
+\r
+               this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);\r
+\r
+               L.DomEvent.disableScrollPropagation(this._contentNode);\r
+               L.DomEvent.on(wrapper, 'contextmenu', L.DomEvent.stopPropagation);\r
+\r
+               this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);\r
+               this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);\r
+       },\r
+\r
+       _updateContent: function () {\r
+               if (!this._content) { return; }\r
+\r
+               if (typeof this._content === 'string') {\r
+                       this._contentNode.innerHTML = this._content;\r
+               } else {\r
+                       while (this._contentNode.hasChildNodes()) {\r
+                               this._contentNode.removeChild(this._contentNode.firstChild);\r
+                       }\r
+                       this._contentNode.appendChild(this._content);\r
+               }\r
+               this.fire('contentupdate');\r
+       },\r
+\r
+       _updateLayout: function () {\r
+               var container = this._contentNode,\r
+                   style = container.style;\r
+\r
+               style.width = '';\r
+               style.whiteSpace = 'nowrap';\r
+\r
+               var width = container.offsetWidth;\r
+               width = Math.min(width, this.options.maxWidth);\r
+               width = Math.max(width, this.options.minWidth);\r
+\r
+               style.width = (width + 1) + 'px';\r
+               style.whiteSpace = '';\r
+\r
+               style.height = '';\r
+\r
+               var height = container.offsetHeight,\r
+                   maxHeight = this.options.maxHeight,\r
+                   scrolledClass = 'leaflet-popup-scrolled';\r
+\r
+               if (maxHeight && height > maxHeight) {\r
+                       style.height = maxHeight + 'px';\r
+                       L.DomUtil.addClass(container, scrolledClass);\r
+               } else {\r
+                       L.DomUtil.removeClass(container, scrolledClass);\r
+               }\r
+\r
+               this._containerWidth = this._container.offsetWidth;\r
+       },\r
+\r
+       _updatePosition: function () {\r
+               if (!this._map) { return; }\r
+\r
+               var pos = this._map.latLngToLayerPoint(this._latlng),\r
+                   animated = this._animated,\r
+                   offset = L.point(this.options.offset);\r
+\r
+               if (animated) {\r
+                       L.DomUtil.setPosition(this._container, pos);\r
+               }\r
+\r
+               this._containerBottom = -offset.y - (animated ? 0 : pos.y);\r
+               this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (animated ? 0 : pos.x);\r
+\r
+               // bottom position the popup in case the height of the popup changes (images loading etc)\r
+               this._container.style.bottom = this._containerBottom + 'px';\r
+               this._container.style.left = this._containerLeft + 'px';\r
+       },\r
+\r
+       _zoomAnimation: function (opt) {\r
+               var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);\r
+\r
+               L.DomUtil.setPosition(this._container, pos);\r
+       },\r
+\r
+       _adjustPan: function () {\r
+               if (!this.options.autoPan) { return; }\r
+\r
+               var map = this._map,\r
+                   containerHeight = this._container.offsetHeight,\r
+                   containerWidth = this._containerWidth,\r
+\r
+                   layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);\r
+\r
+               if (this._animated) {\r
+                       layerPos._add(L.DomUtil.getPosition(this._container));\r
+               }\r
+\r
+               var containerPos = map.layerPointToContainerPoint(layerPos),\r
+                   padding = L.point(this.options.autoPanPadding),\r
+                   paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding),\r
+                   paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding),\r
+                   size = map.getSize(),\r
+                   dx = 0,\r
+                   dy = 0;\r
+\r
+               if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right\r
+                       dx = containerPos.x + containerWidth - size.x + paddingBR.x;\r
+               }\r
+               if (containerPos.x - dx - paddingTL.x < 0) { // left\r
+                       dx = containerPos.x - paddingTL.x;\r
+               }\r
+               if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom\r
+                       dy = containerPos.y + containerHeight - size.y + paddingBR.y;\r
+               }\r
+               if (containerPos.y - dy - paddingTL.y < 0) { // top\r
+                       dy = containerPos.y - paddingTL.y;\r
+               }\r
+\r
+               if (dx || dy) {\r
+                       map\r
+                           .fire('autopanstart')\r
+                           .panBy([dx, dy]);\r
+               }\r
+       },\r
+\r
+       _onCloseButtonClick: function (e) {\r
+               this._close();\r
+               L.DomEvent.stop(e);\r
+       }\r
+});\r
+\r
+L.popup = function (options, source) {\r
+       return new L.Popup(options, source);\r
+};\r
+\r
+\r
+L.Map.include({\r
+       openPopup: function (popup, latlng, options) { // (Popup) or (String || HTMLElement, LatLng[, Object])\r
+               this.closePopup();\r
+\r
+               if (!(popup instanceof L.Popup)) {\r
+                       var content = popup;\r
+\r
+                       popup = new L.Popup(options)\r
+                           .setLatLng(latlng)\r
+                           .setContent(content);\r
+               }\r
+               popup._isOpen = true;\r
+\r
+               this._popup = popup;\r
+               return this.addLayer(popup);\r
+       },\r
+\r
+       closePopup: function (popup) {\r
+               if (!popup || popup === this._popup) {\r
+                       popup = this._popup;\r
+                       this._popup = null;\r
+               }\r
+               if (popup) {\r
+                       this.removeLayer(popup);\r
+                       popup._isOpen = false;\r
+               }\r
+               return this;\r
+       }\r
+});\r
+
+
+/*\r
+ * Popup extension to L.Marker, adding popup-related methods.\r
+ */\r
+\r
+L.Marker.include({\r
+       openPopup: function () {\r
+               if (this._popup && this._map && !this._map.hasLayer(this._popup)) {\r
+                       this._popup.setLatLng(this._latlng);\r
+                       this._map.openPopup(this._popup);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       closePopup: function () {\r
+               if (this._popup) {\r
+                       this._popup._close();\r
+               }\r
+               return this;\r
+       },\r
+\r
+       togglePopup: function () {\r
+               if (this._popup) {\r
+                       if (this._popup._isOpen) {\r
+                               this.closePopup();\r
+                       } else {\r
+                               this.openPopup();\r
+                       }\r
+               }\r
+               return this;\r
+       },\r
+\r
+       bindPopup: function (content, options) {\r
+               var anchor = L.point(this.options.icon.options.popupAnchor || [0, 0]);\r
+\r
+               anchor = anchor.add(L.Popup.prototype.options.offset);\r
+\r
+               if (options && options.offset) {\r
+                       anchor = anchor.add(options.offset);\r
+               }\r
+\r
+               options = L.extend({offset: anchor}, options);\r
+\r
+               if (!this._popupHandlersAdded) {\r
+                       this\r
+                           .on('click', this.togglePopup, this)\r
+                           .on('remove', this.closePopup, this)\r
+                           .on('move', this._movePopup, this);\r
+                       this._popupHandlersAdded = true;\r
+               }\r
+\r
+               if (content instanceof L.Popup) {\r
+                       L.setOptions(content, options);\r
+                       this._popup = content;\r
+                       content._source = this;\r
+               } else {\r
+                       this._popup = new L.Popup(options, this)\r
+                               .setContent(content);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       setPopupContent: function (content) {\r
+               if (this._popup) {\r
+                       this._popup.setContent(content);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       unbindPopup: function () {\r
+               if (this._popup) {\r
+                       this._popup = null;\r
+                       this\r
+                           .off('click', this.togglePopup, this)\r
+                           .off('remove', this.closePopup, this)\r
+                           .off('move', this._movePopup, this);\r
+                       this._popupHandlersAdded = false;\r
+               }\r
+               return this;\r
+       },\r
+\r
+       getPopup: function () {\r
+               return this._popup;\r
+       },\r
+\r
+       _movePopup: function (e) {\r
+               this._popup.setLatLng(e.latlng);\r
+       }\r
+});\r
+
+
+/*\r
+ * L.LayerGroup is a class to combine several layers into one so that\r
+ * you can manipulate the group (e.g. add/remove it) as one layer.\r
+ */\r
+\r
+L.LayerGroup = L.Class.extend({\r
+       initialize: function (layers) {\r
+               this._layers = {};\r
+\r
+               var i, len;\r
+\r
+               if (layers) {\r
+                       for (i = 0, len = layers.length; i < len; i++) {\r
+                               this.addLayer(layers[i]);\r
+                       }\r
+               }\r
+       },\r
+\r
+       addLayer: function (layer) {\r
+               var id = this.getLayerId(layer);\r
+\r
+               this._layers[id] = layer;\r
+\r
+               if (this._map) {\r
+                       this._map.addLayer(layer);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       removeLayer: function (layer) {\r
+               var id = layer in this._layers ? layer : this.getLayerId(layer);\r
+\r
+               if (this._map && this._layers[id]) {\r
+                       this._map.removeLayer(this._layers[id]);\r
+               }\r
+\r
+               delete this._layers[id];\r
+\r
+               return this;\r
+       },\r
+\r
+       hasLayer: function (layer) {\r
+               if (!layer) { return false; }\r
+\r
+               return (layer in this._layers || this.getLayerId(layer) in this._layers);\r
+       },\r
+\r
+       clearLayers: function () {\r
+               this.eachLayer(this.removeLayer, this);\r
+               return this;\r
+       },\r
+\r
+       invoke: function (methodName) {\r
+               var args = Array.prototype.slice.call(arguments, 1),\r
+                   i, layer;\r
+\r
+               for (i in this._layers) {\r
+                       layer = this._layers[i];\r
+\r
+                       if (layer[methodName]) {\r
+                               layer[methodName].apply(layer, args);\r
+                       }\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       onAdd: function (map) {\r
+               this._map = map;\r
+               this.eachLayer(map.addLayer, map);\r
+       },\r
+\r
+       onRemove: function (map) {\r
+               this.eachLayer(map.removeLayer, map);\r
+               this._map = null;\r
+       },\r
+\r
+       addTo: function (map) {\r
+               map.addLayer(this);\r
+               return this;\r
+       },\r
+\r
+       eachLayer: function (method, context) {\r
+               for (var i in this._layers) {\r
+                       method.call(context, this._layers[i]);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       getLayer: function (id) {\r
+               return this._layers[id];\r
+       },\r
+\r
+       getLayers: function () {\r
+               var layers = [];\r
+\r
+               for (var i in this._layers) {\r
+                       layers.push(this._layers[i]);\r
+               }\r
+               return layers;\r
+       },\r
+\r
+       setZIndex: function (zIndex) {\r
+               return this.invoke('setZIndex', zIndex);\r
+       },\r
+\r
+       getLayerId: function (layer) {\r
+               return L.stamp(layer);\r
+       }\r
+});\r
+\r
+L.layerGroup = function (layers) {\r
+       return new L.LayerGroup(layers);\r
+};\r
+
+
+/*\r
+ * L.FeatureGroup extends L.LayerGroup by introducing mouse events and additional methods\r
+ * shared between a group of interactive layers (like vectors or markers).\r
+ */\r
+\r
+L.FeatureGroup = L.LayerGroup.extend({\r
+       includes: L.Mixin.Events,\r
+\r
+       statics: {\r
+               EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose'\r
+       },\r
+\r
+       addLayer: function (layer) {\r
+               if (this.hasLayer(layer)) {\r
+                       return this;\r
+               }\r
+\r
+               if ('on' in layer) {\r
+                       layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);\r
+               }\r
+\r
+               L.LayerGroup.prototype.addLayer.call(this, layer);\r
+\r
+               if (this._popupContent && layer.bindPopup) {\r
+                       layer.bindPopup(this._popupContent, this._popupOptions);\r
+               }\r
+\r
+               return this.fire('layeradd', {layer: layer});\r
+       },\r
+\r
+       removeLayer: function (layer) {\r
+               if (!this.hasLayer(layer)) {\r
+                       return this;\r
+               }\r
+               if (layer in this._layers) {\r
+                       layer = this._layers[layer];\r
+               }\r
+\r
+               if ('off' in layer) {\r
+                       layer.off(L.FeatureGroup.EVENTS, this._propagateEvent, this);\r
+               }\r
+\r
+               L.LayerGroup.prototype.removeLayer.call(this, layer);\r
+\r
+               if (this._popupContent) {\r
+                       this.invoke('unbindPopup');\r
+               }\r
+\r
+               return this.fire('layerremove', {layer: layer});\r
+       },\r
+\r
+       bindPopup: function (content, options) {\r
+               this._popupContent = content;\r
+               this._popupOptions = options;\r
+               return this.invoke('bindPopup', content, options);\r
+       },\r
+\r
+       openPopup: function (latlng) {\r
+               // open popup on the first layer\r
+               for (var id in this._layers) {\r
+                       this._layers[id].openPopup(latlng);\r
+                       break;\r
+               }\r
+               return this;\r
+       },\r
+\r
+       setStyle: function (style) {\r
+               return this.invoke('setStyle', style);\r
+       },\r
+\r
+       bringToFront: function () {\r
+               return this.invoke('bringToFront');\r
+       },\r
+\r
+       bringToBack: function () {\r
+               return this.invoke('bringToBack');\r
+       },\r
+\r
+       getBounds: function () {\r
+               var bounds = new L.LatLngBounds();\r
+\r
+               this.eachLayer(function (layer) {\r
+                       bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds());\r
+               });\r
+\r
+               return bounds;\r
+       },\r
+\r
+       _propagateEvent: function (e) {\r
+               e = L.extend({\r
+                       layer: e.target,\r
+                       target: this\r
+               }, e);\r
+               this.fire(e.type, e);\r
+       }\r
+});\r
+\r
+L.featureGroup = function (layers) {\r
+       return new L.FeatureGroup(layers);\r
+};\r
+
+
+/*\r
+ * L.Path is a base class for rendering vector paths on a map. Inherited by Polyline, Circle, etc.\r
+ */\r
+\r
+L.Path = L.Class.extend({\r
+       includes: [L.Mixin.Events],\r
+\r
+       statics: {\r
+               // how much to extend the clip area around the map view\r
+               // (relative to its size, e.g. 0.5 is half the screen in each direction)\r
+               // set it so that SVG element doesn't exceed 1280px (vectors flicker on dragend if it is)\r
+               CLIP_PADDING: (function () {\r
+                       var max = L.Browser.mobile ? 1280 : 2000,\r
+                           target = (max / Math.max(window.outerWidth, window.outerHeight) - 1) / 2;\r
+                       return Math.max(0, Math.min(0.5, target));\r
+               })()\r
+       },\r
+\r
+       options: {\r
+               stroke: true,\r
+               color: '#0033ff',\r
+               dashArray: null,\r
+               lineCap: null,\r
+               lineJoin: null,\r
+               weight: 5,\r
+               opacity: 0.5,\r
+\r
+               fill: false,\r
+               fillColor: null, //same as color by default\r
+               fillOpacity: 0.2,\r
+\r
+               clickable: true\r
+       },\r
+\r
+       initialize: function (options) {\r
+               L.setOptions(this, options);\r
+       },\r
+\r
+       onAdd: function (map) {\r
+               this._map = map;\r
+\r
+               if (!this._container) {\r
+                       this._initElements();\r
+                       this._initEvents();\r
+               }\r
+\r
+               this.projectLatlngs();\r
+               this._updatePath();\r
+\r
+               if (this._container) {\r
+                       this._map._pathRoot.appendChild(this._container);\r
+               }\r
+\r
+               this.fire('add');\r
+\r
+               map.on({\r
+                       'viewreset': this.projectLatlngs,\r
+                       'moveend': this._updatePath\r
+               }, this);\r
+       },\r
+\r
+       addTo: function (map) {\r
+               map.addLayer(this);\r
+               return this;\r
+       },\r
+\r
+       onRemove: function (map) {\r
+               map._pathRoot.removeChild(this._container);\r
+\r
+               // Need to fire remove event before we set _map to null as the event hooks might need the object\r
+               this.fire('remove');\r
+               this._map = null;\r
+\r
+               if (L.Browser.vml) {\r
+                       this._container = null;\r
+                       this._stroke = null;\r
+                       this._fill = null;\r
+               }\r
+\r
+               map.off({\r
+                       'viewreset': this.projectLatlngs,\r
+                       'moveend': this._updatePath\r
+               }, this);\r
+       },\r
+\r
+       projectLatlngs: function () {\r
+               // do all projection stuff here\r
+       },\r
+\r
+       setStyle: function (style) {\r
+               L.setOptions(this, style);\r
+\r
+               if (this._container) {\r
+                       this._updateStyle();\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       redraw: function () {\r
+               if (this._map) {\r
+                       this.projectLatlngs();\r
+                       this._updatePath();\r
+               }\r
+               return this;\r
+       }\r
+});\r
+\r
+L.Map.include({\r
+       _updatePathViewport: function () {\r
+               var p = L.Path.CLIP_PADDING,\r
+                   size = this.getSize(),\r
+                   panePos = L.DomUtil.getPosition(this._mapPane),\r
+                   min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)._round()),\r
+                   max = min.add(size.multiplyBy(1 + p * 2)._round());\r
+\r
+               this._pathViewport = new L.Bounds(min, max);\r
+       }\r
+});\r
+
+
+/*\r
+ * Extends L.Path with SVG-specific rendering code.\r
+ */\r
+\r
+L.Path.SVG_NS = 'http://www.w3.org/2000/svg';\r
+\r
+L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect);\r
+\r
+L.Path = L.Path.extend({\r
+       statics: {\r
+               SVG: L.Browser.svg\r
+       },\r
+\r
+       bringToFront: function () {\r
+               var root = this._map._pathRoot,\r
+                   path = this._container;\r
+\r
+               if (path && root.lastChild !== path) {\r
+                       root.appendChild(path);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       bringToBack: function () {\r
+               var root = this._map._pathRoot,\r
+                   path = this._container,\r
+                   first = root.firstChild;\r
+\r
+               if (path && first !== path) {\r
+                       root.insertBefore(path, first);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       getPathString: function () {\r
+               // form path string here\r
+       },\r
+\r
+       _createElement: function (name) {\r
+               return document.createElementNS(L.Path.SVG_NS, name);\r
+       },\r
+\r
+       _initElements: function () {\r
+               this._map._initPathRoot();\r
+               this._initPath();\r
+               this._initStyle();\r
+       },\r
+\r
+       _initPath: function () {\r
+               this._container = this._createElement('g');\r
+\r
+               this._path = this._createElement('path');\r
+\r
+               if (this.options.className) {\r
+                       L.DomUtil.addClass(this._path, this.options.className);\r
+               }\r
+\r
+               this._container.appendChild(this._path);\r
+       },\r
+\r
+       _initStyle: function () {\r
+               if (this.options.stroke) {\r
+                       this._path.setAttribute('stroke-linejoin', 'round');\r
+                       this._path.setAttribute('stroke-linecap', 'round');\r
+               }\r
+               if (this.options.fill) {\r
+                       this._path.setAttribute('fill-rule', 'evenodd');\r
+               }\r
+               if (this.options.pointerEvents) {\r
+                       this._path.setAttribute('pointer-events', this.options.pointerEvents);\r
+               }\r
+               if (!this.options.clickable && !this.options.pointerEvents) {\r
+                       this._path.setAttribute('pointer-events', 'none');\r
+               }\r
+               this._updateStyle();\r
+       },\r
+\r
+       _updateStyle: function () {\r
+               if (this.options.stroke) {\r
+                       this._path.setAttribute('stroke', this.options.color);\r
+                       this._path.setAttribute('stroke-opacity', this.options.opacity);\r
+                       this._path.setAttribute('stroke-width', this.options.weight);\r
+                       if (this.options.dashArray) {\r
+                               this._path.setAttribute('stroke-dasharray', this.options.dashArray);\r
+                       } else {\r
+                               this._path.removeAttribute('stroke-dasharray');\r
+                       }\r
+                       if (this.options.lineCap) {\r
+                               this._path.setAttribute('stroke-linecap', this.options.lineCap);\r
+                       }\r
+                       if (this.options.lineJoin) {\r
+                               this._path.setAttribute('stroke-linejoin', this.options.lineJoin);\r
+                       }\r
+               } else {\r
+                       this._path.setAttribute('stroke', 'none');\r
+               }\r
+               if (this.options.fill) {\r
+                       this._path.setAttribute('fill', this.options.fillColor || this.options.color);\r
+                       this._path.setAttribute('fill-opacity', this.options.fillOpacity);\r
+               } else {\r
+                       this._path.setAttribute('fill', 'none');\r
+               }\r
+       },\r
+\r
+       _updatePath: function () {\r
+               var str = this.getPathString();\r
+               if (!str) {\r
+                       // fix webkit empty string parsing bug\r
+                       str = 'M0 0';\r
+               }\r
+               this._path.setAttribute('d', str);\r
+       },\r
+\r
+       // TODO remove duplication with L.Map\r
+       _initEvents: function () {\r
+               if (this.options.clickable) {\r
+                       if (L.Browser.svg || !L.Browser.vml) {\r
+                               L.DomUtil.addClass(this._path, 'leaflet-clickable');\r
+                       }\r
+\r
+                       L.DomEvent.on(this._container, 'click', this._onMouseClick, this);\r
+\r
+                       var events = ['dblclick', 'mousedown', 'mouseover',\r
+                                     'mouseout', 'mousemove', 'contextmenu'];\r
+                       for (var i = 0; i < events.length; i++) {\r
+                               L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);\r
+                       }\r
+               }\r
+       },\r
+\r
+       _onMouseClick: function (e) {\r
+               if (this._map.dragging && this._map.dragging.moved()) { return; }\r
+\r
+               this._fireMouseEvent(e);\r
+       },\r
+\r
+       _fireMouseEvent: function (e) {\r
+               if (!this._map || !this.hasEventListeners(e.type)) { return; }\r
+\r
+               var map = this._map,\r
+                   containerPoint = map.mouseEventToContainerPoint(e),\r
+                   layerPoint = map.containerPointToLayerPoint(containerPoint),\r
+                   latlng = map.layerPointToLatLng(layerPoint);\r
+\r
+               this.fire(e.type, {\r
+                       latlng: latlng,\r
+                       layerPoint: layerPoint,\r
+                       containerPoint: containerPoint,\r
+                       originalEvent: e\r
+               });\r
+\r
+               if (e.type === 'contextmenu') {\r
+                       L.DomEvent.preventDefault(e);\r
+               }\r
+               if (e.type !== 'mousemove') {\r
+                       L.DomEvent.stopPropagation(e);\r
+               }\r
+       }\r
+});\r
+\r
+L.Map.include({\r
+       _initPathRoot: function () {\r
+               if (!this._pathRoot) {\r
+                       this._pathRoot = L.Path.prototype._createElement('svg');\r
+                       this._panes.overlayPane.appendChild(this._pathRoot);\r
+\r
+                       if (this.options.zoomAnimation && L.Browser.any3d) {\r
+                               L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-animated');\r
+\r
+                               this.on({\r
+                                       'zoomanim': this._animatePathZoom,\r
+                                       'zoomend': this._endPathZoom\r
+                               });\r
+                       } else {\r
+                               L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-hide');\r
+                       }\r
+\r
+                       this.on('moveend', this._updateSvgViewport);\r
+                       this._updateSvgViewport();\r
+               }\r
+       },\r
+\r
+       _animatePathZoom: function (e) {\r
+               var scale = this.getZoomScale(e.zoom),\r
+                   offset = this._getCenterOffset(e.center)._multiplyBy(-scale)._add(this._pathViewport.min);\r
+\r
+               this._pathRoot.style[L.DomUtil.TRANSFORM] =\r
+                       L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ') ';\r
+\r
+               this._pathZooming = true;\r
+       },\r
+\r
+       _endPathZoom: function () {\r
+               this._pathZooming = false;\r
+       },\r
+\r
+       _updateSvgViewport: function () {\r
+\r
+               if (this._pathZooming) {\r
+                       // Do not update SVGs while a zoom animation is going on otherwise the animation will break.\r
+                       // When the zoom animation ends we will be updated again anyway\r
+                       // This fixes the case where you do a momentum move and zoom while the move is still ongoing.\r
+                       return;\r
+               }\r
+\r
+               this._updatePathViewport();\r
+\r
+               var vp = this._pathViewport,\r
+                   min = vp.min,\r
+                   max = vp.max,\r
+                   width = max.x - min.x,\r
+                   height = max.y - min.y,\r
+                   root = this._pathRoot,\r
+                   pane = this._panes.overlayPane;\r
+\r
+               // Hack to make flicker on drag end on mobile webkit less irritating\r
+               if (L.Browser.mobileWebkit) {\r
+                       pane.removeChild(root);\r
+               }\r
+\r
+               L.DomUtil.setPosition(root, min);\r
+               root.setAttribute('width', width);\r
+               root.setAttribute('height', height);\r
+               root.setAttribute('viewBox', [min.x, min.y, width, height].join(' '));\r
+\r
+               if (L.Browser.mobileWebkit) {\r
+                       pane.appendChild(root);\r
+               }\r
+       }\r
+});\r
+
+
+/*\r
+ * Popup extension to L.Path (polylines, polygons, circles), adding popup-related methods.\r
+ */\r
+\r
+L.Path.include({\r
+\r
+       bindPopup: function (content, options) {\r
+\r
+               if (content instanceof L.Popup) {\r
+                       this._popup = content;\r
+               } else {\r
+                       if (!this._popup || options) {\r
+                               this._popup = new L.Popup(options, this);\r
+                       }\r
+                       this._popup.setContent(content);\r
+               }\r
+\r
+               if (!this._popupHandlersAdded) {\r
+                       this\r
+                           .on('click', this._openPopup, this)\r
+                           .on('remove', this.closePopup, this);\r
+\r
+                       this._popupHandlersAdded = true;\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       unbindPopup: function () {\r
+               if (this._popup) {\r
+                       this._popup = null;\r
+                       this\r
+                           .off('click', this._openPopup)\r
+                           .off('remove', this.closePopup);\r
+\r
+                       this._popupHandlersAdded = false;\r
+               }\r
+               return this;\r
+       },\r
+\r
+       openPopup: function (latlng) {\r
+\r
+               if (this._popup) {\r
+                       // open the popup from one of the path's points if not specified\r
+                       latlng = latlng || this._latlng ||\r
+                                this._latlngs[Math.floor(this._latlngs.length / 2)];\r
+\r
+                       this._openPopup({latlng: latlng});\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       closePopup: function () {\r
+               if (this._popup) {\r
+                       this._popup._close();\r
+               }\r
+               return this;\r
+       },\r
+\r
+       _openPopup: function (e) {\r
+               this._popup.setLatLng(e.latlng);\r
+               this._map.openPopup(this._popup);\r
+       }\r
+});\r
+
+
+/*\r
+ * Vector rendering for IE6-8 through VML.\r
+ * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!\r
+ */\r
+\r
+L.Browser.vml = !L.Browser.svg && (function () {\r
+       try {\r
+               var div = document.createElement('div');\r
+               div.innerHTML = '<v:shape adj="1"/>';\r
+\r
+               var shape = div.firstChild;\r
+               shape.style.behavior = 'url(#default#VML)';\r
+\r
+               return shape && (typeof shape.adj === 'object');\r
+\r
+       } catch (e) {\r
+               return false;\r
+       }\r
+}());\r
+\r
+L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({\r
+       statics: {\r
+               VML: true,\r
+               CLIP_PADDING: 0.02\r
+       },\r
+\r
+       _createElement: (function () {\r
+               try {\r
+                       document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');\r
+                       return function (name) {\r
+                               return document.createElement('<lvml:' + name + ' class="lvml">');\r
+                       };\r
+               } catch (e) {\r
+                       return function (name) {\r
+                               return document.createElement(\r
+                                       '<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');\r
+                       };\r
+               }\r
+       }()),\r
+\r
+       _initPath: function () {\r
+               var container = this._container = this._createElement('shape');\r
+\r
+               L.DomUtil.addClass(container, 'leaflet-vml-shape' +\r
+                       (this.options.className ? ' ' + this.options.className : ''));\r
+\r
+               if (this.options.clickable) {\r
+                       L.DomUtil.addClass(container, 'leaflet-clickable');\r
+               }\r
+\r
+               container.coordsize = '1 1';\r
+\r
+               this._path = this._createElement('path');\r
+               container.appendChild(this._path);\r
+\r
+               this._map._pathRoot.appendChild(container);\r
+       },\r
+\r
+       _initStyle: function () {\r
+               this._updateStyle();\r
+       },\r
+\r
+       _updateStyle: function () {\r
+               var stroke = this._stroke,\r
+                   fill = this._fill,\r
+                   options = this.options,\r
+                   container = this._container;\r
+\r
+               container.stroked = options.stroke;\r
+               container.filled = options.fill;\r
+\r
+               if (options.stroke) {\r
+                       if (!stroke) {\r
+                               stroke = this._stroke = this._createElement('stroke');\r
+                               stroke.endcap = 'round';\r
+                               container.appendChild(stroke);\r
+                       }\r
+                       stroke.weight = options.weight + 'px';\r
+                       stroke.color = options.color;\r
+                       stroke.opacity = options.opacity;\r
+\r
+                       if (options.dashArray) {\r
+                               stroke.dashStyle = L.Util.isArray(options.dashArray) ?\r
+                                   options.dashArray.join(' ') :\r
+                                   options.dashArray.replace(/( *, *)/g, ' ');\r
+                       } else {\r
+                               stroke.dashStyle = '';\r
+                       }\r
+                       if (options.lineCap) {\r
+                               stroke.endcap = options.lineCap.replace('butt', 'flat');\r
+                       }\r
+                       if (options.lineJoin) {\r
+                               stroke.joinstyle = options.lineJoin;\r
+                       }\r
+\r
+               } else if (stroke) {\r
+                       container.removeChild(stroke);\r
+                       this._stroke = null;\r
+               }\r
+\r
+               if (options.fill) {\r
+                       if (!fill) {\r
+                               fill = this._fill = this._createElement('fill');\r
+                               container.appendChild(fill);\r
+                       }\r
+                       fill.color = options.fillColor || options.color;\r
+                       fill.opacity = options.fillOpacity;\r
+\r
+               } else if (fill) {\r
+                       container.removeChild(fill);\r
+                       this._fill = null;\r
+               }\r
+       },\r
+\r
+       _updatePath: function () {\r
+               var style = this._container.style;\r
+\r
+               style.display = 'none';\r
+               this._path.v = this.getPathString() + ' '; // the space fixes IE empty path string bug\r
+               style.display = '';\r
+       }\r
+});\r
+\r
+L.Map.include(L.Browser.svg || !L.Browser.vml ? {} : {\r
+       _initPathRoot: function () {\r
+               if (this._pathRoot) { return; }\r
+\r
+               var root = this._pathRoot = document.createElement('div');\r
+               root.className = 'leaflet-vml-container';\r
+               this._panes.overlayPane.appendChild(root);\r
+\r
+               this.on('moveend', this._updatePathViewport);\r
+               this._updatePathViewport();\r
+       }\r
+});\r
+
+
+/*\r
+ * Vector rendering for all browsers that support canvas.\r
+ */\r
+\r
+L.Browser.canvas = (function () {\r
+       return !!document.createElement('canvas').getContext;\r
+}());\r
+\r
+L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({\r
+       statics: {\r
+               //CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value\r
+               CANVAS: true,\r
+               SVG: false\r
+       },\r
+\r
+       redraw: function () {\r
+               if (this._map) {\r
+                       this.projectLatlngs();\r
+                       this._requestUpdate();\r
+               }\r
+               return this;\r
+       },\r
+\r
+       setStyle: function (style) {\r
+               L.setOptions(this, style);\r
+\r
+               if (this._map) {\r
+                       this._updateStyle();\r
+                       this._requestUpdate();\r
+               }\r
+               return this;\r
+       },\r
+\r
+       onRemove: function (map) {\r
+               map\r
+                   .off('viewreset', this.projectLatlngs, this)\r
+                   .off('moveend', this._updatePath, this);\r
+\r
+               if (this.options.clickable) {\r
+                       this._map.off('click', this._onClick, this);\r
+                       this._map.off('mousemove', this._onMouseMove, this);\r
+               }\r
+\r
+               this._requestUpdate();\r
+               \r
+               this.fire('remove');\r
+               this._map = null;\r
+       },\r
+\r
+       _requestUpdate: function () {\r
+               if (this._map && !L.Path._updateRequest) {\r
+                       L.Path._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map);\r
+               }\r
+       },\r
+\r
+       _fireMapMoveEnd: function () {\r
+               L.Path._updateRequest = null;\r
+               this.fire('moveend');\r
+       },\r
+\r
+       _initElements: function () {\r
+               this._map._initPathRoot();\r
+               this._ctx = this._map._canvasCtx;\r
+       },\r
+\r
+       _updateStyle: function () {\r
+               var options = this.options;\r
+\r
+               if (options.stroke) {\r
+                       this._ctx.lineWidth = options.weight;\r
+                       this._ctx.strokeStyle = options.color;\r
+               }\r
+               if (options.fill) {\r
+                       this._ctx.fillStyle = options.fillColor || options.color;\r
+               }\r
+\r
+               if (options.lineCap) {\r
+                       this._ctx.lineCap = options.lineCap;\r
+               }\r
+               if (options.lineJoin) {\r
+                       this._ctx.lineJoin = options.lineJoin;\r
+               }\r
+       },\r
+\r
+       _drawPath: function () {\r
+               var i, j, len, len2, point, drawMethod;\r
+\r
+               this._ctx.beginPath();\r
+\r
+               for (i = 0, len = this._parts.length; i < len; i++) {\r
+                       for (j = 0, len2 = this._parts[i].length; j < len2; j++) {\r
+                               point = this._parts[i][j];\r
+                               drawMethod = (j === 0 ? 'move' : 'line') + 'To';\r
+\r
+                               this._ctx[drawMethod](point.x, point.y);\r
+                       }\r
+                       // TODO refactor ugly hack\r
+                       if (this instanceof L.Polygon) {\r
+                               this._ctx.closePath();\r
+                       }\r
+               }\r
+       },\r
+\r
+       _checkIfEmpty: function () {\r
+               return !this._parts.length;\r
+       },\r
+\r
+       _updatePath: function () {\r
+               if (this._checkIfEmpty()) { return; }\r
+\r
+               var ctx = this._ctx,\r
+                   options = this.options;\r
+\r
+               this._drawPath();\r
+               ctx.save();\r
+               this._updateStyle();\r
+\r
+               if (options.fill) {\r
+                       ctx.globalAlpha = options.fillOpacity;\r
+                       ctx.fill(options.fillRule || 'evenodd');\r
+               }\r
+\r
+               if (options.stroke) {\r
+                       ctx.globalAlpha = options.opacity;\r
+                       ctx.stroke();\r
+               }\r
+\r
+               ctx.restore();\r
+\r
+               // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature\r
+       },\r
+\r
+       _initEvents: function () {\r
+               if (this.options.clickable) {\r
+                       this._map.on('mousemove', this._onMouseMove, this);\r
+                       this._map.on('click dblclick contextmenu', this._fireMouseEvent, this);\r
+               }\r
+       },\r
+\r
+       _fireMouseEvent: function (e) {\r
+               if (this._containsPoint(e.layerPoint)) {\r
+                       this.fire(e.type, e);\r
+               }\r
+       },\r
+\r
+       _onMouseMove: function (e) {\r
+               if (!this._map || this._map._animatingZoom) { return; }\r
+\r
+               // TODO don't do on each move\r
+               if (this._containsPoint(e.layerPoint)) {\r
+                       this._ctx.canvas.style.cursor = 'pointer';\r
+                       this._mouseInside = true;\r
+                       this.fire('mouseover', e);\r
+\r
+               } else if (this._mouseInside) {\r
+                       this._ctx.canvas.style.cursor = '';\r
+                       this._mouseInside = false;\r
+                       this.fire('mouseout', e);\r
+               }\r
+       }\r
+});\r
+\r
+L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : {\r
+       _initPathRoot: function () {\r
+               var root = this._pathRoot,\r
+                   ctx;\r
+\r
+               if (!root) {\r
+                       root = this._pathRoot = document.createElement('canvas');\r
+                       root.style.position = 'absolute';\r
+                       ctx = this._canvasCtx = root.getContext('2d');\r
+\r
+                       ctx.lineCap = 'round';\r
+                       ctx.lineJoin = 'round';\r
+\r
+                       this._panes.overlayPane.appendChild(root);\r
+\r
+                       if (this.options.zoomAnimation) {\r
+                               this._pathRoot.className = 'leaflet-zoom-animated';\r
+                               this.on('zoomanim', this._animatePathZoom);\r
+                               this.on('zoomend', this._endPathZoom);\r
+                       }\r
+                       this.on('moveend', this._updateCanvasViewport);\r
+                       this._updateCanvasViewport();\r
+               }\r
+       },\r
+\r
+       _updateCanvasViewport: function () {\r
+               // don't redraw while zooming. See _updateSvgViewport for more details\r
+               if (this._pathZooming) { return; }\r
+               this._updatePathViewport();\r
+\r
+               var vp = this._pathViewport,\r
+                   min = vp.min,\r
+                   size = vp.max.subtract(min),\r
+                   root = this._pathRoot;\r
+\r
+               //TODO check if this works properly on mobile webkit\r
+               L.DomUtil.setPosition(root, min);\r
+               root.width = size.x;\r
+               root.height = size.y;\r
+               root.getContext('2d').translate(-min.x, -min.y);\r
+       }\r
+});\r
+
+
+/*\r
+ * L.LineUtil contains different utility functions for line segments\r
+ * and polylines (clipping, simplification, distances, etc.)\r
+ */\r
+\r
+/*jshint bitwise:false */ // allow bitwise operations for this file\r
+\r
+L.LineUtil = {\r
+\r
+       // Simplify polyline with vertex reduction and Douglas-Peucker simplification.\r
+       // Improves rendering performance dramatically by lessening the number of points to draw.\r
+\r
+       simplify: function (/*Point[]*/ points, /*Number*/ tolerance) {\r
+               if (!tolerance || !points.length) {\r
+                       return points.slice();\r
+               }\r
+\r
+               var sqTolerance = tolerance * tolerance;\r
+\r
+               // stage 1: vertex reduction\r
+               points = this._reducePoints(points, sqTolerance);\r
+\r
+               // stage 2: Douglas-Peucker simplification\r
+               points = this._simplifyDP(points, sqTolerance);\r
+\r
+               return points;\r
+       },\r
+\r
+       // distance from a point to a segment between two points\r
+       pointToSegmentDistance:  function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {\r
+               return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));\r
+       },\r
+\r
+       closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {\r
+               return this._sqClosestPointOnSegment(p, p1, p2);\r
+       },\r
+\r
+       // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm\r
+       _simplifyDP: function (points, sqTolerance) {\r
+\r
+               var len = points.length,\r
+                   ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,\r
+                   markers = new ArrayConstructor(len);\r
+\r
+               markers[0] = markers[len - 1] = 1;\r
+\r
+               this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);\r
+\r
+               var i,\r
+                   newPoints = [];\r
+\r
+               for (i = 0; i < len; i++) {\r
+                       if (markers[i]) {\r
+                               newPoints.push(points[i]);\r
+                       }\r
+               }\r
+\r
+               return newPoints;\r
+       },\r
+\r
+       _simplifyDPStep: function (points, markers, sqTolerance, first, last) {\r
+\r
+               var maxSqDist = 0,\r
+                   index, i, sqDist;\r
+\r
+               for (i = first + 1; i <= last - 1; i++) {\r
+                       sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);\r
+\r
+                       if (sqDist > maxSqDist) {\r
+                               index = i;\r
+                               maxSqDist = sqDist;\r
+                       }\r
+               }\r
+\r
+               if (maxSqDist > sqTolerance) {\r
+                       markers[index] = 1;\r
+\r
+                       this._simplifyDPStep(points, markers, sqTolerance, first, index);\r
+                       this._simplifyDPStep(points, markers, sqTolerance, index, last);\r
+               }\r
+       },\r
+\r
+       // reduce points that are too close to each other to a single point\r
+       _reducePoints: function (points, sqTolerance) {\r
+               var reducedPoints = [points[0]];\r
+\r
+               for (var i = 1, prev = 0, len = points.length; i < len; i++) {\r
+                       if (this._sqDist(points[i], points[prev]) > sqTolerance) {\r
+                               reducedPoints.push(points[i]);\r
+                               prev = i;\r
+                       }\r
+               }\r
+               if (prev < len - 1) {\r
+                       reducedPoints.push(points[len - 1]);\r
+               }\r
+               return reducedPoints;\r
+       },\r
+\r
+       // Cohen-Sutherland line clipping algorithm.\r
+       // Used to avoid rendering parts of a polyline that are not currently visible.\r
+\r
+       clipSegment: function (a, b, bounds, useLastCode) {\r
+               var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),\r
+                   codeB = this._getBitCode(b, bounds),\r
+\r
+                   codeOut, p, newCode;\r
+\r
+               // save 2nd code to avoid calculating it on the next segment\r
+               this._lastCode = codeB;\r
+\r
+               while (true) {\r
+                       // if a,b is inside the clip window (trivial accept)\r
+                       if (!(codeA | codeB)) {\r
+                               return [a, b];\r
+                       // if a,b is outside the clip window (trivial reject)\r
+                       } else if (codeA & codeB) {\r
+                               return false;\r
+                       // other cases\r
+                       } else {\r
+                               codeOut = codeA || codeB;\r
+                               p = this._getEdgeIntersection(a, b, codeOut, bounds);\r
+                               newCode = this._getBitCode(p, bounds);\r
+\r
+                               if (codeOut === codeA) {\r
+                                       a = p;\r
+                                       codeA = newCode;\r
+                               } else {\r
+                                       b = p;\r
+                                       codeB = newCode;\r
+                               }\r
+                       }\r
+               }\r
+       },\r
+\r
+       _getEdgeIntersection: function (a, b, code, bounds) {\r
+               var dx = b.x - a.x,\r
+                   dy = b.y - a.y,\r
+                   min = bounds.min,\r
+                   max = bounds.max;\r
+\r
+               if (code & 8) { // top\r
+                       return new L.Point(a.x + dx * (max.y - a.y) / dy, max.y);\r
+               } else if (code & 4) { // bottom\r
+                       return new L.Point(a.x + dx * (min.y - a.y) / dy, min.y);\r
+               } else if (code & 2) { // right\r
+                       return new L.Point(max.x, a.y + dy * (max.x - a.x) / dx);\r
+               } else if (code & 1) { // left\r
+                       return new L.Point(min.x, a.y + dy * (min.x - a.x) / dx);\r
+               }\r
+       },\r
+\r
+       _getBitCode: function (/*Point*/ p, bounds) {\r
+               var code = 0;\r
+\r
+               if (p.x < bounds.min.x) { // left\r
+                       code |= 1;\r
+               } else if (p.x > bounds.max.x) { // right\r
+                       code |= 2;\r
+               }\r
+               if (p.y < bounds.min.y) { // bottom\r
+                       code |= 4;\r
+               } else if (p.y > bounds.max.y) { // top\r
+                       code |= 8;\r
+               }\r
+\r
+               return code;\r
+       },\r
+\r
+       // square distance (to avoid unnecessary Math.sqrt calls)\r
+       _sqDist: function (p1, p2) {\r
+               var dx = p2.x - p1.x,\r
+                   dy = p2.y - p1.y;\r
+               return dx * dx + dy * dy;\r
+       },\r
+\r
+       // return closest point on segment or distance to that point\r
+       _sqClosestPointOnSegment: function (p, p1, p2, sqDist) {\r
+               var x = p1.x,\r
+                   y = p1.y,\r
+                   dx = p2.x - x,\r
+                   dy = p2.y - y,\r
+                   dot = dx * dx + dy * dy,\r
+                   t;\r
+\r
+               if (dot > 0) {\r
+                       t = ((p.x - x) * dx + (p.y - y) * dy) / dot;\r
+\r
+                       if (t > 1) {\r
+                               x = p2.x;\r
+                               y = p2.y;\r
+                       } else if (t > 0) {\r
+                               x += dx * t;\r
+                               y += dy * t;\r
+                       }\r
+               }\r
+\r
+               dx = p.x - x;\r
+               dy = p.y - y;\r
+\r
+               return sqDist ? dx * dx + dy * dy : new L.Point(x, y);\r
+       }\r
+};\r
+
+
+/*\r
+ * L.Polyline is used to display polylines on a map.\r
+ */\r
+\r
+L.Polyline = L.Path.extend({\r
+       initialize: function (latlngs, options) {\r
+               L.Path.prototype.initialize.call(this, options);\r
+\r
+               this._latlngs = this._convertLatLngs(latlngs);\r
+       },\r
+\r
+       options: {\r
+               // how much to simplify the polyline on each zoom level\r
+               // more = better performance and smoother look, less = more accurate\r
+               smoothFactor: 1.0,\r
+               noClip: false\r
+       },\r
+\r
+       projectLatlngs: function () {\r
+               this._originalPoints = [];\r
+\r
+               for (var i = 0, len = this._latlngs.length; i < len; i++) {\r
+                       this._originalPoints[i] = this._map.latLngToLayerPoint(this._latlngs[i]);\r
+               }\r
+       },\r
+\r
+       getPathString: function () {\r
+               for (var i = 0, len = this._parts.length, str = ''; i < len; i++) {\r
+                       str += this._getPathPartStr(this._parts[i]);\r
+               }\r
+               return str;\r
+       },\r
+\r
+       getLatLngs: function () {\r
+               return this._latlngs;\r
+       },\r
+\r
+       setLatLngs: function (latlngs) {\r
+               this._latlngs = this._convertLatLngs(latlngs);\r
+               return this.redraw();\r
+       },\r
+\r
+       addLatLng: function (latlng) {\r
+               this._latlngs.push(L.latLng(latlng));\r
+               return this.redraw();\r
+       },\r
+\r
+       spliceLatLngs: function () { // (Number index, Number howMany)\r
+               var removed = [].splice.apply(this._latlngs, arguments);\r
+               this._convertLatLngs(this._latlngs, true);\r
+               this.redraw();\r
+               return removed;\r
+       },\r
+\r
+       closestLayerPoint: function (p) {\r
+               var minDistance = Infinity, parts = this._parts, p1, p2, minPoint = null;\r
+\r
+               for (var j = 0, jLen = parts.length; j < jLen; j++) {\r
+                       var points = parts[j];\r
+                       for (var i = 1, len = points.length; i < len; i++) {\r
+                               p1 = points[i - 1];\r
+                               p2 = points[i];\r
+                               var sqDist = L.LineUtil._sqClosestPointOnSegment(p, p1, p2, true);\r
+                               if (sqDist < minDistance) {\r
+                                       minDistance = sqDist;\r
+                                       minPoint = L.LineUtil._sqClosestPointOnSegment(p, p1, p2);\r
+                               }\r
+                       }\r
+               }\r
+               if (minPoint) {\r
+                       minPoint.distance = Math.sqrt(minDistance);\r
+               }\r
+               return minPoint;\r
+       },\r
+\r
+       getBounds: function () {\r
+               return new L.LatLngBounds(this.getLatLngs());\r
+       },\r
+\r
+       _convertLatLngs: function (latlngs, overwrite) {\r
+               var i, len, target = overwrite ? latlngs : [];\r
+\r
+               for (i = 0, len = latlngs.length; i < len; i++) {\r
+                       if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') {\r
+                               return;\r
+                       }\r
+                       target[i] = L.latLng(latlngs[i]);\r
+               }\r
+               return target;\r
+       },\r
+\r
+       _initEvents: function () {\r
+               L.Path.prototype._initEvents.call(this);\r
+       },\r
+\r
+       _getPathPartStr: function (points) {\r
+               var round = L.Path.VML;\r
+\r
+               for (var j = 0, len2 = points.length, str = '', p; j < len2; j++) {\r
+                       p = points[j];\r
+                       if (round) {\r
+                               p._round();\r
+                       }\r
+                       str += (j ? 'L' : 'M') + p.x + ' ' + p.y;\r
+               }\r
+               return str;\r
+       },\r
+\r
+       _clipPoints: function () {\r
+               var points = this._originalPoints,\r
+                   len = points.length,\r
+                   i, k, segment;\r
+\r
+               if (this.options.noClip) {\r
+                       this._parts = [points];\r
+                       return;\r
+               }\r
+\r
+               this._parts = [];\r
+\r
+               var parts = this._parts,\r
+                   vp = this._map._pathViewport,\r
+                   lu = L.LineUtil;\r
+\r
+               for (i = 0, k = 0; i < len - 1; i++) {\r
+                       segment = lu.clipSegment(points[i], points[i + 1], vp, i);\r
+                       if (!segment) {\r
+                               continue;\r
+                       }\r
+\r
+                       parts[k] = parts[k] || [];\r
+                       parts[k].push(segment[0]);\r
+\r
+                       // if segment goes out of screen, or it's the last one, it's the end of the line part\r
+                       if ((segment[1] !== points[i + 1]) || (i === len - 2)) {\r
+                               parts[k].push(segment[1]);\r
+                               k++;\r
+                       }\r
+               }\r
+       },\r
+\r
+       // simplify each clipped part of the polyline\r
+       _simplifyPoints: function () {\r
+               var parts = this._parts,\r
+                   lu = L.LineUtil;\r
+\r
+               for (var i = 0, len = parts.length; i < len; i++) {\r
+                       parts[i] = lu.simplify(parts[i], this.options.smoothFactor);\r
+               }\r
+       },\r
+\r
+       _updatePath: function () {\r
+               if (!this._map) { return; }\r
+\r
+               this._clipPoints();\r
+               this._simplifyPoints();\r
+\r
+               L.Path.prototype._updatePath.call(this);\r
+       }\r
+});\r
+\r
+L.polyline = function (latlngs, options) {\r
+       return new L.Polyline(latlngs, options);\r
+};\r
+
+
+/*\r
+ * L.PolyUtil contains utility functions for polygons (clipping, etc.).\r
+ */\r
+\r
+/*jshint bitwise:false */ // allow bitwise operations here\r
+\r
+L.PolyUtil = {};\r
+\r
+/*\r
+ * Sutherland-Hodgeman polygon clipping algorithm.\r
+ * Used to avoid rendering parts of a polygon that are not currently visible.\r
+ */\r
+L.PolyUtil.clipPolygon = function (points, bounds) {\r
+       var clippedPoints,\r
+           edges = [1, 4, 2, 8],\r
+           i, j, k,\r
+           a, b,\r
+           len, edge, p,\r
+           lu = L.LineUtil;\r
+\r
+       for (i = 0, len = points.length; i < len; i++) {\r
+               points[i]._code = lu._getBitCode(points[i], bounds);\r
+       }\r
+\r
+       // for each edge (left, bottom, right, top)\r
+       for (k = 0; k < 4; k++) {\r
+               edge = edges[k];\r
+               clippedPoints = [];\r
+\r
+               for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {\r
+                       a = points[i];\r
+                       b = points[j];\r
+\r
+                       // if a is inside the clip window\r
+                       if (!(a._code & edge)) {\r
+                               // if b is outside the clip window (a->b goes out of screen)\r
+                               if (b._code & edge) {\r
+                                       p = lu._getEdgeIntersection(b, a, edge, bounds);\r
+                                       p._code = lu._getBitCode(p, bounds);\r
+                                       clippedPoints.push(p);\r
+                               }\r
+                               clippedPoints.push(a);\r
+\r
+                       // else if b is inside the clip window (a->b enters the screen)\r
+                       } else if (!(b._code & edge)) {\r
+                               p = lu._getEdgeIntersection(b, a, edge, bounds);\r
+                               p._code = lu._getBitCode(p, bounds);\r
+                               clippedPoints.push(p);\r
+                       }\r
+               }\r
+               points = clippedPoints;\r
+       }\r
+\r
+       return points;\r
+};\r
+
+
+/*\r
+ * L.Polygon is used to display polygons on a map.\r
+ */\r
+\r
+L.Polygon = L.Polyline.extend({\r
+       options: {\r
+               fill: true\r
+       },\r
+\r
+       initialize: function (latlngs, options) {\r
+               L.Polyline.prototype.initialize.call(this, latlngs, options);\r
+               this._initWithHoles(latlngs);\r
+       },\r
+\r
+       _initWithHoles: function (latlngs) {\r
+               var i, len, hole;\r
+               if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) {\r
+                       this._latlngs = this._convertLatLngs(latlngs[0]);\r
+                       this._holes = latlngs.slice(1);\r
+\r
+                       for (i = 0, len = this._holes.length; i < len; i++) {\r
+                               hole = this._holes[i] = this._convertLatLngs(this._holes[i]);\r
+                               if (hole[0].equals(hole[hole.length - 1])) {\r
+                                       hole.pop();\r
+                               }\r
+                       }\r
+               }\r
+\r
+               // filter out last point if its equal to the first one\r
+               latlngs = this._latlngs;\r
+\r
+               if (latlngs.length >= 2 && latlngs[0].equals(latlngs[latlngs.length - 1])) {\r
+                       latlngs.pop();\r
+               }\r
+       },\r
+\r
+       projectLatlngs: function () {\r
+               L.Polyline.prototype.projectLatlngs.call(this);\r
+\r
+               // project polygon holes points\r
+               // TODO move this logic to Polyline to get rid of duplication\r
+               this._holePoints = [];\r
+\r
+               if (!this._holes) { return; }\r
+\r
+               var i, j, len, len2;\r
+\r
+               for (i = 0, len = this._holes.length; i < len; i++) {\r
+                       this._holePoints[i] = [];\r
+\r
+                       for (j = 0, len2 = this._holes[i].length; j < len2; j++) {\r
+                               this._holePoints[i][j] = this._map.latLngToLayerPoint(this._holes[i][j]);\r
+                       }\r
+               }\r
+       },\r
+\r
+       setLatLngs: function (latlngs) {\r
+               if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) {\r
+                       this._initWithHoles(latlngs);\r
+                       return this.redraw();\r
+               } else {\r
+                       return L.Polyline.prototype.setLatLngs.call(this, latlngs);\r
+               }\r
+       },\r
+\r
+       _clipPoints: function () {\r
+               var points = this._originalPoints,\r
+                   newParts = [];\r
+\r
+               this._parts = [points].concat(this._holePoints);\r
+\r
+               if (this.options.noClip) { return; }\r
+\r
+               for (var i = 0, len = this._parts.length; i < len; i++) {\r
+                       var clipped = L.PolyUtil.clipPolygon(this._parts[i], this._map._pathViewport);\r
+                       if (clipped.length) {\r
+                               newParts.push(clipped);\r
+                       }\r
+               }\r
+\r
+               this._parts = newParts;\r
+       },\r
+\r
+       _getPathPartStr: function (points) {\r
+               var str = L.Polyline.prototype._getPathPartStr.call(this, points);\r
+               return str + (L.Browser.svg ? 'z' : 'x');\r
+       }\r
+});\r
+\r
+L.polygon = function (latlngs, options) {\r
+       return new L.Polygon(latlngs, options);\r
+};\r
+
+
+/*\r
+ * Contains L.MultiPolyline and L.MultiPolygon layers.\r
+ */\r
+\r
+(function () {\r
+       function createMulti(Klass) {\r
+\r
+               return L.FeatureGroup.extend({\r
+\r
+                       initialize: function (latlngs, options) {\r
+                               this._layers = {};\r
+                               this._options = options;\r
+                               this.setLatLngs(latlngs);\r
+                       },\r
+\r
+                       setLatLngs: function (latlngs) {\r
+                               var i = 0,\r
+                                   len = latlngs.length;\r
+\r
+                               this.eachLayer(function (layer) {\r
+                                       if (i < len) {\r
+                                               layer.setLatLngs(latlngs[i++]);\r
+                                       } else {\r
+                                               this.removeLayer(layer);\r
+                                       }\r
+                               }, this);\r
+\r
+                               while (i < len) {\r
+                                       this.addLayer(new Klass(latlngs[i++], this._options));\r
+                               }\r
+\r
+                               return this;\r
+                       },\r
+\r
+                       getLatLngs: function () {\r
+                               var latlngs = [];\r
+\r
+                               this.eachLayer(function (layer) {\r
+                                       latlngs.push(layer.getLatLngs());\r
+                               });\r
+\r
+                               return latlngs;\r
+                       }\r
+               });\r
+       }\r
+\r
+       L.MultiPolyline = createMulti(L.Polyline);\r
+       L.MultiPolygon = createMulti(L.Polygon);\r
+\r
+       L.multiPolyline = function (latlngs, options) {\r
+               return new L.MultiPolyline(latlngs, options);\r
+       };\r
+\r
+       L.multiPolygon = function (latlngs, options) {\r
+               return new L.MultiPolygon(latlngs, options);\r
+       };\r
+}());\r
+
+
+/*\r
+ * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.\r
+ */\r
+\r
+L.Rectangle = L.Polygon.extend({\r
+       initialize: function (latLngBounds, options) {\r
+               L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);\r
+       },\r
+\r
+       setBounds: function (latLngBounds) {\r
+               this.setLatLngs(this._boundsToLatLngs(latLngBounds));\r
+       },\r
+\r
+       _boundsToLatLngs: function (latLngBounds) {\r
+               latLngBounds = L.latLngBounds(latLngBounds);\r
+               return [\r
+                       latLngBounds.getSouthWest(),\r
+                       latLngBounds.getNorthWest(),\r
+                       latLngBounds.getNorthEast(),\r
+                       latLngBounds.getSouthEast()\r
+               ];\r
+       }\r
+});\r
+\r
+L.rectangle = function (latLngBounds, options) {\r
+       return new L.Rectangle(latLngBounds, options);\r
+};\r
+
+
+/*\r
+ * L.Circle is a circle overlay (with a certain radius in meters).\r
+ */\r
+\r
+L.Circle = L.Path.extend({\r
+       initialize: function (latlng, radius, options) {\r
+               L.Path.prototype.initialize.call(this, options);\r
+\r
+               this._latlng = L.latLng(latlng);\r
+               this._mRadius = radius;\r
+       },\r
+\r
+       options: {\r
+               fill: true\r
+       },\r
+\r
+       setLatLng: function (latlng) {\r
+               this._latlng = L.latLng(latlng);\r
+               return this.redraw();\r
+       },\r
+\r
+       setRadius: function (radius) {\r
+               this._mRadius = radius;\r
+               return this.redraw();\r
+       },\r
+\r
+       projectLatlngs: function () {\r
+               var lngRadius = this._getLngRadius(),\r
+                   latlng = this._latlng,\r
+                   pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]);\r
+\r
+               this._point = this._map.latLngToLayerPoint(latlng);\r
+               this._radius = Math.max(this._point.x - pointLeft.x, 1);\r
+       },\r
+\r
+       getBounds: function () {\r
+               var lngRadius = this._getLngRadius(),\r
+                   latRadius = (this._mRadius / 40075017) * 360,\r
+                   latlng = this._latlng;\r
+\r
+               return new L.LatLngBounds(\r
+                       [latlng.lat - latRadius, latlng.lng - lngRadius],\r
+                       [latlng.lat + latRadius, latlng.lng + lngRadius]);\r
+       },\r
+\r
+       getLatLng: function () {\r
+               return this._latlng;\r
+       },\r
+\r
+       getPathString: function () {\r
+               var p = this._point,\r
+                   r = this._radius;\r
+\r
+               if (this._checkIfEmpty()) {\r
+                       return '';\r
+               }\r
+\r
+               if (L.Browser.svg) {\r
+                       return 'M' + p.x + ',' + (p.y - r) +\r
+                              'A' + r + ',' + r + ',0,1,1,' +\r
+                              (p.x - 0.1) + ',' + (p.y - r) + ' z';\r
+               } else {\r
+                       p._round();\r
+                       r = Math.round(r);\r
+                       return 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r + ' 0,' + (65535 * 360);\r
+               }\r
+       },\r
+\r
+       getRadius: function () {\r
+               return this._mRadius;\r
+       },\r
+\r
+       // TODO Earth hardcoded, move into projection code!\r
+\r
+       _getLatRadius: function () {\r
+               return (this._mRadius / 40075017) * 360;\r
+       },\r
+\r
+       _getLngRadius: function () {\r
+               return this._getLatRadius() / Math.cos(L.LatLng.DEG_TO_RAD * this._latlng.lat);\r
+       },\r
+\r
+       _checkIfEmpty: function () {\r
+               if (!this._map) {\r
+                       return false;\r
+               }\r
+               var vp = this._map._pathViewport,\r
+                   r = this._radius,\r
+                   p = this._point;\r
+\r
+               return p.x - r > vp.max.x || p.y - r > vp.max.y ||\r
+                      p.x + r < vp.min.x || p.y + r < vp.min.y;\r
+       }\r
+});\r
+\r
+L.circle = function (latlng, radius, options) {\r
+       return new L.Circle(latlng, radius, options);\r
+};\r
+
+
+/*\r
+ * L.CircleMarker is a circle overlay with a permanent pixel radius.\r
+ */\r
+\r
+L.CircleMarker = L.Circle.extend({\r
+       options: {\r
+               radius: 10,\r
+               weight: 2\r
+       },\r
+\r
+       initialize: function (latlng, options) {\r
+               L.Circle.prototype.initialize.call(this, latlng, null, options);\r
+               this._radius = this.options.radius;\r
+       },\r
+\r
+       projectLatlngs: function () {\r
+               this._point = this._map.latLngToLayerPoint(this._latlng);\r
+       },\r
+\r
+       _updateStyle : function () {\r
+               L.Circle.prototype._updateStyle.call(this);\r
+               this.setRadius(this.options.radius);\r
+       },\r
+\r
+       setLatLng: function (latlng) {\r
+               L.Circle.prototype.setLatLng.call(this, latlng);\r
+               if (this._popup && this._popup._isOpen) {\r
+                       this._popup.setLatLng(latlng);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       setRadius: function (radius) {\r
+               this.options.radius = this._radius = radius;\r
+               return this.redraw();\r
+       },\r
+\r
+       getRadius: function () {\r
+               return this._radius;\r
+       }\r
+});\r
+\r
+L.circleMarker = function (latlng, options) {\r
+       return new L.CircleMarker(latlng, options);\r
+};\r
+
+
+/*\r
+ * Extends L.Polyline to be able to manually detect clicks on Canvas-rendered polylines.\r
+ */\r
+\r
+L.Polyline.include(!L.Path.CANVAS ? {} : {\r
+       _containsPoint: function (p, closed) {\r
+               var i, j, k, len, len2, dist, part,\r
+                   w = this.options.weight / 2;\r
+\r
+               if (L.Browser.touch) {\r
+                       w += 10; // polyline click tolerance on touch devices\r
+               }\r
+\r
+               for (i = 0, len = this._parts.length; i < len; i++) {\r
+                       part = this._parts[i];\r
+                       for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {\r
+                               if (!closed && (j === 0)) {\r
+                                       continue;\r
+                               }\r
+\r
+                               dist = L.LineUtil.pointToSegmentDistance(p, part[k], part[j]);\r
+\r
+                               if (dist <= w) {\r
+                                       return true;\r
+                               }\r
+                       }\r
+               }\r
+               return false;\r
+       }\r
+});\r
+
+
+/*\r
+ * Extends L.Polygon to be able to manually detect clicks on Canvas-rendered polygons.\r
+ */\r
+\r
+L.Polygon.include(!L.Path.CANVAS ? {} : {\r
+       _containsPoint: function (p) {\r
+               var inside = false,\r
+                   part, p1, p2,\r
+                   i, j, k,\r
+                   len, len2;\r
+\r
+               // TODO optimization: check if within bounds first\r
+\r
+               if (L.Polyline.prototype._containsPoint.call(this, p, true)) {\r
+                       // click on polygon border\r
+                       return true;\r
+               }\r
+\r
+               // ray casting algorithm for detecting if point is in polygon\r
+\r
+               for (i = 0, len = this._parts.length; i < len; i++) {\r
+                       part = this._parts[i];\r
+\r
+                       for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {\r
+                               p1 = part[j];\r
+                               p2 = part[k];\r
+\r
+                               if (((p1.y > p.y) !== (p2.y > p.y)) &&\r
+                                               (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {\r
+                                       inside = !inside;\r
+                               }\r
+                       }\r
+               }\r
+\r
+               return inside;\r
+       }\r
+});\r
+
+
+/*\r
+ * Extends L.Circle with Canvas-specific code.\r
+ */\r
+\r
+L.Circle.include(!L.Path.CANVAS ? {} : {\r
+       _drawPath: function () {\r
+               var p = this._point;\r
+               this._ctx.beginPath();\r
+               this._ctx.arc(p.x, p.y, this._radius, 0, Math.PI * 2, false);\r
+       },\r
+\r
+       _containsPoint: function (p) {\r
+               var center = this._point,\r
+                   w2 = this.options.stroke ? this.options.weight / 2 : 0;\r
+\r
+               return (p.distanceTo(center) <= this._radius + w2);\r
+       }\r
+});\r
+
+
+/*
+ * CircleMarker canvas specific drawing parts.
+ */
+
+L.CircleMarker.include(!L.Path.CANVAS ? {} : {
+       _updateStyle: function () {
+               L.Path.prototype._updateStyle.call(this);
+       }
+});
+
+
+/*\r
+ * L.GeoJSON turns any GeoJSON data into a Leaflet layer.\r
+ */\r
+\r
+L.GeoJSON = L.FeatureGroup.extend({\r
+\r
+       initialize: function (geojson, options) {\r
+               L.setOptions(this, options);\r
+\r
+               this._layers = {};\r
+\r
+               if (geojson) {\r
+                       this.addData(geojson);\r
+               }\r
+       },\r
+\r
+       addData: function (geojson) {\r
+               var features = L.Util.isArray(geojson) ? geojson : geojson.features,\r
+                   i, len, feature;\r
+\r
+               if (features) {\r
+                       for (i = 0, len = features.length; i < len; i++) {\r
+                               // Only add this if geometry or geometries are set and not null\r
+                               feature = features[i];\r
+                               if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {\r
+                                       this.addData(features[i]);\r
+                               }\r
+                       }\r
+                       return this;\r
+               }\r
+\r
+               var options = this.options;\r
+\r
+               if (options.filter && !options.filter(geojson)) { return; }\r
+\r
+               var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer, options.coordsToLatLng, options);\r
+               layer.feature = L.GeoJSON.asFeature(geojson);\r
+\r
+               layer.defaultOptions = layer.options;\r
+               this.resetStyle(layer);\r
+\r
+               if (options.onEachFeature) {\r
+                       options.onEachFeature(geojson, layer);\r
+               }\r
+\r
+               return this.addLayer(layer);\r
+       },\r
+\r
+       resetStyle: function (layer) {\r
+               var style = this.options.style;\r
+               if (style) {\r
+                       // reset any custom styles\r
+                       L.Util.extend(layer.options, layer.defaultOptions);\r
+\r
+                       this._setLayerStyle(layer, style);\r
+               }\r
+       },\r
+\r
+       setStyle: function (style) {\r
+               this.eachLayer(function (layer) {\r
+                       this._setLayerStyle(layer, style);\r
+               }, this);\r
+       },\r
+\r
+       _setLayerStyle: function (layer, style) {\r
+               if (typeof style === 'function') {\r
+                       style = style(layer.feature);\r
+               }\r
+               if (layer.setStyle) {\r
+                       layer.setStyle(style);\r
+               }\r
+       }\r
+});\r
+\r
+L.extend(L.GeoJSON, {\r
+       geometryToLayer: function (geojson, pointToLayer, coordsToLatLng, vectorOptions) {\r
+               var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,\r
+                   coords = geometry.coordinates,\r
+                   layers = [],\r
+                   latlng, latlngs, i, len;\r
+\r
+               coordsToLatLng = coordsToLatLng || this.coordsToLatLng;\r
+\r
+               switch (geometry.type) {\r
+               case 'Point':\r
+                       latlng = coordsToLatLng(coords);\r
+                       return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);\r
+\r
+               case 'MultiPoint':\r
+                       for (i = 0, len = coords.length; i < len; i++) {\r
+                               latlng = coordsToLatLng(coords[i]);\r
+                               layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng));\r
+                       }\r
+                       return new L.FeatureGroup(layers);\r
+\r
+               case 'LineString':\r
+                       latlngs = this.coordsToLatLngs(coords, 0, coordsToLatLng);\r
+                       return new L.Polyline(latlngs, vectorOptions);\r
+\r
+               case 'Polygon':\r
+                       if (coords.length === 2 && !coords[1].length) {\r
+                               throw new Error('Invalid GeoJSON object.');\r
+                       }\r
+                       latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng);\r
+                       return new L.Polygon(latlngs, vectorOptions);\r
+\r
+               case 'MultiLineString':\r
+                       latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng);\r
+                       return new L.MultiPolyline(latlngs, vectorOptions);\r
+\r
+               case 'MultiPolygon':\r
+                       latlngs = this.coordsToLatLngs(coords, 2, coordsToLatLng);\r
+                       return new L.MultiPolygon(latlngs, vectorOptions);\r
+\r
+               case 'GeometryCollection':\r
+                       for (i = 0, len = geometry.geometries.length; i < len; i++) {\r
+\r
+                               layers.push(this.geometryToLayer({\r
+                                       geometry: geometry.geometries[i],\r
+                                       type: 'Feature',\r
+                                       properties: geojson.properties\r
+                               }, pointToLayer, coordsToLatLng, vectorOptions));\r
+                       }\r
+                       return new L.FeatureGroup(layers);\r
+\r
+               default:\r
+                       throw new Error('Invalid GeoJSON object.');\r
+               }\r
+       },\r
+\r
+       coordsToLatLng: function (coords) { // (Array[, Boolean]) -> LatLng\r
+               return new L.LatLng(coords[1], coords[0], coords[2]);\r
+       },\r
+\r
+       coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) { // (Array[, Number, Function]) -> Array\r
+               var latlng, i, len,\r
+                   latlngs = [];\r
+\r
+               for (i = 0, len = coords.length; i < len; i++) {\r
+                       latlng = levelsDeep ?\r
+                               this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) :\r
+                               (coordsToLatLng || this.coordsToLatLng)(coords[i]);\r
+\r
+                       latlngs.push(latlng);\r
+               }\r
+\r
+               return latlngs;\r
+       },\r
+\r
+       latLngToCoords: function (latlng) {\r
+               var coords = [latlng.lng, latlng.lat];\r
+\r
+               if (latlng.alt !== undefined) {\r
+                       coords.push(latlng.alt);\r
+               }\r
+               return coords;\r
+       },\r
+\r
+       latLngsToCoords: function (latLngs) {\r
+               var coords = [];\r
+\r
+               for (var i = 0, len = latLngs.length; i < len; i++) {\r
+                       coords.push(L.GeoJSON.latLngToCoords(latLngs[i]));\r
+               }\r
+\r
+               return coords;\r
+       },\r
+\r
+       getFeature: function (layer, newGeometry) {\r
+               return layer.feature ? L.extend({}, layer.feature, {geometry: newGeometry}) : L.GeoJSON.asFeature(newGeometry);\r
+       },\r
+\r
+       asFeature: function (geoJSON) {\r
+               if (geoJSON.type === 'Feature') {\r
+                       return geoJSON;\r
+               }\r
+\r
+               return {\r
+                       type: 'Feature',\r
+                       properties: {},\r
+                       geometry: geoJSON\r
+               };\r
+       }\r
+});\r
+\r
+var PointToGeoJSON = {\r
+       toGeoJSON: function () {\r
+               return L.GeoJSON.getFeature(this, {\r
+                       type: 'Point',\r
+                       coordinates: L.GeoJSON.latLngToCoords(this.getLatLng())\r
+               });\r
+       }\r
+};\r
+\r
+L.Marker.include(PointToGeoJSON);\r
+L.Circle.include(PointToGeoJSON);\r
+L.CircleMarker.include(PointToGeoJSON);\r
+\r
+L.Polyline.include({\r
+       toGeoJSON: function () {\r
+               return L.GeoJSON.getFeature(this, {\r
+                       type: 'LineString',\r
+                       coordinates: L.GeoJSON.latLngsToCoords(this.getLatLngs())\r
+               });\r
+       }\r
+});\r
+\r
+L.Polygon.include({\r
+       toGeoJSON: function () {\r
+               var coords = [L.GeoJSON.latLngsToCoords(this.getLatLngs())],\r
+                   i, len, hole;\r
+\r
+               coords[0].push(coords[0][0]);\r
+\r
+               if (this._holes) {\r
+                       for (i = 0, len = this._holes.length; i < len; i++) {\r
+                               hole = L.GeoJSON.latLngsToCoords(this._holes[i]);\r
+                               hole.push(hole[0]);\r
+                               coords.push(hole);\r
+                       }\r
+               }\r
+\r
+               return L.GeoJSON.getFeature(this, {\r
+                       type: 'Polygon',\r
+                       coordinates: coords\r
+               });\r
+       }\r
+});\r
+\r
+(function () {\r
+       function multiToGeoJSON(type) {\r
+               return function () {\r
+                       var coords = [];\r
+\r
+                       this.eachLayer(function (layer) {\r
+                               coords.push(layer.toGeoJSON().geometry.coordinates);\r
+                       });\r
+\r
+                       return L.GeoJSON.getFeature(this, {\r
+                               type: type,\r
+                               coordinates: coords\r
+                       });\r
+               };\r
+       }\r
+\r
+       L.MultiPolyline.include({toGeoJSON: multiToGeoJSON('MultiLineString')});\r
+       L.MultiPolygon.include({toGeoJSON: multiToGeoJSON('MultiPolygon')});\r
+\r
+       L.LayerGroup.include({\r
+               toGeoJSON: function () {\r
+\r
+                       var geometry = this.feature && this.feature.geometry,\r
+                               jsons = [],\r
+                               json;\r
+\r
+                       if (geometry && geometry.type === 'MultiPoint') {\r
+                               return multiToGeoJSON('MultiPoint').call(this);\r
+                       }\r
+\r
+                       var isGeometryCollection = geometry && geometry.type === 'GeometryCollection';\r
+\r
+                       this.eachLayer(function (layer) {\r
+                               if (layer.toGeoJSON) {\r
+                                       json = layer.toGeoJSON();\r
+                                       jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json));\r
+                               }\r
+                       });\r
+\r
+                       if (isGeometryCollection) {\r
+                               return L.GeoJSON.getFeature(this, {\r
+                                       geometries: jsons,\r
+                                       type: 'GeometryCollection'\r
+                               });\r
+                       }\r
+\r
+                       return {\r
+                               type: 'FeatureCollection',\r
+                               features: jsons\r
+                       };\r
+               }\r
+       });\r
+}());\r
+\r
+L.geoJson = function (geojson, options) {\r
+       return new L.GeoJSON(geojson, options);\r
+};\r
+
+
+/*\r
+ * L.DomEvent contains functions for working with DOM events.\r
+ */\r
+\r
+L.DomEvent = {\r
+       /* inspired by John Resig, Dean Edwards and YUI addEvent implementations */\r
+       addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object])\r
+\r
+               var id = L.stamp(fn),\r
+                   key = '_leaflet_' + type + id,\r
+                   handler, originalHandler, newType;\r
+\r
+               if (obj[key]) { return this; }\r
+\r
+               handler = function (e) {\r
+                       return fn.call(context || obj, e || L.DomEvent._getEvent());\r
+               };\r
+\r
+               if (L.Browser.pointer && type.indexOf('touch') === 0) {\r
+                       return this.addPointerListener(obj, type, handler, id);\r
+               }\r
+               if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {\r
+                       this.addDoubleTapListener(obj, handler, id);\r
+               }\r
+\r
+               if ('addEventListener' in obj) {\r
+\r
+                       if (type === 'mousewheel') {\r
+                               obj.addEventListener('DOMMouseScroll', handler, false);\r
+                               obj.addEventListener(type, handler, false);\r
+\r
+                       } else if ((type === 'mouseenter') || (type === 'mouseleave')) {\r
+\r
+                               originalHandler = handler;\r
+                               newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout');\r
+\r
+                               handler = function (e) {\r
+                                       if (!L.DomEvent._checkMouse(obj, e)) { return; }\r
+                                       return originalHandler(e);\r
+                               };\r
+\r
+                               obj.addEventListener(newType, handler, false);\r
+\r
+                       } else if (type === 'click' && L.Browser.android) {\r
+                               originalHandler = handler;\r
+                               handler = function (e) {\r
+                                       return L.DomEvent._filterClick(e, originalHandler);\r
+                               };\r
+\r
+                               obj.addEventListener(type, handler, false);\r
+                       } else {\r
+                               obj.addEventListener(type, handler, false);\r
+                       }\r
+\r
+               } else if ('attachEvent' in obj) {\r
+                       obj.attachEvent('on' + type, handler);\r
+               }\r
+\r
+               obj[key] = handler;\r
+\r
+               return this;\r
+       },\r
+\r
+       removeListener: function (obj, type, fn) {  // (HTMLElement, String, Function)\r
+\r
+               var id = L.stamp(fn),\r
+                   key = '_leaflet_' + type + id,\r
+                   handler = obj[key];\r
+\r
+               if (!handler) { return this; }\r
+\r
+               if (L.Browser.pointer && type.indexOf('touch') === 0) {\r
+                       this.removePointerListener(obj, type, id);\r
+               } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {\r
+                       this.removeDoubleTapListener(obj, id);\r
+\r
+               } else if ('removeEventListener' in obj) {\r
+\r
+                       if (type === 'mousewheel') {\r
+                               obj.removeEventListener('DOMMouseScroll', handler, false);\r
+                               obj.removeEventListener(type, handler, false);\r
+\r
+                       } else if ((type === 'mouseenter') || (type === 'mouseleave')) {\r
+                               obj.removeEventListener((type === 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false);\r
+                       } else {\r
+                               obj.removeEventListener(type, handler, false);\r
+                       }\r
+               } else if ('detachEvent' in obj) {\r
+                       obj.detachEvent('on' + type, handler);\r
+               }\r
+\r
+               obj[key] = null;\r
+\r
+               return this;\r
+       },\r
+\r
+       stopPropagation: function (e) {\r
+\r
+               if (e.stopPropagation) {\r
+                       e.stopPropagation();\r
+               } else {\r
+                       e.cancelBubble = true;\r
+               }\r
+               L.DomEvent._skipped(e);\r
+\r
+               return this;\r
+       },\r
+\r
+       disableScrollPropagation: function (el) {\r
+               var stop = L.DomEvent.stopPropagation;\r
+\r
+               return L.DomEvent\r
+                       .on(el, 'mousewheel', stop)\r
+                       .on(el, 'MozMousePixelScroll', stop);\r
+       },\r
+\r
+       disableClickPropagation: function (el) {\r
+               var stop = L.DomEvent.stopPropagation;\r
+\r
+               for (var i = L.Draggable.START.length - 1; i >= 0; i--) {\r
+                       L.DomEvent.on(el, L.Draggable.START[i], stop);\r
+               }\r
+\r
+               return L.DomEvent\r
+                       .on(el, 'click', L.DomEvent._fakeStop)\r
+                       .on(el, 'dblclick', stop);\r
+       },\r
+\r
+       preventDefault: function (e) {\r
+\r
+               if (e.preventDefault) {\r
+                       e.preventDefault();\r
+               } else {\r
+                       e.returnValue = false;\r
+               }\r
+               return this;\r
+       },\r
+\r
+       stop: function (e) {\r
+               return L.DomEvent\r
+                       .preventDefault(e)\r
+                       .stopPropagation(e);\r
+       },\r
+\r
+       getMousePosition: function (e, container) {\r
+               if (!container) {\r
+                       return new L.Point(e.clientX, e.clientY);\r
+               }\r
+\r
+               var rect = container.getBoundingClientRect();\r
+\r
+               return new L.Point(\r
+                       e.clientX - rect.left - container.clientLeft,\r
+                       e.clientY - rect.top - container.clientTop);\r
+       },\r
+\r
+       getWheelDelta: function (e) {\r
+\r
+               var delta = 0;\r
+\r
+               if (e.wheelDelta) {\r
+                       delta = e.wheelDelta / 120;\r
+               }\r
+               if (e.detail) {\r
+                       delta = -e.detail / 3;\r
+               }\r
+               return delta;\r
+       },\r
+\r
+       _skipEvents: {},\r
+\r
+       _fakeStop: function (e) {\r
+               // fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e)\r
+               L.DomEvent._skipEvents[e.type] = true;\r
+       },\r
+\r
+       _skipped: function (e) {\r
+               var skipped = this._skipEvents[e.type];\r
+               // reset when checking, as it's only used in map container and propagates outside of the map\r
+               this._skipEvents[e.type] = false;\r
+               return skipped;\r
+       },\r
+\r
+       // check if element really left/entered the event target (for mouseenter/mouseleave)\r
+       _checkMouse: function (el, e) {\r
+\r
+               var related = e.relatedTarget;\r
+\r
+               if (!related) { return true; }\r
+\r
+               try {\r
+                       while (related && (related !== el)) {\r
+                               related = related.parentNode;\r
+                       }\r
+               } catch (err) {\r
+                       return false;\r
+               }\r
+               return (related !== el);\r
+       },\r
+\r
+       _getEvent: function () { // evil magic for IE\r
+               /*jshint noarg:false */\r
+               var e = window.event;\r
+               if (!e) {\r
+                       var caller = arguments.callee.caller;\r
+                       while (caller) {\r
+                               e = caller['arguments'][0];\r
+                               if (e && window.Event === e.constructor) {\r
+                                       break;\r
+                               }\r
+                               caller = caller.caller;\r
+                       }\r
+               }\r
+               return e;\r
+       },\r
+\r
+       // this is a horrible workaround for a bug in Android where a single touch triggers two click events\r
+       _filterClick: function (e, handler) {\r
+               var timeStamp = (e.timeStamp || e.originalEvent.timeStamp),\r
+                       elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);\r
+\r
+               // are they closer together than 500ms yet more than 100ms?\r
+               // Android typically triggers them ~300ms apart while multiple listeners\r
+               // on the same event should be triggered far faster;\r
+               // or check if click is simulated on the element, and if it is, reject any non-simulated events\r
+\r
+               if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {\r
+                       L.DomEvent.stop(e);\r
+                       return;\r
+               }\r
+               L.DomEvent._lastClick = timeStamp;\r
+\r
+               return handler(e);\r
+       }\r
+};\r
+\r
+L.DomEvent.on = L.DomEvent.addListener;\r
+L.DomEvent.off = L.DomEvent.removeListener;\r
+
+
+/*\r
+ * L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too.\r
+ */\r
+\r
+L.Draggable = L.Class.extend({\r
+       includes: L.Mixin.Events,\r
+\r
+       statics: {\r
+               START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],\r
+               END: {\r
+                       mousedown: 'mouseup',\r
+                       touchstart: 'touchend',\r
+                       pointerdown: 'touchend',\r
+                       MSPointerDown: 'touchend'\r
+               },\r
+               MOVE: {\r
+                       mousedown: 'mousemove',\r
+                       touchstart: 'touchmove',\r
+                       pointerdown: 'touchmove',\r
+                       MSPointerDown: 'touchmove'\r
+               }\r
+       },\r
+\r
+       initialize: function (element, dragStartTarget) {\r
+               this._element = element;\r
+               this._dragStartTarget = dragStartTarget || element;\r
+       },\r
+\r
+       enable: function () {\r
+               if (this._enabled) { return; }\r
+\r
+               for (var i = L.Draggable.START.length - 1; i >= 0; i--) {\r
+                       L.DomEvent.on(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);\r
+               }\r
+\r
+               this._enabled = true;\r
+       },\r
+\r
+       disable: function () {\r
+               if (!this._enabled) { return; }\r
+\r
+               for (var i = L.Draggable.START.length - 1; i >= 0; i--) {\r
+                       L.DomEvent.off(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);\r
+               }\r
+\r
+               this._enabled = false;\r
+               this._moved = false;\r
+       },\r
+\r
+       _onDown: function (e) {\r
+               this._moved = false;\r
+\r
+               if (e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }\r
+\r
+               L.DomEvent.stopPropagation(e);\r
+\r
+               if (L.Draggable._disabled) { return; }\r
+\r
+               L.DomUtil.disableImageDrag();\r
+               L.DomUtil.disableTextSelection();\r
+\r
+               if (this._moving) { return; }\r
+\r
+               var first = e.touches ? e.touches[0] : e;\r
+\r
+               this._startPoint = new L.Point(first.clientX, first.clientY);\r
+               this._startPos = this._newPos = L.DomUtil.getPosition(this._element);\r
+\r
+               L.DomEvent\r
+                   .on(document, L.Draggable.MOVE[e.type], this._onMove, this)\r
+                   .on(document, L.Draggable.END[e.type], this._onUp, this);\r
+       },\r
+\r
+       _onMove: function (e) {\r
+               if (e.touches && e.touches.length > 1) {\r
+                       this._moved = true;\r
+                       return;\r
+               }\r
+\r
+               var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),\r
+                   newPoint = new L.Point(first.clientX, first.clientY),\r
+                   offset = newPoint.subtract(this._startPoint);\r
+\r
+               if (!offset.x && !offset.y) { return; }\r
+               if (L.Browser.touch && Math.abs(offset.x) + Math.abs(offset.y) < 3) { return; }\r
+\r
+               L.DomEvent.preventDefault(e);\r
+\r
+               if (!this._moved) {\r
+                       this.fire('dragstart');\r
+\r
+                       this._moved = true;\r
+                       this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);\r
+\r
+                       L.DomUtil.addClass(document.body, 'leaflet-dragging');\r
+                       this._lastTarget = e.target || e.srcElement;\r
+                       L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target');\r
+               }\r
+\r
+               this._newPos = this._startPos.add(offset);\r
+               this._moving = true;\r
+\r
+               L.Util.cancelAnimFrame(this._animRequest);\r
+               this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true, this._dragStartTarget);\r
+       },\r
+\r
+       _updatePosition: function () {\r
+               this.fire('predrag');\r
+               L.DomUtil.setPosition(this._element, this._newPos);\r
+               this.fire('drag');\r
+       },\r
+\r
+       _onUp: function () {\r
+               L.DomUtil.removeClass(document.body, 'leaflet-dragging');\r
+\r
+               if (this._lastTarget) {\r
+                       L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target');\r
+                       this._lastTarget = null;\r
+               }\r
+\r
+               for (var i in L.Draggable.MOVE) {\r
+                       L.DomEvent\r
+                           .off(document, L.Draggable.MOVE[i], this._onMove)\r
+                           .off(document, L.Draggable.END[i], this._onUp);\r
+               }\r
+\r
+               L.DomUtil.enableImageDrag();\r
+               L.DomUtil.enableTextSelection();\r
+\r
+               if (this._moved && this._moving) {\r
+                       // ensure drag is not fired after dragend\r
+                       L.Util.cancelAnimFrame(this._animRequest);\r
+\r
+                       this.fire('dragend', {\r
+                               distance: this._newPos.distanceTo(this._startPos)\r
+                       });\r
+               }\r
+\r
+               this._moving = false;\r
+       }\r
+});\r
+
+
+/*
+       L.Handler is a base class for handler classes that are used internally to inject
+       interaction features like dragging to classes like Map and Marker.
+*/
+
+L.Handler = L.Class.extend({
+       initialize: function (map) {
+               this._map = map;
+       },
+
+       enable: function () {
+               if (this._enabled) { return; }
+
+               this._enabled = true;
+               this.addHooks();
+       },
+
+       disable: function () {
+               if (!this._enabled) { return; }
+
+               this._enabled = false;
+               this.removeHooks();
+       },
+
+       enabled: function () {
+               return !!this._enabled;
+       }
+});
+
+
+/*
+ * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
+ */
+
+L.Map.mergeOptions({
+       dragging: true,
+
+       inertia: !L.Browser.android23,
+       inertiaDeceleration: 3400, // px/s^2
+       inertiaMaxSpeed: Infinity, // px/s
+       inertiaThreshold: L.Browser.touch ? 32 : 18, // ms
+       easeLinearity: 0.25,
+
+       // TODO refactor, move to CRS
+       worldCopyJump: false
+});
+
+L.Map.Drag = L.Handler.extend({
+       addHooks: function () {
+               if (!this._draggable) {
+                       var map = this._map;
+
+                       this._draggable = new L.Draggable(map._mapPane, map._container);
+
+                       this._draggable.on({
+                               'dragstart': this._onDragStart,
+                               'drag': this._onDrag,
+                               'dragend': this._onDragEnd
+                       }, this);
+
+                       if (map.options.worldCopyJump) {
+                               this._draggable.on('predrag', this._onPreDrag, this);
+                               map.on('viewreset', this._onViewReset, this);
+
+                               map.whenReady(this._onViewReset, this);
+                       }
+               }
+               this._draggable.enable();
+       },
+
+       removeHooks: function () {
+               this._draggable.disable();
+       },
+
+       moved: function () {
+               return this._draggable && this._draggable._moved;
+       },
+
+       _onDragStart: function () {
+               var map = this._map;
+
+               if (map._panAnim) {
+                       map._panAnim.stop();
+               }
+
+               map
+                   .fire('movestart')
+                   .fire('dragstart');
+
+               if (map.options.inertia) {
+                       this._positions = [];
+                       this._times = [];
+               }
+       },
+
+       _onDrag: function () {
+               if (this._map.options.inertia) {
+                       var time = this._lastTime = +new Date(),
+                           pos = this._lastPos = this._draggable._newPos;
+
+                       this._positions.push(pos);
+                       this._times.push(time);
+
+                       if (time - this._times[0] > 200) {
+                               this._positions.shift();
+                               this._times.shift();
+                       }
+               }
+
+               this._map
+                   .fire('move')
+                   .fire('drag');
+       },
+
+       _onViewReset: function () {
+               // TODO fix hardcoded Earth values
+               var pxCenter = this._map.getSize()._divideBy(2),
+                   pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
+
+               this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
+               this._worldWidth = this._map.project([0, 180]).x;
+       },
+
+       _onPreDrag: function () {
+               // TODO refactor to be able to adjust map pane position after zoom
+               var worldWidth = this._worldWidth,
+                   halfWidth = Math.round(worldWidth / 2),
+                   dx = this._initialWorldOffset,
+                   x = this._draggable._newPos.x,
+                   newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
+                   newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
+                   newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
+
+               this._draggable._newPos.x = newX;
+       },
+
+       _onDragEnd: function (e) {
+               var map = this._map,
+                   options = map.options,
+                   delay = +new Date() - this._lastTime,
+
+                   noInertia = !options.inertia || delay > options.inertiaThreshold || !this._positions[0];
+
+               map.fire('dragend', e);
+
+               if (noInertia) {
+                       map.fire('moveend');
+
+               } else {
+
+                       var direction = this._lastPos.subtract(this._positions[0]),
+                           duration = (this._lastTime + delay - this._times[0]) / 1000,
+                           ease = options.easeLinearity,
+
+                           speedVector = direction.multiplyBy(ease / duration),
+                           speed = speedVector.distanceTo([0, 0]),
+
+                           limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
+                           limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
+
+                           decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
+                           offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
+
+                       if (!offset.x || !offset.y) {
+                               map.fire('moveend');
+
+                       } else {
+                               offset = map._limitOffset(offset, map.options.maxBounds);
+
+                               L.Util.requestAnimFrame(function () {
+                                       map.panBy(offset, {
+                                               duration: decelerationDuration,
+                                               easeLinearity: ease,
+                                               noMoveStart: true
+                                       });
+                               });
+                       }
+               }
+       }
+});
+
+L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
+
+
+/*
+ * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
+ */
+
+L.Map.mergeOptions({
+       doubleClickZoom: true
+});
+
+L.Map.DoubleClickZoom = L.Handler.extend({
+       addHooks: function () {
+               this._map.on('dblclick', this._onDoubleClick, this);
+       },
+
+       removeHooks: function () {
+               this._map.off('dblclick', this._onDoubleClick, this);
+       },
+
+       _onDoubleClick: function (e) {
+               var map = this._map,
+                   zoom = map.getZoom() + (e.originalEvent.shiftKey ? -1 : 1);
+
+               if (map.options.doubleClickZoom === 'center') {
+                       map.setZoom(zoom);
+               } else {
+                       map.setZoomAround(e.containerPoint, zoom);
+               }
+       }
+});
+
+L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
+
+
+/*
+ * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
+ */
+
+L.Map.mergeOptions({
+       scrollWheelZoom: true
+});
+
+L.Map.ScrollWheelZoom = L.Handler.extend({
+       addHooks: function () {
+               L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
+               L.DomEvent.on(this._map._container, 'MozMousePixelScroll', L.DomEvent.preventDefault);
+               this._delta = 0;
+       },
+
+       removeHooks: function () {
+               L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll);
+               L.DomEvent.off(this._map._container, 'MozMousePixelScroll', L.DomEvent.preventDefault);
+       },
+
+       _onWheelScroll: function (e) {
+               var delta = L.DomEvent.getWheelDelta(e);
+
+               this._delta += delta;
+               this._lastMousePos = this._map.mouseEventToContainerPoint(e);
+
+               if (!this._startTime) {
+                       this._startTime = +new Date();
+               }
+
+               var left = Math.max(40 - (+new Date() - this._startTime), 0);
+
+               clearTimeout(this._timer);
+               this._timer = setTimeout(L.bind(this._performZoom, this), left);
+
+               L.DomEvent.preventDefault(e);
+               L.DomEvent.stopPropagation(e);
+       },
+
+       _performZoom: function () {
+               var map = this._map,
+                   delta = this._delta,
+                   zoom = map.getZoom();
+
+               delta = delta > 0 ? Math.ceil(delta) : Math.floor(delta);
+               delta = Math.max(Math.min(delta, 4), -4);
+               delta = map._limitZoom(zoom + delta) - zoom;
+
+               this._delta = 0;
+               this._startTime = null;
+
+               if (!delta) { return; }
+
+               if (map.options.scrollWheelZoom === 'center') {
+                       map.setZoom(zoom + delta);
+               } else {
+                       map.setZoomAround(this._lastMousePos, zoom + delta);
+               }
+       }
+});
+
+L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
+
+
+/*\r
+ * Extends the event handling code with double tap support for mobile browsers.\r
+ */\r
+\r
+L.extend(L.DomEvent, {\r
+\r
+       _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',\r
+       _touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend',\r
+\r
+       // inspired by Zepto touch code by Thomas Fuchs\r
+       addDoubleTapListener: function (obj, handler, id) {\r
+               var last,\r
+                   doubleTap = false,\r
+                   delay = 250,\r
+                   touch,\r
+                   pre = '_leaflet_',\r
+                   touchstart = this._touchstart,\r
+                   touchend = this._touchend,\r
+                   trackedTouches = [];\r
+\r
+               function onTouchStart(e) {\r
+                       var count;\r
+\r
+                       if (L.Browser.pointer) {\r
+                               trackedTouches.push(e.pointerId);\r
+                               count = trackedTouches.length;\r
+                       } else {\r
+                               count = e.touches.length;\r
+                       }\r
+                       if (count > 1) {\r
+                               return;\r
+                       }\r
+\r
+                       var now = Date.now(),\r
+                               delta = now - (last || now);\r
+\r
+                       touch = e.touches ? e.touches[0] : e;\r
+                       doubleTap = (delta > 0 && delta <= delay);\r
+                       last = now;\r
+               }\r
+\r
+               function onTouchEnd(e) {\r
+                       if (L.Browser.pointer) {\r
+                               var idx = trackedTouches.indexOf(e.pointerId);\r
+                               if (idx === -1) {\r
+                                       return;\r
+                               }\r
+                               trackedTouches.splice(idx, 1);\r
+                       }\r
+\r
+                       if (doubleTap) {\r
+                               if (L.Browser.pointer) {\r
+                                       // work around .type being readonly with MSPointer* events\r
+                                       var newTouch = { },\r
+                                               prop;\r
+\r
+                                       // jshint forin:false\r
+                                       for (var i in touch) {\r
+                                               prop = touch[i];\r
+                                               if (typeof prop === 'function') {\r
+                                                       newTouch[i] = prop.bind(touch);\r
+                                               } else {\r
+                                                       newTouch[i] = prop;\r
+                                               }\r
+                                       }\r
+                                       touch = newTouch;\r
+                               }\r
+                               touch.type = 'dblclick';\r
+                               handler(touch);\r
+                               last = null;\r
+                       }\r
+               }\r
+               obj[pre + touchstart + id] = onTouchStart;\r
+               obj[pre + touchend + id] = onTouchEnd;\r
+\r
+               // on pointer we need to listen on the document, otherwise a drag starting on the map and moving off screen\r
+               // will not come through to us, so we will lose track of how many touches are ongoing\r
+               var endElement = L.Browser.pointer ? document.documentElement : obj;\r
+\r
+               obj.addEventListener(touchstart, onTouchStart, false);\r
+               endElement.addEventListener(touchend, onTouchEnd, false);\r
+\r
+               if (L.Browser.pointer) {\r
+                       endElement.addEventListener(L.DomEvent.POINTER_CANCEL, onTouchEnd, false);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       removeDoubleTapListener: function (obj, id) {\r
+               var pre = '_leaflet_';\r
+\r
+               obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false);\r
+               (L.Browser.pointer ? document.documentElement : obj).removeEventListener(\r
+                       this._touchend, obj[pre + this._touchend + id], false);\r
+\r
+               if (L.Browser.pointer) {\r
+                       document.documentElement.removeEventListener(L.DomEvent.POINTER_CANCEL, obj[pre + this._touchend + id],\r
+                               false);\r
+               }\r
+\r
+               return this;\r
+       }\r
+});\r
+
+
+/*
+ * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
+ */
+
+L.extend(L.DomEvent, {
+
+       //static
+       POINTER_DOWN: L.Browser.msPointer ? 'MSPointerDown' : 'pointerdown',
+       POINTER_MOVE: L.Browser.msPointer ? 'MSPointerMove' : 'pointermove',
+       POINTER_UP: L.Browser.msPointer ? 'MSPointerUp' : 'pointerup',
+       POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel',
+
+       _pointers: [],
+       _pointerDocumentListener: false,
+
+       // Provides a touch events wrapper for (ms)pointer events.
+       // Based on changes by veproza https://github.com/CloudMade/Leaflet/pull/1019
+       //ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
+
+       addPointerListener: function (obj, type, handler, id) {
+
+               switch (type) {
+               case 'touchstart':
+                       return this.addPointerListenerStart(obj, type, handler, id);
+               case 'touchend':
+                       return this.addPointerListenerEnd(obj, type, handler, id);
+               case 'touchmove':
+                       return this.addPointerListenerMove(obj, type, handler, id);
+               default:
+                       throw 'Unknown touch event type';
+               }
+       },
+
+       addPointerListenerStart: function (obj, type, handler, id) {
+               var pre = '_leaflet_',
+                   pointers = this._pointers;
+
+               var cb = function (e) {
+                       if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
+                               L.DomEvent.preventDefault(e);
+                       }
+
+                       var alreadyInArray = false;
+                       for (var i = 0; i < pointers.length; i++) {
+                               if (pointers[i].pointerId === e.pointerId) {
+                                       alreadyInArray = true;
+                                       break;
+                               }
+                       }
+                       if (!alreadyInArray) {
+                               pointers.push(e);
+                       }
+
+                       e.touches = pointers.slice();
+                       e.changedTouches = [e];
+
+                       handler(e);
+               };
+
+               obj[pre + 'touchstart' + id] = cb;
+               obj.addEventListener(this.POINTER_DOWN, cb, false);
+
+               // need to also listen for end events to keep the _pointers list accurate
+               // this needs to be on the body and never go away
+               if (!this._pointerDocumentListener) {
+                       var internalCb = function (e) {
+                               for (var i = 0; i < pointers.length; i++) {
+                                       if (pointers[i].pointerId === e.pointerId) {
+                                               pointers.splice(i, 1);
+                                               break;
+                                       }
+                               }
+                       };
+                       //We listen on the documentElement as any drags that end by moving the touch off the screen get fired there
+                       document.documentElement.addEventListener(this.POINTER_UP, internalCb, false);
+                       document.documentElement.addEventListener(this.POINTER_CANCEL, internalCb, false);
+
+                       this._pointerDocumentListener = true;
+               }
+
+               return this;
+       },
+
+       addPointerListenerMove: function (obj, type, handler, id) {
+               var pre = '_leaflet_',
+                   touches = this._pointers;
+
+               function cb(e) {
+
+                       // don't fire touch moves when mouse isn't down
+                       if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
+
+                       for (var i = 0; i < touches.length; i++) {
+                               if (touches[i].pointerId === e.pointerId) {
+                                       touches[i] = e;
+                                       break;
+                               }
+                       }
+
+                       e.touches = touches.slice();
+                       e.changedTouches = [e];
+
+                       handler(e);
+               }
+
+               obj[pre + 'touchmove' + id] = cb;
+               obj.addEventListener(this.POINTER_MOVE, cb, false);
+
+               return this;
+       },
+
+       addPointerListenerEnd: function (obj, type, handler, id) {
+               var pre = '_leaflet_',
+                   touches = this._pointers;
+
+               var cb = function (e) {
+                       for (var i = 0; i < touches.length; i++) {
+                               if (touches[i].pointerId === e.pointerId) {
+                                       touches.splice(i, 1);
+                                       break;
+                               }
+                       }
+
+                       e.touches = touches.slice();
+                       e.changedTouches = [e];
+
+                       handler(e);
+               };
+
+               obj[pre + 'touchend' + id] = cb;
+               obj.addEventListener(this.POINTER_UP, cb, false);
+               obj.addEventListener(this.POINTER_CANCEL, cb, false);
+
+               return this;
+       },
+
+       removePointerListener: function (obj, type, id) {
+               var pre = '_leaflet_',
+                   cb = obj[pre + type + id];
+
+               switch (type) {
+               case 'touchstart':
+                       obj.removeEventListener(this.POINTER_DOWN, cb, false);
+                       break;
+               case 'touchmove':
+                       obj.removeEventListener(this.POINTER_MOVE, cb, false);
+                       break;
+               case 'touchend':
+                       obj.removeEventListener(this.POINTER_UP, cb, false);
+                       obj.removeEventListener(this.POINTER_CANCEL, cb, false);
+                       break;
+               }
+
+               return this;
+       }
+});
+
+
+/*
+ * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
+ */
+
+L.Map.mergeOptions({
+       touchZoom: L.Browser.touch && !L.Browser.android23,
+       bounceAtZoomLimits: true
+});
+
+L.Map.TouchZoom = L.Handler.extend({
+       addHooks: function () {
+               L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
+       },
+
+       removeHooks: function () {
+               L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
+       },
+
+       _onTouchStart: function (e) {
+               var map = this._map;
+
+               if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
+
+               var p1 = map.mouseEventToLayerPoint(e.touches[0]),
+                   p2 = map.mouseEventToLayerPoint(e.touches[1]),
+                   viewCenter = map._getCenterLayerPoint();
+
+               this._startCenter = p1.add(p2)._divideBy(2);
+               this._startDist = p1.distanceTo(p2);
+
+               this._moved = false;
+               this._zooming = true;
+
+               this._centerOffset = viewCenter.subtract(this._startCenter);
+
+               if (map._panAnim) {
+                       map._panAnim.stop();
+               }
+
+               L.DomEvent
+                   .on(document, 'touchmove', this._onTouchMove, this)
+                   .on(document, 'touchend', this._onTouchEnd, this);
+
+               L.DomEvent.preventDefault(e);
+       },
+
+       _onTouchMove: function (e) {
+               var map = this._map;
+
+               if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
+
+               var p1 = map.mouseEventToLayerPoint(e.touches[0]),
+                   p2 = map.mouseEventToLayerPoint(e.touches[1]);
+
+               this._scale = p1.distanceTo(p2) / this._startDist;
+               this._delta = p1._add(p2)._divideBy(2)._subtract(this._startCenter);
+
+               if (this._scale === 1) { return; }
+
+               if (!map.options.bounceAtZoomLimits) {
+                       if ((map.getZoom() === map.getMinZoom() && this._scale < 1) ||
+                           (map.getZoom() === map.getMaxZoom() && this._scale > 1)) { return; }
+               }
+
+               if (!this._moved) {
+                       L.DomUtil.addClass(map._mapPane, 'leaflet-touching');
+
+                       map
+                           .fire('movestart')
+                           .fire('zoomstart');
+
+                       this._moved = true;
+               }
+
+               L.Util.cancelAnimFrame(this._animRequest);
+               this._animRequest = L.Util.requestAnimFrame(
+                       this._updateOnMove, this, true, this._map._container);
+
+               L.DomEvent.preventDefault(e);
+       },
+
+       _updateOnMove: function () {
+               var map = this._map,
+                   origin = this._getScaleOrigin(),
+                   center = map.layerPointToLatLng(origin),
+                   zoom = map.getScaleZoom(this._scale);
+
+               map._animateZoom(center, zoom, this._startCenter, this._scale, this._delta, false, true);
+       },
+
+       _onTouchEnd: function () {
+               if (!this._moved || !this._zooming) {
+                       this._zooming = false;
+                       return;
+               }
+
+               var map = this._map;
+
+               this._zooming = false;
+               L.DomUtil.removeClass(map._mapPane, 'leaflet-touching');
+               L.Util.cancelAnimFrame(this._animRequest);
+
+               L.DomEvent
+                   .off(document, 'touchmove', this._onTouchMove)
+                   .off(document, 'touchend', this._onTouchEnd);
+
+               var origin = this._getScaleOrigin(),
+                   center = map.layerPointToLatLng(origin),
+
+                   oldZoom = map.getZoom(),
+                   floatZoomDelta = map.getScaleZoom(this._scale) - oldZoom,
+                   roundZoomDelta = (floatZoomDelta > 0 ?
+                           Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)),
+
+                   zoom = map._limitZoom(oldZoom + roundZoomDelta),
+                   scale = map.getZoomScale(zoom) / this._scale;
+
+               map._animateZoom(center, zoom, origin, scale);
+       },
+
+       _getScaleOrigin: function () {
+               var centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale);
+               return this._startCenter.add(centerOffset);
+       }
+});
+
+L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
+
+
+/*
+ * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
+ */
+
+L.Map.mergeOptions({
+       tap: true,
+       tapTolerance: 15
+});
+
+L.Map.Tap = L.Handler.extend({
+       addHooks: function () {
+               L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this);
+       },
+
+       removeHooks: function () {
+               L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this);
+       },
+
+       _onDown: function (e) {
+               if (!e.touches) { return; }
+
+               L.DomEvent.preventDefault(e);
+
+               this._fireClick = true;
+
+               // don't simulate click or track longpress if more than 1 touch
+               if (e.touches.length > 1) {
+                       this._fireClick = false;
+                       clearTimeout(this._holdTimeout);
+                       return;
+               }
+
+               var first = e.touches[0],
+                   el = first.target;
+
+               this._startPos = this._newPos = new L.Point(first.clientX, first.clientY);
+
+               // if touching a link, highlight it
+               if (el.tagName && el.tagName.toLowerCase() === 'a') {
+                       L.DomUtil.addClass(el, 'leaflet-active');
+               }
+
+               // simulate long hold but setting a timeout
+               this._holdTimeout = setTimeout(L.bind(function () {
+                       if (this._isTapValid()) {
+                               this._fireClick = false;
+                               this._onUp();
+                               this._simulateEvent('contextmenu', first);
+                       }
+               }, this), 1000);
+
+               L.DomEvent
+                       .on(document, 'touchmove', this._onMove, this)
+                       .on(document, 'touchend', this._onUp, this);
+       },
+
+       _onUp: function (e) {
+               clearTimeout(this._holdTimeout);
+
+               L.DomEvent
+                       .off(document, 'touchmove', this._onMove, this)
+                       .off(document, 'touchend', this._onUp, this);
+
+               if (this._fireClick && e && e.changedTouches) {
+
+                       var first = e.changedTouches[0],
+                           el = first.target;
+
+                       if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
+                               L.DomUtil.removeClass(el, 'leaflet-active');
+                       }
+
+                       // simulate click if the touch didn't move too much
+                       if (this._isTapValid()) {
+                               this._simulateEvent('click', first);
+                       }
+               }
+       },
+
+       _isTapValid: function () {
+               return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
+       },
+
+       _onMove: function (e) {
+               var first = e.touches[0];
+               this._newPos = new L.Point(first.clientX, first.clientY);
+       },
+
+       _simulateEvent: function (type, e) {
+               var simulatedEvent = document.createEvent('MouseEvents');
+
+               simulatedEvent._simulated = true;
+               e.target._simulatedClick = true;
+
+               simulatedEvent.initMouseEvent(
+                       type, true, true, window, 1,
+                       e.screenX, e.screenY,
+                       e.clientX, e.clientY,
+                       false, false, false, false, 0, null);
+
+               e.target.dispatchEvent(simulatedEvent);
+       }
+});
+
+if (L.Browser.touch && !L.Browser.pointer) {
+       L.Map.addInitHook('addHandler', 'tap', L.Map.Tap);
+}
+
+
+/*
+ * L.Handler.ShiftDragZoom is used to add shift-drag zoom interaction to the map
+  * (zoom to a selected bounding box), enabled by default.
+ */
+
+L.Map.mergeOptions({
+       boxZoom: true
+});
+
+L.Map.BoxZoom = L.Handler.extend({
+       initialize: function (map) {
+               this._map = map;
+               this._container = map._container;
+               this._pane = map._panes.overlayPane;
+               this._moved = false;
+       },
+
+       addHooks: function () {
+               L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
+       },
+
+       removeHooks: function () {
+               L.DomEvent.off(this._container, 'mousedown', this._onMouseDown);
+               this._moved = false;
+       },
+
+       moved: function () {
+               return this._moved;
+       },
+
+       _onMouseDown: function (e) {
+               this._moved = false;
+
+               if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
+
+               L.DomUtil.disableTextSelection();
+               L.DomUtil.disableImageDrag();
+
+               this._startLayerPoint = this._map.mouseEventToLayerPoint(e);
+
+               L.DomEvent
+                   .on(document, 'mousemove', this._onMouseMove, this)
+                   .on(document, 'mouseup', this._onMouseUp, this)
+                   .on(document, 'keydown', this._onKeyDown, this);
+       },
+
+       _onMouseMove: function (e) {
+               if (!this._moved) {
+                       this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._pane);
+                       L.DomUtil.setPosition(this._box, this._startLayerPoint);
+
+                       //TODO refactor: move cursor to styles
+                       this._container.style.cursor = 'crosshair';
+                       this._map.fire('boxzoomstart');
+               }
+
+               var startPoint = this._startLayerPoint,
+                   box = this._box,
+
+                   layerPoint = this._map.mouseEventToLayerPoint(e),
+                   offset = layerPoint.subtract(startPoint),
+
+                   newPos = new L.Point(
+                       Math.min(layerPoint.x, startPoint.x),
+                       Math.min(layerPoint.y, startPoint.y));
+
+               L.DomUtil.setPosition(box, newPos);
+
+               this._moved = true;
+
+               // TODO refactor: remove hardcoded 4 pixels
+               box.style.width  = (Math.max(0, Math.abs(offset.x) - 4)) + 'px';
+               box.style.height = (Math.max(0, Math.abs(offset.y) - 4)) + 'px';
+       },
+
+       _finish: function () {
+               if (this._moved) {
+                       this._pane.removeChild(this._box);
+                       this._container.style.cursor = '';
+               }
+
+               L.DomUtil.enableTextSelection();
+               L.DomUtil.enableImageDrag();
+
+               L.DomEvent
+                   .off(document, 'mousemove', this._onMouseMove)
+                   .off(document, 'mouseup', this._onMouseUp)
+                   .off(document, 'keydown', this._onKeyDown);
+       },
+
+       _onMouseUp: function (e) {
+
+               this._finish();
+
+               var map = this._map,
+                   layerPoint = map.mouseEventToLayerPoint(e);
+
+               if (this._startLayerPoint.equals(layerPoint)) { return; }
+
+               var bounds = new L.LatLngBounds(
+                       map.layerPointToLatLng(this._startLayerPoint),
+                       map.layerPointToLatLng(layerPoint));
+
+               map.fitBounds(bounds);
+
+               map.fire('boxzoomend', {
+                       boxZoomBounds: bounds
+               });
+       },
+
+       _onKeyDown: function (e) {
+               if (e.keyCode === 27) {
+                       this._finish();
+               }
+       }
+});
+
+L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
+
+
+/*
+ * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
+ */
+
+L.Map.mergeOptions({
+       keyboard: true,
+       keyboardPanOffset: 80,
+       keyboardZoomOffset: 1
+});
+
+L.Map.Keyboard = L.Handler.extend({
+
+       keyCodes: {
+               left:    [37],
+               right:   [39],
+               down:    [40],
+               up:      [38],
+               zoomIn:  [187, 107, 61, 171],
+               zoomOut: [189, 109, 173]
+       },
+
+       initialize: function (map) {
+               this._map = map;
+
+               this._setPanOffset(map.options.keyboardPanOffset);
+               this._setZoomOffset(map.options.keyboardZoomOffset);
+       },
+
+       addHooks: function () {
+               var container = this._map._container;
+
+               // make the container focusable by tabbing
+               if (container.tabIndex === -1) {
+                       container.tabIndex = '0';
+               }
+
+               L.DomEvent
+                   .on(container, 'focus', this._onFocus, this)
+                   .on(container, 'blur', this._onBlur, this)
+                   .on(container, 'mousedown', this._onMouseDown, this);
+
+               this._map
+                   .on('focus', this._addHooks, this)
+                   .on('blur', this._removeHooks, this);
+       },
+
+       removeHooks: function () {
+               this._removeHooks();
+
+               var container = this._map._container;
+
+               L.DomEvent
+                   .off(container, 'focus', this._onFocus, this)
+                   .off(container, 'blur', this._onBlur, this)
+                   .off(container, 'mousedown', this._onMouseDown, this);
+
+               this._map
+                   .off('focus', this._addHooks, this)
+                   .off('blur', this._removeHooks, this);
+       },
+
+       _onMouseDown: function () {
+               if (this._focused) { return; }
+
+               var body = document.body,
+                   docEl = document.documentElement,
+                   top = body.scrollTop || docEl.scrollTop,
+                   left = body.scrollLeft || docEl.scrollLeft;
+
+               this._map._container.focus();
+
+               window.scrollTo(left, top);
+       },
+
+       _onFocus: function () {
+               this._focused = true;
+               this._map.fire('focus');
+       },
+
+       _onBlur: function () {
+               this._focused = false;
+               this._map.fire('blur');
+       },
+
+       _setPanOffset: function (pan) {
+               var keys = this._panKeys = {},
+                   codes = this.keyCodes,
+                   i, len;
+
+               for (i = 0, len = codes.left.length; i < len; i++) {
+                       keys[codes.left[i]] = [-1 * pan, 0];
+               }
+               for (i = 0, len = codes.right.length; i < len; i++) {
+                       keys[codes.right[i]] = [pan, 0];
+               }
+               for (i = 0, len = codes.down.length; i < len; i++) {
+                       keys[codes.down[i]] = [0, pan];
+               }
+               for (i = 0, len = codes.up.length; i < len; i++) {
+                       keys[codes.up[i]] = [0, -1 * pan];
+               }
+       },
+
+       _setZoomOffset: function (zoom) {
+               var keys = this._zoomKeys = {},
+                   codes = this.keyCodes,
+                   i, len;
+
+               for (i = 0, len = codes.zoomIn.length; i < len; i++) {
+                       keys[codes.zoomIn[i]] = zoom;
+               }
+               for (i = 0, len = codes.zoomOut.length; i < len; i++) {
+                       keys[codes.zoomOut[i]] = -zoom;
+               }
+       },
+
+       _addHooks: function () {
+               L.DomEvent.on(document, 'keydown', this._onKeyDown, this);
+       },
+
+       _removeHooks: function () {
+               L.DomEvent.off(document, 'keydown', this._onKeyDown, this);
+       },
+
+       _onKeyDown: function (e) {
+               var key = e.keyCode,
+                   map = this._map;
+
+               if (key in this._panKeys) {
+
+                       if (map._panAnim && map._panAnim._inProgress) { return; }
+
+                       map.panBy(this._panKeys[key]);
+
+                       if (map.options.maxBounds) {
+                               map.panInsideBounds(map.options.maxBounds);
+                       }
+
+               } else if (key in this._zoomKeys) {
+                       map.setZoom(map.getZoom() + this._zoomKeys[key]);
+
+               } else {
+                       return;
+               }
+
+               L.DomEvent.stop(e);
+       }
+});
+
+L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);
+
+
+/*
+ * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
+ */
+
+L.Handler.MarkerDrag = L.Handler.extend({
+       initialize: function (marker) {
+               this._marker = marker;
+       },
+
+       addHooks: function () {
+               var icon = this._marker._icon;
+               if (!this._draggable) {
+                       this._draggable = new L.Draggable(icon, icon);
+               }
+
+               this._draggable
+                       .on('dragstart', this._onDragStart, this)
+                       .on('drag', this._onDrag, this)
+                       .on('dragend', this._onDragEnd, this);
+               this._draggable.enable();
+               L.DomUtil.addClass(this._marker._icon, 'leaflet-marker-draggable');
+       },
+
+       removeHooks: function () {
+               this._draggable
+                       .off('dragstart', this._onDragStart, this)
+                       .off('drag', this._onDrag, this)
+                       .off('dragend', this._onDragEnd, this);
+
+               this._draggable.disable();
+               L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable');
+       },
+
+       moved: function () {
+               return this._draggable && this._draggable._moved;
+       },
+
+       _onDragStart: function () {
+               this._marker
+                   .closePopup()
+                   .fire('movestart')
+                   .fire('dragstart');
+       },
+
+       _onDrag: function () {
+               var marker = this._marker,
+                   shadow = marker._shadow,
+                   iconPos = L.DomUtil.getPosition(marker._icon),
+                   latlng = marker._map.layerPointToLatLng(iconPos);
+
+               // update shadow position
+               if (shadow) {
+                       L.DomUtil.setPosition(shadow, iconPos);
+               }
+
+               marker._latlng = latlng;
+
+               marker
+                   .fire('move', {latlng: latlng})
+                   .fire('drag');
+       },
+
+       _onDragEnd: function (e) {
+               this._marker
+                   .fire('moveend')
+                   .fire('dragend', e);
+       }
+});
+
+
+/*\r
+ * L.Control is a base class for implementing map controls. Handles positioning.\r
+ * All other controls extend from this class.\r
+ */\r
+\r
+L.Control = L.Class.extend({\r
+       options: {\r
+               position: 'topright'\r
+       },\r
+\r
+       initialize: function (options) {\r
+               L.setOptions(this, options);\r
+       },\r
+\r
+       getPosition: function () {\r
+               return this.options.position;\r
+       },\r
+\r
+       setPosition: function (position) {\r
+               var map = this._map;\r
+\r
+               if (map) {\r
+                       map.removeControl(this);\r
+               }\r
+\r
+               this.options.position = position;\r
+\r
+               if (map) {\r
+                       map.addControl(this);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       getContainer: function () {\r
+               return this._container;\r
+       },\r
+\r
+       addTo: function (map) {\r
+               this._map = map;\r
+\r
+               var container = this._container = this.onAdd(map),\r
+                   pos = this.getPosition(),\r
+                   corner = map._controlCorners[pos];\r
+\r
+               L.DomUtil.addClass(container, 'leaflet-control');\r
+\r
+               if (pos.indexOf('bottom') !== -1) {\r
+                       corner.insertBefore(container, corner.firstChild);\r
+               } else {\r
+                       corner.appendChild(container);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       removeFrom: function (map) {\r
+               var pos = this.getPosition(),\r
+                   corner = map._controlCorners[pos];\r
+\r
+               corner.removeChild(this._container);\r
+               this._map = null;\r
+\r
+               if (this.onRemove) {\r
+                       this.onRemove(map);\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       _refocusOnMap: function () {\r
+               if (this._map) {\r
+                       this._map.getContainer().focus();\r
+               }\r
+       }\r
+});\r
+\r
+L.control = function (options) {\r
+       return new L.Control(options);\r
+};\r
+\r
+\r
+// adds control-related methods to L.Map\r
+\r
+L.Map.include({\r
+       addControl: function (control) {\r
+               control.addTo(this);\r
+               return this;\r
+       },\r
+\r
+       removeControl: function (control) {\r
+               control.removeFrom(this);\r
+               return this;\r
+       },\r
+\r
+       _initControlPos: function () {\r
+               var corners = this._controlCorners = {},\r
+                   l = 'leaflet-',\r
+                   container = this._controlContainer =\r
+                           L.DomUtil.create('div', l + 'control-container', this._container);\r
+\r
+               function createCorner(vSide, hSide) {\r
+                       var className = l + vSide + ' ' + l + hSide;\r
+\r
+                       corners[vSide + hSide] = L.DomUtil.create('div', className, container);\r
+               }\r
+\r
+               createCorner('top', 'left');\r
+               createCorner('top', 'right');\r
+               createCorner('bottom', 'left');\r
+               createCorner('bottom', 'right');\r
+       },\r
+\r
+       _clearControlPos: function () {\r
+               this._container.removeChild(this._controlContainer);\r
+       }\r
+});\r
+
+
+/*\r
+ * L.Control.Zoom is used for the default zoom buttons on the map.\r
+ */\r
+\r
+L.Control.Zoom = L.Control.extend({\r
+       options: {\r
+               position: 'topleft',\r
+               zoomInText: '+',\r
+               zoomInTitle: 'Zoom in',\r
+               zoomOutText: '-',\r
+               zoomOutTitle: 'Zoom out'\r
+       },\r
+\r
+       onAdd: function (map) {\r
+               var zoomName = 'leaflet-control-zoom',\r
+                   container = L.DomUtil.create('div', zoomName + ' leaflet-bar');\r
+\r
+               this._map = map;\r
+\r
+               this._zoomInButton  = this._createButton(\r
+                       this.options.zoomInText, this.options.zoomInTitle,\r
+                       zoomName + '-in',  container, this._zoomIn,  this);\r
+               this._zoomOutButton = this._createButton(\r
+                       this.options.zoomOutText, this.options.zoomOutTitle,\r
+                       zoomName + '-out', container, this._zoomOut, this);\r
+\r
+               this._updateDisabled();\r
+               map.on('zoomend zoomlevelschange', this._updateDisabled, this);\r
+\r
+               return container;\r
+       },\r
+\r
+       onRemove: function (map) {\r
+               map.off('zoomend zoomlevelschange', this._updateDisabled, this);\r
+       },\r
+\r
+       _zoomIn: function (e) {\r
+               this._map.zoomIn(e.shiftKey ? 3 : 1);\r
+       },\r
+\r
+       _zoomOut: function (e) {\r
+               this._map.zoomOut(e.shiftKey ? 3 : 1);\r
+       },\r
+\r
+       _createButton: function (html, title, className, container, fn, context) {\r
+               var link = L.DomUtil.create('a', className, container);\r
+               link.innerHTML = html;\r
+               link.href = '#';\r
+               link.title = title;\r
+\r
+               var stop = L.DomEvent.stopPropagation;\r
+\r
+               L.DomEvent\r
+                   .on(link, 'click', stop)\r
+                   .on(link, 'mousedown', stop)\r
+                   .on(link, 'dblclick', stop)\r
+                   .on(link, 'click', L.DomEvent.preventDefault)\r
+                   .on(link, 'click', fn, context)\r
+                   .on(link, 'click', this._refocusOnMap, context);\r
+\r
+               return link;\r
+       },\r
+\r
+       _updateDisabled: function () {\r
+               var map = this._map,\r
+                       className = 'leaflet-disabled';\r
+\r
+               L.DomUtil.removeClass(this._zoomInButton, className);\r
+               L.DomUtil.removeClass(this._zoomOutButton, className);\r
+\r
+               if (map._zoom === map.getMinZoom()) {\r
+                       L.DomUtil.addClass(this._zoomOutButton, className);\r
+               }\r
+               if (map._zoom === map.getMaxZoom()) {\r
+                       L.DomUtil.addClass(this._zoomInButton, className);\r
+               }\r
+       }\r
+});\r
+\r
+L.Map.mergeOptions({\r
+       zoomControl: true\r
+});\r
+\r
+L.Map.addInitHook(function () {\r
+       if (this.options.zoomControl) {\r
+               this.zoomControl = new L.Control.Zoom();\r
+               this.addControl(this.zoomControl);\r
+       }\r
+});\r
+\r
+L.control.zoom = function (options) {\r
+       return new L.Control.Zoom(options);\r
+};\r
+\r
+
+
+/*\r
+ * L.Control.Attribution is used for displaying attribution on the map (added by default).\r
+ */\r
+\r
+L.Control.Attribution = L.Control.extend({\r
+       options: {\r
+               position: 'bottomright',\r
+               prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'\r
+       },\r
+\r
+       initialize: function (options) {\r
+               L.setOptions(this, options);\r
+\r
+               this._attributions = {};\r
+       },\r
+\r
+       onAdd: function (map) {\r
+               this._container = L.DomUtil.create('div', 'leaflet-control-attribution');\r
+               L.DomEvent.disableClickPropagation(this._container);\r
+\r
+               for (var i in map._layers) {\r
+                       if (map._layers[i].getAttribution) {\r
+                               this.addAttribution(map._layers[i].getAttribution());\r
+                       }\r
+               }\r
+               \r
+               map\r
+                   .on('layeradd', this._onLayerAdd, this)\r
+                   .on('layerremove', this._onLayerRemove, this);\r
+\r
+               this._update();\r
+\r
+               return this._container;\r
+       },\r
+\r
+       onRemove: function (map) {\r
+               map\r
+                   .off('layeradd', this._onLayerAdd)\r
+                   .off('layerremove', this._onLayerRemove);\r
+\r
+       },\r
+\r
+       setPrefix: function (prefix) {\r
+               this.options.prefix = prefix;\r
+               this._update();\r
+               return this;\r
+       },\r
+\r
+       addAttribution: function (text) {\r
+               if (!text) { return; }\r
+\r
+               if (!this._attributions[text]) {\r
+                       this._attributions[text] = 0;\r
+               }\r
+               this._attributions[text]++;\r
+\r
+               this._update();\r
+\r
+               return this;\r
+       },\r
+\r
+       removeAttribution: function (text) {\r
+               if (!text) { return; }\r
+\r
+               if (this._attributions[text]) {\r
+                       this._attributions[text]--;\r
+                       this._update();\r
+               }\r
+\r
+               return this;\r
+       },\r
+\r
+       _update: function () {\r
+               if (!this._map) { return; }\r
+\r
+               var attribs = [];\r
+\r
+               for (var i in this._attributions) {\r
+                       if (this._attributions[i]) {\r
+                               attribs.push(i);\r
+                       }\r
+               }\r
+\r
+               var prefixAndAttribs = [];\r
+\r
+               if (this.options.prefix) {\r
+                       prefixAndAttribs.push(this.options.prefix);\r
+               }\r
+               if (attribs.length) {\r
+                       prefixAndAttribs.push(attribs.join(', '));\r
+               }\r
+\r
+               this._container.innerHTML = prefixAndAttribs.join(' | ');\r
+       },\r
+\r
+       _onLayerAdd: function (e) {\r
+               if (e.layer.getAttribution) {\r
+                       this.addAttribution(e.layer.getAttribution());\r
+               }\r
+       },\r
+\r
+       _onLayerRemove: function (e) {\r
+               if (e.layer.getAttribution) {\r
+                       this.removeAttribution(e.layer.getAttribution());\r
+               }\r
+       }\r
+});\r
+\r
+L.Map.mergeOptions({\r
+       attributionControl: true\r
+});\r
+\r
+L.Map.addInitHook(function () {\r
+       if (this.options.attributionControl) {\r
+               this.attributionControl = (new L.Control.Attribution()).addTo(this);\r
+       }\r
+});\r
+\r
+L.control.attribution = function (options) {\r
+       return new L.Control.Attribution(options);\r
+};\r
+
+
+/*
+ * L.Control.Scale is used for displaying metric/imperial scale on the map.
+ */
+
+L.Control.Scale = L.Control.extend({
+       options: {
+               position: 'bottomleft',
+               maxWidth: 100,
+               metric: true,
+               imperial: true,
+               updateWhenIdle: false
+       },
+
+       onAdd: function (map) {
+               this._map = map;
+
+               var className = 'leaflet-control-scale',
+                   container = L.DomUtil.create('div', className),
+                   options = this.options;
+
+               this._addScales(options, className, container);
+
+               map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
+               map.whenReady(this._update, this);
+
+               return container;
+       },
+
+       onRemove: function (map) {
+               map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
+       },
+
+       _addScales: function (options, className, container) {
+               if (options.metric) {
+                       this._mScale = L.DomUtil.create('div', className + '-line', container);
+               }
+               if (options.imperial) {
+                       this._iScale = L.DomUtil.create('div', className + '-line', container);
+               }
+       },
+
+       _update: function () {
+               var bounds = this._map.getBounds(),
+                   centerLat = bounds.getCenter().lat,
+                   halfWorldMeters = 6378137 * Math.PI * Math.cos(centerLat * Math.PI / 180),
+                   dist = halfWorldMeters * (bounds.getNorthEast().lng - bounds.getSouthWest().lng) / 180,
+
+                   size = this._map.getSize(),
+                   options = this.options,
+                   maxMeters = 0;
+
+               if (size.x > 0) {
+                       maxMeters = dist * (options.maxWidth / size.x);
+               }
+
+               this._updateScales(options, maxMeters);
+       },
+
+       _updateScales: function (options, maxMeters) {
+               if (options.metric && maxMeters) {
+                       this._updateMetric(maxMeters);
+               }
+
+               if (options.imperial && maxMeters) {
+                       this._updateImperial(maxMeters);
+               }
+       },
+
+       _updateMetric: function (maxMeters) {
+               var meters = this._getRoundNum(maxMeters);
+
+               this._mScale.style.width = this._getScaleWidth(meters / maxMeters) + 'px';
+               this._mScale.innerHTML = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
+       },
+
+       _updateImperial: function (maxMeters) {
+               var maxFeet = maxMeters * 3.2808399,
+                   scale = this._iScale,
+                   maxMiles, miles, feet;
+
+               if (maxFeet > 5280) {
+                       maxMiles = maxFeet / 5280;
+                       miles = this._getRoundNum(maxMiles);
+
+                       scale.style.width = this._getScaleWidth(miles / maxMiles) + 'px';
+                       scale.innerHTML = miles + ' mi';
+
+               } else {
+                       feet = this._getRoundNum(maxFeet);
+
+                       scale.style.width = this._getScaleWidth(feet / maxFeet) + 'px';
+                       scale.innerHTML = feet + ' ft';
+               }
+       },
+
+       _getScaleWidth: function (ratio) {
+               return Math.round(this.options.maxWidth * ratio) - 10;
+       },
+
+       _getRoundNum: function (num) {
+               var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
+                   d = num / pow10;
+
+               d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1;
+
+               return pow10 * d;
+       }
+});
+
+L.control.scale = function (options) {
+       return new L.Control.Scale(options);
+};
+
+
+/*\r
+ * L.Control.Layers is a control to allow users to switch between different layers on the map.\r
+ */\r
+\r
+L.Control.Layers = L.Control.extend({\r
+       options: {\r
+               collapsed: true,\r
+               position: 'topright',\r
+               autoZIndex: true\r
+       },\r
+\r
+       initialize: function (baseLayers, overlays, options) {\r
+               L.setOptions(this, options);\r
+\r
+               this._layers = {};\r
+               this._lastZIndex = 0;\r
+               this._handlingClick = false;\r
+\r
+               for (var i in baseLayers) {\r
+                       this._addLayer(baseLayers[i], i);\r
+               }\r
+\r
+               for (i in overlays) {\r
+                       this._addLayer(overlays[i], i, true);\r
+               }\r
+       },\r
+\r
+       onAdd: function (map) {\r
+               this._initLayout();\r
+               this._update();\r
+\r
+               map\r
+                   .on('layeradd', this._onLayerChange, this)\r
+                   .on('layerremove', this._onLayerChange, this);\r
+\r
+               return this._container;\r
+       },\r
+\r
+       onRemove: function (map) {\r
+               map\r
+                   .off('layeradd', this._onLayerChange, this)\r
+                   .off('layerremove', this._onLayerChange, this);\r
+       },\r
+\r
+       addBaseLayer: function (layer, name) {\r
+               this._addLayer(layer, name);\r
+               this._update();\r
+               return this;\r
+       },\r
+\r
+       addOverlay: function (layer, name) {\r
+               this._addLayer(layer, name, true);\r
+               this._update();\r
+               return this;\r
+       },\r
+\r
+       removeLayer: function (layer) {\r
+               var id = L.stamp(layer);\r
+               delete this._layers[id];\r
+               this._update();\r
+               return this;\r
+       },\r
+\r
+       _initLayout: function () {\r
+               var className = 'leaflet-control-layers',\r
+                   container = this._container = L.DomUtil.create('div', className);\r
+\r
+               //Makes this work on IE10 Touch devices by stopping it from firing a mouseout event when the touch is released\r
+               container.setAttribute('aria-haspopup', true);\r
+\r
+               if (!L.Browser.touch) {\r
+                       L.DomEvent\r
+                               .disableClickPropagation(container)\r
+                               .disableScrollPropagation(container);\r
+               } else {\r
+                       L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);\r
+               }\r
+\r
+               var form = this._form = L.DomUtil.create('form', className + '-list');\r
+\r
+               if (this.options.collapsed) {\r
+                       if (!L.Browser.android) {\r
+                               L.DomEvent\r
+                                   .on(container, 'mouseover', this._expand, this)\r
+                                   .on(container, 'mouseout', this._collapse, this);\r
+                       }\r
+                       var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);\r
+                       link.href = '#';\r
+                       link.title = 'Layers';\r
+\r
+                       if (L.Browser.touch) {\r
+                               L.DomEvent\r
+                                   .on(link, 'click', L.DomEvent.stop)\r
+                                   .on(link, 'click', this._expand, this);\r
+                       }\r
+                       else {\r
+                               L.DomEvent.on(link, 'focus', this._expand, this);\r
+                       }\r
+                       //Work around for Firefox android issue https://github.com/Leaflet/Leaflet/issues/2033\r
+                       L.DomEvent.on(form, 'click', function () {\r
+                               setTimeout(L.bind(this._onInputClick, this), 0);\r
+                       }, this);\r
+\r
+                       this._map.on('click', this._collapse, this);\r
+                       // TODO keyboard accessibility\r
+               } else {\r
+                       this._expand();\r
+               }\r
+\r
+               this._baseLayersList = L.DomUtil.create('div', className + '-base', form);\r
+               this._separator = L.DomUtil.create('div', className + '-separator', form);\r
+               this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);\r
+\r
+               container.appendChild(form);\r
+       },\r
+\r
+       _addLayer: function (layer, name, overlay) {\r
+               var id = L.stamp(layer);\r
+\r
+               this._layers[id] = {\r
+                       layer: layer,\r
+                       name: name,\r
+                       overlay: overlay\r
+               };\r
+\r
+               if (this.options.autoZIndex && layer.setZIndex) {\r
+                       this._lastZIndex++;\r
+                       layer.setZIndex(this._lastZIndex);\r
+               }\r
+       },\r
+\r
+       _update: function () {\r
+               if (!this._container) {\r
+                       return;\r
+               }\r
+\r
+               this._baseLayersList.innerHTML = '';\r
+               this._overlaysList.innerHTML = '';\r
+\r
+               var baseLayersPresent = false,\r
+                   overlaysPresent = false,\r
+                   i, obj;\r
+\r
+               for (i in this._layers) {\r
+                       obj = this._layers[i];\r
+                       this._addItem(obj);\r
+                       overlaysPresent = overlaysPresent || obj.overlay;\r
+                       baseLayersPresent = baseLayersPresent || !obj.overlay;\r
+               }\r
+\r
+               this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';\r
+       },\r
+\r
+       _onLayerChange: function (e) {\r
+               var obj = this._layers[L.stamp(e.layer)];\r
+\r
+               if (!obj) { return; }\r
+\r
+               if (!this._handlingClick) {\r
+                       this._update();\r
+               }\r
+\r
+               var type = obj.overlay ?\r
+                       (e.type === 'layeradd' ? 'overlayadd' : 'overlayremove') :\r
+                       (e.type === 'layeradd' ? 'baselayerchange' : null);\r
+\r
+               if (type) {\r
+                       this._map.fire(type, obj);\r
+               }\r
+       },\r
+\r
+       // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)\r
+       _createRadioElement: function (name, checked) {\r
+\r
+               var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + name + '"';\r
+               if (checked) {\r
+                       radioHtml += ' checked="checked"';\r
+               }\r
+               radioHtml += '/>';\r
+\r
+               var radioFragment = document.createElement('div');\r
+               radioFragment.innerHTML = radioHtml;\r
+\r
+               return radioFragment.firstChild;\r
+       },\r
+\r
+       _addItem: function (obj) {\r
+               var label = document.createElement('label'),\r
+                   input,\r
+                   checked = this._map.hasLayer(obj.layer);\r
+\r
+               if (obj.overlay) {\r
+                       input = document.createElement('input');\r
+                       input.type = 'checkbox';\r
+                       input.className = 'leaflet-control-layers-selector';\r
+                       input.defaultChecked = checked;\r
+               } else {\r
+                       input = this._createRadioElement('leaflet-base-layers', checked);\r
+               }\r
+\r
+               input.layerId = L.stamp(obj.layer);\r
+\r
+               L.DomEvent.on(input, 'click', this._onInputClick, this);\r
+\r
+               var name = document.createElement('span');\r
+               name.innerHTML = ' ' + obj.name;\r
+\r
+               label.appendChild(input);\r
+               label.appendChild(name);\r
+\r
+               var container = obj.overlay ? this._overlaysList : this._baseLayersList;\r
+               container.appendChild(label);\r
+\r
+               return label;\r
+       },\r
+\r
+       _onInputClick: function () {\r
+               var i, input, obj,\r
+                   inputs = this._form.getElementsByTagName('input'),\r
+                   inputsLen = inputs.length;\r
+\r
+               this._handlingClick = true;\r
+\r
+               for (i = 0; i < inputsLen; i++) {\r
+                       input = inputs[i];\r
+                       obj = this._layers[input.layerId];\r
+\r
+                       if (input.checked && !this._map.hasLayer(obj.layer)) {\r
+                               this._map.addLayer(obj.layer);\r
+\r
+                       } else if (!input.checked && this._map.hasLayer(obj.layer)) {\r
+                               this._map.removeLayer(obj.layer);\r
+                       }\r
+               }\r
+\r
+               this._handlingClick = false;\r
+\r
+               this._refocusOnMap();\r
+       },\r
+\r
+       _expand: function () {\r
+               L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');\r
+       },\r
+\r
+       _collapse: function () {\r
+               this._container.className = this._container.className.replace(' leaflet-control-layers-expanded', '');\r
+       }\r
+});\r
+\r
+L.control.layers = function (baseLayers, overlays, options) {\r
+       return new L.Control.Layers(baseLayers, overlays, options);\r
+};\r
+
+
+/*
+ * L.PosAnimation is used by Leaflet internally for pan animations.
+ */
+
+L.PosAnimation = L.Class.extend({
+       includes: L.Mixin.Events,
+
+       run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number])
+               this.stop();
+
+               this._el = el;
+               this._inProgress = true;
+               this._newPos = newPos;
+
+               this.fire('start');
+
+               el.style[L.DomUtil.TRANSITION] = 'all ' + (duration || 0.25) +
+                       's cubic-bezier(0,0,' + (easeLinearity || 0.5) + ',1)';
+
+               L.DomEvent.on(el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this);
+               L.DomUtil.setPosition(el, newPos);
+
+               // toggle reflow, Chrome flickers for some reason if you don't do this
+               L.Util.falseFn(el.offsetWidth);
+
+               // there's no native way to track value updates of transitioned properties, so we imitate this
+               this._stepTimer = setInterval(L.bind(this._onStep, this), 50);
+       },
+
+       stop: function () {
+               if (!this._inProgress) { return; }
+
+               // if we just removed the transition property, the element would jump to its final position,
+               // so we need to make it stay at the current position
+
+               L.DomUtil.setPosition(this._el, this._getPos());
+               this._onTransitionEnd();
+               L.Util.falseFn(this._el.offsetWidth); // force reflow in case we are about to start a new animation
+       },
+
+       _onStep: function () {
+               var stepPos = this._getPos();
+               if (!stepPos) {
+                       this._onTransitionEnd();
+                       return;
+               }
+               // jshint camelcase: false
+               // make L.DomUtil.getPosition return intermediate position value during animation
+               this._el._leaflet_pos = stepPos;
+
+               this.fire('step');
+       },
+
+       // you can't easily get intermediate values of properties animated with CSS3 Transitions,
+       // we need to parse computed style (in case of transform it returns matrix string)
+
+       _transformRe: /([-+]?(?:\d*\.)?\d+)\D*, ([-+]?(?:\d*\.)?\d+)\D*\)/,
+
+       _getPos: function () {
+               var left, top, matches,
+                   el = this._el,
+                   style = window.getComputedStyle(el);
+
+               if (L.Browser.any3d) {
+                       matches = style[L.DomUtil.TRANSFORM].match(this._transformRe);
+                       if (!matches) { return; }
+                       left = parseFloat(matches[1]);
+                       top  = parseFloat(matches[2]);
+               } else {
+                       left = parseFloat(style.left);
+                       top  = parseFloat(style.top);
+               }
+
+               return new L.Point(left, top, true);
+       },
+
+       _onTransitionEnd: function () {
+               L.DomEvent.off(this._el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this);
+
+               if (!this._inProgress) { return; }
+               this._inProgress = false;
+
+               this._el.style[L.DomUtil.TRANSITION] = '';
+
+               // jshint camelcase: false
+               // make sure L.DomUtil.getPosition returns the final position value after animation
+               this._el._leaflet_pos = this._newPos;
+
+               clearInterval(this._stepTimer);
+
+               this.fire('step').fire('end');
+       }
+
+});
+
+
+/*
+ * Extends L.Map to handle panning animations.
+ */
+
+L.Map.include({
+
+       setView: function (center, zoom, options) {
+
+               zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
+               center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds);
+               options = options || {};
+
+               if (this._panAnim) {
+                       this._panAnim.stop();
+               }
+
+               if (this._loaded && !options.reset && options !== true) {
+
+                       if (options.animate !== undefined) {
+                               options.zoom = L.extend({animate: options.animate}, options.zoom);
+                               options.pan = L.extend({animate: options.animate}, options.pan);
+                       }
+
+                       // try animating pan or zoom
+                       var animated = (this._zoom !== zoom) ?
+                               this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
+                               this._tryAnimatedPan(center, options.pan);
+
+                       if (animated) {
+                               // prevent resize handler call, the view will refresh after animation anyway
+                               clearTimeout(this._sizeTimer);
+                               return this;
+                       }
+               }
+
+               // animation didn't start, just reset the map view
+               this._resetView(center, zoom);
+
+               return this;
+       },
+
+       panBy: function (offset, options) {
+               offset = L.point(offset).round();
+               options = options || {};
+
+               if (!offset.x && !offset.y) {
+                       return this;
+               }
+
+               if (!this._panAnim) {
+                       this._panAnim = new L.PosAnimation();
+
+                       this._panAnim.on({
+                               'step': this._onPanTransitionStep,
+                               'end': this._onPanTransitionEnd
+                       }, this);
+               }
+
+               // don't fire movestart if animating inertia
+               if (!options.noMoveStart) {
+                       this.fire('movestart');
+               }
+
+               // animate pan unless animate: false specified
+               if (options.animate !== false) {
+                       L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
+
+                       var newPos = this._getMapPanePos().subtract(offset);
+                       this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
+               } else {
+                       this._rawPanBy(offset);
+                       this.fire('move').fire('moveend');
+               }
+
+               return this;
+       },
+
+       _onPanTransitionStep: function () {
+               this.fire('move');
+       },
+
+       _onPanTransitionEnd: function () {
+               L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
+               this.fire('moveend');
+       },
+
+       _tryAnimatedPan: function (center, options) {
+               // difference between the new and current centers in pixels
+               var offset = this._getCenterOffset(center)._floor();
+
+               // don't animate too far unless animate: true specified in options
+               if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
+
+               this.panBy(offset, options);
+
+               return true;
+       }
+});
+
+
+/*
+ * L.PosAnimation fallback implementation that powers Leaflet pan animations
+ * in browsers that don't support CSS3 Transitions.
+ */
+
+L.PosAnimation = L.DomUtil.TRANSITION ? L.PosAnimation : L.PosAnimation.extend({
+
+       run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number])
+               this.stop();
+
+               this._el = el;
+               this._inProgress = true;
+               this._duration = duration || 0.25;
+               this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
+
+               this._startPos = L.DomUtil.getPosition(el);
+               this._offset = newPos.subtract(this._startPos);
+               this._startTime = +new Date();
+
+               this.fire('start');
+
+               this._animate();
+       },
+
+       stop: function () {
+               if (!this._inProgress) { return; }
+
+               this._step();
+               this._complete();
+       },
+
+       _animate: function () {
+               // animation loop
+               this._animId = L.Util.requestAnimFrame(this._animate, this);
+               this._step();
+       },
+
+       _step: function () {
+               var elapsed = (+new Date()) - this._startTime,
+                   duration = this._duration * 1000;
+
+               if (elapsed < duration) {
+                       this._runFrame(this._easeOut(elapsed / duration));
+               } else {
+                       this._runFrame(1);
+                       this._complete();
+               }
+       },
+
+       _runFrame: function (progress) {
+               var pos = this._startPos.add(this._offset.multiplyBy(progress));
+               L.DomUtil.setPosition(this._el, pos);
+
+               this.fire('step');
+       },
+
+       _complete: function () {
+               L.Util.cancelAnimFrame(this._animId);
+
+               this._inProgress = false;
+               this.fire('end');
+       },
+
+       _easeOut: function (t) {
+               return 1 - Math.pow(1 - t, this._easeOutPower);
+       }
+});
+
+
+/*
+ * Extends L.Map to handle zoom animations.
+ */
+
+L.Map.mergeOptions({
+       zoomAnimation: true,
+       zoomAnimationThreshold: 4
+});
+
+if (L.DomUtil.TRANSITION) {
+
+       L.Map.addInitHook(function () {
+               // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
+               this._zoomAnimated = this.options.zoomAnimation && L.DomUtil.TRANSITION &&
+                               L.Browser.any3d && !L.Browser.android23 && !L.Browser.mobileOpera;
+
+               // zoom transitions run with the same duration for all layers, so if one of transitionend events
+               // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
+               if (this._zoomAnimated) {
+                       L.DomEvent.on(this._mapPane, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
+               }
+       });
+}
+
+L.Map.include(!L.DomUtil.TRANSITION ? {} : {
+
+       _catchTransitionEnd: function (e) {
+               if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
+                       this._onZoomTransitionEnd();
+               }
+       },
+
+       _nothingToAnimate: function () {
+               return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
+       },
+
+       _tryAnimatedZoom: function (center, zoom, options) {
+
+               if (this._animatingZoom) { return true; }
+
+               options = options || {};
+
+               // don't animate if disabled, not supported or zoom difference is too large
+               if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
+                       Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
+
+               // offset is the pixel coords of the zoom origin relative to the current center
+               var scale = this.getZoomScale(zoom),
+                   offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale),
+                       origin = this._getCenterLayerPoint()._add(offset);
+
+               // don't animate if the zoom origin isn't within one screen from the current center, unless forced
+               if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
+
+               this
+                   .fire('movestart')
+                   .fire('zoomstart');
+
+               this._animateZoom(center, zoom, origin, scale, null, true);
+
+               return true;
+       },
+
+       _animateZoom: function (center, zoom, origin, scale, delta, backwards, forTouchZoom) {
+
+               if (!forTouchZoom) {
+                       this._animatingZoom = true;
+               }
+
+               // put transform transition on all layers with leaflet-zoom-animated class
+               L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
+
+               // remember what center/zoom to set after animation
+               this._animateToCenter = center;
+               this._animateToZoom = zoom;
+
+               // disable any dragging during animation
+               if (L.Draggable) {
+                       L.Draggable._disabled = true;
+               }
+
+               L.Util.requestAnimFrame(function () {
+                       this.fire('zoomanim', {
+                               center: center,
+                               zoom: zoom,
+                               origin: origin,
+                               scale: scale,
+                               delta: delta,
+                               backwards: backwards
+                       });
+                       // horrible hack to work around a Chrome bug https://github.com/Leaflet/Leaflet/issues/3689
+                       setTimeout(L.bind(this._onZoomTransitionEnd, this), 250);
+               }, this);
+       },
+
+       _onZoomTransitionEnd: function () {
+               if (!this._animatingZoom) { return; }
+
+               this._animatingZoom = false;
+
+               L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
+
+               L.Util.requestAnimFrame(function () {
+                       this._resetView(this._animateToCenter, this._animateToZoom, true, true);
+
+                       if (L.Draggable) {
+                               L.Draggable._disabled = false;
+                       }
+               }, this);
+       }
+});
+
+
+/*
+       Zoom animation logic for L.TileLayer.
+*/
+
+L.TileLayer.include({
+       _animateZoom: function (e) {
+               if (!this._animating) {
+                       this._animating = true;
+                       this._prepareBgBuffer();
+               }
+
+               var bg = this._bgBuffer,
+                   transform = L.DomUtil.TRANSFORM,
+                   initialTransform = e.delta ? L.DomUtil.getTranslateString(e.delta) : bg.style[transform],
+                   scaleStr = L.DomUtil.getScaleString(e.scale, e.origin);
+
+               bg.style[transform] = e.backwards ?
+                               scaleStr + ' ' + initialTransform :
+                               initialTransform + ' ' + scaleStr;
+       },
+
+       _endZoomAnim: function () {
+               var front = this._tileContainer,
+                   bg = this._bgBuffer;
+
+               front.style.visibility = '';
+               front.parentNode.appendChild(front); // Bring to fore
+
+               // force reflow
+               L.Util.falseFn(bg.offsetWidth);
+
+               var zoom = this._map.getZoom();
+               if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
+                       this._clearBgBuffer();
+               }
+
+               this._animating = false;
+       },
+
+       _clearBgBuffer: function () {
+               var map = this._map;
+
+               if (map && !map._animatingZoom && !map.touchZoom._zooming) {
+                       this._bgBuffer.innerHTML = '';
+                       this._bgBuffer.style[L.DomUtil.TRANSFORM] = '';
+               }
+       },
+
+       _prepareBgBuffer: function () {
+
+               var front = this._tileContainer,
+                   bg = this._bgBuffer;
+
+               // if foreground layer doesn't have many tiles but bg layer does,
+               // keep the existing bg layer and just zoom it some more
+
+               var bgLoaded = this._getLoadedTilesPercentage(bg),
+                   frontLoaded = this._getLoadedTilesPercentage(front);
+
+               if (bg && bgLoaded > 0.5 && frontLoaded < 0.5) {
+
+                       front.style.visibility = 'hidden';
+                       this._stopLoadingImages(front);
+                       return;
+               }
+
+               // prepare the buffer to become the front tile pane
+               bg.style.visibility = 'hidden';
+               bg.style[L.DomUtil.TRANSFORM] = '';
+
+               // switch out the current layer to be the new bg layer (and vice-versa)
+               this._tileContainer = bg;
+               bg = this._bgBuffer = front;
+
+               this._stopLoadingImages(bg);
+
+               //prevent bg buffer from clearing right after zoom
+               clearTimeout(this._clearBgBufferTimer);
+       },
+
+       _getLoadedTilesPercentage: function (container) {
+               var tiles = container.getElementsByTagName('img'),
+                   i, len, count = 0;
+
+               for (i = 0, len = tiles.length; i < len; i++) {
+                       if (tiles[i].complete) {
+                               count++;
+                       }
+               }
+               return count / len;
+       },
+
+       // stops loading all tiles in the background layer
+       _stopLoadingImages: function (container) {
+               var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
+                   i, len, tile;
+
+               for (i = 0, len = tiles.length; i < len; i++) {
+                       tile = tiles[i];
+
+                       if (!tile.complete) {
+                               tile.onload = L.Util.falseFn;
+                               tile.onerror = L.Util.falseFn;
+                               tile.src = L.Util.emptyImageUrl;
+
+                               tile.parentNode.removeChild(tile);
+                       }
+               }
+       }
+});
+
+
+/*\r
+ * Provides L.Map with convenient shortcuts for using browser geolocation features.\r
+ */\r
+\r
+L.Map.include({\r
+       _defaultLocateOptions: {\r
+               watch: false,\r
+               setView: false,\r
+               maxZoom: Infinity,\r
+               timeout: 10000,\r
+               maximumAge: 0,\r
+               enableHighAccuracy: false\r
+       },\r
+\r
+       locate: function (/*Object*/ options) {\r
+\r
+               options = this._locateOptions = L.extend(this._defaultLocateOptions, options);\r
+\r
+               if (!navigator.geolocation) {\r
+                       this._handleGeolocationError({\r
+                               code: 0,\r
+                               message: 'Geolocation not supported.'\r
+                       });\r
+                       return this;\r
+               }\r
+\r
+               var onResponse = L.bind(this._handleGeolocationResponse, this),\r
+                       onError = L.bind(this._handleGeolocationError, this);\r
+\r
+               if (options.watch) {\r
+                       this._locationWatchId =\r
+                               navigator.geolocation.watchPosition(onResponse, onError, options);\r
+               } else {\r
+                       navigator.geolocation.getCurrentPosition(onResponse, onError, options);\r
+               }\r
+               return this;\r
+       },\r
+\r
+       stopLocate: function () {\r
+               if (navigator.geolocation) {\r
+                       navigator.geolocation.clearWatch(this._locationWatchId);\r
+               }\r
+               if (this._locateOptions) {\r
+                       this._locateOptions.setView = false;\r
+               }\r
+               return this;\r
+       },\r
+\r
+       _handleGeolocationError: function (error) {\r
+               var c = error.code,\r
+                   message = error.message ||\r
+                           (c === 1 ? 'permission denied' :\r
+                           (c === 2 ? 'position unavailable' : 'timeout'));\r
+\r
+               if (this._locateOptions.setView && !this._loaded) {\r
+                       this.fitWorld();\r
+               }\r
+\r
+               this.fire('locationerror', {\r
+                       code: c,\r
+                       message: 'Geolocation error: ' + message + '.'\r
+               });\r
+       },\r
+\r
+       _handleGeolocationResponse: function (pos) {\r
+               var lat = pos.coords.latitude,\r
+                   lng = pos.coords.longitude,\r
+                   latlng = new L.LatLng(lat, lng),\r
+\r
+                   latAccuracy = 180 * pos.coords.accuracy / 40075017,\r
+                   lngAccuracy = latAccuracy / Math.cos(L.LatLng.DEG_TO_RAD * lat),\r
+\r
+                   bounds = L.latLngBounds(\r
+                           [lat - latAccuracy, lng - lngAccuracy],\r
+                           [lat + latAccuracy, lng + lngAccuracy]),\r
+\r
+                   options = this._locateOptions;\r
+\r
+               if (options.setView) {\r
+                       var zoom = Math.min(this.getBoundsZoom(bounds), options.maxZoom);\r
+                       this.setView(latlng, zoom);\r
+               }\r
+\r
+               var data = {\r
+                       latlng: latlng,\r
+                       bounds: bounds,\r
+                       timestamp: pos.timestamp\r
+               };\r
+\r
+               for (var i in pos.coords) {\r
+                       if (typeof pos.coords[i] === 'number') {\r
+                               data[i] = pos.coords[i];\r
+                       }\r
+               }\r
+\r
+               this.fire('locationfound', data);\r
+       }\r
+});\r
+
+
+}(window, document));
\ No newline at end of file
diff --git a/www/plugins/gis/lib/leaflet/dist/leaflet.css b/www/plugins/gis/lib/leaflet/dist/leaflet.css
new file mode 100644 (file)
index 0000000..c161c31
--- /dev/null
@@ -0,0 +1,479 @@
+/* required styles */\r
+\r
+.leaflet-map-pane,\r
+.leaflet-tile,\r
+.leaflet-marker-icon,\r
+.leaflet-marker-shadow,\r
+.leaflet-tile-pane,\r
+.leaflet-tile-container,\r
+.leaflet-overlay-pane,\r
+.leaflet-shadow-pane,\r
+.leaflet-marker-pane,\r
+.leaflet-popup-pane,\r
+.leaflet-overlay-pane svg,\r
+.leaflet-zoom-box,\r
+.leaflet-image-layer,\r
+.leaflet-layer {\r
+       position: absolute;\r
+       left: 0;\r
+       top: 0;\r
+       }\r
+.leaflet-container {\r
+       overflow: hidden;\r
+       -ms-touch-action: none;\r
+       touch-action: none;\r
+       }\r
+.leaflet-tile,\r
+.leaflet-marker-icon,\r
+.leaflet-marker-shadow {\r
+       -webkit-user-select: none;\r
+          -moz-user-select: none;\r
+               user-select: none;\r
+       -webkit-user-drag: none;\r
+       }\r
+.leaflet-marker-icon,\r
+.leaflet-marker-shadow {\r
+       display: block;\r
+       }\r
+/* map is broken in FF if you have max-width: 100% on tiles */\r
+.leaflet-container img {\r
+       max-width: none !important;\r
+       }\r
+/* stupid Android 2 doesn't understand "max-width: none" properly */\r
+.leaflet-container img.leaflet-image-layer {\r
+       max-width: 15000px !important;\r
+       }\r
+.leaflet-tile {\r
+       filter: inherit;\r
+       visibility: hidden;\r
+       }\r
+.leaflet-tile-loaded {\r
+       visibility: inherit;\r
+       }\r
+.leaflet-zoom-box {\r
+       width: 0;\r
+       height: 0;\r
+       }\r
+/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */\r
+.leaflet-overlay-pane svg {\r
+       -moz-user-select: none;\r
+       }\r
+\r
+.leaflet-tile-pane    { z-index: 2; }\r
+.leaflet-objects-pane { z-index: 3; }\r
+.leaflet-overlay-pane { z-index: 4; }\r
+.leaflet-shadow-pane  { z-index: 5; }\r
+.leaflet-marker-pane  { z-index: 6; }\r
+.leaflet-popup-pane   { z-index: 7; }\r
+\r
+.leaflet-vml-shape {\r
+       width: 1px;\r
+       height: 1px;\r
+       }\r
+.lvml {\r
+       behavior: url(#default#VML);\r
+       display: inline-block;\r
+       position: absolute;\r
+       }\r
+\r
+\r
+/* control positioning */\r
+\r
+.leaflet-control {\r
+       position: relative;\r
+       z-index: 7;\r
+       pointer-events: auto;\r
+       }\r
+.leaflet-top,\r
+.leaflet-bottom {\r
+       position: absolute;\r
+       z-index: 1000;\r
+       pointer-events: none;\r
+       }\r
+.leaflet-top {\r
+       top: 0;\r
+       }\r
+.leaflet-right {\r
+       right: 0;\r
+       }\r
+.leaflet-bottom {\r
+       bottom: 0;\r
+       }\r
+.leaflet-left {\r
+       left: 0;\r
+       }\r
+.leaflet-control {\r
+       float: left;\r
+       clear: both;\r
+       }\r
+.leaflet-right .leaflet-control {\r
+       float: right;\r
+       }\r
+.leaflet-top .leaflet-control {\r
+       margin-top: 10px;\r
+       }\r
+.leaflet-bottom .leaflet-control {\r
+       margin-bottom: 10px;\r
+       }\r
+.leaflet-left .leaflet-control {\r
+       margin-left: 10px;\r
+       }\r
+.leaflet-right .leaflet-control {\r
+       margin-right: 10px;\r
+       }\r
+\r
+\r
+/* zoom and fade animations */\r
+\r
+.leaflet-fade-anim .leaflet-tile,\r
+.leaflet-fade-anim .leaflet-popup {\r
+       opacity: 0;\r
+       -webkit-transition: opacity 0.2s linear;\r
+          -moz-transition: opacity 0.2s linear;\r
+            -o-transition: opacity 0.2s linear;\r
+               transition: opacity 0.2s linear;\r
+       }\r
+.leaflet-fade-anim .leaflet-tile-loaded,\r
+.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {\r
+       opacity: 1;\r
+       }\r
+\r
+.leaflet-zoom-anim .leaflet-zoom-animated {\r
+       -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);\r
+          -moz-transition:    -moz-transform 0.25s cubic-bezier(0,0,0.25,1);\r
+            -o-transition:      -o-transform 0.25s cubic-bezier(0,0,0.25,1);\r
+               transition:         transform 0.25s cubic-bezier(0,0,0.25,1);\r
+       }\r
+.leaflet-zoom-anim .leaflet-tile,\r
+.leaflet-pan-anim .leaflet-tile,\r
+.leaflet-touching .leaflet-zoom-animated {\r
+       -webkit-transition: none;\r
+          -moz-transition: none;\r
+            -o-transition: none;\r
+               transition: none;\r
+       }\r
+\r
+.leaflet-zoom-anim .leaflet-zoom-hide {\r
+       visibility: hidden;\r
+       }\r
+\r
+\r
+/* cursors */\r
+\r
+.leaflet-clickable {\r
+       cursor: pointer;\r
+       }\r
+.leaflet-container {\r
+       cursor: -webkit-grab;\r
+       cursor:    -moz-grab;\r
+       }\r
+.leaflet-popup-pane,\r
+.leaflet-control {\r
+       cursor: auto;\r
+       }\r
+.leaflet-dragging .leaflet-container,\r
+.leaflet-dragging .leaflet-clickable {\r
+       cursor: move;\r
+       cursor: -webkit-grabbing;\r
+       cursor:    -moz-grabbing;\r
+       }\r
+\r
+\r
+/* visual tweaks */\r
+\r
+.leaflet-container {\r
+       background: #ddd;\r
+       outline: 0;\r
+       }\r
+.leaflet-container a {\r
+       color: #0078A8;\r
+       }\r
+.leaflet-container a.leaflet-active {\r
+       outline: 2px solid orange;\r
+       }\r
+.leaflet-zoom-box {\r
+       border: 2px dotted #38f;\r
+       background: rgba(255,255,255,0.5);\r
+       }\r
+\r
+\r
+/* general typography */\r
+.leaflet-container {\r
+       font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;\r
+       }\r
+\r
+\r
+/* general toolbar styles */\r
+\r
+.leaflet-bar {\r
+       box-shadow: 0 1px 5px rgba(0,0,0,0.65);\r
+       border-radius: 4px;\r
+       }\r
+.leaflet-bar a,\r
+.leaflet-bar a:hover {\r
+       background-color: #fff;\r
+       border-bottom: 1px solid #ccc;\r
+       width: 26px;\r
+       height: 26px;\r
+       line-height: 26px;\r
+       display: block;\r
+       text-align: center;\r
+       text-decoration: none;\r
+       color: black;\r
+       }\r
+.leaflet-bar a,\r
+.leaflet-control-layers-toggle {\r
+       background-position: 50% 50%;\r
+       background-repeat: no-repeat;\r
+       display: block;\r
+       }\r
+.leaflet-bar a:hover {\r
+       background-color: #f4f4f4;\r
+       }\r
+.leaflet-bar a:first-child {\r
+       border-top-left-radius: 4px;\r
+       border-top-right-radius: 4px;\r
+       }\r
+.leaflet-bar a:last-child {\r
+       border-bottom-left-radius: 4px;\r
+       border-bottom-right-radius: 4px;\r
+       border-bottom: none;\r
+       }\r
+.leaflet-bar a.leaflet-disabled {\r
+       cursor: default;\r
+       background-color: #f4f4f4;\r
+       color: #bbb;\r
+       }\r
+\r
+.leaflet-touch .leaflet-bar a {\r
+       width: 30px;\r
+       height: 30px;\r
+       line-height: 30px;\r
+       }\r
+\r
+\r
+/* zoom control */\r
+\r
+.leaflet-control-zoom-in,\r
+.leaflet-control-zoom-out {\r
+       font: bold 18px 'Lucida Console', Monaco, monospace;\r
+       text-indent: 1px;\r
+       }\r
+.leaflet-control-zoom-out {\r
+       font-size: 20px;\r
+       }\r
+\r
+.leaflet-touch .leaflet-control-zoom-in {\r
+       font-size: 22px;\r
+       }\r
+.leaflet-touch .leaflet-control-zoom-out {\r
+       font-size: 24px;\r
+       }\r
+\r
+\r
+/* layers control */\r
+\r
+.leaflet-control-layers {\r
+       box-shadow: 0 1px 5px rgba(0,0,0,0.4);\r
+       background: #fff;\r
+       border-radius: 5px;\r
+       }\r
+.leaflet-control-layers-toggle {\r
+       background-image: url(images/layers.png);\r
+       width: 36px;\r
+       height: 36px;\r
+       }\r
+.leaflet-retina .leaflet-control-layers-toggle {\r
+       background-image: url(images/layers-2x.png);\r
+       background-size: 26px 26px;\r
+       }\r
+.leaflet-touch .leaflet-control-layers-toggle {\r
+       width: 44px;\r
+       height: 44px;\r
+       }\r
+.leaflet-control-layers .leaflet-control-layers-list,\r
+.leaflet-control-layers-expanded .leaflet-control-layers-toggle {\r
+       display: none;\r
+       }\r
+.leaflet-control-layers-expanded .leaflet-control-layers-list {\r
+       display: block;\r
+       position: relative;\r
+       }\r
+.leaflet-control-layers-expanded {\r
+       padding: 6px 10px 6px 6px;\r
+       color: #333;\r
+       background: #fff;\r
+       }\r
+.leaflet-control-layers-selector {\r
+       margin-top: 2px;\r
+       position: relative;\r
+       top: 1px;\r
+       }\r
+.leaflet-control-layers label {\r
+       display: block;\r
+       }\r
+.leaflet-control-layers-separator {\r
+       height: 0;\r
+       border-top: 1px solid #ddd;\r
+       margin: 5px -10px 5px -6px;\r
+       }\r
+\r
+\r
+/* attribution and scale controls */\r
+\r
+.leaflet-container .leaflet-control-attribution {\r
+       background: #fff;\r
+       background: rgba(255, 255, 255, 0.7);\r
+       margin: 0;\r
+       }\r
+.leaflet-control-attribution,\r
+.leaflet-control-scale-line {\r
+       padding: 0 5px;\r
+       color: #333;\r
+       }\r
+.leaflet-control-attribution a {\r
+       text-decoration: none;\r
+       }\r
+.leaflet-control-attribution a:hover {\r
+       text-decoration: underline;\r
+       }\r
+.leaflet-container .leaflet-control-attribution,\r
+.leaflet-container .leaflet-control-scale {\r
+       font-size: 11px;\r
+       }\r
+.leaflet-left .leaflet-control-scale {\r
+       margin-left: 5px;\r
+       }\r
+.leaflet-bottom .leaflet-control-scale {\r
+       margin-bottom: 5px;\r
+       }\r
+.leaflet-control-scale-line {\r
+       border: 2px solid #777;\r
+       border-top: none;\r
+       line-height: 1.1;\r
+       padding: 2px 5px 1px;\r
+       font-size: 11px;\r
+       white-space: nowrap;\r
+       overflow: hidden;\r
+       -moz-box-sizing: content-box;\r
+            box-sizing: content-box;\r
+\r
+       background: #fff;\r
+       background: rgba(255, 255, 255, 0.5);\r
+       }\r
+.leaflet-control-scale-line:not(:first-child) {\r
+       border-top: 2px solid #777;\r
+       border-bottom: none;\r
+       margin-top: -2px;\r
+       }\r
+.leaflet-control-scale-line:not(:first-child):not(:last-child) {\r
+       border-bottom: 2px solid #777;\r
+       }\r
+\r
+.leaflet-touch .leaflet-control-attribution,\r
+.leaflet-touch .leaflet-control-layers,\r
+.leaflet-touch .leaflet-bar {\r
+       box-shadow: none;\r
+       }\r
+.leaflet-touch .leaflet-control-layers,\r
+.leaflet-touch .leaflet-bar {\r
+       border: 2px solid rgba(0,0,0,0.2);\r
+       background-clip: padding-box;\r
+       }\r
+\r
+\r
+/* popup */\r
+\r
+.leaflet-popup {\r
+       position: absolute;\r
+       text-align: center;\r
+       }\r
+.leaflet-popup-content-wrapper {\r
+       padding: 1px;\r
+       text-align: left;\r
+       border-radius: 12px;\r
+       }\r
+.leaflet-popup-content {\r
+       margin: 13px 19px;\r
+       line-height: 1.4;\r
+       }\r
+.leaflet-popup-content p {\r
+       margin: 18px 0;\r
+       }\r
+.leaflet-popup-tip-container {\r
+       margin: 0 auto;\r
+       width: 40px;\r
+       height: 20px;\r
+       position: relative;\r
+       overflow: hidden;\r
+       }\r
+.leaflet-popup-tip {\r
+       width: 17px;\r
+       height: 17px;\r
+       padding: 1px;\r
+\r
+       margin: -10px auto 0;\r
+\r
+       -webkit-transform: rotate(45deg);\r
+          -moz-transform: rotate(45deg);\r
+           -ms-transform: rotate(45deg);\r
+            -o-transform: rotate(45deg);\r
+               transform: rotate(45deg);\r
+       }\r
+.leaflet-popup-content-wrapper,\r
+.leaflet-popup-tip {\r
+       background: white;\r
+\r
+       box-shadow: 0 3px 14px rgba(0,0,0,0.4);\r
+       }\r
+.leaflet-container a.leaflet-popup-close-button {\r
+       position: absolute;\r
+       top: 0;\r
+       right: 0;\r
+       padding: 4px 4px 0 0;\r
+       text-align: center;\r
+       width: 18px;\r
+       height: 14px;\r
+       font: 16px/14px Tahoma, Verdana, sans-serif;\r
+       color: #c3c3c3;\r
+       text-decoration: none;\r
+       font-weight: bold;\r
+       background: transparent;\r
+       }\r
+.leaflet-container a.leaflet-popup-close-button:hover {\r
+       color: #999;\r
+       }\r
+.leaflet-popup-scrolled {\r
+       overflow: auto;\r
+       border-bottom: 1px solid #ddd;\r
+       border-top: 1px solid #ddd;\r
+       }\r
+\r
+.leaflet-oldie .leaflet-popup-content-wrapper {\r
+       zoom: 1;\r
+       }\r
+.leaflet-oldie .leaflet-popup-tip {\r
+       width: 24px;\r
+       margin: 0 auto;\r
+\r
+       -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";\r
+       filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);\r
+       }\r
+.leaflet-oldie .leaflet-popup-tip-container {\r
+       margin-top: -1px;\r
+       }\r
+\r
+.leaflet-oldie .leaflet-control-zoom,\r
+.leaflet-oldie .leaflet-control-layers,\r
+.leaflet-oldie .leaflet-popup-content-wrapper,\r
+.leaflet-oldie .leaflet-popup-tip {\r
+       border: 1px solid #999;\r
+       }\r
+\r
+\r
+/* div icon */\r
+\r
+.leaflet-div-icon {\r
+       background: #fff;\r
+       border: 1px solid #666;\r
+       }\r
diff --git a/www/plugins/gis/lib/leaflet/dist/leaflet.js b/www/plugins/gis/lib/leaflet/dist/leaflet.js
new file mode 100644 (file)
index 0000000..ee5ff5a
--- /dev/null
@@ -0,0 +1,9 @@
+/*
+ Leaflet, a JavaScript library for mobile-friendly interactive maps. http://leafletjs.com
+ (c) 2010-2013, Vladimir Agafonkin
+ (c) 2010-2011, CloudMade
+*/
+!function(t,e,i){var n=t.L,o={};o.version="0.7.7","object"==typeof module&&"object"==typeof module.exports?module.exports=o:"function"==typeof define&&define.amd&&define(o),o.noConflict=function(){return t.L=n,this},t.L=o,o.Util={extend:function(t){var e,i,n,o,s=Array.prototype.slice.call(arguments,1);for(i=0,n=s.length;n>i;i++){o=s[i]||{};for(e in o)o.hasOwnProperty(e)&&(t[e]=o[e])}return t},bind:function(t,e){var i=arguments.length>2?Array.prototype.slice.call(arguments,2):null;return function(){return t.apply(e,i||arguments)}},stamp:function(){var t=0,e="_leaflet_id";return function(i){return i[e]=i[e]||++t,i[e]}}(),invokeEach:function(t,e,i){var n,o;if("object"==typeof t){o=Array.prototype.slice.call(arguments,3);for(n in t)e.apply(i,[n,t[n]].concat(o));return!0}return!1},limitExecByInterval:function(t,e,i){var n,o;return function s(){var a=arguments;return n?void(o=!0):(n=!0,setTimeout(function(){n=!1,o&&(s.apply(i,a),o=!1)},e),void t.apply(i,a))}},falseFn:function(){return!1},formatNum:function(t,e){var i=Math.pow(10,e||5);return Math.round(t*i)/i},trim:function(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")},splitWords:function(t){return o.Util.trim(t).split(/\s+/)},setOptions:function(t,e){return t.options=o.extend({},t.options,e),t.options},getParamString:function(t,e,i){var n=[];for(var o in t)n.push(encodeURIComponent(i?o.toUpperCase():o)+"="+encodeURIComponent(t[o]));return(e&&-1!==e.indexOf("?")?"&":"?")+n.join("&")},template:function(t,e){return t.replace(/\{ *([\w_]+) *\}/g,function(t,n){var o=e[n];if(o===i)throw new Error("No value provided for variable "+t);return"function"==typeof o&&(o=o(e)),o})},isArray:Array.isArray||function(t){return"[object Array]"===Object.prototype.toString.call(t)},emptyImageUrl:"data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="},function(){function e(e){var i,n,o=["webkit","moz","o","ms"];for(i=0;i<o.length&&!n;i++)n=t[o[i]+e];return n}function i(e){var i=+new Date,o=Math.max(0,16-(i-n));return n=i+o,t.setTimeout(e,o)}var n=0,s=t.requestAnimationFrame||e("RequestAnimationFrame")||i,a=t.cancelAnimationFrame||e("CancelAnimationFrame")||e("CancelRequestAnimationFrame")||function(e){t.clearTimeout(e)};o.Util.requestAnimFrame=function(e,n,a,r){return e=o.bind(e,n),a&&s===i?void e():s.call(t,e,r)},o.Util.cancelAnimFrame=function(e){e&&a.call(t,e)}}(),o.extend=o.Util.extend,o.bind=o.Util.bind,o.stamp=o.Util.stamp,o.setOptions=o.Util.setOptions,o.Class=function(){},o.Class.extend=function(t){var e=function(){this.initialize&&this.initialize.apply(this,arguments),this._initHooks&&this.callInitHooks()},i=function(){};i.prototype=this.prototype;var n=new i;n.constructor=e,e.prototype=n;for(var s in this)this.hasOwnProperty(s)&&"prototype"!==s&&(e[s]=this[s]);t.statics&&(o.extend(e,t.statics),delete t.statics),t.includes&&(o.Util.extend.apply(null,[n].concat(t.includes)),delete t.includes),t.options&&n.options&&(t.options=o.extend({},n.options,t.options)),o.extend(n,t),n._initHooks=[];var a=this;return e.__super__=a.prototype,n.callInitHooks=function(){if(!this._initHooksCalled){a.prototype.callInitHooks&&a.prototype.callInitHooks.call(this),this._initHooksCalled=!0;for(var t=0,e=n._initHooks.length;e>t;t++)n._initHooks[t].call(this)}},e},o.Class.include=function(t){o.extend(this.prototype,t)},o.Class.mergeOptions=function(t){o.extend(this.prototype.options,t)},o.Class.addInitHook=function(t){var e=Array.prototype.slice.call(arguments,1),i="function"==typeof t?t:function(){this[t].apply(this,e)};this.prototype._initHooks=this.prototype._initHooks||[],this.prototype._initHooks.push(i)};var s="_leaflet_events";o.Mixin={},o.Mixin.Events={addEventListener:function(t,e,i){if(o.Util.invokeEach(t,this.addEventListener,this,e,i))return this;var n,a,r,h,l,u,c,d=this[s]=this[s]||{},p=i&&i!==this&&o.stamp(i);for(t=o.Util.splitWords(t),n=0,a=t.length;a>n;n++)r={action:e,context:i||this},h=t[n],p?(l=h+"_idx",u=l+"_len",c=d[l]=d[l]||{},c[p]||(c[p]=[],d[u]=(d[u]||0)+1),c[p].push(r)):(d[h]=d[h]||[],d[h].push(r));return this},hasEventListeners:function(t){var e=this[s];return!!e&&(t in e&&e[t].length>0||t+"_idx"in e&&e[t+"_idx_len"]>0)},removeEventListener:function(t,e,i){if(!this[s])return this;if(!t)return this.clearAllEventListeners();if(o.Util.invokeEach(t,this.removeEventListener,this,e,i))return this;var n,a,r,h,l,u,c,d,p,_=this[s],m=i&&i!==this&&o.stamp(i);for(t=o.Util.splitWords(t),n=0,a=t.length;a>n;n++)if(r=t[n],u=r+"_idx",c=u+"_len",d=_[u],e){if(h=m&&d?d[m]:_[r]){for(l=h.length-1;l>=0;l--)h[l].action!==e||i&&h[l].context!==i||(p=h.splice(l,1),p[0].action=o.Util.falseFn);i&&d&&0===h.length&&(delete d[m],_[c]--)}}else delete _[r],delete _[u],delete _[c];return this},clearAllEventListeners:function(){return delete this[s],this},fireEvent:function(t,e){if(!this.hasEventListeners(t))return this;var i,n,a,r,h,l=o.Util.extend({},e,{type:t,target:this}),u=this[s];if(u[t])for(i=u[t].slice(),n=0,a=i.length;a>n;n++)i[n].action.call(i[n].context,l);r=u[t+"_idx"];for(h in r)if(i=r[h].slice())for(n=0,a=i.length;a>n;n++)i[n].action.call(i[n].context,l);return this},addOneTimeEventListener:function(t,e,i){if(o.Util.invokeEach(t,this.addOneTimeEventListener,this,e,i))return this;var n=o.bind(function(){this.removeEventListener(t,e,i).removeEventListener(t,n,i)},this);return this.addEventListener(t,e,i).addEventListener(t,n,i)}},o.Mixin.Events.on=o.Mixin.Events.addEventListener,o.Mixin.Events.off=o.Mixin.Events.removeEventListener,o.Mixin.Events.once=o.Mixin.Events.addOneTimeEventListener,o.Mixin.Events.fire=o.Mixin.Events.fireEvent,function(){var n="ActiveXObject"in t,s=n&&!e.addEventListener,a=navigator.userAgent.toLowerCase(),r=-1!==a.indexOf("webkit"),h=-1!==a.indexOf("chrome"),l=-1!==a.indexOf("phantom"),u=-1!==a.indexOf("android"),c=-1!==a.search("android [23]"),d=-1!==a.indexOf("gecko"),p=typeof orientation!=i+"",_=!t.PointerEvent&&t.MSPointerEvent,m=t.PointerEvent&&t.navigator.pointerEnabled||_,f="devicePixelRatio"in t&&t.devicePixelRatio>1||"matchMedia"in t&&t.matchMedia("(min-resolution:144dpi)")&&t.matchMedia("(min-resolution:144dpi)").matches,g=e.documentElement,v=n&&"transition"in g.style,y="WebKitCSSMatrix"in t&&"m11"in new t.WebKitCSSMatrix&&!c,P="MozPerspective"in g.style,L="OTransition"in g.style,x=!t.L_DISABLE_3D&&(v||y||P||L)&&!l,w=!t.L_NO_TOUCH&&!l&&(m||"ontouchstart"in t||t.DocumentTouch&&e instanceof t.DocumentTouch);o.Browser={ie:n,ielt9:s,webkit:r,gecko:d&&!r&&!t.opera&&!n,android:u,android23:c,chrome:h,ie3d:v,webkit3d:y,gecko3d:P,opera3d:L,any3d:x,mobile:p,mobileWebkit:p&&r,mobileWebkit3d:p&&y,mobileOpera:p&&t.opera,touch:w,msPointer:_,pointer:m,retina:f}}(),o.Point=function(t,e,i){this.x=i?Math.round(t):t,this.y=i?Math.round(e):e},o.Point.prototype={clone:function(){return new o.Point(this.x,this.y)},add:function(t){return this.clone()._add(o.point(t))},_add:function(t){return this.x+=t.x,this.y+=t.y,this},subtract:function(t){return this.clone()._subtract(o.point(t))},_subtract:function(t){return this.x-=t.x,this.y-=t.y,this},divideBy:function(t){return this.clone()._divideBy(t)},_divideBy:function(t){return this.x/=t,this.y/=t,this},multiplyBy:function(t){return this.clone()._multiplyBy(t)},_multiplyBy:function(t){return this.x*=t,this.y*=t,this},round:function(){return this.clone()._round()},_round:function(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this},floor:function(){return this.clone()._floor()},_floor:function(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this},distanceTo:function(t){t=o.point(t);var e=t.x-this.x,i=t.y-this.y;return Math.sqrt(e*e+i*i)},equals:function(t){return t=o.point(t),t.x===this.x&&t.y===this.y},contains:function(t){return t=o.point(t),Math.abs(t.x)<=Math.abs(this.x)&&Math.abs(t.y)<=Math.abs(this.y)},toString:function(){return"Point("+o.Util.formatNum(this.x)+", "+o.Util.formatNum(this.y)+")"}},o.point=function(t,e,n){return t instanceof o.Point?t:o.Util.isArray(t)?new o.Point(t[0],t[1]):t===i||null===t?t:new o.Point(t,e,n)},o.Bounds=function(t,e){if(t)for(var i=e?[t,e]:t,n=0,o=i.length;o>n;n++)this.extend(i[n])},o.Bounds.prototype={extend:function(t){return t=o.point(t),this.min||this.max?(this.min.x=Math.min(t.x,this.min.x),this.max.x=Math.max(t.x,this.max.x),this.min.y=Math.min(t.y,this.min.y),this.max.y=Math.max(t.y,this.max.y)):(this.min=t.clone(),this.max=t.clone()),this},getCenter:function(t){return new o.Point((this.min.x+this.max.x)/2,(this.min.y+this.max.y)/2,t)},getBottomLeft:function(){return new o.Point(this.min.x,this.max.y)},getTopRight:function(){return new o.Point(this.max.x,this.min.y)},getSize:function(){return this.max.subtract(this.min)},contains:function(t){var e,i;return t="number"==typeof t[0]||t instanceof o.Point?o.point(t):o.bounds(t),t instanceof o.Bounds?(e=t.min,i=t.max):e=i=t,e.x>=this.min.x&&i.x<=this.max.x&&e.y>=this.min.y&&i.y<=this.max.y},intersects:function(t){t=o.bounds(t);var e=this.min,i=this.max,n=t.min,s=t.max,a=s.x>=e.x&&n.x<=i.x,r=s.y>=e.y&&n.y<=i.y;return a&&r},isValid:function(){return!(!this.min||!this.max)}},o.bounds=function(t,e){return!t||t instanceof o.Bounds?t:new o.Bounds(t,e)},o.Transformation=function(t,e,i,n){this._a=t,this._b=e,this._c=i,this._d=n},o.Transformation.prototype={transform:function(t,e){return this._transform(t.clone(),e)},_transform:function(t,e){return e=e||1,t.x=e*(this._a*t.x+this._b),t.y=e*(this._c*t.y+this._d),t},untransform:function(t,e){return e=e||1,new o.Point((t.x/e-this._b)/this._a,(t.y/e-this._d)/this._c)}},o.DomUtil={get:function(t){return"string"==typeof t?e.getElementById(t):t},getStyle:function(t,i){var n=t.style[i];if(!n&&t.currentStyle&&(n=t.currentStyle[i]),(!n||"auto"===n)&&e.defaultView){var o=e.defaultView.getComputedStyle(t,null);n=o?o[i]:null}return"auto"===n?null:n},getViewportOffset:function(t){var i,n=0,s=0,a=t,r=e.body,h=e.documentElement;do{if(n+=a.offsetTop||0,s+=a.offsetLeft||0,n+=parseInt(o.DomUtil.getStyle(a,"borderTopWidth"),10)||0,s+=parseInt(o.DomUtil.getStyle(a,"borderLeftWidth"),10)||0,i=o.DomUtil.getStyle(a,"position"),a.offsetParent===r&&"absolute"===i)break;if("fixed"===i){n+=r.scrollTop||h.scrollTop||0,s+=r.scrollLeft||h.scrollLeft||0;break}if("relative"===i&&!a.offsetLeft){var l=o.DomUtil.getStyle(a,"width"),u=o.DomUtil.getStyle(a,"max-width"),c=a.getBoundingClientRect();("none"!==l||"none"!==u)&&(s+=c.left+a.clientLeft),n+=c.top+(r.scrollTop||h.scrollTop||0);break}a=a.offsetParent}while(a);a=t;do{if(a===r)break;n-=a.scrollTop||0,s-=a.scrollLeft||0,a=a.parentNode}while(a);return new o.Point(s,n)},documentIsLtr:function(){return o.DomUtil._docIsLtrCached||(o.DomUtil._docIsLtrCached=!0,o.DomUtil._docIsLtr="ltr"===o.DomUtil.getStyle(e.body,"direction")),o.DomUtil._docIsLtr},create:function(t,i,n){var o=e.createElement(t);return o.className=i,n&&n.appendChild(o),o},hasClass:function(t,e){if(t.classList!==i)return t.classList.contains(e);var n=o.DomUtil._getClass(t);return n.length>0&&new RegExp("(^|\\s)"+e+"(\\s|$)").test(n)},addClass:function(t,e){if(t.classList!==i)for(var n=o.Util.splitWords(e),s=0,a=n.length;a>s;s++)t.classList.add(n[s]);else if(!o.DomUtil.hasClass(t,e)){var r=o.DomUtil._getClass(t);o.DomUtil._setClass(t,(r?r+" ":"")+e)}},removeClass:function(t,e){t.classList!==i?t.classList.remove(e):o.DomUtil._setClass(t,o.Util.trim((" "+o.DomUtil._getClass(t)+" ").replace(" "+e+" "," ")))},_setClass:function(t,e){t.className.baseVal===i?t.className=e:t.className.baseVal=e},_getClass:function(t){return t.className.baseVal===i?t.className:t.className.baseVal},setOpacity:function(t,e){if("opacity"in t.style)t.style.opacity=e;else if("filter"in t.style){var i=!1,n="DXImageTransform.Microsoft.Alpha";try{i=t.filters.item(n)}catch(o){if(1===e)return}e=Math.round(100*e),i?(i.Enabled=100!==e,i.Opacity=e):t.style.filter+=" progid:"+n+"(opacity="+e+")"}},testProp:function(t){for(var i=e.documentElement.style,n=0;n<t.length;n++)if(t[n]in i)return t[n];return!1},getTranslateString:function(t){var e=o.Browser.webkit3d,i="translate"+(e?"3d":"")+"(",n=(e?",0":"")+")";return i+t.x+"px,"+t.y+"px"+n},getScaleString:function(t,e){var i=o.DomUtil.getTranslateString(e.add(e.multiplyBy(-1*t))),n=" scale("+t+") ";return i+n},setPosition:function(t,e,i){t._leaflet_pos=e,!i&&o.Browser.any3d?t.style[o.DomUtil.TRANSFORM]=o.DomUtil.getTranslateString(e):(t.style.left=e.x+"px",t.style.top=e.y+"px")},getPosition:function(t){return t._leaflet_pos}},o.DomUtil.TRANSFORM=o.DomUtil.testProp(["transform","WebkitTransform","OTransform","MozTransform","msTransform"]),o.DomUtil.TRANSITION=o.DomUtil.testProp(["webkitTransition","transition","OTransition","MozTransition","msTransition"]),o.DomUtil.TRANSITION_END="webkitTransition"===o.DomUtil.TRANSITION||"OTransition"===o.DomUtil.TRANSITION?o.DomUtil.TRANSITION+"End":"transitionend",function(){if("onselectstart"in e)o.extend(o.DomUtil,{disableTextSelection:function(){o.DomEvent.on(t,"selectstart",o.DomEvent.preventDefault)},enableTextSelection:function(){o.DomEvent.off(t,"selectstart",o.DomEvent.preventDefault)}});else{var i=o.DomUtil.testProp(["userSelect","WebkitUserSelect","OUserSelect","MozUserSelect","msUserSelect"]);o.extend(o.DomUtil,{disableTextSelection:function(){if(i){var t=e.documentElement.style;this._userSelect=t[i],t[i]="none"}},enableTextSelection:function(){i&&(e.documentElement.style[i]=this._userSelect,delete this._userSelect)}})}o.extend(o.DomUtil,{disableImageDrag:function(){o.DomEvent.on(t,"dragstart",o.DomEvent.preventDefault)},enableImageDrag:function(){o.DomEvent.off(t,"dragstart",o.DomEvent.preventDefault)}})}(),o.LatLng=function(t,e,n){if(t=parseFloat(t),e=parseFloat(e),isNaN(t)||isNaN(e))throw new Error("Invalid LatLng object: ("+t+", "+e+")");this.lat=t,this.lng=e,n!==i&&(this.alt=parseFloat(n))},o.extend(o.LatLng,{DEG_TO_RAD:Math.PI/180,RAD_TO_DEG:180/Math.PI,MAX_MARGIN:1e-9}),o.LatLng.prototype={equals:function(t){if(!t)return!1;t=o.latLng(t);var e=Math.max(Math.abs(this.lat-t.lat),Math.abs(this.lng-t.lng));return e<=o.LatLng.MAX_MARGIN},toString:function(t){return"LatLng("+o.Util.formatNum(this.lat,t)+", "+o.Util.formatNum(this.lng,t)+")"},distanceTo:function(t){t=o.latLng(t);var e=6378137,i=o.LatLng.DEG_TO_RAD,n=(t.lat-this.lat)*i,s=(t.lng-this.lng)*i,a=this.lat*i,r=t.lat*i,h=Math.sin(n/2),l=Math.sin(s/2),u=h*h+l*l*Math.cos(a)*Math.cos(r);return 2*e*Math.atan2(Math.sqrt(u),Math.sqrt(1-u))},wrap:function(t,e){var i=this.lng;return t=t||-180,e=e||180,i=(i+e)%(e-t)+(t>i||i===e?e:t),new o.LatLng(this.lat,i)}},o.latLng=function(t,e){return t instanceof o.LatLng?t:o.Util.isArray(t)?"number"==typeof t[0]||"string"==typeof t[0]?new o.LatLng(t[0],t[1],t[2]):null:t===i||null===t?t:"object"==typeof t&&"lat"in t?new o.LatLng(t.lat,"lng"in t?t.lng:t.lon):e===i?null:new o.LatLng(t,e)},o.LatLngBounds=function(t,e){if(t)for(var i=e?[t,e]:t,n=0,o=i.length;o>n;n++)this.extend(i[n])},o.LatLngBounds.prototype={extend:function(t){if(!t)return this;var e=o.latLng(t);return t=null!==e?e:o.latLngBounds(t),t instanceof o.LatLng?this._southWest||this._northEast?(this._southWest.lat=Math.min(t.lat,this._southWest.lat),this._southWest.lng=Math.min(t.lng,this._southWest.lng),this._northEast.lat=Math.max(t.lat,this._northEast.lat),this._northEast.lng=Math.max(t.lng,this._northEast.lng)):(this._southWest=new o.LatLng(t.lat,t.lng),this._northEast=new o.LatLng(t.lat,t.lng)):t instanceof o.LatLngBounds&&(this.extend(t._southWest),this.extend(t._northEast)),this},pad:function(t){var e=this._southWest,i=this._northEast,n=Math.abs(e.lat-i.lat)*t,s=Math.abs(e.lng-i.lng)*t;return new o.LatLngBounds(new o.LatLng(e.lat-n,e.lng-s),new o.LatLng(i.lat+n,i.lng+s))},getCenter:function(){return new o.LatLng((this._southWest.lat+this._northEast.lat)/2,(this._southWest.lng+this._northEast.lng)/2)},getSouthWest:function(){return this._southWest},getNorthEast:function(){return this._northEast},getNorthWest:function(){return new o.LatLng(this.getNorth(),this.getWest())},getSouthEast:function(){return new o.LatLng(this.getSouth(),this.getEast())},getWest:function(){return this._southWest.lng},getSouth:function(){return this._southWest.lat},getEast:function(){return this._northEast.lng},getNorth:function(){return this._northEast.lat},contains:function(t){t="number"==typeof t[0]||t instanceof o.LatLng?o.latLng(t):o.latLngBounds(t);var e,i,n=this._southWest,s=this._northEast;return t instanceof o.LatLngBounds?(e=t.getSouthWest(),i=t.getNorthEast()):e=i=t,e.lat>=n.lat&&i.lat<=s.lat&&e.lng>=n.lng&&i.lng<=s.lng},intersects:function(t){t=o.latLngBounds(t);var e=this._southWest,i=this._northEast,n=t.getSouthWest(),s=t.getNorthEast(),a=s.lat>=e.lat&&n.lat<=i.lat,r=s.lng>=e.lng&&n.lng<=i.lng;return a&&r},toBBoxString:function(){return[this.getWest(),this.getSouth(),this.getEast(),this.getNorth()].join(",")},equals:function(t){return t?(t=o.latLngBounds(t),this._southWest.equals(t.getSouthWest())&&this._northEast.equals(t.getNorthEast())):!1},isValid:function(){return!(!this._southWest||!this._northEast)}},o.latLngBounds=function(t,e){return!t||t instanceof o.LatLngBounds?t:new o.LatLngBounds(t,e)},o.Projection={},o.Projection.SphericalMercator={MAX_LATITUDE:85.0511287798,project:function(t){var e=o.LatLng.DEG_TO_RAD,i=this.MAX_LATITUDE,n=Math.max(Math.min(i,t.lat),-i),s=t.lng*e,a=n*e;return a=Math.log(Math.tan(Math.PI/4+a/2)),new o.Point(s,a)},unproject:function(t){var e=o.LatLng.RAD_TO_DEG,i=t.x*e,n=(2*Math.atan(Math.exp(t.y))-Math.PI/2)*e;return new o.LatLng(n,i)}},o.Projection.LonLat={project:function(t){return new o.Point(t.lng,t.lat)},unproject:function(t){return new o.LatLng(t.y,t.x)}},o.CRS={latLngToPoint:function(t,e){var i=this.projection.project(t),n=this.scale(e);return this.transformation._transform(i,n)},pointToLatLng:function(t,e){var i=this.scale(e),n=this.transformation.untransform(t,i);return this.projection.unproject(n)},project:function(t){return this.projection.project(t)},scale:function(t){return 256*Math.pow(2,t)},getSize:function(t){var e=this.scale(t);return o.point(e,e)}},o.CRS.Simple=o.extend({},o.CRS,{projection:o.Projection.LonLat,transformation:new o.Transformation(1,0,-1,0),scale:function(t){return Math.pow(2,t)}}),o.CRS.EPSG3857=o.extend({},o.CRS,{code:"EPSG:3857",projection:o.Projection.SphericalMercator,transformation:new o.Transformation(.5/Math.PI,.5,-.5/Math.PI,.5),project:function(t){var e=this.projection.project(t),i=6378137;return e.multiplyBy(i)}}),o.CRS.EPSG900913=o.extend({},o.CRS.EPSG3857,{code:"EPSG:900913"}),o.CRS.EPSG4326=o.extend({},o.CRS,{code:"EPSG:4326",projection:o.Projection.LonLat,transformation:new o.Transformation(1/360,.5,-1/360,.5)}),o.Map=o.Class.extend({includes:o.Mixin.Events,options:{crs:o.CRS.EPSG3857,fadeAnimation:o.DomUtil.TRANSITION&&!o.Browser.android23,trackResize:!0,markerZoomAnimation:o.DomUtil.TRANSITION&&o.Browser.any3d},initialize:function(t,e){e=o.setOptions(this,e),this._initContainer(t),this._initLayout(),this._onResize=o.bind(this._onResize,this),this._initEvents(),e.maxBounds&&this.setMaxBounds(e.maxBounds),e.center&&e.zoom!==i&&this.setView(o.latLng(e.center),e.zoom,{reset:!0}),this._handlers=[],this._layers={},this._zoomBoundLayers={},this._tileLayersNum=0,this.callInitHooks(),this._addLayers(e.layers)},setView:function(t,e){return e=e===i?this.getZoom():e,this._resetView(o.latLng(t),this._limitZoom(e)),this},setZoom:function(t,e){return this._loaded?this.setView(this.getCenter(),t,{zoom:e}):(this._zoom=this._limitZoom(t),this)},zoomIn:function(t,e){return this.setZoom(this._zoom+(t||1),e)},zoomOut:function(t,e){return this.setZoom(this._zoom-(t||1),e)},setZoomAround:function(t,e,i){var n=this.getZoomScale(e),s=this.getSize().divideBy(2),a=t instanceof o.Point?t:this.latLngToContainerPoint(t),r=a.subtract(s).multiplyBy(1-1/n),h=this.containerPointToLatLng(s.add(r));return this.setView(h,e,{zoom:i})},fitBounds:function(t,e){e=e||{},t=t.getBounds?t.getBounds():o.latLngBounds(t);var i=o.point(e.paddingTopLeft||e.padding||[0,0]),n=o.point(e.paddingBottomRight||e.padding||[0,0]),s=this.getBoundsZoom(t,!1,i.add(n));s=e.maxZoom?Math.min(e.maxZoom,s):s;var a=n.subtract(i).divideBy(2),r=this.project(t.getSouthWest(),s),h=this.project(t.getNorthEast(),s),l=this.unproject(r.add(h).divideBy(2).add(a),s);return this.setView(l,s,e)},fitWorld:function(t){return this.fitBounds([[-90,-180],[90,180]],t)},panTo:function(t,e){return this.setView(t,this._zoom,{pan:e})},panBy:function(t){return this.fire("movestart"),this._rawPanBy(o.point(t)),this.fire("move"),this.fire("moveend")},setMaxBounds:function(t){return t=o.latLngBounds(t),this.options.maxBounds=t,t?(this._loaded&&this._panInsideMaxBounds(),this.on("moveend",this._panInsideMaxBounds,this)):this.off("moveend",this._panInsideMaxBounds,this)},panInsideBounds:function(t,e){var i=this.getCenter(),n=this._limitCenter(i,this._zoom,t);return i.equals(n)?this:this.panTo(n,e)},addLayer:function(t){var e=o.stamp(t);return this._layers[e]?this:(this._layers[e]=t,!t.options||isNaN(t.options.maxZoom)&&isNaN(t.options.minZoom)||(this._zoomBoundLayers[e]=t,this._updateZoomLevels()),this.options.zoomAnimation&&o.TileLayer&&t instanceof o.TileLayer&&(this._tileLayersNum++,this._tileLayersToLoad++,t.on("load",this._onTileLayerLoad,this)),this._loaded&&this._layerAdd(t),this)},removeLayer:function(t){var e=o.stamp(t);return this._layers[e]?(this._loaded&&t.onRemove(this),delete this._layers[e],this._loaded&&this.fire("layerremove",{layer:t}),this._zoomBoundLayers[e]&&(delete this._zoomBoundLayers[e],this._updateZoomLevels()),this.options.zoomAnimation&&o.TileLayer&&t instanceof o.TileLayer&&(this._tileLayersNum--,this._tileLayersToLoad--,t.off("load",this._onTileLayerLoad,this)),this):this},hasLayer:function(t){return t?o.stamp(t)in this._layers:!1},eachLayer:function(t,e){for(var i in this._layers)t.call(e,this._layers[i]);return this},invalidateSize:function(t){if(!this._loaded)return this;t=o.extend({animate:!1,pan:!0},t===!0?{animate:!0}:t);var e=this.getSize();this._sizeChanged=!0,this._initialCenter=null;var i=this.getSize(),n=e.divideBy(2).round(),s=i.divideBy(2).round(),a=n.subtract(s);return a.x||a.y?(t.animate&&t.pan?this.panBy(a):(t.pan&&this._rawPanBy(a),this.fire("move"),t.debounceMoveend?(clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(o.bind(this.fire,this,"moveend"),200)):this.fire("moveend")),this.fire("resize",{oldSize:e,newSize:i})):this},addHandler:function(t,e){if(!e)return this;var i=this[t]=new e(this);return this._handlers.push(i),this.options[t]&&i.enable(),this},remove:function(){this._loaded&&this.fire("unload"),this._initEvents("off");try{delete this._container._leaflet}catch(t){this._container._leaflet=i}return this._clearPanes(),this._clearControlPos&&this._clearControlPos(),this._clearHandlers(),this},getCenter:function(){return this._checkIfLoaded(),this._initialCenter&&!this._moved()?this._initialCenter:this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var t=this.getPixelBounds(),e=this.unproject(t.getBottomLeft()),i=this.unproject(t.getTopRight());return new o.LatLngBounds(e,i)},getMinZoom:function(){return this.options.minZoom===i?this._layersMinZoom===i?0:this._layersMinZoom:this.options.minZoom},getMaxZoom:function(){return this.options.maxZoom===i?this._layersMaxZoom===i?1/0:this._layersMaxZoom:this.options.maxZoom},getBoundsZoom:function(t,e,i){t=o.latLngBounds(t);var n,s=this.getMinZoom()-(e?1:0),a=this.getMaxZoom(),r=this.getSize(),h=t.getNorthWest(),l=t.getSouthEast(),u=!0;i=o.point(i||[0,0]);do s++,n=this.project(l,s).subtract(this.project(h,s)).add(i),u=e?n.x<r.x||n.y<r.y:r.contains(n);while(u&&a>=s);return u&&e?null:e?s:s-1},getSize:function(){return(!this._size||this._sizeChanged)&&(this._size=new o.Point(this._container.clientWidth,this._container.clientHeight),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(){var t=this._getTopLeftPoint();return new o.Bounds(t,t.add(this.getSize()))},getPixelOrigin:function(){return this._checkIfLoaded(),this._initialTopLeftPoint},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(t){var e=this.options.crs;return e.scale(t)/e.scale(this._zoom)},getScaleZoom:function(t){return this._zoom+Math.log(t)/Math.LN2},project:function(t,e){return e=e===i?this._zoom:e,this.options.crs.latLngToPoint(o.latLng(t),e)},unproject:function(t,e){return e=e===i?this._zoom:e,this.options.crs.pointToLatLng(o.point(t),e)},layerPointToLatLng:function(t){var e=o.point(t).add(this.getPixelOrigin());return this.unproject(e)},latLngToLayerPoint:function(t){var e=this.project(o.latLng(t))._round();return e._subtract(this.getPixelOrigin())},containerPointToLayerPoint:function(t){return o.point(t).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(t){return o.point(t).add(this._getMapPanePos())},containerPointToLatLng:function(t){var e=this.containerPointToLayerPoint(o.point(t));return this.layerPointToLatLng(e)},latLngToContainerPoint:function(t){return this.layerPointToContainerPoint(this.latLngToLayerPoint(o.latLng(t)))},mouseEventToContainerPoint:function(t){return o.DomEvent.getMousePosition(t,this._container)},mouseEventToLayerPoint:function(t){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t))},mouseEventToLatLng:function(t){return this.layerPointToLatLng(this.mouseEventToLayerPoint(t))},_initContainer:function(t){var e=this._container=o.DomUtil.get(t);if(!e)throw new Error("Map container not found.");if(e._leaflet)throw new Error("Map container is already initialized.");e._leaflet=!0},_initLayout:function(){var t=this._container;o.DomUtil.addClass(t,"leaflet-container"+(o.Browser.touch?" leaflet-touch":"")+(o.Browser.retina?" leaflet-retina":"")+(o.Browser.ielt9?" leaflet-oldie":"")+(this.options.fadeAnimation?" leaflet-fade-anim":""));var e=o.DomUtil.getStyle(t,"position");"absolute"!==e&&"relative"!==e&&"fixed"!==e&&(t.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var t=this._panes={};this._mapPane=t.mapPane=this._createPane("leaflet-map-pane",this._container),this._tilePane=t.tilePane=this._createPane("leaflet-tile-pane",this._mapPane),t.objectsPane=this._createPane("leaflet-objects-pane",this._mapPane),t.shadowPane=this._createPane("leaflet-shadow-pane"),t.overlayPane=this._createPane("leaflet-overlay-pane"),t.markerPane=this._createPane("leaflet-marker-pane"),t.popupPane=this._createPane("leaflet-popup-pane");var e=" leaflet-zoom-hide";this.options.markerZoomAnimation||(o.DomUtil.addClass(t.markerPane,e),o.DomUtil.addClass(t.shadowPane,e),o.DomUtil.addClass(t.popupPane,e))},_createPane:function(t,e){return o.DomUtil.create("div",t,e||this._panes.objectsPane)},_clearPanes:function(){this._container.removeChild(this._mapPane)},_addLayers:function(t){t=t?o.Util.isArray(t)?t:[t]:[];for(var e=0,i=t.length;i>e;e++)this.addLayer(t[e])},_resetView:function(t,e,i,n){var s=this._zoom!==e;n||(this.fire("movestart"),s&&this.fire("zoomstart")),this._zoom=e,this._initialCenter=t,this._initialTopLeftPoint=this._getNewTopLeftPoint(t),i?this._initialTopLeftPoint._add(this._getMapPanePos()):o.DomUtil.setPosition(this._mapPane,new o.Point(0,0)),this._tileLayersToLoad=this._tileLayersNum;var a=!this._loaded;this._loaded=!0,this.fire("viewreset",{hard:!i}),a&&(this.fire("load"),this.eachLayer(this._layerAdd,this)),this.fire("move"),(s||n)&&this.fire("zoomend"),this.fire("moveend",{hard:!i})},_rawPanBy:function(t){o.DomUtil.setPosition(this._mapPane,this._getMapPanePos().subtract(t))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_updateZoomLevels:function(){var t,e=1/0,n=-(1/0),o=this._getZoomSpan();for(t in this._zoomBoundLayers){var s=this._zoomBoundLayers[t];isNaN(s.options.minZoom)||(e=Math.min(e,s.options.minZoom)),isNaN(s.options.maxZoom)||(n=Math.max(n,s.options.maxZoom))}t===i?this._layersMaxZoom=this._layersMinZoom=i:(this._layersMaxZoom=n,this._layersMinZoom=e),o!==this._getZoomSpan()&&this.fire("zoomlevelschange")},_panInsideMaxBounds:function(){this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw new Error("Set map center and zoom first.")},_initEvents:function(e){if(o.DomEvent){e=e||"on",o.DomEvent[e](this._container,"click",this._onMouseClick,this);var i,n,s=["dblclick","mousedown","mouseup","mouseenter","mouseleave","mousemove","contextmenu"];for(i=0,n=s.length;n>i;i++)o.DomEvent[e](this._container,s[i],this._fireMouseEvent,this);this.options.trackResize&&o.DomEvent[e](t,"resize",this._onResize,this)}},_onResize:function(){o.Util.cancelAnimFrame(this._resizeRequest),this._resizeRequest=o.Util.requestAnimFrame(function(){this.invalidateSize({debounceMoveend:!0})},this,!1,this._container)},_onMouseClick:function(t){!this._loaded||!t._simulated&&(this.dragging&&this.dragging.moved()||this.boxZoom&&this.boxZoom.moved())||o.DomEvent._skipped(t)||(this.fire("preclick"),this._fireMouseEvent(t))},_fireMouseEvent:function(t){if(this._loaded&&!o.DomEvent._skipped(t)){var e=t.type;if(e="mouseenter"===e?"mouseover":"mouseleave"===e?"mouseout":e,this.hasEventListeners(e)){"contextmenu"===e&&o.DomEvent.preventDefault(t);var i=this.mouseEventToContainerPoint(t),n=this.containerPointToLayerPoint(i),s=this.layerPointToLatLng(n);this.fire(e,{latlng:s,layerPoint:n,containerPoint:i,originalEvent:t})}}},_onTileLayerLoad:function(){this._tileLayersToLoad--,this._tileLayersNum&&!this._tileLayersToLoad&&this.fire("tilelayersload")},_clearHandlers:function(){for(var t=0,e=this._handlers.length;e>t;t++)this._handlers[t].disable()},whenReady:function(t,e){return this._loaded?t.call(e||this,this):this.on("load",t,e),this},_layerAdd:function(t){t.onAdd(this),this.fire("layeradd",{layer:t})},_getMapPanePos:function(){return o.DomUtil.getPosition(this._mapPane)},_moved:function(){var t=this._getMapPanePos();return t&&!t.equals([0,0])},_getTopLeftPoint:function(){return this.getPixelOrigin().subtract(this._getMapPanePos())},_getNewTopLeftPoint:function(t,e){var i=this.getSize()._divideBy(2);return this.project(t,e)._subtract(i)._round()},_latLngToNewLayerPoint:function(t,e,i){var n=this._getNewTopLeftPoint(i,e).add(this._getMapPanePos());return this.project(t,e)._subtract(n)},_getCenterLayerPoint:function(){return this.containerPointToLayerPoint(this.getSize()._divideBy(2))},_getCenterOffset:function(t){return this.latLngToLayerPoint(t).subtract(this._getCenterLayerPoint())},_limitCenter:function(t,e,i){if(!i)return t;var n=this.project(t,e),s=this.getSize().divideBy(2),a=new o.Bounds(n.subtract(s),n.add(s)),r=this._getBoundsOffset(a,i,e);return this.unproject(n.add(r),e)},_limitOffset:function(t,e){if(!e)return t;var i=this.getPixelBounds(),n=new o.Bounds(i.min.add(t),i.max.add(t));return t.add(this._getBoundsOffset(n,e))},_getBoundsOffset:function(t,e,i){var n=this.project(e.getNorthWest(),i).subtract(t.min),s=this.project(e.getSouthEast(),i).subtract(t.max),a=this._rebound(n.x,-s.x),r=this._rebound(n.y,-s.y);return new o.Point(a,r)},_rebound:function(t,e){return t+e>0?Math.round(t-e)/2:Math.max(0,Math.ceil(t))-Math.max(0,Math.floor(e))},_limitZoom:function(t){var e=this.getMinZoom(),i=this.getMaxZoom();return Math.max(e,Math.min(i,t))}}),o.map=function(t,e){return new o.Map(t,e)},o.Projection.Mercator={MAX_LATITUDE:85.0840591556,R_MINOR:6356752.314245179,R_MAJOR:6378137,project:function(t){var e=o.LatLng.DEG_TO_RAD,i=this.MAX_LATITUDE,n=Math.max(Math.min(i,t.lat),-i),s=this.R_MAJOR,a=this.R_MINOR,r=t.lng*e*s,h=n*e,l=a/s,u=Math.sqrt(1-l*l),c=u*Math.sin(h);c=Math.pow((1-c)/(1+c),.5*u);var d=Math.tan(.5*(.5*Math.PI-h))/c;return h=-s*Math.log(d),new o.Point(r,h)},unproject:function(t){for(var e,i=o.LatLng.RAD_TO_DEG,n=this.R_MAJOR,s=this.R_MINOR,a=t.x*i/n,r=s/n,h=Math.sqrt(1-r*r),l=Math.exp(-t.y/n),u=Math.PI/2-2*Math.atan(l),c=15,d=1e-7,p=c,_=.1;Math.abs(_)>d&&--p>0;)e=h*Math.sin(u),_=Math.PI/2-2*Math.atan(l*Math.pow((1-e)/(1+e),.5*h))-u,u+=_;return new o.LatLng(u*i,a)}},o.CRS.EPSG3395=o.extend({},o.CRS,{code:"EPSG:3395",projection:o.Projection.Mercator,
+transformation:function(){var t=o.Projection.Mercator,e=t.R_MAJOR,i=.5/(Math.PI*e);return new o.Transformation(i,.5,-i,.5)}()}),o.TileLayer=o.Class.extend({includes:o.Mixin.Events,options:{minZoom:0,maxZoom:18,tileSize:256,subdomains:"abc",errorTileUrl:"",attribution:"",zoomOffset:0,opacity:1,unloadInvisibleTiles:o.Browser.mobile,updateWhenIdle:o.Browser.mobile},initialize:function(t,e){e=o.setOptions(this,e),e.detectRetina&&o.Browser.retina&&e.maxZoom>0&&(e.tileSize=Math.floor(e.tileSize/2),e.zoomOffset++,e.minZoom>0&&e.minZoom--,this.options.maxZoom--),e.bounds&&(e.bounds=o.latLngBounds(e.bounds)),this._url=t;var i=this.options.subdomains;"string"==typeof i&&(this.options.subdomains=i.split(""))},onAdd:function(t){this._map=t,this._animated=t._zoomAnimated,this._initContainer(),t.on({viewreset:this._reset,moveend:this._update},this),this._animated&&t.on({zoomanim:this._animateZoom,zoomend:this._endZoomAnim},this),this.options.updateWhenIdle||(this._limitedUpdate=o.Util.limitExecByInterval(this._update,150,this),t.on("move",this._limitedUpdate,this)),this._reset(),this._update()},addTo:function(t){return t.addLayer(this),this},onRemove:function(t){this._container.parentNode.removeChild(this._container),t.off({viewreset:this._reset,moveend:this._update},this),this._animated&&t.off({zoomanim:this._animateZoom,zoomend:this._endZoomAnim},this),this.options.updateWhenIdle||t.off("move",this._limitedUpdate,this),this._container=null,this._map=null},bringToFront:function(){var t=this._map._panes.tilePane;return this._container&&(t.appendChild(this._container),this._setAutoZIndex(t,Math.max)),this},bringToBack:function(){var t=this._map._panes.tilePane;return this._container&&(t.insertBefore(this._container,t.firstChild),this._setAutoZIndex(t,Math.min)),this},getAttribution:function(){return this.options.attribution},getContainer:function(){return this._container},setOpacity:function(t){return this.options.opacity=t,this._map&&this._updateOpacity(),this},setZIndex:function(t){return this.options.zIndex=t,this._updateZIndex(),this},setUrl:function(t,e){return this._url=t,e||this.redraw(),this},redraw:function(){return this._map&&(this._reset({hard:!0}),this._update()),this},_updateZIndex:function(){this._container&&this.options.zIndex!==i&&(this._container.style.zIndex=this.options.zIndex)},_setAutoZIndex:function(t,e){var i,n,o,s=t.children,a=-e(1/0,-(1/0));for(n=0,o=s.length;o>n;n++)s[n]!==this._container&&(i=parseInt(s[n].style.zIndex,10),isNaN(i)||(a=e(a,i)));this.options.zIndex=this._container.style.zIndex=(isFinite(a)?a:0)+e(1,-1)},_updateOpacity:function(){var t,e=this._tiles;if(o.Browser.ielt9)for(t in e)o.DomUtil.setOpacity(e[t],this.options.opacity);else o.DomUtil.setOpacity(this._container,this.options.opacity)},_initContainer:function(){var t=this._map._panes.tilePane;if(!this._container){if(this._container=o.DomUtil.create("div","leaflet-layer"),this._updateZIndex(),this._animated){var e="leaflet-tile-container";this._bgBuffer=o.DomUtil.create("div",e,this._container),this._tileContainer=o.DomUtil.create("div",e,this._container)}else this._tileContainer=this._container;t.appendChild(this._container),this.options.opacity<1&&this._updateOpacity()}},_reset:function(t){for(var e in this._tiles)this.fire("tileunload",{tile:this._tiles[e]});this._tiles={},this._tilesToLoad=0,this.options.reuseTiles&&(this._unusedTiles=[]),this._tileContainer.innerHTML="",this._animated&&t&&t.hard&&this._clearBgBuffer(),this._initContainer()},_getTileSize:function(){var t=this._map,e=t.getZoom()+this.options.zoomOffset,i=this.options.maxNativeZoom,n=this.options.tileSize;return i&&e>i&&(n=Math.round(t.getZoomScale(e)/t.getZoomScale(i)*n)),n},_update:function(){if(this._map){var t=this._map,e=t.getPixelBounds(),i=t.getZoom(),n=this._getTileSize();if(!(i>this.options.maxZoom||i<this.options.minZoom)){var s=o.bounds(e.min.divideBy(n)._floor(),e.max.divideBy(n)._floor());this._addTilesFromCenterOut(s),(this.options.unloadInvisibleTiles||this.options.reuseTiles)&&this._removeOtherTiles(s)}}},_addTilesFromCenterOut:function(t){var i,n,s,a=[],r=t.getCenter();for(i=t.min.y;i<=t.max.y;i++)for(n=t.min.x;n<=t.max.x;n++)s=new o.Point(n,i),this._tileShouldBeLoaded(s)&&a.push(s);var h=a.length;if(0!==h){a.sort(function(t,e){return t.distanceTo(r)-e.distanceTo(r)});var l=e.createDocumentFragment();for(this._tilesToLoad||this.fire("loading"),this._tilesToLoad+=h,n=0;h>n;n++)this._addTile(a[n],l);this._tileContainer.appendChild(l)}},_tileShouldBeLoaded:function(t){if(t.x+":"+t.y in this._tiles)return!1;var e=this.options;if(!e.continuousWorld){var i=this._getWrapTileNum();if(e.noWrap&&(t.x<0||t.x>=i.x)||t.y<0||t.y>=i.y)return!1}if(e.bounds){var n=this._getTileSize(),o=t.multiplyBy(n),s=o.add([n,n]),a=this._map.unproject(o),r=this._map.unproject(s);if(e.continuousWorld||e.noWrap||(a=a.wrap(),r=r.wrap()),!e.bounds.intersects([a,r]))return!1}return!0},_removeOtherTiles:function(t){var e,i,n,o;for(o in this._tiles)e=o.split(":"),i=parseInt(e[0],10),n=parseInt(e[1],10),(i<t.min.x||i>t.max.x||n<t.min.y||n>t.max.y)&&this._removeTile(o)},_removeTile:function(t){var e=this._tiles[t];this.fire("tileunload",{tile:e,url:e.src}),this.options.reuseTiles?(o.DomUtil.removeClass(e,"leaflet-tile-loaded"),this._unusedTiles.push(e)):e.parentNode===this._tileContainer&&this._tileContainer.removeChild(e),o.Browser.android||(e.onload=null,e.src=o.Util.emptyImageUrl),delete this._tiles[t]},_addTile:function(t,e){var i=this._getTilePos(t),n=this._getTile();o.DomUtil.setPosition(n,i,o.Browser.chrome),this._tiles[t.x+":"+t.y]=n,this._loadTile(n,t),n.parentNode!==this._tileContainer&&e.appendChild(n)},_getZoomForUrl:function(){var t=this.options,e=this._map.getZoom();return t.zoomReverse&&(e=t.maxZoom-e),e+=t.zoomOffset,t.maxNativeZoom?Math.min(e,t.maxNativeZoom):e},_getTilePos:function(t){var e=this._map.getPixelOrigin(),i=this._getTileSize();return t.multiplyBy(i).subtract(e)},getTileUrl:function(t){return o.Util.template(this._url,o.extend({s:this._getSubdomain(t),z:t.z,x:t.x,y:t.y},this.options))},_getWrapTileNum:function(){var t=this._map.options.crs,e=t.getSize(this._map.getZoom());return e.divideBy(this._getTileSize())._floor()},_adjustTilePoint:function(t){var e=this._getWrapTileNum();this.options.continuousWorld||this.options.noWrap||(t.x=(t.x%e.x+e.x)%e.x),this.options.tms&&(t.y=e.y-t.y-1),t.z=this._getZoomForUrl()},_getSubdomain:function(t){var e=Math.abs(t.x+t.y)%this.options.subdomains.length;return this.options.subdomains[e]},_getTile:function(){if(this.options.reuseTiles&&this._unusedTiles.length>0){var t=this._unusedTiles.pop();return this._resetTile(t),t}return this._createTile()},_resetTile:function(){},_createTile:function(){var t=o.DomUtil.create("img","leaflet-tile");return t.style.width=t.style.height=this._getTileSize()+"px",t.galleryimg="no",t.onselectstart=t.onmousemove=o.Util.falseFn,o.Browser.ielt9&&this.options.opacity!==i&&o.DomUtil.setOpacity(t,this.options.opacity),o.Browser.mobileWebkit3d&&(t.style.WebkitBackfaceVisibility="hidden"),t},_loadTile:function(t,e){t._layer=this,t.onload=this._tileOnLoad,t.onerror=this._tileOnError,this._adjustTilePoint(e),t.src=this.getTileUrl(e),this.fire("tileloadstart",{tile:t,url:t.src})},_tileLoaded:function(){this._tilesToLoad--,this._animated&&o.DomUtil.addClass(this._tileContainer,"leaflet-zoom-animated"),this._tilesToLoad||(this.fire("load"),this._animated&&(clearTimeout(this._clearBgBufferTimer),this._clearBgBufferTimer=setTimeout(o.bind(this._clearBgBuffer,this),500)))},_tileOnLoad:function(){var t=this._layer;this.src!==o.Util.emptyImageUrl&&(o.DomUtil.addClass(this,"leaflet-tile-loaded"),t.fire("tileload",{tile:this,url:this.src})),t._tileLoaded()},_tileOnError:function(){var t=this._layer;t.fire("tileerror",{tile:this,url:this.src});var e=t.options.errorTileUrl;e&&(this.src=e),t._tileLoaded()}}),o.tileLayer=function(t,e){return new o.TileLayer(t,e)},o.TileLayer.WMS=o.TileLayer.extend({defaultWmsParams:{service:"WMS",request:"GetMap",version:"1.1.1",layers:"",styles:"",format:"image/jpeg",transparent:!1},initialize:function(t,e){this._url=t;var i=o.extend({},this.defaultWmsParams),n=e.tileSize||this.options.tileSize;e.detectRetina&&o.Browser.retina?i.width=i.height=2*n:i.width=i.height=n;for(var s in e)this.options.hasOwnProperty(s)||"crs"===s||(i[s]=e[s]);this.wmsParams=i,o.setOptions(this,e)},onAdd:function(t){this._crs=this.options.crs||t.options.crs,this._wmsVersion=parseFloat(this.wmsParams.version);var e=this._wmsVersion>=1.3?"crs":"srs";this.wmsParams[e]=this._crs.code,o.TileLayer.prototype.onAdd.call(this,t)},getTileUrl:function(t){var e=this._map,i=this.options.tileSize,n=t.multiplyBy(i),s=n.add([i,i]),a=this._crs.project(e.unproject(n,t.z)),r=this._crs.project(e.unproject(s,t.z)),h=this._wmsVersion>=1.3&&this._crs===o.CRS.EPSG4326?[r.y,a.x,a.y,r.x].join(","):[a.x,r.y,r.x,a.y].join(","),l=o.Util.template(this._url,{s:this._getSubdomain(t)});return l+o.Util.getParamString(this.wmsParams,l,!0)+"&BBOX="+h},setParams:function(t,e){return o.extend(this.wmsParams,t),e||this.redraw(),this}}),o.tileLayer.wms=function(t,e){return new o.TileLayer.WMS(t,e)},o.TileLayer.Canvas=o.TileLayer.extend({options:{async:!1},initialize:function(t){o.setOptions(this,t)},redraw:function(){this._map&&(this._reset({hard:!0}),this._update());for(var t in this._tiles)this._redrawTile(this._tiles[t]);return this},_redrawTile:function(t){this.drawTile(t,t._tilePoint,this._map._zoom)},_createTile:function(){var t=o.DomUtil.create("canvas","leaflet-tile");return t.width=t.height=this.options.tileSize,t.onselectstart=t.onmousemove=o.Util.falseFn,t},_loadTile:function(t,e){t._layer=this,t._tilePoint=e,this._redrawTile(t),this.options.async||this.tileDrawn(t)},drawTile:function(){},tileDrawn:function(t){this._tileOnLoad.call(t)}}),o.tileLayer.canvas=function(t){return new o.TileLayer.Canvas(t)},o.ImageOverlay=o.Class.extend({includes:o.Mixin.Events,options:{opacity:1},initialize:function(t,e,i){this._url=t,this._bounds=o.latLngBounds(e),o.setOptions(this,i)},onAdd:function(t){this._map=t,this._image||this._initImage(),t._panes.overlayPane.appendChild(this._image),t.on("viewreset",this._reset,this),t.options.zoomAnimation&&o.Browser.any3d&&t.on("zoomanim",this._animateZoom,this),this._reset()},onRemove:function(t){t.getPanes().overlayPane.removeChild(this._image),t.off("viewreset",this._reset,this),t.options.zoomAnimation&&t.off("zoomanim",this._animateZoom,this)},addTo:function(t){return t.addLayer(this),this},setOpacity:function(t){return this.options.opacity=t,this._updateOpacity(),this},bringToFront:function(){return this._image&&this._map._panes.overlayPane.appendChild(this._image),this},bringToBack:function(){var t=this._map._panes.overlayPane;return this._image&&t.insertBefore(this._image,t.firstChild),this},setUrl:function(t){this._url=t,this._image.src=this._url},getAttribution:function(){return this.options.attribution},_initImage:function(){this._image=o.DomUtil.create("img","leaflet-image-layer"),this._map.options.zoomAnimation&&o.Browser.any3d?o.DomUtil.addClass(this._image,"leaflet-zoom-animated"):o.DomUtil.addClass(this._image,"leaflet-zoom-hide"),this._updateOpacity(),o.extend(this._image,{galleryimg:"no",onselectstart:o.Util.falseFn,onmousemove:o.Util.falseFn,onload:o.bind(this._onImageLoad,this),src:this._url})},_animateZoom:function(t){var e=this._map,i=this._image,n=e.getZoomScale(t.zoom),s=this._bounds.getNorthWest(),a=this._bounds.getSouthEast(),r=e._latLngToNewLayerPoint(s,t.zoom,t.center),h=e._latLngToNewLayerPoint(a,t.zoom,t.center)._subtract(r),l=r._add(h._multiplyBy(.5*(1-1/n)));i.style[o.DomUtil.TRANSFORM]=o.DomUtil.getTranslateString(l)+" scale("+n+") "},_reset:function(){var t=this._image,e=this._map.latLngToLayerPoint(this._bounds.getNorthWest()),i=this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(e);o.DomUtil.setPosition(t,e),t.style.width=i.x+"px",t.style.height=i.y+"px"},_onImageLoad:function(){this.fire("load")},_updateOpacity:function(){o.DomUtil.setOpacity(this._image,this.options.opacity)}}),o.imageOverlay=function(t,e,i){return new o.ImageOverlay(t,e,i)},o.Icon=o.Class.extend({options:{className:""},initialize:function(t){o.setOptions(this,t)},createIcon:function(t){return this._createIcon("icon",t)},createShadow:function(t){return this._createIcon("shadow",t)},_createIcon:function(t,e){var i=this._getIconUrl(t);if(!i){if("icon"===t)throw new Error("iconUrl not set in Icon options (see the docs).");return null}var n;return n=e&&"IMG"===e.tagName?this._createImg(i,e):this._createImg(i),this._setIconStyles(n,t),n},_setIconStyles:function(t,e){var i,n=this.options,s=o.point(n[e+"Size"]);i="shadow"===e?o.point(n.shadowAnchor||n.iconAnchor):o.point(n.iconAnchor),!i&&s&&(i=s.divideBy(2,!0)),t.className="leaflet-marker-"+e+" "+n.className,i&&(t.style.marginLeft=-i.x+"px",t.style.marginTop=-i.y+"px"),s&&(t.style.width=s.x+"px",t.style.height=s.y+"px")},_createImg:function(t,i){return i=i||e.createElement("img"),i.src=t,i},_getIconUrl:function(t){return o.Browser.retina&&this.options[t+"RetinaUrl"]?this.options[t+"RetinaUrl"]:this.options[t+"Url"]}}),o.icon=function(t){return new o.Icon(t)},o.Icon.Default=o.Icon.extend({options:{iconSize:[25,41],iconAnchor:[12,41],popupAnchor:[1,-34],shadowSize:[41,41]},_getIconUrl:function(t){var e=t+"Url";if(this.options[e])return this.options[e];o.Browser.retina&&"icon"===t&&(t+="-2x");var i=o.Icon.Default.imagePath;if(!i)throw new Error("Couldn't autodetect L.Icon.Default.imagePath, set it manually.");return i+"/marker-"+t+".png"}}),o.Icon.Default.imagePath=function(){var t,i,n,o,s,a=e.getElementsByTagName("script"),r=/[\/^]leaflet[\-\._]?([\w\-\._]*)\.js\??/;for(t=0,i=a.length;i>t;t++)if(n=a[t].src,o=n.match(r))return s=n.split(r)[0],(s?s+"/":"")+"images"}(),o.Marker=o.Class.extend({includes:o.Mixin.Events,options:{icon:new o.Icon.Default,title:"",alt:"",clickable:!0,draggable:!1,keyboard:!0,zIndexOffset:0,opacity:1,riseOnHover:!1,riseOffset:250},initialize:function(t,e){o.setOptions(this,e),this._latlng=o.latLng(t)},onAdd:function(t){this._map=t,t.on("viewreset",this.update,this),this._initIcon(),this.update(),this.fire("add"),t.options.zoomAnimation&&t.options.markerZoomAnimation&&t.on("zoomanim",this._animateZoom,this)},addTo:function(t){return t.addLayer(this),this},onRemove:function(t){this.dragging&&this.dragging.disable(),this._removeIcon(),this._removeShadow(),this.fire("remove"),t.off({viewreset:this.update,zoomanim:this._animateZoom},this),this._map=null},getLatLng:function(){return this._latlng},setLatLng:function(t){return this._latlng=o.latLng(t),this.update(),this.fire("move",{latlng:this._latlng})},setZIndexOffset:function(t){return this.options.zIndexOffset=t,this.update(),this},setIcon:function(t){return this.options.icon=t,this._map&&(this._initIcon(),this.update()),this._popup&&this.bindPopup(this._popup),this},update:function(){return this._icon&&this._setPos(this._map.latLngToLayerPoint(this._latlng).round()),this},_initIcon:function(){var t=this.options,e=this._map,i=e.options.zoomAnimation&&e.options.markerZoomAnimation,n=i?"leaflet-zoom-animated":"leaflet-zoom-hide",s=t.icon.createIcon(this._icon),a=!1;s!==this._icon&&(this._icon&&this._removeIcon(),a=!0,t.title&&(s.title=t.title),t.alt&&(s.alt=t.alt)),o.DomUtil.addClass(s,n),t.keyboard&&(s.tabIndex="0"),this._icon=s,this._initInteraction(),t.riseOnHover&&o.DomEvent.on(s,"mouseover",this._bringToFront,this).on(s,"mouseout",this._resetZIndex,this);var r=t.icon.createShadow(this._shadow),h=!1;r!==this._shadow&&(this._removeShadow(),h=!0),r&&o.DomUtil.addClass(r,n),this._shadow=r,t.opacity<1&&this._updateOpacity();var l=this._map._panes;a&&l.markerPane.appendChild(this._icon),r&&h&&l.shadowPane.appendChild(this._shadow)},_removeIcon:function(){this.options.riseOnHover&&o.DomEvent.off(this._icon,"mouseover",this._bringToFront).off(this._icon,"mouseout",this._resetZIndex),this._map._panes.markerPane.removeChild(this._icon),this._icon=null},_removeShadow:function(){this._shadow&&this._map._panes.shadowPane.removeChild(this._shadow),this._shadow=null},_setPos:function(t){o.DomUtil.setPosition(this._icon,t),this._shadow&&o.DomUtil.setPosition(this._shadow,t),this._zIndex=t.y+this.options.zIndexOffset,this._resetZIndex()},_updateZIndex:function(t){this._icon.style.zIndex=this._zIndex+t},_animateZoom:function(t){var e=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center).round();this._setPos(e)},_initInteraction:function(){if(this.options.clickable){var t=this._icon,e=["dblclick","mousedown","mouseover","mouseout","contextmenu"];o.DomUtil.addClass(t,"leaflet-clickable"),o.DomEvent.on(t,"click",this._onMouseClick,this),o.DomEvent.on(t,"keypress",this._onKeyPress,this);for(var i=0;i<e.length;i++)o.DomEvent.on(t,e[i],this._fireMouseEvent,this);o.Handler.MarkerDrag&&(this.dragging=new o.Handler.MarkerDrag(this),this.options.draggable&&this.dragging.enable())}},_onMouseClick:function(t){var e=this.dragging&&this.dragging.moved();(this.hasEventListeners(t.type)||e)&&o.DomEvent.stopPropagation(t),e||(this.dragging&&this.dragging._enabled||!this._map.dragging||!this._map.dragging.moved())&&this.fire(t.type,{originalEvent:t,latlng:this._latlng})},_onKeyPress:function(t){13===t.keyCode&&this.fire("click",{originalEvent:t,latlng:this._latlng})},_fireMouseEvent:function(t){this.fire(t.type,{originalEvent:t,latlng:this._latlng}),"contextmenu"===t.type&&this.hasEventListeners(t.type)&&o.DomEvent.preventDefault(t),"mousedown"!==t.type?o.DomEvent.stopPropagation(t):o.DomEvent.preventDefault(t)},setOpacity:function(t){return this.options.opacity=t,this._map&&this._updateOpacity(),this},_updateOpacity:function(){o.DomUtil.setOpacity(this._icon,this.options.opacity),this._shadow&&o.DomUtil.setOpacity(this._shadow,this.options.opacity)},_bringToFront:function(){this._updateZIndex(this.options.riseOffset)},_resetZIndex:function(){this._updateZIndex(0)}}),o.marker=function(t,e){return new o.Marker(t,e)},o.DivIcon=o.Icon.extend({options:{iconSize:[12,12],className:"leaflet-div-icon",html:!1},createIcon:function(t){var i=t&&"DIV"===t.tagName?t:e.createElement("div"),n=this.options;return n.html!==!1?i.innerHTML=n.html:i.innerHTML="",n.bgPos&&(i.style.backgroundPosition=-n.bgPos.x+"px "+-n.bgPos.y+"px"),this._setIconStyles(i,"icon"),i},createShadow:function(){return null}}),o.divIcon=function(t){return new o.DivIcon(t)},o.Map.mergeOptions({closePopupOnClick:!0}),o.Popup=o.Class.extend({includes:o.Mixin.Events,options:{minWidth:50,maxWidth:300,autoPan:!0,closeButton:!0,offset:[0,7],autoPanPadding:[5,5],keepInView:!1,className:"",zoomAnimation:!0},initialize:function(t,e){o.setOptions(this,t),this._source=e,this._animated=o.Browser.any3d&&this.options.zoomAnimation,this._isOpen=!1},onAdd:function(t){this._map=t,this._container||this._initLayout();var e=t.options.fadeAnimation;e&&o.DomUtil.setOpacity(this._container,0),t._panes.popupPane.appendChild(this._container),t.on(this._getEvents(),this),this.update(),e&&o.DomUtil.setOpacity(this._container,1),this.fire("open"),t.fire("popupopen",{popup:this}),this._source&&this._source.fire("popupopen",{popup:this})},addTo:function(t){return t.addLayer(this),this},openOn:function(t){return t.openPopup(this),this},onRemove:function(t){t._panes.popupPane.removeChild(this._container),o.Util.falseFn(this._container.offsetWidth),t.off(this._getEvents(),this),t.options.fadeAnimation&&o.DomUtil.setOpacity(this._container,0),this._map=null,this.fire("close"),t.fire("popupclose",{popup:this}),this._source&&this._source.fire("popupclose",{popup:this})},getLatLng:function(){return this._latlng},setLatLng:function(t){return this._latlng=o.latLng(t),this._map&&(this._updatePosition(),this._adjustPan()),this},getContent:function(){return this._content},setContent:function(t){return this._content=t,this.update(),this},update:function(){this._map&&(this._container.style.visibility="hidden",this._updateContent(),this._updateLayout(),this._updatePosition(),this._container.style.visibility="",this._adjustPan())},_getEvents:function(){var t={viewreset:this._updatePosition};return this._animated&&(t.zoomanim=this._zoomAnimation),("closeOnClick"in this.options?this.options.closeOnClick:this._map.options.closePopupOnClick)&&(t.preclick=this._close),this.options.keepInView&&(t.moveend=this._adjustPan),t},_close:function(){this._map&&this._map.closePopup(this)},_initLayout:function(){var t,e="leaflet-popup",i=e+" "+this.options.className+" leaflet-zoom-"+(this._animated?"animated":"hide"),n=this._container=o.DomUtil.create("div",i);this.options.closeButton&&(t=this._closeButton=o.DomUtil.create("a",e+"-close-button",n),t.href="#close",t.innerHTML="&#215;",o.DomEvent.disableClickPropagation(t),o.DomEvent.on(t,"click",this._onCloseButtonClick,this));var s=this._wrapper=o.DomUtil.create("div",e+"-content-wrapper",n);o.DomEvent.disableClickPropagation(s),this._contentNode=o.DomUtil.create("div",e+"-content",s),o.DomEvent.disableScrollPropagation(this._contentNode),o.DomEvent.on(s,"contextmenu",o.DomEvent.stopPropagation),this._tipContainer=o.DomUtil.create("div",e+"-tip-container",n),this._tip=o.DomUtil.create("div",e+"-tip",this._tipContainer)},_updateContent:function(){if(this._content){if("string"==typeof this._content)this._contentNode.innerHTML=this._content;else{for(;this._contentNode.hasChildNodes();)this._contentNode.removeChild(this._contentNode.firstChild);this._contentNode.appendChild(this._content)}this.fire("contentupdate")}},_updateLayout:function(){var t=this._contentNode,e=t.style;e.width="",e.whiteSpace="nowrap";var i=t.offsetWidth;i=Math.min(i,this.options.maxWidth),i=Math.max(i,this.options.minWidth),e.width=i+1+"px",e.whiteSpace="",e.height="";var n=t.offsetHeight,s=this.options.maxHeight,a="leaflet-popup-scrolled";s&&n>s?(e.height=s+"px",o.DomUtil.addClass(t,a)):o.DomUtil.removeClass(t,a),this._containerWidth=this._container.offsetWidth},_updatePosition:function(){if(this._map){var t=this._map.latLngToLayerPoint(this._latlng),e=this._animated,i=o.point(this.options.offset);e&&o.DomUtil.setPosition(this._container,t),this._containerBottom=-i.y-(e?0:t.y),this._containerLeft=-Math.round(this._containerWidth/2)+i.x+(e?0:t.x),this._container.style.bottom=this._containerBottom+"px",this._container.style.left=this._containerLeft+"px"}},_zoomAnimation:function(t){var e=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center);o.DomUtil.setPosition(this._container,e)},_adjustPan:function(){if(this.options.autoPan){var t=this._map,e=this._container.offsetHeight,i=this._containerWidth,n=new o.Point(this._containerLeft,-e-this._containerBottom);this._animated&&n._add(o.DomUtil.getPosition(this._container));var s=t.layerPointToContainerPoint(n),a=o.point(this.options.autoPanPadding),r=o.point(this.options.autoPanPaddingTopLeft||a),h=o.point(this.options.autoPanPaddingBottomRight||a),l=t.getSize(),u=0,c=0;s.x+i+h.x>l.x&&(u=s.x+i-l.x+h.x),s.x-u-r.x<0&&(u=s.x-r.x),s.y+e+h.y>l.y&&(c=s.y+e-l.y+h.y),s.y-c-r.y<0&&(c=s.y-r.y),(u||c)&&t.fire("autopanstart").panBy([u,c])}},_onCloseButtonClick:function(t){this._close(),o.DomEvent.stop(t)}}),o.popup=function(t,e){return new o.Popup(t,e)},o.Map.include({openPopup:function(t,e,i){if(this.closePopup(),!(t instanceof o.Popup)){var n=t;t=new o.Popup(i).setLatLng(e).setContent(n)}return t._isOpen=!0,this._popup=t,this.addLayer(t)},closePopup:function(t){return t&&t!==this._popup||(t=this._popup,this._popup=null),t&&(this.removeLayer(t),t._isOpen=!1),this}}),o.Marker.include({openPopup:function(){return this._popup&&this._map&&!this._map.hasLayer(this._popup)&&(this._popup.setLatLng(this._latlng),this._map.openPopup(this._popup)),this},closePopup:function(){return this._popup&&this._popup._close(),this},togglePopup:function(){return this._popup&&(this._popup._isOpen?this.closePopup():this.openPopup()),this},bindPopup:function(t,e){var i=o.point(this.options.icon.options.popupAnchor||[0,0]);return i=i.add(o.Popup.prototype.options.offset),e&&e.offset&&(i=i.add(e.offset)),e=o.extend({offset:i},e),this._popupHandlersAdded||(this.on("click",this.togglePopup,this).on("remove",this.closePopup,this).on("move",this._movePopup,this),this._popupHandlersAdded=!0),t instanceof o.Popup?(o.setOptions(t,e),this._popup=t,t._source=this):this._popup=new o.Popup(e,this).setContent(t),this},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},unbindPopup:function(){return this._popup&&(this._popup=null,this.off("click",this.togglePopup,this).off("remove",this.closePopup,this).off("move",this._movePopup,this),this._popupHandlersAdded=!1),this},getPopup:function(){return this._popup},_movePopup:function(t){this._popup.setLatLng(t.latlng)}}),o.LayerGroup=o.Class.extend({initialize:function(t){this._layers={};var e,i;if(t)for(e=0,i=t.length;i>e;e++)this.addLayer(t[e])},addLayer:function(t){var e=this.getLayerId(t);return this._layers[e]=t,this._map&&this._map.addLayer(t),this},removeLayer:function(t){var e=t in this._layers?t:this.getLayerId(t);return this._map&&this._layers[e]&&this._map.removeLayer(this._layers[e]),delete this._layers[e],this},hasLayer:function(t){return t?t in this._layers||this.getLayerId(t)in this._layers:!1},clearLayers:function(){return this.eachLayer(this.removeLayer,this),this},invoke:function(t){var e,i,n=Array.prototype.slice.call(arguments,1);for(e in this._layers)i=this._layers[e],i[t]&&i[t].apply(i,n);return this},onAdd:function(t){this._map=t,this.eachLayer(t.addLayer,t)},onRemove:function(t){this.eachLayer(t.removeLayer,t),this._map=null},addTo:function(t){return t.addLayer(this),this},eachLayer:function(t,e){for(var i in this._layers)t.call(e,this._layers[i]);return this},getLayer:function(t){return this._layers[t]},getLayers:function(){var t=[];for(var e in this._layers)t.push(this._layers[e]);return t},setZIndex:function(t){return this.invoke("setZIndex",t)},getLayerId:function(t){return o.stamp(t)}}),o.layerGroup=function(t){return new o.LayerGroup(t)},o.FeatureGroup=o.LayerGroup.extend({includes:o.Mixin.Events,statics:{EVENTS:"click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose"},addLayer:function(t){return this.hasLayer(t)?this:("on"in t&&t.on(o.FeatureGroup.EVENTS,this._propagateEvent,this),o.LayerGroup.prototype.addLayer.call(this,t),this._popupContent&&t.bindPopup&&t.bindPopup(this._popupContent,this._popupOptions),this.fire("layeradd",{layer:t}))},removeLayer:function(t){return this.hasLayer(t)?(t in this._layers&&(t=this._layers[t]),"off"in t&&t.off(o.FeatureGroup.EVENTS,this._propagateEvent,this),o.LayerGroup.prototype.removeLayer.call(this,t),this._popupContent&&this.invoke("unbindPopup"),this.fire("layerremove",{layer:t})):this},bindPopup:function(t,e){return this._popupContent=t,this._popupOptions=e,this.invoke("bindPopup",t,e)},openPopup:function(t){for(var e in this._layers){this._layers[e].openPopup(t);break}return this},setStyle:function(t){return this.invoke("setStyle",t)},bringToFront:function(){return this.invoke("bringToFront")},bringToBack:function(){return this.invoke("bringToBack")},getBounds:function(){var t=new o.LatLngBounds;return this.eachLayer(function(e){t.extend(e instanceof o.Marker?e.getLatLng():e.getBounds())}),t},_propagateEvent:function(t){t=o.extend({layer:t.target,target:this},t),this.fire(t.type,t)}}),o.featureGroup=function(t){return new o.FeatureGroup(t)},o.Path=o.Class.extend({includes:[o.Mixin.Events],statics:{CLIP_PADDING:function(){var e=o.Browser.mobile?1280:2e3,i=(e/Math.max(t.outerWidth,t.outerHeight)-1)/2;return Math.max(0,Math.min(.5,i))}()},options:{stroke:!0,color:"#0033ff",dashArray:null,lineCap:null,lineJoin:null,weight:5,opacity:.5,fill:!1,fillColor:null,fillOpacity:.2,clickable:!0},initialize:function(t){o.setOptions(this,t)},onAdd:function(t){this._map=t,this._container||(this._initElements(),this._initEvents()),this.projectLatlngs(),this._updatePath(),this._container&&this._map._pathRoot.appendChild(this._container),this.fire("add"),t.on({viewreset:this.projectLatlngs,moveend:this._updatePath},this)},addTo:function(t){return t.addLayer(this),this},onRemove:function(t){t._pathRoot.removeChild(this._container),this.fire("remove"),this._map=null,o.Browser.vml&&(this._container=null,this._stroke=null,this._fill=null),t.off({viewreset:this.projectLatlngs,moveend:this._updatePath},this)},projectLatlngs:function(){},setStyle:function(t){return o.setOptions(this,t),this._container&&this._updateStyle(),this},redraw:function(){return this._map&&(this.projectLatlngs(),this._updatePath()),this}}),o.Map.include({_updatePathViewport:function(){var t=o.Path.CLIP_PADDING,e=this.getSize(),i=o.DomUtil.getPosition(this._mapPane),n=i.multiplyBy(-1)._subtract(e.multiplyBy(t)._round()),s=n.add(e.multiplyBy(1+2*t)._round());this._pathViewport=new o.Bounds(n,s)}}),o.Path.SVG_NS="http://www.w3.org/2000/svg",o.Browser.svg=!(!e.createElementNS||!e.createElementNS(o.Path.SVG_NS,"svg").createSVGRect),o.Path=o.Path.extend({statics:{SVG:o.Browser.svg},bringToFront:function(){var t=this._map._pathRoot,e=this._container;return e&&t.lastChild!==e&&t.appendChild(e),this},bringToBack:function(){var t=this._map._pathRoot,e=this._container,i=t.firstChild;return e&&i!==e&&t.insertBefore(e,i),this},getPathString:function(){},_createElement:function(t){return e.createElementNS(o.Path.SVG_NS,t)},_initElements:function(){this._map._initPathRoot(),this._initPath(),this._initStyle()},_initPath:function(){this._container=this._createElement("g"),this._path=this._createElement("path"),this.options.className&&o.DomUtil.addClass(this._path,this.options.className),this._container.appendChild(this._path)},_initStyle:function(){this.options.stroke&&(this._path.setAttribute("stroke-linejoin","round"),this._path.setAttribute("stroke-linecap","round")),this.options.fill&&this._path.setAttribute("fill-rule","evenodd"),this.options.pointerEvents&&this._path.setAttribute("pointer-events",this.options.pointerEvents),this.options.clickable||this.options.pointerEvents||this._path.setAttribute("pointer-events","none"),this._updateStyle()},_updateStyle:function(){this.options.stroke?(this._path.setAttribute("stroke",this.options.color),this._path.setAttribute("stroke-opacity",this.options.opacity),this._path.setAttribute("stroke-width",this.options.weight),this.options.dashArray?this._path.setAttribute("stroke-dasharray",this.options.dashArray):this._path.removeAttribute("stroke-dasharray"),this.options.lineCap&&this._path.setAttribute("stroke-linecap",this.options.lineCap),this.options.lineJoin&&this._path.setAttribute("stroke-linejoin",this.options.lineJoin)):this._path.setAttribute("stroke","none"),this.options.fill?(this._path.setAttribute("fill",this.options.fillColor||this.options.color),this._path.setAttribute("fill-opacity",this.options.fillOpacity)):this._path.setAttribute("fill","none")},_updatePath:function(){var t=this.getPathString();t||(t="M0 0"),this._path.setAttribute("d",t)},_initEvents:function(){if(this.options.clickable){(o.Browser.svg||!o.Browser.vml)&&o.DomUtil.addClass(this._path,"leaflet-clickable"),o.DomEvent.on(this._container,"click",this._onMouseClick,this);for(var t=["dblclick","mousedown","mouseover","mouseout","mousemove","contextmenu"],e=0;e<t.length;e++)o.DomEvent.on(this._container,t[e],this._fireMouseEvent,this)}},_onMouseClick:function(t){this._map.dragging&&this._map.dragging.moved()||this._fireMouseEvent(t)},_fireMouseEvent:function(t){if(this._map&&this.hasEventListeners(t.type)){var e=this._map,i=e.mouseEventToContainerPoint(t),n=e.containerPointToLayerPoint(i),s=e.layerPointToLatLng(n);this.fire(t.type,{latlng:s,layerPoint:n,containerPoint:i,originalEvent:t}),"contextmenu"===t.type&&o.DomEvent.preventDefault(t),"mousemove"!==t.type&&o.DomEvent.stopPropagation(t)}}}),o.Map.include({_initPathRoot:function(){this._pathRoot||(this._pathRoot=o.Path.prototype._createElement("svg"),this._panes.overlayPane.appendChild(this._pathRoot),this.options.zoomAnimation&&o.Browser.any3d?(o.DomUtil.addClass(this._pathRoot,"leaflet-zoom-animated"),
+this.on({zoomanim:this._animatePathZoom,zoomend:this._endPathZoom})):o.DomUtil.addClass(this._pathRoot,"leaflet-zoom-hide"),this.on("moveend",this._updateSvgViewport),this._updateSvgViewport())},_animatePathZoom:function(t){var e=this.getZoomScale(t.zoom),i=this._getCenterOffset(t.center)._multiplyBy(-e)._add(this._pathViewport.min);this._pathRoot.style[o.DomUtil.TRANSFORM]=o.DomUtil.getTranslateString(i)+" scale("+e+") ",this._pathZooming=!0},_endPathZoom:function(){this._pathZooming=!1},_updateSvgViewport:function(){if(!this._pathZooming){this._updatePathViewport();var t=this._pathViewport,e=t.min,i=t.max,n=i.x-e.x,s=i.y-e.y,a=this._pathRoot,r=this._panes.overlayPane;o.Browser.mobileWebkit&&r.removeChild(a),o.DomUtil.setPosition(a,e),a.setAttribute("width",n),a.setAttribute("height",s),a.setAttribute("viewBox",[e.x,e.y,n,s].join(" ")),o.Browser.mobileWebkit&&r.appendChild(a)}}}),o.Path.include({bindPopup:function(t,e){return t instanceof o.Popup?this._popup=t:((!this._popup||e)&&(this._popup=new o.Popup(e,this)),this._popup.setContent(t)),this._popupHandlersAdded||(this.on("click",this._openPopup,this).on("remove",this.closePopup,this),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this._popup=null,this.off("click",this._openPopup).off("remove",this.closePopup),this._popupHandlersAdded=!1),this},openPopup:function(t){return this._popup&&(t=t||this._latlng||this._latlngs[Math.floor(this._latlngs.length/2)],this._openPopup({latlng:t})),this},closePopup:function(){return this._popup&&this._popup._close(),this},_openPopup:function(t){this._popup.setLatLng(t.latlng),this._map.openPopup(this._popup)}}),o.Browser.vml=!o.Browser.svg&&function(){try{var t=e.createElement("div");t.innerHTML='<v:shape adj="1"/>';var i=t.firstChild;return i.style.behavior="url(#default#VML)",i&&"object"==typeof i.adj}catch(n){return!1}}(),o.Path=o.Browser.svg||!o.Browser.vml?o.Path:o.Path.extend({statics:{VML:!0,CLIP_PADDING:.02},_createElement:function(){try{return e.namespaces.add("lvml","urn:schemas-microsoft-com:vml"),function(t){return e.createElement("<lvml:"+t+' class="lvml">')}}catch(t){return function(t){return e.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}}(),_initPath:function(){var t=this._container=this._createElement("shape");o.DomUtil.addClass(t,"leaflet-vml-shape"+(this.options.className?" "+this.options.className:"")),this.options.clickable&&o.DomUtil.addClass(t,"leaflet-clickable"),t.coordsize="1 1",this._path=this._createElement("path"),t.appendChild(this._path),this._map._pathRoot.appendChild(t)},_initStyle:function(){this._updateStyle()},_updateStyle:function(){var t=this._stroke,e=this._fill,i=this.options,n=this._container;n.stroked=i.stroke,n.filled=i.fill,i.stroke?(t||(t=this._stroke=this._createElement("stroke"),t.endcap="round",n.appendChild(t)),t.weight=i.weight+"px",t.color=i.color,t.opacity=i.opacity,i.dashArray?t.dashStyle=o.Util.isArray(i.dashArray)?i.dashArray.join(" "):i.dashArray.replace(/( *, *)/g," "):t.dashStyle="",i.lineCap&&(t.endcap=i.lineCap.replace("butt","flat")),i.lineJoin&&(t.joinstyle=i.lineJoin)):t&&(n.removeChild(t),this._stroke=null),i.fill?(e||(e=this._fill=this._createElement("fill"),n.appendChild(e)),e.color=i.fillColor||i.color,e.opacity=i.fillOpacity):e&&(n.removeChild(e),this._fill=null)},_updatePath:function(){var t=this._container.style;t.display="none",this._path.v=this.getPathString()+" ",t.display=""}}),o.Map.include(o.Browser.svg||!o.Browser.vml?{}:{_initPathRoot:function(){if(!this._pathRoot){var t=this._pathRoot=e.createElement("div");t.className="leaflet-vml-container",this._panes.overlayPane.appendChild(t),this.on("moveend",this._updatePathViewport),this._updatePathViewport()}}}),o.Browser.canvas=function(){return!!e.createElement("canvas").getContext}(),o.Path=o.Path.SVG&&!t.L_PREFER_CANVAS||!o.Browser.canvas?o.Path:o.Path.extend({statics:{CANVAS:!0,SVG:!1},redraw:function(){return this._map&&(this.projectLatlngs(),this._requestUpdate()),this},setStyle:function(t){return o.setOptions(this,t),this._map&&(this._updateStyle(),this._requestUpdate()),this},onRemove:function(t){t.off("viewreset",this.projectLatlngs,this).off("moveend",this._updatePath,this),this.options.clickable&&(this._map.off("click",this._onClick,this),this._map.off("mousemove",this._onMouseMove,this)),this._requestUpdate(),this.fire("remove"),this._map=null},_requestUpdate:function(){this._map&&!o.Path._updateRequest&&(o.Path._updateRequest=o.Util.requestAnimFrame(this._fireMapMoveEnd,this._map))},_fireMapMoveEnd:function(){o.Path._updateRequest=null,this.fire("moveend")},_initElements:function(){this._map._initPathRoot(),this._ctx=this._map._canvasCtx},_updateStyle:function(){var t=this.options;t.stroke&&(this._ctx.lineWidth=t.weight,this._ctx.strokeStyle=t.color),t.fill&&(this._ctx.fillStyle=t.fillColor||t.color),t.lineCap&&(this._ctx.lineCap=t.lineCap),t.lineJoin&&(this._ctx.lineJoin=t.lineJoin)},_drawPath:function(){var t,e,i,n,s,a;for(this._ctx.beginPath(),t=0,i=this._parts.length;i>t;t++){for(e=0,n=this._parts[t].length;n>e;e++)s=this._parts[t][e],a=(0===e?"move":"line")+"To",this._ctx[a](s.x,s.y);this instanceof o.Polygon&&this._ctx.closePath()}},_checkIfEmpty:function(){return!this._parts.length},_updatePath:function(){if(!this._checkIfEmpty()){var t=this._ctx,e=this.options;this._drawPath(),t.save(),this._updateStyle(),e.fill&&(t.globalAlpha=e.fillOpacity,t.fill(e.fillRule||"evenodd")),e.stroke&&(t.globalAlpha=e.opacity,t.stroke()),t.restore()}},_initEvents:function(){this.options.clickable&&(this._map.on("mousemove",this._onMouseMove,this),this._map.on("click dblclick contextmenu",this._fireMouseEvent,this))},_fireMouseEvent:function(t){this._containsPoint(t.layerPoint)&&this.fire(t.type,t)},_onMouseMove:function(t){this._map&&!this._map._animatingZoom&&(this._containsPoint(t.layerPoint)?(this._ctx.canvas.style.cursor="pointer",this._mouseInside=!0,this.fire("mouseover",t)):this._mouseInside&&(this._ctx.canvas.style.cursor="",this._mouseInside=!1,this.fire("mouseout",t)))}}),o.Map.include(o.Path.SVG&&!t.L_PREFER_CANVAS||!o.Browser.canvas?{}:{_initPathRoot:function(){var t,i=this._pathRoot;i||(i=this._pathRoot=e.createElement("canvas"),i.style.position="absolute",t=this._canvasCtx=i.getContext("2d"),t.lineCap="round",t.lineJoin="round",this._panes.overlayPane.appendChild(i),this.options.zoomAnimation&&(this._pathRoot.className="leaflet-zoom-animated",this.on("zoomanim",this._animatePathZoom),this.on("zoomend",this._endPathZoom)),this.on("moveend",this._updateCanvasViewport),this._updateCanvasViewport())},_updateCanvasViewport:function(){if(!this._pathZooming){this._updatePathViewport();var t=this._pathViewport,e=t.min,i=t.max.subtract(e),n=this._pathRoot;o.DomUtil.setPosition(n,e),n.width=i.x,n.height=i.y,n.getContext("2d").translate(-e.x,-e.y)}}}),o.LineUtil={simplify:function(t,e){if(!e||!t.length)return t.slice();var i=e*e;return t=this._reducePoints(t,i),t=this._simplifyDP(t,i)},pointToSegmentDistance:function(t,e,i){return Math.sqrt(this._sqClosestPointOnSegment(t,e,i,!0))},closestPointOnSegment:function(t,e,i){return this._sqClosestPointOnSegment(t,e,i)},_simplifyDP:function(t,e){var n=t.length,o=typeof Uint8Array!=i+""?Uint8Array:Array,s=new o(n);s[0]=s[n-1]=1,this._simplifyDPStep(t,s,e,0,n-1);var a,r=[];for(a=0;n>a;a++)s[a]&&r.push(t[a]);return r},_simplifyDPStep:function(t,e,i,n,o){var s,a,r,h=0;for(a=n+1;o-1>=a;a++)r=this._sqClosestPointOnSegment(t[a],t[n],t[o],!0),r>h&&(s=a,h=r);h>i&&(e[s]=1,this._simplifyDPStep(t,e,i,n,s),this._simplifyDPStep(t,e,i,s,o))},_reducePoints:function(t,e){for(var i=[t[0]],n=1,o=0,s=t.length;s>n;n++)this._sqDist(t[n],t[o])>e&&(i.push(t[n]),o=n);return s-1>o&&i.push(t[s-1]),i},clipSegment:function(t,e,i,n){var o,s,a,r=n?this._lastCode:this._getBitCode(t,i),h=this._getBitCode(e,i);for(this._lastCode=h;;){if(!(r|h))return[t,e];if(r&h)return!1;o=r||h,s=this._getEdgeIntersection(t,e,o,i),a=this._getBitCode(s,i),o===r?(t=s,r=a):(e=s,h=a)}},_getEdgeIntersection:function(t,e,i,n){var s=e.x-t.x,a=e.y-t.y,r=n.min,h=n.max;return 8&i?new o.Point(t.x+s*(h.y-t.y)/a,h.y):4&i?new o.Point(t.x+s*(r.y-t.y)/a,r.y):2&i?new o.Point(h.x,t.y+a*(h.x-t.x)/s):1&i?new o.Point(r.x,t.y+a*(r.x-t.x)/s):void 0},_getBitCode:function(t,e){var i=0;return t.x<e.min.x?i|=1:t.x>e.max.x&&(i|=2),t.y<e.min.y?i|=4:t.y>e.max.y&&(i|=8),i},_sqDist:function(t,e){var i=e.x-t.x,n=e.y-t.y;return i*i+n*n},_sqClosestPointOnSegment:function(t,e,i,n){var s,a=e.x,r=e.y,h=i.x-a,l=i.y-r,u=h*h+l*l;return u>0&&(s=((t.x-a)*h+(t.y-r)*l)/u,s>1?(a=i.x,r=i.y):s>0&&(a+=h*s,r+=l*s)),h=t.x-a,l=t.y-r,n?h*h+l*l:new o.Point(a,r)}},o.Polyline=o.Path.extend({initialize:function(t,e){o.Path.prototype.initialize.call(this,e),this._latlngs=this._convertLatLngs(t)},options:{smoothFactor:1,noClip:!1},projectLatlngs:function(){this._originalPoints=[];for(var t=0,e=this._latlngs.length;e>t;t++)this._originalPoints[t]=this._map.latLngToLayerPoint(this._latlngs[t])},getPathString:function(){for(var t=0,e=this._parts.length,i="";e>t;t++)i+=this._getPathPartStr(this._parts[t]);return i},getLatLngs:function(){return this._latlngs},setLatLngs:function(t){return this._latlngs=this._convertLatLngs(t),this.redraw()},addLatLng:function(t){return this._latlngs.push(o.latLng(t)),this.redraw()},spliceLatLngs:function(){var t=[].splice.apply(this._latlngs,arguments);return this._convertLatLngs(this._latlngs,!0),this.redraw(),t},closestLayerPoint:function(t){for(var e,i,n=1/0,s=this._parts,a=null,r=0,h=s.length;h>r;r++)for(var l=s[r],u=1,c=l.length;c>u;u++){e=l[u-1],i=l[u];var d=o.LineUtil._sqClosestPointOnSegment(t,e,i,!0);n>d&&(n=d,a=o.LineUtil._sqClosestPointOnSegment(t,e,i))}return a&&(a.distance=Math.sqrt(n)),a},getBounds:function(){return new o.LatLngBounds(this.getLatLngs())},_convertLatLngs:function(t,e){var i,n,s=e?t:[];for(i=0,n=t.length;n>i;i++){if(o.Util.isArray(t[i])&&"number"!=typeof t[i][0])return;s[i]=o.latLng(t[i])}return s},_initEvents:function(){o.Path.prototype._initEvents.call(this)},_getPathPartStr:function(t){for(var e,i=o.Path.VML,n=0,s=t.length,a="";s>n;n++)e=t[n],i&&e._round(),a+=(n?"L":"M")+e.x+" "+e.y;return a},_clipPoints:function(){var t,e,i,n=this._originalPoints,s=n.length;if(this.options.noClip)return void(this._parts=[n]);this._parts=[];var a=this._parts,r=this._map._pathViewport,h=o.LineUtil;for(t=0,e=0;s-1>t;t++)i=h.clipSegment(n[t],n[t+1],r,t),i&&(a[e]=a[e]||[],a[e].push(i[0]),(i[1]!==n[t+1]||t===s-2)&&(a[e].push(i[1]),e++))},_simplifyPoints:function(){for(var t=this._parts,e=o.LineUtil,i=0,n=t.length;n>i;i++)t[i]=e.simplify(t[i],this.options.smoothFactor)},_updatePath:function(){this._map&&(this._clipPoints(),this._simplifyPoints(),o.Path.prototype._updatePath.call(this))}}),o.polyline=function(t,e){return new o.Polyline(t,e)},o.PolyUtil={},o.PolyUtil.clipPolygon=function(t,e){var i,n,s,a,r,h,l,u,c,d=[1,4,2,8],p=o.LineUtil;for(n=0,l=t.length;l>n;n++)t[n]._code=p._getBitCode(t[n],e);for(a=0;4>a;a++){for(u=d[a],i=[],n=0,l=t.length,s=l-1;l>n;s=n++)r=t[n],h=t[s],r._code&u?h._code&u||(c=p._getEdgeIntersection(h,r,u,e),c._code=p._getBitCode(c,e),i.push(c)):(h._code&u&&(c=p._getEdgeIntersection(h,r,u,e),c._code=p._getBitCode(c,e),i.push(c)),i.push(r));t=i}return t},o.Polygon=o.Polyline.extend({options:{fill:!0},initialize:function(t,e){o.Polyline.prototype.initialize.call(this,t,e),this._initWithHoles(t)},_initWithHoles:function(t){var e,i,n;if(t&&o.Util.isArray(t[0])&&"number"!=typeof t[0][0])for(this._latlngs=this._convertLatLngs(t[0]),this._holes=t.slice(1),e=0,i=this._holes.length;i>e;e++)n=this._holes[e]=this._convertLatLngs(this._holes[e]),n[0].equals(n[n.length-1])&&n.pop();t=this._latlngs,t.length>=2&&t[0].equals(t[t.length-1])&&t.pop()},projectLatlngs:function(){if(o.Polyline.prototype.projectLatlngs.call(this),this._holePoints=[],this._holes){var t,e,i,n;for(t=0,i=this._holes.length;i>t;t++)for(this._holePoints[t]=[],e=0,n=this._holes[t].length;n>e;e++)this._holePoints[t][e]=this._map.latLngToLayerPoint(this._holes[t][e])}},setLatLngs:function(t){return t&&o.Util.isArray(t[0])&&"number"!=typeof t[0][0]?(this._initWithHoles(t),this.redraw()):o.Polyline.prototype.setLatLngs.call(this,t)},_clipPoints:function(){var t=this._originalPoints,e=[];if(this._parts=[t].concat(this._holePoints),!this.options.noClip){for(var i=0,n=this._parts.length;n>i;i++){var s=o.PolyUtil.clipPolygon(this._parts[i],this._map._pathViewport);s.length&&e.push(s)}this._parts=e}},_getPathPartStr:function(t){var e=o.Polyline.prototype._getPathPartStr.call(this,t);return e+(o.Browser.svg?"z":"x")}}),o.polygon=function(t,e){return new o.Polygon(t,e)},function(){function t(t){return o.FeatureGroup.extend({initialize:function(t,e){this._layers={},this._options=e,this.setLatLngs(t)},setLatLngs:function(e){var i=0,n=e.length;for(this.eachLayer(function(t){n>i?t.setLatLngs(e[i++]):this.removeLayer(t)},this);n>i;)this.addLayer(new t(e[i++],this._options));return this},getLatLngs:function(){var t=[];return this.eachLayer(function(e){t.push(e.getLatLngs())}),t}})}o.MultiPolyline=t(o.Polyline),o.MultiPolygon=t(o.Polygon),o.multiPolyline=function(t,e){return new o.MultiPolyline(t,e)},o.multiPolygon=function(t,e){return new o.MultiPolygon(t,e)}}(),o.Rectangle=o.Polygon.extend({initialize:function(t,e){o.Polygon.prototype.initialize.call(this,this._boundsToLatLngs(t),e)},setBounds:function(t){this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return t=o.latLngBounds(t),[t.getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}}),o.rectangle=function(t,e){return new o.Rectangle(t,e)},o.Circle=o.Path.extend({initialize:function(t,e,i){o.Path.prototype.initialize.call(this,i),this._latlng=o.latLng(t),this._mRadius=e},options:{fill:!0},setLatLng:function(t){return this._latlng=o.latLng(t),this.redraw()},setRadius:function(t){return this._mRadius=t,this.redraw()},projectLatlngs:function(){var t=this._getLngRadius(),e=this._latlng,i=this._map.latLngToLayerPoint([e.lat,e.lng-t]);this._point=this._map.latLngToLayerPoint(e),this._radius=Math.max(this._point.x-i.x,1)},getBounds:function(){var t=this._getLngRadius(),e=this._mRadius/40075017*360,i=this._latlng;return new o.LatLngBounds([i.lat-e,i.lng-t],[i.lat+e,i.lng+t])},getLatLng:function(){return this._latlng},getPathString:function(){var t=this._point,e=this._radius;return this._checkIfEmpty()?"":o.Browser.svg?"M"+t.x+","+(t.y-e)+"A"+e+","+e+",0,1,1,"+(t.x-.1)+","+(t.y-e)+" z":(t._round(),e=Math.round(e),"AL "+t.x+","+t.y+" "+e+","+e+" 0,23592600")},getRadius:function(){return this._mRadius},_getLatRadius:function(){return this._mRadius/40075017*360},_getLngRadius:function(){return this._getLatRadius()/Math.cos(o.LatLng.DEG_TO_RAD*this._latlng.lat)},_checkIfEmpty:function(){if(!this._map)return!1;var t=this._map._pathViewport,e=this._radius,i=this._point;return i.x-e>t.max.x||i.y-e>t.max.y||i.x+e<t.min.x||i.y+e<t.min.y}}),o.circle=function(t,e,i){return new o.Circle(t,e,i)},o.CircleMarker=o.Circle.extend({options:{radius:10,weight:2},initialize:function(t,e){o.Circle.prototype.initialize.call(this,t,null,e),this._radius=this.options.radius},projectLatlngs:function(){this._point=this._map.latLngToLayerPoint(this._latlng)},_updateStyle:function(){o.Circle.prototype._updateStyle.call(this),this.setRadius(this.options.radius)},setLatLng:function(t){return o.Circle.prototype.setLatLng.call(this,t),this._popup&&this._popup._isOpen&&this._popup.setLatLng(t),this},setRadius:function(t){return this.options.radius=this._radius=t,this.redraw()},getRadius:function(){return this._radius}}),o.circleMarker=function(t,e){return new o.CircleMarker(t,e)},o.Polyline.include(o.Path.CANVAS?{_containsPoint:function(t,e){var i,n,s,a,r,h,l,u=this.options.weight/2;for(o.Browser.touch&&(u+=10),i=0,a=this._parts.length;a>i;i++)for(l=this._parts[i],n=0,r=l.length,s=r-1;r>n;s=n++)if((e||0!==n)&&(h=o.LineUtil.pointToSegmentDistance(t,l[s],l[n]),u>=h))return!0;return!1}}:{}),o.Polygon.include(o.Path.CANVAS?{_containsPoint:function(t){var e,i,n,s,a,r,h,l,u=!1;if(o.Polyline.prototype._containsPoint.call(this,t,!0))return!0;for(s=0,h=this._parts.length;h>s;s++)for(e=this._parts[s],a=0,l=e.length,r=l-1;l>a;r=a++)i=e[a],n=e[r],i.y>t.y!=n.y>t.y&&t.x<(n.x-i.x)*(t.y-i.y)/(n.y-i.y)+i.x&&(u=!u);return u}}:{}),o.Circle.include(o.Path.CANVAS?{_drawPath:function(){var t=this._point;this._ctx.beginPath(),this._ctx.arc(t.x,t.y,this._radius,0,2*Math.PI,!1)},_containsPoint:function(t){var e=this._point,i=this.options.stroke?this.options.weight/2:0;return t.distanceTo(e)<=this._radius+i}}:{}),o.CircleMarker.include(o.Path.CANVAS?{_updateStyle:function(){o.Path.prototype._updateStyle.call(this)}}:{}),o.GeoJSON=o.FeatureGroup.extend({initialize:function(t,e){o.setOptions(this,e),this._layers={},t&&this.addData(t)},addData:function(t){var e,i,n,s=o.Util.isArray(t)?t:t.features;if(s){for(e=0,i=s.length;i>e;e++)n=s[e],(n.geometries||n.geometry||n.features||n.coordinates)&&this.addData(s[e]);return this}var a=this.options;if(!a.filter||a.filter(t)){var r=o.GeoJSON.geometryToLayer(t,a.pointToLayer,a.coordsToLatLng,a);return r.feature=o.GeoJSON.asFeature(t),r.defaultOptions=r.options,this.resetStyle(r),a.onEachFeature&&a.onEachFeature(t,r),this.addLayer(r)}},resetStyle:function(t){var e=this.options.style;e&&(o.Util.extend(t.options,t.defaultOptions),this._setLayerStyle(t,e))},setStyle:function(t){this.eachLayer(function(e){this._setLayerStyle(e,t)},this)},_setLayerStyle:function(t,e){"function"==typeof e&&(e=e(t.feature)),t.setStyle&&t.setStyle(e)}}),o.extend(o.GeoJSON,{geometryToLayer:function(t,e,i,n){var s,a,r,h,l="Feature"===t.type?t.geometry:t,u=l.coordinates,c=[];switch(i=i||this.coordsToLatLng,l.type){case"Point":return s=i(u),e?e(t,s):new o.Marker(s);case"MultiPoint":for(r=0,h=u.length;h>r;r++)s=i(u[r]),c.push(e?e(t,s):new o.Marker(s));return new o.FeatureGroup(c);case"LineString":return a=this.coordsToLatLngs(u,0,i),new o.Polyline(a,n);case"Polygon":if(2===u.length&&!u[1].length)throw new Error("Invalid GeoJSON object.");return a=this.coordsToLatLngs(u,1,i),new o.Polygon(a,n);case"MultiLineString":return a=this.coordsToLatLngs(u,1,i),new o.MultiPolyline(a,n);case"MultiPolygon":return a=this.coordsToLatLngs(u,2,i),new o.MultiPolygon(a,n);case"GeometryCollection":for(r=0,h=l.geometries.length;h>r;r++)c.push(this.geometryToLayer({geometry:l.geometries[r],type:"Feature",properties:t.properties},e,i,n));return new o.FeatureGroup(c);default:throw new Error("Invalid GeoJSON object.")}},coordsToLatLng:function(t){return new o.LatLng(t[1],t[0],t[2])},coordsToLatLngs:function(t,e,i){var n,o,s,a=[];for(o=0,s=t.length;s>o;o++)n=e?this.coordsToLatLngs(t[o],e-1,i):(i||this.coordsToLatLng)(t[o]),a.push(n);return a},latLngToCoords:function(t){var e=[t.lng,t.lat];return t.alt!==i&&e.push(t.alt),e},latLngsToCoords:function(t){for(var e=[],i=0,n=t.length;n>i;i++)e.push(o.GeoJSON.latLngToCoords(t[i]));return e},getFeature:function(t,e){return t.feature?o.extend({},t.feature,{geometry:e}):o.GeoJSON.asFeature(e)},asFeature:function(t){return"Feature"===t.type?t:{type:"Feature",properties:{},geometry:t}}});var a={toGeoJSON:function(){return o.GeoJSON.getFeature(this,{type:"Point",coordinates:o.GeoJSON.latLngToCoords(this.getLatLng())})}};o.Marker.include(a),o.Circle.include(a),o.CircleMarker.include(a),o.Polyline.include({toGeoJSON:function(){return o.GeoJSON.getFeature(this,{type:"LineString",coordinates:o.GeoJSON.latLngsToCoords(this.getLatLngs())})}}),o.Polygon.include({toGeoJSON:function(){var t,e,i,n=[o.GeoJSON.latLngsToCoords(this.getLatLngs())];if(n[0].push(n[0][0]),this._holes)for(t=0,e=this._holes.length;e>t;t++)i=o.GeoJSON.latLngsToCoords(this._holes[t]),i.push(i[0]),n.push(i);return o.GeoJSON.getFeature(this,{type:"Polygon",coordinates:n})}}),function(){function t(t){return function(){var e=[];return this.eachLayer(function(t){e.push(t.toGeoJSON().geometry.coordinates)}),o.GeoJSON.getFeature(this,{type:t,coordinates:e})}}o.MultiPolyline.include({toGeoJSON:t("MultiLineString")}),o.MultiPolygon.include({toGeoJSON:t("MultiPolygon")}),o.LayerGroup.include({toGeoJSON:function(){var e,i=this.feature&&this.feature.geometry,n=[];if(i&&"MultiPoint"===i.type)return t("MultiPoint").call(this);var s=i&&"GeometryCollection"===i.type;return this.eachLayer(function(t){t.toGeoJSON&&(e=t.toGeoJSON(),n.push(s?e.geometry:o.GeoJSON.asFeature(e)))}),s?o.GeoJSON.getFeature(this,{geometries:n,type:"GeometryCollection"}):{type:"FeatureCollection",features:n}}})}(),o.geoJson=function(t,e){return new o.GeoJSON(t,e)},o.DomEvent={addListener:function(t,e,i,n){var s,a,r,h=o.stamp(i),l="_leaflet_"+e+h;return t[l]?this:(s=function(e){return i.call(n||t,e||o.DomEvent._getEvent())},o.Browser.pointer&&0===e.indexOf("touch")?this.addPointerListener(t,e,s,h):(o.Browser.touch&&"dblclick"===e&&this.addDoubleTapListener&&this.addDoubleTapListener(t,s,h),"addEventListener"in t?"mousewheel"===e?(t.addEventListener("DOMMouseScroll",s,!1),t.addEventListener(e,s,!1)):"mouseenter"===e||"mouseleave"===e?(a=s,r="mouseenter"===e?"mouseover":"mouseout",s=function(e){return o.DomEvent._checkMouse(t,e)?a(e):void 0},t.addEventListener(r,s,!1)):"click"===e&&o.Browser.android?(a=s,s=function(t){return o.DomEvent._filterClick(t,a)},t.addEventListener(e,s,!1)):t.addEventListener(e,s,!1):"attachEvent"in t&&t.attachEvent("on"+e,s),t[l]=s,this))},removeListener:function(t,e,i){var n=o.stamp(i),s="_leaflet_"+e+n,a=t[s];return a?(o.Browser.pointer&&0===e.indexOf("touch")?this.removePointerListener(t,e,n):o.Browser.touch&&"dblclick"===e&&this.removeDoubleTapListener?this.removeDoubleTapListener(t,n):"removeEventListener"in t?"mousewheel"===e?(t.removeEventListener("DOMMouseScroll",a,!1),t.removeEventListener(e,a,!1)):"mouseenter"===e||"mouseleave"===e?t.removeEventListener("mouseenter"===e?"mouseover":"mouseout",a,!1):t.removeEventListener(e,a,!1):"detachEvent"in t&&t.detachEvent("on"+e,a),t[s]=null,this):this},stopPropagation:function(t){return t.stopPropagation?t.stopPropagation():t.cancelBubble=!0,o.DomEvent._skipped(t),this},disableScrollPropagation:function(t){var e=o.DomEvent.stopPropagation;return o.DomEvent.on(t,"mousewheel",e).on(t,"MozMousePixelScroll",e)},disableClickPropagation:function(t){for(var e=o.DomEvent.stopPropagation,i=o.Draggable.START.length-1;i>=0;i--)o.DomEvent.on(t,o.Draggable.START[i],e);return o.DomEvent.on(t,"click",o.DomEvent._fakeStop).on(t,"dblclick",e)},preventDefault:function(t){return t.preventDefault?t.preventDefault():t.returnValue=!1,this},stop:function(t){return o.DomEvent.preventDefault(t).stopPropagation(t)},getMousePosition:function(t,e){if(!e)return new o.Point(t.clientX,t.clientY);var i=e.getBoundingClientRect();return new o.Point(t.clientX-i.left-e.clientLeft,t.clientY-i.top-e.clientTop)},getWheelDelta:function(t){var e=0;return t.wheelDelta&&(e=t.wheelDelta/120),t.detail&&(e=-t.detail/3),e},_skipEvents:{},_fakeStop:function(t){o.DomEvent._skipEvents[t.type]=!0},_skipped:function(t){var e=this._skipEvents[t.type];return this._skipEvents[t.type]=!1,e},_checkMouse:function(t,e){var i=e.relatedTarget;if(!i)return!0;try{for(;i&&i!==t;)i=i.parentNode}catch(n){return!1}return i!==t},_getEvent:function(){var e=t.event;if(!e)for(var i=arguments.callee.caller;i&&(e=i.arguments[0],!e||t.Event!==e.constructor);)i=i.caller;return e},_filterClick:function(t,e){var i=t.timeStamp||t.originalEvent.timeStamp,n=o.DomEvent._lastClick&&i-o.DomEvent._lastClick;return n&&n>100&&500>n||t.target._simulatedClick&&!t._simulated?void o.DomEvent.stop(t):(o.DomEvent._lastClick=i,e(t))}},o.DomEvent.on=o.DomEvent.addListener,o.DomEvent.off=o.DomEvent.removeListener,o.Draggable=o.Class.extend({includes:o.Mixin.Events,statics:{START:o.Browser.touch?["touchstart","mousedown"]:["mousedown"],END:{mousedown:"mouseup",touchstart:"touchend",pointerdown:"touchend",MSPointerDown:"touchend"},MOVE:{mousedown:"mousemove",touchstart:"touchmove",pointerdown:"touchmove",MSPointerDown:"touchmove"}},initialize:function(t,e){this._element=t,this._dragStartTarget=e||t},enable:function(){if(!this._enabled){for(var t=o.Draggable.START.length-1;t>=0;t--)o.DomEvent.on(this._dragStartTarget,o.Draggable.START[t],this._onDown,this);this._enabled=!0}},disable:function(){if(this._enabled){for(var t=o.Draggable.START.length-1;t>=0;t--)o.DomEvent.off(this._dragStartTarget,o.Draggable.START[t],this._onDown,this);this._enabled=!1,this._moved=!1}},_onDown:function(t){if(this._moved=!1,!t.shiftKey&&(1===t.which||1===t.button||t.touches)&&(o.DomEvent.stopPropagation(t),!o.Draggable._disabled&&(o.DomUtil.disableImageDrag(),o.DomUtil.disableTextSelection(),!this._moving))){var i=t.touches?t.touches[0]:t;this._startPoint=new o.Point(i.clientX,i.clientY),this._startPos=this._newPos=o.DomUtil.getPosition(this._element),o.DomEvent.on(e,o.Draggable.MOVE[t.type],this._onMove,this).on(e,o.Draggable.END[t.type],this._onUp,this)}},_onMove:function(t){if(t.touches&&t.touches.length>1)return void(this._moved=!0);var i=t.touches&&1===t.touches.length?t.touches[0]:t,n=new o.Point(i.clientX,i.clientY),s=n.subtract(this._startPoint);(s.x||s.y)&&(o.Browser.touch&&Math.abs(s.x)+Math.abs(s.y)<3||(o.DomEvent.preventDefault(t),this._moved||(this.fire("dragstart"),this._moved=!0,this._startPos=o.DomUtil.getPosition(this._element).subtract(s),o.DomUtil.addClass(e.body,"leaflet-dragging"),this._lastTarget=t.target||t.srcElement,o.DomUtil.addClass(this._lastTarget,"leaflet-drag-target")),this._newPos=this._startPos.add(s),this._moving=!0,o.Util.cancelAnimFrame(this._animRequest),this._animRequest=o.Util.requestAnimFrame(this._updatePosition,this,!0,this._dragStartTarget)))},_updatePosition:function(){this.fire("predrag"),o.DomUtil.setPosition(this._element,this._newPos),this.fire("drag")},_onUp:function(){o.DomUtil.removeClass(e.body,"leaflet-dragging"),this._lastTarget&&(o.DomUtil.removeClass(this._lastTarget,"leaflet-drag-target"),this._lastTarget=null);for(var t in o.Draggable.MOVE)o.DomEvent.off(e,o.Draggable.MOVE[t],this._onMove).off(e,o.Draggable.END[t],this._onUp);o.DomUtil.enableImageDrag(),o.DomUtil.enableTextSelection(),this._moved&&this._moving&&(o.Util.cancelAnimFrame(this._animRequest),this.fire("dragend",{distance:this._newPos.distanceTo(this._startPos)})),this._moving=!1}}),o.Handler=o.Class.extend({initialize:function(t){this._map=t},enable:function(){this._enabled||(this._enabled=!0,this.addHooks())},disable:function(){this._enabled&&(this._enabled=!1,this.removeHooks())},enabled:function(){return!!this._enabled}}),o.Map.mergeOptions({dragging:!0,inertia:!o.Browser.android23,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,inertiaThreshold:o.Browser.touch?32:18,easeLinearity:.25,worldCopyJump:!1}),o.Map.Drag=o.Handler.extend({addHooks:function(){if(!this._draggable){var t=this._map;this._draggable=new o.Draggable(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDrag,this),t.on("viewreset",this._onViewReset,this),t.whenReady(this._onViewReset,this))}this._draggable.enable()},removeHooks:function(){this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},_onDragStart:function(){var t=this._map;t._panAnim&&t._panAnim.stop(),t.fire("movestart").fire("dragstart"),t.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(){if(this._map.options.inertia){var t=this._lastTime=+new Date,e=this._lastPos=this._draggable._newPos;this._positions.push(e),this._times.push(t),t-this._times[0]>200&&(this._positions.shift(),this._times.shift())}this._map.fire("move").fire("drag")},_onViewReset:function(){var t=this._map.getSize()._divideBy(2),e=this._map.latLngToLayerPoint([0,0]);this._initialWorldOffset=e.subtract(t).x,this._worldWidth=this._map.project([0,180]).x},_onPreDrag:function(){var t=this._worldWidth,e=Math.round(t/2),i=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-e+i)%t+e-i,s=(n+e+i)%t-e-i,a=Math.abs(o+i)<Math.abs(s+i)?o:s;this._draggable._newPos.x=a},_onDragEnd:function(t){var e=this._map,i=e.options,n=+new Date-this._lastTime,s=!i.inertia||n>i.inertiaThreshold||!this._positions[0];if(e.fire("dragend",t),s)e.fire("moveend");else{var a=this._lastPos.subtract(this._positions[0]),r=(this._lastTime+n-this._times[0])/1e3,h=i.easeLinearity,l=a.multiplyBy(h/r),u=l.distanceTo([0,0]),c=Math.min(i.inertiaMaxSpeed,u),d=l.multiplyBy(c/u),p=c/(i.inertiaDeceleration*h),_=d.multiplyBy(-p/2).round();_.x&&_.y?(_=e._limitOffset(_,e.options.maxBounds),o.Util.requestAnimFrame(function(){e.panBy(_,{duration:p,easeLinearity:h,noMoveStart:!0})})):e.fire("moveend")}}}),o.Map.addInitHook("addHandler","dragging",o.Map.Drag),o.Map.mergeOptions({doubleClickZoom:!0}),o.Map.DoubleClickZoom=o.Handler.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick,this)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick,this)},_onDoubleClick:function(t){var e=this._map,i=e.getZoom()+(t.originalEvent.shiftKey?-1:1);"center"===e.options.doubleClickZoom?e.setZoom(i):e.setZoomAround(t.containerPoint,i)}}),o.Map.addInitHook("addHandler","doubleClickZoom",o.Map.DoubleClickZoom),o.Map.mergeOptions({scrollWheelZoom:!0}),o.Map.ScrollWheelZoom=o.Handler.extend({addHooks:function(){o.DomEvent.on(this._map._container,"mousewheel",this._onWheelScroll,this),o.DomEvent.on(this._map._container,"MozMousePixelScroll",o.DomEvent.preventDefault),this._delta=0},removeHooks:function(){o.DomEvent.off(this._map._container,"mousewheel",this._onWheelScroll),o.DomEvent.off(this._map._container,"MozMousePixelScroll",o.DomEvent.preventDefault)},_onWheelScroll:function(t){var e=o.DomEvent.getWheelDelta(t);this._delta+=e,this._lastMousePos=this._map.mouseEventToContainerPoint(t),this._startTime||(this._startTime=+new Date);var i=Math.max(40-(+new Date-this._startTime),0);clearTimeout(this._timer),this._timer=setTimeout(o.bind(this._performZoom,this),i),o.DomEvent.preventDefault(t),o.DomEvent.stopPropagation(t)},_performZoom:function(){var t=this._map,e=this._delta,i=t.getZoom();e=e>0?Math.ceil(e):Math.floor(e),e=Math.max(Math.min(e,4),-4),e=t._limitZoom(i+e)-i,this._delta=0,this._startTime=null,e&&("center"===t.options.scrollWheelZoom?t.setZoom(i+e):t.setZoomAround(this._lastMousePos,i+e))}}),o.Map.addInitHook("addHandler","scrollWheelZoom",o.Map.ScrollWheelZoom),o.extend(o.DomEvent,{_touchstart:o.Browser.msPointer?"MSPointerDown":o.Browser.pointer?"pointerdown":"touchstart",_touchend:o.Browser.msPointer?"MSPointerUp":o.Browser.pointer?"pointerup":"touchend",addDoubleTapListener:function(t,i,n){function s(t){var e;if(o.Browser.pointer?(_.push(t.pointerId),e=_.length):e=t.touches.length,!(e>1)){var i=Date.now(),n=i-(r||i);h=t.touches?t.touches[0]:t,l=n>0&&u>=n,r=i}}function a(t){if(o.Browser.pointer){var e=_.indexOf(t.pointerId);if(-1===e)return;_.splice(e,1)}if(l){if(o.Browser.pointer){var n,s={};for(var a in h)n=h[a],"function"==typeof n?s[a]=n.bind(h):s[a]=n;h=s}h.type="dblclick",i(h),r=null}}var r,h,l=!1,u=250,c="_leaflet_",d=this._touchstart,p=this._touchend,_=[];t[c+d+n]=s,t[c+p+n]=a;var m=o.Browser.pointer?e.documentElement:t;return t.addEventListener(d,s,!1),m.addEventListener(p,a,!1),o.Browser.pointer&&m.addEventListener(o.DomEvent.POINTER_CANCEL,a,!1),this},removeDoubleTapListener:function(t,i){var n="_leaflet_";return t.removeEventListener(this._touchstart,t[n+this._touchstart+i],!1),(o.Browser.pointer?e.documentElement:t).removeEventListener(this._touchend,t[n+this._touchend+i],!1),o.Browser.pointer&&e.documentElement.removeEventListener(o.DomEvent.POINTER_CANCEL,t[n+this._touchend+i],!1),this}}),o.extend(o.DomEvent,{POINTER_DOWN:o.Browser.msPointer?"MSPointerDown":"pointerdown",POINTER_MOVE:o.Browser.msPointer?"MSPointerMove":"pointermove",POINTER_UP:o.Browser.msPointer?"MSPointerUp":"pointerup",POINTER_CANCEL:o.Browser.msPointer?"MSPointerCancel":"pointercancel",_pointers:[],_pointerDocumentListener:!1,addPointerListener:function(t,e,i,n){switch(e){case"touchstart":return this.addPointerListenerStart(t,e,i,n);
+case"touchend":return this.addPointerListenerEnd(t,e,i,n);case"touchmove":return this.addPointerListenerMove(t,e,i,n);default:throw"Unknown touch event type"}},addPointerListenerStart:function(t,i,n,s){var a="_leaflet_",r=this._pointers,h=function(t){"mouse"!==t.pointerType&&t.pointerType!==t.MSPOINTER_TYPE_MOUSE&&o.DomEvent.preventDefault(t);for(var e=!1,i=0;i<r.length;i++)if(r[i].pointerId===t.pointerId){e=!0;break}e||r.push(t),t.touches=r.slice(),t.changedTouches=[t],n(t)};if(t[a+"touchstart"+s]=h,t.addEventListener(this.POINTER_DOWN,h,!1),!this._pointerDocumentListener){var l=function(t){for(var e=0;e<r.length;e++)if(r[e].pointerId===t.pointerId){r.splice(e,1);break}};e.documentElement.addEventListener(this.POINTER_UP,l,!1),e.documentElement.addEventListener(this.POINTER_CANCEL,l,!1),this._pointerDocumentListener=!0}return this},addPointerListenerMove:function(t,e,i,n){function o(t){if(t.pointerType!==t.MSPOINTER_TYPE_MOUSE&&"mouse"!==t.pointerType||0!==t.buttons){for(var e=0;e<a.length;e++)if(a[e].pointerId===t.pointerId){a[e]=t;break}t.touches=a.slice(),t.changedTouches=[t],i(t)}}var s="_leaflet_",a=this._pointers;return t[s+"touchmove"+n]=o,t.addEventListener(this.POINTER_MOVE,o,!1),this},addPointerListenerEnd:function(t,e,i,n){var o="_leaflet_",s=this._pointers,a=function(t){for(var e=0;e<s.length;e++)if(s[e].pointerId===t.pointerId){s.splice(e,1);break}t.touches=s.slice(),t.changedTouches=[t],i(t)};return t[o+"touchend"+n]=a,t.addEventListener(this.POINTER_UP,a,!1),t.addEventListener(this.POINTER_CANCEL,a,!1),this},removePointerListener:function(t,e,i){var n="_leaflet_",o=t[n+e+i];switch(e){case"touchstart":t.removeEventListener(this.POINTER_DOWN,o,!1);break;case"touchmove":t.removeEventListener(this.POINTER_MOVE,o,!1);break;case"touchend":t.removeEventListener(this.POINTER_UP,o,!1),t.removeEventListener(this.POINTER_CANCEL,o,!1)}return this}}),o.Map.mergeOptions({touchZoom:o.Browser.touch&&!o.Browser.android23,bounceAtZoomLimits:!0}),o.Map.TouchZoom=o.Handler.extend({addHooks:function(){o.DomEvent.on(this._map._container,"touchstart",this._onTouchStart,this)},removeHooks:function(){o.DomEvent.off(this._map._container,"touchstart",this._onTouchStart,this)},_onTouchStart:function(t){var i=this._map;if(t.touches&&2===t.touches.length&&!i._animatingZoom&&!this._zooming){var n=i.mouseEventToLayerPoint(t.touches[0]),s=i.mouseEventToLayerPoint(t.touches[1]),a=i._getCenterLayerPoint();this._startCenter=n.add(s)._divideBy(2),this._startDist=n.distanceTo(s),this._moved=!1,this._zooming=!0,this._centerOffset=a.subtract(this._startCenter),i._panAnim&&i._panAnim.stop(),o.DomEvent.on(e,"touchmove",this._onTouchMove,this).on(e,"touchend",this._onTouchEnd,this),o.DomEvent.preventDefault(t)}},_onTouchMove:function(t){var e=this._map;if(t.touches&&2===t.touches.length&&this._zooming){var i=e.mouseEventToLayerPoint(t.touches[0]),n=e.mouseEventToLayerPoint(t.touches[1]);this._scale=i.distanceTo(n)/this._startDist,this._delta=i._add(n)._divideBy(2)._subtract(this._startCenter),1!==this._scale&&(e.options.bounceAtZoomLimits||!(e.getZoom()===e.getMinZoom()&&this._scale<1||e.getZoom()===e.getMaxZoom()&&this._scale>1))&&(this._moved||(o.DomUtil.addClass(e._mapPane,"leaflet-touching"),e.fire("movestart").fire("zoomstart"),this._moved=!0),o.Util.cancelAnimFrame(this._animRequest),this._animRequest=o.Util.requestAnimFrame(this._updateOnMove,this,!0,this._map._container),o.DomEvent.preventDefault(t))}},_updateOnMove:function(){var t=this._map,e=this._getScaleOrigin(),i=t.layerPointToLatLng(e),n=t.getScaleZoom(this._scale);t._animateZoom(i,n,this._startCenter,this._scale,this._delta,!1,!0)},_onTouchEnd:function(){if(!this._moved||!this._zooming)return void(this._zooming=!1);var t=this._map;this._zooming=!1,o.DomUtil.removeClass(t._mapPane,"leaflet-touching"),o.Util.cancelAnimFrame(this._animRequest),o.DomEvent.off(e,"touchmove",this._onTouchMove).off(e,"touchend",this._onTouchEnd);var i=this._getScaleOrigin(),n=t.layerPointToLatLng(i),s=t.getZoom(),a=t.getScaleZoom(this._scale)-s,r=a>0?Math.ceil(a):Math.floor(a),h=t._limitZoom(s+r),l=t.getZoomScale(h)/this._scale;t._animateZoom(n,h,i,l)},_getScaleOrigin:function(){var t=this._centerOffset.subtract(this._delta).divideBy(this._scale);return this._startCenter.add(t)}}),o.Map.addInitHook("addHandler","touchZoom",o.Map.TouchZoom),o.Map.mergeOptions({tap:!0,tapTolerance:15}),o.Map.Tap=o.Handler.extend({addHooks:function(){o.DomEvent.on(this._map._container,"touchstart",this._onDown,this)},removeHooks:function(){o.DomEvent.off(this._map._container,"touchstart",this._onDown,this)},_onDown:function(t){if(t.touches){if(o.DomEvent.preventDefault(t),this._fireClick=!0,t.touches.length>1)return this._fireClick=!1,void clearTimeout(this._holdTimeout);var i=t.touches[0],n=i.target;this._startPos=this._newPos=new o.Point(i.clientX,i.clientY),n.tagName&&"a"===n.tagName.toLowerCase()&&o.DomUtil.addClass(n,"leaflet-active"),this._holdTimeout=setTimeout(o.bind(function(){this._isTapValid()&&(this._fireClick=!1,this._onUp(),this._simulateEvent("contextmenu",i))},this),1e3),o.DomEvent.on(e,"touchmove",this._onMove,this).on(e,"touchend",this._onUp,this)}},_onUp:function(t){if(clearTimeout(this._holdTimeout),o.DomEvent.off(e,"touchmove",this._onMove,this).off(e,"touchend",this._onUp,this),this._fireClick&&t&&t.changedTouches){var i=t.changedTouches[0],n=i.target;n&&n.tagName&&"a"===n.tagName.toLowerCase()&&o.DomUtil.removeClass(n,"leaflet-active"),this._isTapValid()&&this._simulateEvent("click",i)}},_isTapValid:function(){return this._newPos.distanceTo(this._startPos)<=this._map.options.tapTolerance},_onMove:function(t){var e=t.touches[0];this._newPos=new o.Point(e.clientX,e.clientY)},_simulateEvent:function(i,n){var o=e.createEvent("MouseEvents");o._simulated=!0,n.target._simulatedClick=!0,o.initMouseEvent(i,!0,!0,t,1,n.screenX,n.screenY,n.clientX,n.clientY,!1,!1,!1,!1,0,null),n.target.dispatchEvent(o)}}),o.Browser.touch&&!o.Browser.pointer&&o.Map.addInitHook("addHandler","tap",o.Map.Tap),o.Map.mergeOptions({boxZoom:!0}),o.Map.BoxZoom=o.Handler.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._moved=!1},addHooks:function(){o.DomEvent.on(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){o.DomEvent.off(this._container,"mousedown",this._onMouseDown),this._moved=!1},moved:function(){return this._moved},_onMouseDown:function(t){return this._moved=!1,!t.shiftKey||1!==t.which&&1!==t.button?!1:(o.DomUtil.disableTextSelection(),o.DomUtil.disableImageDrag(),this._startLayerPoint=this._map.mouseEventToLayerPoint(t),void o.DomEvent.on(e,"mousemove",this._onMouseMove,this).on(e,"mouseup",this._onMouseUp,this).on(e,"keydown",this._onKeyDown,this))},_onMouseMove:function(t){this._moved||(this._box=o.DomUtil.create("div","leaflet-zoom-box",this._pane),o.DomUtil.setPosition(this._box,this._startLayerPoint),this._container.style.cursor="crosshair",this._map.fire("boxzoomstart"));var e=this._startLayerPoint,i=this._box,n=this._map.mouseEventToLayerPoint(t),s=n.subtract(e),a=new o.Point(Math.min(n.x,e.x),Math.min(n.y,e.y));o.DomUtil.setPosition(i,a),this._moved=!0,i.style.width=Math.max(0,Math.abs(s.x)-4)+"px",i.style.height=Math.max(0,Math.abs(s.y)-4)+"px"},_finish:function(){this._moved&&(this._pane.removeChild(this._box),this._container.style.cursor=""),o.DomUtil.enableTextSelection(),o.DomUtil.enableImageDrag(),o.DomEvent.off(e,"mousemove",this._onMouseMove).off(e,"mouseup",this._onMouseUp).off(e,"keydown",this._onKeyDown)},_onMouseUp:function(t){this._finish();var e=this._map,i=e.mouseEventToLayerPoint(t);if(!this._startLayerPoint.equals(i)){var n=new o.LatLngBounds(e.layerPointToLatLng(this._startLayerPoint),e.layerPointToLatLng(i));e.fitBounds(n),e.fire("boxzoomend",{boxZoomBounds:n})}},_onKeyDown:function(t){27===t.keyCode&&this._finish()}}),o.Map.addInitHook("addHandler","boxZoom",o.Map.BoxZoom),o.Map.mergeOptions({keyboard:!0,keyboardPanOffset:80,keyboardZoomOffset:1}),o.Map.Keyboard=o.Handler.extend({keyCodes:{left:[37],right:[39],down:[40],up:[38],zoomIn:[187,107,61,171],zoomOut:[189,109,173]},initialize:function(t){this._map=t,this._setPanOffset(t.options.keyboardPanOffset),this._setZoomOffset(t.options.keyboardZoomOffset)},addHooks:function(){var t=this._map._container;-1===t.tabIndex&&(t.tabIndex="0"),o.DomEvent.on(t,"focus",this._onFocus,this).on(t,"blur",this._onBlur,this).on(t,"mousedown",this._onMouseDown,this),this._map.on("focus",this._addHooks,this).on("blur",this._removeHooks,this)},removeHooks:function(){this._removeHooks();var t=this._map._container;o.DomEvent.off(t,"focus",this._onFocus,this).off(t,"blur",this._onBlur,this).off(t,"mousedown",this._onMouseDown,this),this._map.off("focus",this._addHooks,this).off("blur",this._removeHooks,this)},_onMouseDown:function(){if(!this._focused){var i=e.body,n=e.documentElement,o=i.scrollTop||n.scrollTop,s=i.scrollLeft||n.scrollLeft;this._map._container.focus(),t.scrollTo(s,o)}},_onFocus:function(){this._focused=!0,this._map.fire("focus")},_onBlur:function(){this._focused=!1,this._map.fire("blur")},_setPanOffset:function(t){var e,i,n=this._panKeys={},o=this.keyCodes;for(e=0,i=o.left.length;i>e;e++)n[o.left[e]]=[-1*t,0];for(e=0,i=o.right.length;i>e;e++)n[o.right[e]]=[t,0];for(e=0,i=o.down.length;i>e;e++)n[o.down[e]]=[0,t];for(e=0,i=o.up.length;i>e;e++)n[o.up[e]]=[0,-1*t]},_setZoomOffset:function(t){var e,i,n=this._zoomKeys={},o=this.keyCodes;for(e=0,i=o.zoomIn.length;i>e;e++)n[o.zoomIn[e]]=t;for(e=0,i=o.zoomOut.length;i>e;e++)n[o.zoomOut[e]]=-t},_addHooks:function(){o.DomEvent.on(e,"keydown",this._onKeyDown,this)},_removeHooks:function(){o.DomEvent.off(e,"keydown",this._onKeyDown,this)},_onKeyDown:function(t){var e=t.keyCode,i=this._map;if(e in this._panKeys){if(i._panAnim&&i._panAnim._inProgress)return;i.panBy(this._panKeys[e]),i.options.maxBounds&&i.panInsideBounds(i.options.maxBounds)}else{if(!(e in this._zoomKeys))return;i.setZoom(i.getZoom()+this._zoomKeys[e])}o.DomEvent.stop(t)}}),o.Map.addInitHook("addHandler","keyboard",o.Map.Keyboard),o.Handler.MarkerDrag=o.Handler.extend({initialize:function(t){this._marker=t},addHooks:function(){var t=this._marker._icon;this._draggable||(this._draggable=new o.Draggable(t,t)),this._draggable.on("dragstart",this._onDragStart,this).on("drag",this._onDrag,this).on("dragend",this._onDragEnd,this),this._draggable.enable(),o.DomUtil.addClass(this._marker._icon,"leaflet-marker-draggable")},removeHooks:function(){this._draggable.off("dragstart",this._onDragStart,this).off("drag",this._onDrag,this).off("dragend",this._onDragEnd,this),this._draggable.disable(),o.DomUtil.removeClass(this._marker._icon,"leaflet-marker-draggable")},moved:function(){return this._draggable&&this._draggable._moved},_onDragStart:function(){this._marker.closePopup().fire("movestart").fire("dragstart")},_onDrag:function(){var t=this._marker,e=t._shadow,i=o.DomUtil.getPosition(t._icon),n=t._map.layerPointToLatLng(i);e&&o.DomUtil.setPosition(e,i),t._latlng=n,t.fire("move",{latlng:n}).fire("drag")},_onDragEnd:function(t){this._marker.fire("moveend").fire("dragend",t)}}),o.Control=o.Class.extend({options:{position:"topright"},initialize:function(t){o.setOptions(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var e=this._map;return e&&e.removeControl(this),this.options.position=t,e&&e.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this._map=t;var e=this._container=this.onAdd(t),i=this.getPosition(),n=t._controlCorners[i];return o.DomUtil.addClass(e,"leaflet-control"),-1!==i.indexOf("bottom")?n.insertBefore(e,n.firstChild):n.appendChild(e),this},removeFrom:function(t){var e=this.getPosition(),i=t._controlCorners[e];return i.removeChild(this._container),this._map=null,this.onRemove&&this.onRemove(t),this},_refocusOnMap:function(){this._map&&this._map.getContainer().focus()}}),o.control=function(t){return new o.Control(t)},o.Map.include({addControl:function(t){return t.addTo(this),this},removeControl:function(t){return t.removeFrom(this),this},_initControlPos:function(){function t(t,s){var a=i+t+" "+i+s;e[t+s]=o.DomUtil.create("div",a,n)}var e=this._controlCorners={},i="leaflet-",n=this._controlContainer=o.DomUtil.create("div",i+"control-container",this._container);t("top","left"),t("top","right"),t("bottom","left"),t("bottom","right")},_clearControlPos:function(){this._container.removeChild(this._controlContainer)}}),o.Control.Zoom=o.Control.extend({options:{position:"topleft",zoomInText:"+",zoomInTitle:"Zoom in",zoomOutText:"-",zoomOutTitle:"Zoom out"},onAdd:function(t){var e="leaflet-control-zoom",i=o.DomUtil.create("div",e+" leaflet-bar");return this._map=t,this._zoomInButton=this._createButton(this.options.zoomInText,this.options.zoomInTitle,e+"-in",i,this._zoomIn,this),this._zoomOutButton=this._createButton(this.options.zoomOutText,this.options.zoomOutTitle,e+"-out",i,this._zoomOut,this),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),i},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},_zoomIn:function(t){this._map.zoomIn(t.shiftKey?3:1)},_zoomOut:function(t){this._map.zoomOut(t.shiftKey?3:1)},_createButton:function(t,e,i,n,s,a){var r=o.DomUtil.create("a",i,n);r.innerHTML=t,r.href="#",r.title=e;var h=o.DomEvent.stopPropagation;return o.DomEvent.on(r,"click",h).on(r,"mousedown",h).on(r,"dblclick",h).on(r,"click",o.DomEvent.preventDefault).on(r,"click",s,a).on(r,"click",this._refocusOnMap,a),r},_updateDisabled:function(){var t=this._map,e="leaflet-disabled";o.DomUtil.removeClass(this._zoomInButton,e),o.DomUtil.removeClass(this._zoomOutButton,e),t._zoom===t.getMinZoom()&&o.DomUtil.addClass(this._zoomOutButton,e),t._zoom===t.getMaxZoom()&&o.DomUtil.addClass(this._zoomInButton,e)}}),o.Map.mergeOptions({zoomControl:!0}),o.Map.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new o.Control.Zoom,this.addControl(this.zoomControl))}),o.control.zoom=function(t){return new o.Control.Zoom(t)},o.Control.Attribution=o.Control.extend({options:{position:"bottomright",prefix:'<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'},initialize:function(t){o.setOptions(this,t),this._attributions={}},onAdd:function(t){this._container=o.DomUtil.create("div","leaflet-control-attribution"),o.DomEvent.disableClickPropagation(this._container);for(var e in t._layers)t._layers[e].getAttribution&&this.addAttribution(t._layers[e].getAttribution());return t.on("layeradd",this._onLayerAdd,this).on("layerremove",this._onLayerRemove,this),this._update(),this._container},onRemove:function(t){t.off("layeradd",this._onLayerAdd).off("layerremove",this._onLayerRemove)},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t?(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update(),this):void 0},removeAttribution:function(t){return t?(this._attributions[t]&&(this._attributions[t]--,this._update()),this):void 0},_update:function(){if(this._map){var t=[];for(var e in this._attributions)this._attributions[e]&&t.push(e);var i=[];this.options.prefix&&i.push(this.options.prefix),t.length&&i.push(t.join(", ")),this._container.innerHTML=i.join(" | ")}},_onLayerAdd:function(t){t.layer.getAttribution&&this.addAttribution(t.layer.getAttribution())},_onLayerRemove:function(t){t.layer.getAttribution&&this.removeAttribution(t.layer.getAttribution())}}),o.Map.mergeOptions({attributionControl:!0}),o.Map.addInitHook(function(){this.options.attributionControl&&(this.attributionControl=(new o.Control.Attribution).addTo(this))}),o.control.attribution=function(t){return new o.Control.Attribution(t)},o.Control.Scale=o.Control.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0,updateWhenIdle:!1},onAdd:function(t){this._map=t;var e="leaflet-control-scale",i=o.DomUtil.create("div",e),n=this.options;return this._addScales(n,e,i),t.on(n.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),i},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,e,i){t.metric&&(this._mScale=o.DomUtil.create("div",e+"-line",i)),t.imperial&&(this._iScale=o.DomUtil.create("div",e+"-line",i))},_update:function(){var t=this._map.getBounds(),e=t.getCenter().lat,i=6378137*Math.PI*Math.cos(e*Math.PI/180),n=i*(t.getNorthEast().lng-t.getSouthWest().lng)/180,o=this._map.getSize(),s=this.options,a=0;o.x>0&&(a=n*(s.maxWidth/o.x)),this._updateScales(s,a)},_updateScales:function(t,e){t.metric&&e&&this._updateMetric(e),t.imperial&&e&&this._updateImperial(e)},_updateMetric:function(t){var e=this._getRoundNum(t);this._mScale.style.width=this._getScaleWidth(e/t)+"px",this._mScale.innerHTML=1e3>e?e+" m":e/1e3+" km"},_updateImperial:function(t){var e,i,n,o=3.2808399*t,s=this._iScale;o>5280?(e=o/5280,i=this._getRoundNum(e),s.style.width=this._getScaleWidth(i/e)+"px",s.innerHTML=i+" mi"):(n=this._getRoundNum(o),s.style.width=this._getScaleWidth(n/o)+"px",s.innerHTML=n+" ft")},_getScaleWidth:function(t){return Math.round(this.options.maxWidth*t)-10},_getRoundNum:function(t){var e=Math.pow(10,(Math.floor(t)+"").length-1),i=t/e;return i=i>=10?10:i>=5?5:i>=3?3:i>=2?2:1,e*i}}),o.control.scale=function(t){return new o.Control.Scale(t)},o.Control.Layers=o.Control.extend({options:{collapsed:!0,position:"topright",autoZIndex:!0},initialize:function(t,e,i){o.setOptions(this,i),this._layers={},this._lastZIndex=0,this._handlingClick=!1;for(var n in t)this._addLayer(t[n],n);for(n in e)this._addLayer(e[n],n,!0)},onAdd:function(t){return this._initLayout(),this._update(),t.on("layeradd",this._onLayerChange,this).on("layerremove",this._onLayerChange,this),this._container},onRemove:function(t){t.off("layeradd",this._onLayerChange,this).off("layerremove",this._onLayerChange,this)},addBaseLayer:function(t,e){return this._addLayer(t,e),this._update(),this},addOverlay:function(t,e){return this._addLayer(t,e,!0),this._update(),this},removeLayer:function(t){var e=o.stamp(t);return delete this._layers[e],this._update(),this},_initLayout:function(){var t="leaflet-control-layers",e=this._container=o.DomUtil.create("div",t);e.setAttribute("aria-haspopup",!0),o.Browser.touch?o.DomEvent.on(e,"click",o.DomEvent.stopPropagation):o.DomEvent.disableClickPropagation(e).disableScrollPropagation(e);var i=this._form=o.DomUtil.create("form",t+"-list");if(this.options.collapsed){o.Browser.android||o.DomEvent.on(e,"mouseover",this._expand,this).on(e,"mouseout",this._collapse,this);var n=this._layersLink=o.DomUtil.create("a",t+"-toggle",e);n.href="#",n.title="Layers",o.Browser.touch?o.DomEvent.on(n,"click",o.DomEvent.stop).on(n,"click",this._expand,this):o.DomEvent.on(n,"focus",this._expand,this),o.DomEvent.on(i,"click",function(){setTimeout(o.bind(this._onInputClick,this),0)},this),this._map.on("click",this._collapse,this)}else this._expand();this._baseLayersList=o.DomUtil.create("div",t+"-base",i),this._separator=o.DomUtil.create("div",t+"-separator",i),this._overlaysList=o.DomUtil.create("div",t+"-overlays",i),e.appendChild(i)},_addLayer:function(t,e,i){var n=o.stamp(t);this._layers[n]={layer:t,name:e,overlay:i},this.options.autoZIndex&&t.setZIndex&&(this._lastZIndex++,t.setZIndex(this._lastZIndex))},_update:function(){if(this._container){this._baseLayersList.innerHTML="",this._overlaysList.innerHTML="";var t,e,i=!1,n=!1;for(t in this._layers)e=this._layers[t],this._addItem(e),n=n||e.overlay,i=i||!e.overlay;this._separator.style.display=n&&i?"":"none"}},_onLayerChange:function(t){var e=this._layers[o.stamp(t.layer)];if(e){this._handlingClick||this._update();var i=e.overlay?"layeradd"===t.type?"overlayadd":"overlayremove":"layeradd"===t.type?"baselayerchange":null;i&&this._map.fire(i,e)}},_createRadioElement:function(t,i){var n='<input type="radio" class="leaflet-control-layers-selector" name="'+t+'"';i&&(n+=' checked="checked"'),n+="/>";var o=e.createElement("div");return o.innerHTML=n,o.firstChild},_addItem:function(t){var i,n=e.createElement("label"),s=this._map.hasLayer(t.layer);t.overlay?(i=e.createElement("input"),i.type="checkbox",i.className="leaflet-control-layers-selector",i.defaultChecked=s):i=this._createRadioElement("leaflet-base-layers",s),i.layerId=o.stamp(t.layer),o.DomEvent.on(i,"click",this._onInputClick,this);var a=e.createElement("span");a.innerHTML=" "+t.name,n.appendChild(i),n.appendChild(a);var r=t.overlay?this._overlaysList:this._baseLayersList;return r.appendChild(n),n},_onInputClick:function(){var t,e,i,n=this._form.getElementsByTagName("input"),o=n.length;for(this._handlingClick=!0,t=0;o>t;t++)e=n[t],i=this._layers[e.layerId],e.checked&&!this._map.hasLayer(i.layer)?this._map.addLayer(i.layer):!e.checked&&this._map.hasLayer(i.layer)&&this._map.removeLayer(i.layer);this._handlingClick=!1,this._refocusOnMap()},_expand:function(){o.DomUtil.addClass(this._container,"leaflet-control-layers-expanded")},_collapse:function(){this._container.className=this._container.className.replace(" leaflet-control-layers-expanded","")}}),o.control.layers=function(t,e,i){return new o.Control.Layers(t,e,i)},o.PosAnimation=o.Class.extend({includes:o.Mixin.Events,run:function(t,e,i,n){this.stop(),this._el=t,this._inProgress=!0,this._newPos=e,this.fire("start"),t.style[o.DomUtil.TRANSITION]="all "+(i||.25)+"s cubic-bezier(0,0,"+(n||.5)+",1)",o.DomEvent.on(t,o.DomUtil.TRANSITION_END,this._onTransitionEnd,this),o.DomUtil.setPosition(t,e),o.Util.falseFn(t.offsetWidth),this._stepTimer=setInterval(o.bind(this._onStep,this),50)},stop:function(){this._inProgress&&(o.DomUtil.setPosition(this._el,this._getPos()),this._onTransitionEnd(),o.Util.falseFn(this._el.offsetWidth))},_onStep:function(){var t=this._getPos();return t?(this._el._leaflet_pos=t,void this.fire("step")):void this._onTransitionEnd()},_transformRe:/([-+]?(?:\d*\.)?\d+)\D*, ([-+]?(?:\d*\.)?\d+)\D*\)/,_getPos:function(){var e,i,n,s=this._el,a=t.getComputedStyle(s);if(o.Browser.any3d){if(n=a[o.DomUtil.TRANSFORM].match(this._transformRe),!n)return;e=parseFloat(n[1]),i=parseFloat(n[2])}else e=parseFloat(a.left),i=parseFloat(a.top);return new o.Point(e,i,!0)},_onTransitionEnd:function(){o.DomEvent.off(this._el,o.DomUtil.TRANSITION_END,this._onTransitionEnd,this),this._inProgress&&(this._inProgress=!1,this._el.style[o.DomUtil.TRANSITION]="",this._el._leaflet_pos=this._newPos,clearInterval(this._stepTimer),this.fire("step").fire("end"))}}),o.Map.include({setView:function(t,e,n){if(e=e===i?this._zoom:this._limitZoom(e),t=this._limitCenter(o.latLng(t),e,this.options.maxBounds),n=n||{},this._panAnim&&this._panAnim.stop(),this._loaded&&!n.reset&&n!==!0){n.animate!==i&&(n.zoom=o.extend({animate:n.animate},n.zoom),n.pan=o.extend({animate:n.animate},n.pan));var s=this._zoom!==e?this._tryAnimatedZoom&&this._tryAnimatedZoom(t,e,n.zoom):this._tryAnimatedPan(t,n.pan);if(s)return clearTimeout(this._sizeTimer),this}return this._resetView(t,e),this},panBy:function(t,e){if(t=o.point(t).round(),e=e||{},!t.x&&!t.y)return this;if(this._panAnim||(this._panAnim=new o.PosAnimation,this._panAnim.on({step:this._onPanTransitionStep,end:this._onPanTransitionEnd},this)),e.noMoveStart||this.fire("movestart"),e.animate!==!1){o.DomUtil.addClass(this._mapPane,"leaflet-pan-anim");var i=this._getMapPanePos().subtract(t);this._panAnim.run(this._mapPane,i,e.duration||.25,e.easeLinearity)}else this._rawPanBy(t),this.fire("move").fire("moveend");return this},_onPanTransitionStep:function(){this.fire("move")},_onPanTransitionEnd:function(){o.DomUtil.removeClass(this._mapPane,"leaflet-pan-anim"),this.fire("moveend")},_tryAnimatedPan:function(t,e){var i=this._getCenterOffset(t)._floor();return(e&&e.animate)===!0||this.getSize().contains(i)?(this.panBy(i,e),!0):!1}}),o.PosAnimation=o.DomUtil.TRANSITION?o.PosAnimation:o.PosAnimation.extend({run:function(t,e,i,n){this.stop(),this._el=t,this._inProgress=!0,this._duration=i||.25,this._easeOutPower=1/Math.max(n||.5,.2),this._startPos=o.DomUtil.getPosition(t),this._offset=e.subtract(this._startPos),this._startTime=+new Date,this.fire("start"),this._animate()},stop:function(){this._inProgress&&(this._step(),this._complete())},_animate:function(){this._animId=o.Util.requestAnimFrame(this._animate,this),this._step()},_step:function(){var t=+new Date-this._startTime,e=1e3*this._duration;e>t?this._runFrame(this._easeOut(t/e)):(this._runFrame(1),this._complete())},_runFrame:function(t){var e=this._startPos.add(this._offset.multiplyBy(t));o.DomUtil.setPosition(this._el,e),this.fire("step")},_complete:function(){o.Util.cancelAnimFrame(this._animId),this._inProgress=!1,this.fire("end")},_easeOut:function(t){return 1-Math.pow(1-t,this._easeOutPower)}}),o.Map.mergeOptions({zoomAnimation:!0,zoomAnimationThreshold:4}),o.DomUtil.TRANSITION&&o.Map.addInitHook(function(){this._zoomAnimated=this.options.zoomAnimation&&o.DomUtil.TRANSITION&&o.Browser.any3d&&!o.Browser.android23&&!o.Browser.mobileOpera,this._zoomAnimated&&o.DomEvent.on(this._mapPane,o.DomUtil.TRANSITION_END,this._catchTransitionEnd,this)}),o.Map.include(o.DomUtil.TRANSITION?{_catchTransitionEnd:function(t){this._animatingZoom&&t.propertyName.indexOf("transform")>=0&&this._onZoomTransitionEnd()},_nothingToAnimate:function(){return!this._container.getElementsByClassName("leaflet-zoom-animated").length},_tryAnimatedZoom:function(t,e,i){if(this._animatingZoom)return!0;if(i=i||{},!this._zoomAnimated||i.animate===!1||this._nothingToAnimate()||Math.abs(e-this._zoom)>this.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(e),o=this._getCenterOffset(t)._divideBy(1-1/n),s=this._getCenterLayerPoint()._add(o);return i.animate===!0||this.getSize().contains(o)?(this.fire("movestart").fire("zoomstart"),this._animateZoom(t,e,s,n,null,!0),!0):!1},_animateZoom:function(t,e,i,n,s,a,r){r||(this._animatingZoom=!0),o.DomUtil.addClass(this._mapPane,"leaflet-zoom-anim"),this._animateToCenter=t,this._animateToZoom=e,o.Draggable&&(o.Draggable._disabled=!0),o.Util.requestAnimFrame(function(){this.fire("zoomanim",{center:t,zoom:e,origin:i,scale:n,delta:s,backwards:a}),setTimeout(o.bind(this._onZoomTransitionEnd,this),250)},this)},_onZoomTransitionEnd:function(){this._animatingZoom&&(this._animatingZoom=!1,o.DomUtil.removeClass(this._mapPane,"leaflet-zoom-anim"),o.Util.requestAnimFrame(function(){this._resetView(this._animateToCenter,this._animateToZoom,!0,!0),o.Draggable&&(o.Draggable._disabled=!1)},this))}}:{}),o.TileLayer.include({_animateZoom:function(t){this._animating||(this._animating=!0,this._prepareBgBuffer());var e=this._bgBuffer,i=o.DomUtil.TRANSFORM,n=t.delta?o.DomUtil.getTranslateString(t.delta):e.style[i],s=o.DomUtil.getScaleString(t.scale,t.origin);e.style[i]=t.backwards?s+" "+n:n+" "+s},_endZoomAnim:function(){var t=this._tileContainer,e=this._bgBuffer;t.style.visibility="",t.parentNode.appendChild(t),o.Util.falseFn(e.offsetWidth);var i=this._map.getZoom();(i>this.options.maxZoom||i<this.options.minZoom)&&this._clearBgBuffer(),this._animating=!1},_clearBgBuffer:function(){var t=this._map;!t||t._animatingZoom||t.touchZoom._zooming||(this._bgBuffer.innerHTML="",this._bgBuffer.style[o.DomUtil.TRANSFORM]="")},_prepareBgBuffer:function(){var t=this._tileContainer,e=this._bgBuffer,i=this._getLoadedTilesPercentage(e),n=this._getLoadedTilesPercentage(t);return e&&i>.5&&.5>n?(t.style.visibility="hidden",void this._stopLoadingImages(t)):(e.style.visibility="hidden",e.style[o.DomUtil.TRANSFORM]="",this._tileContainer=e,e=this._bgBuffer=t,this._stopLoadingImages(e),void clearTimeout(this._clearBgBufferTimer))},_getLoadedTilesPercentage:function(t){var e,i,n=t.getElementsByTagName("img"),o=0;for(e=0,i=n.length;i>e;e++)n[e].complete&&o++;return o/i},_stopLoadingImages:function(t){var e,i,n,s=Array.prototype.slice.call(t.getElementsByTagName("img"));for(e=0,i=s.length;i>e;e++)n=s[e],n.complete||(n.onload=o.Util.falseFn,n.onerror=o.Util.falseFn,n.src=o.Util.emptyImageUrl,n.parentNode.removeChild(n))}}),o.Map.include({_defaultLocateOptions:{watch:!1,setView:!1,maxZoom:1/0,timeout:1e4,maximumAge:0,enableHighAccuracy:!1},locate:function(t){if(t=this._locateOptions=o.extend(this._defaultLocateOptions,t),!navigator.geolocation)return this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this;var e=o.bind(this._handleGeolocationResponse,this),i=o.bind(this._handleGeolocationError,this);return t.watch?this._locationWatchId=navigator.geolocation.watchPosition(e,i,t):navigator.geolocation.getCurrentPosition(e,i,t),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(t){var e=t.code,i=t.message||(1===e?"permission denied":2===e?"position unavailable":"timeout");this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:e,message:"Geolocation error: "+i+"."})},_handleGeolocationResponse:function(t){var e=t.coords.latitude,i=t.coords.longitude,n=new o.LatLng(e,i),s=180*t.coords.accuracy/40075017,a=s/Math.cos(o.LatLng.DEG_TO_RAD*e),r=o.latLngBounds([e-s,i-a],[e+s,i+a]),h=this._locateOptions;if(h.setView){var l=Math.min(this.getBoundsZoom(r),h.maxZoom);this.setView(n,l)}var u={latlng:n,bounds:r,timestamp:t.timestamp};for(var c in t.coords)"number"==typeof t.coords[c]&&(u[c]=t.coords[c]);this.fire("locationfound",u)}})}(window,document);
\ No newline at end of file
diff --git a/www/plugins/gis/lib/leaflet/plugins/Bing.js b/www/plugins/gis/lib/leaflet/plugins/Bing.js
new file mode 100755 (executable)
index 0000000..1a98f70
--- /dev/null
@@ -0,0 +1,130 @@
+L.BingLayer = L.TileLayer.extend({
+       options: {
+               subdomains: [0, 1, 2, 3],
+               type: 'Aerial',
+               attribution: 'Bing',
+               culture: ''
+       },
+
+       initialize: function(key, options) {
+               L.Util.setOptions(this, options);
+
+               this._key = key;
+               this._url = null;
+               this.metaRequested = false;
+       },
+
+       tile2quad: function(x, y, z) {
+               var quad = '';
+               for (var i = z; i > 0; i--) {
+                       var digit = 0;
+                       var mask = 1 << (i - 1);
+                       if ((x & mask) !== 0) digit += 1;
+                       if ((y & mask) !== 0) digit += 2;
+                       quad = quad + digit;
+               }
+               return quad;
+       },
+
+       getTileUrl: function(p, z) {
+               var zoom = this._getZoomForUrl();
+               var subdomains = this.options.subdomains,
+                       s = this.options.subdomains[Math.abs((p.x + p.y) % subdomains.length)];
+               return this._url.replace('{subdomain}', s)
+                               .replace('{quadkey}', this.tile2quad(p.x, p.y, zoom))
+                               .replace('{culture}', this.options.culture);
+       },
+
+       loadMetadata: function() {
+               if (this.metaRequested) return;
+               this.metaRequested = true;
+               var _this = this;
+               var cbid = '_bing_metadata_' + L.Util.stamp(this);
+               window[cbid] = function (meta) {
+                       window[cbid] = undefined;
+                       var e = document.getElementById(cbid);
+                       e.parentNode.removeChild(e);
+                       if (meta.errorDetails) {
+                               return;
+                       }
+                       _this.initMetadata(meta);
+               };
+               var urlScheme = (document.location.protocol === 'file:') ? 'http' : document.location.protocol.slice(0, -1);
+               var url = urlScheme + '://dev.virtualearth.net/REST/v1/Imagery/Metadata/'
+                                       + this.options.type + '?include=ImageryProviders&jsonp=' + cbid +
+                                       '&key=' + this._key + '&UriScheme=' + urlScheme;
+               var script = document.createElement('script');
+               script.type = 'text/javascript';
+               script.src = url;
+               script.id = cbid;
+               document.getElementsByTagName('head')[0].appendChild(script);
+       },
+
+       initMetadata: function(meta) {
+               var r = meta.resourceSets[0].resources[0];
+               this.options.subdomains = r.imageUrlSubdomains;
+               this._url = r.imageUrl;
+               this._providers = [];
+               if (r.imageryProviders) {
+                       for (var i = 0; i < r.imageryProviders.length; i++) {
+                               var p = r.imageryProviders[i];
+                               for (var j = 0; j < p.coverageAreas.length; j++) {
+                                       var c = p.coverageAreas[j];
+                                       var coverage = {zoomMin: c.zoomMin, zoomMax: c.zoomMax, active: false};
+                                       var bounds = new L.LatLngBounds(
+                                                       new L.LatLng(c.bbox[0]+0.01, c.bbox[1]+0.01),
+                                                       new L.LatLng(c.bbox[2]-0.01, c.bbox[3]-0.01)
+                                       );
+                                       coverage.bounds = bounds;
+                                       coverage.attrib = p.attribution;
+                                       this._providers.push(coverage);
+                               }
+                       }
+               }
+               this._update();
+       },
+
+       _update: function() {
+               if (this._url === null || !this._map) return;
+               this._update_attribution();
+               L.TileLayer.prototype._update.apply(this, []);
+       },
+
+       _update_attribution: function() {
+               var bounds = this._map.getBounds();
+               var zoom = this._map.getZoom();
+               for (var i = 0; i < this._providers.length; i++) {
+                       var p = this._providers[i];
+                       if ((zoom <= p.zoomMax && zoom >= p.zoomMin) &&
+                                       bounds.intersects(p.bounds)) {
+                               if (!p.active && this._map.attributionControl)
+                                       this._map.attributionControl.addAttribution(p.attrib);
+                               p.active = true;
+                       } else {
+                               if (p.active && this._map.attributionControl)
+                                       this._map.attributionControl.removeAttribution(p.attrib);
+                               p.active = false;
+                       }
+               }
+       },
+       
+       onAdd: function(map) {
+               this.loadMetadata();
+               L.TileLayer.prototype.onAdd.apply(this, [map]);
+       },
+
+       onRemove: function(map) {
+               for (var i = 0; i < this._providers.length; i++) {
+                       var p = this._providers[i];
+                       if (p.active && this._map.attributionControl) {
+                               this._map.attributionControl.removeAttribution(p.attrib);
+                               p.active = false;
+                       }
+               }
+               L.TileLayer.prototype.onRemove.apply(this, [map]);
+       }
+});
+
+L.bingLayer = function (key, options) {
+    return new L.BingLayer(key, options);
+};
diff --git a/www/plugins/gis/lib/leaflet/plugins/Control.FullScreen.js b/www/plugins/gis/lib/leaflet/plugins/Control.FullScreen.js
new file mode 100644 (file)
index 0000000..d356a4e
--- /dev/null
@@ -0,0 +1,171 @@
+(function() {
+
+L.Control.FullScreen = L.Control.extend({
+       options: {
+               position: 'topleft',
+               title: 'Full Screen',
+               forceSeparateButton: false,
+               forcePseudoFullscreen: false
+       },
+       
+       onAdd: function (map) {
+               var className = 'leaflet-control-zoom-fullscreen', container, content = '';
+               
+               if (map.zoomControl && !this.options.forceSeparateButton) {
+                       container = map.zoomControl._container;
+               } else {
+                       container = L.DomUtil.create('div', 'leaflet-bar');
+               }
+               
+               if (this.options.content) {
+                       content = this.options.content;
+               } else {
+                       className += ' fullscreen-icon';
+               }
+
+               this._createButton(this.options.title, className, content, container, this.toggleFullScreen, this);
+
+               return container;
+       },
+       
+       _createButton: function (title, className, content,container, fn, context) {
+               var link = L.DomUtil.create('a', className, container);
+               link.href = '#';
+               link.title = title;
+               link.innerHTML = content;
+
+               L.DomEvent
+                       .addListener(link, 'click', L.DomEvent.stopPropagation)
+                       .addListener(link, 'click', L.DomEvent.preventDefault)
+                       .addListener(link, 'click', fn, context);
+               
+               L.DomEvent
+                       .addListener(container, fullScreenApi.fullScreenEventName, L.DomEvent.stopPropagation)
+                       .addListener(container, fullScreenApi.fullScreenEventName, L.DomEvent.preventDefault)
+                       .addListener(container, fullScreenApi.fullScreenEventName, this._handleEscKey, context);
+               
+               L.DomEvent
+                       .addListener(document, fullScreenApi.fullScreenEventName, L.DomEvent.stopPropagation)
+                       .addListener(document, fullScreenApi.fullScreenEventName, L.DomEvent.preventDefault)
+                       .addListener(document, fullScreenApi.fullScreenEventName, this._handleEscKey, context);
+
+               return link;
+       },
+       
+       toggleFullScreen: function () {
+               var map = this._map;
+               map._exitFired = false;
+               if (map._isFullscreen) {
+                       if (fullScreenApi.supportsFullScreen && !this.options.forcePseudoFullscreen) {
+                               fullScreenApi.cancelFullScreen(map._container);
+                       } else {
+                               L.DomUtil.removeClass(map._container, 'leaflet-pseudo-fullscreen');
+                       }
+                       map.invalidateSize();
+                       map.fire('exitFullscreen');
+                       map._exitFired = true;
+                       map._isFullscreen = false;
+               }
+               else {
+                       if (fullScreenApi.supportsFullScreen && !this.options.forcePseudoFullscreen) {
+                               fullScreenApi.requestFullScreen(map._container);
+                       } else {
+                               L.DomUtil.addClass(map._container, 'leaflet-pseudo-fullscreen');
+                       }
+                       map.invalidateSize();
+                       map.fire('enterFullscreen');
+                       map._isFullscreen = true;
+               }
+       },
+       
+       _handleEscKey: function () {
+               var map = this._map;
+               if (!fullScreenApi.isFullScreen(map) && !map._exitFired) {
+                       map.fire('exitFullscreen');
+                       map._exitFired = true;
+                       map._isFullscreen = false;
+               }
+       }
+});
+
+L.Map.addInitHook(function () {
+       if (this.options.fullscreenControl) {
+               this.fullscreenControl = L.control.fullscreen(this.options.fullscreenControlOptions);
+               this.addControl(this.fullscreenControl);
+       }
+});
+
+L.control.fullscreen = function (options) {
+       return new L.Control.FullScreen(options);
+};
+
+/* 
+Native FullScreen JavaScript API
+-------------
+Assumes Mozilla naming conventions instead of W3C for now
+
+source : http://johndyer.name/native-fullscreen-javascript-api-plus-jquery-plugin/
+
+*/
+
+       var 
+               fullScreenApi = { 
+                       supportsFullScreen: false,
+                       isFullScreen: function() { return false; }, 
+                       requestFullScreen: function() {}, 
+                       cancelFullScreen: function() {},
+                       fullScreenEventName: '',
+                       prefix: ''
+               },
+               browserPrefixes = 'webkit moz o ms khtml'.split(' ');
+       
+       // check for native support
+       if (typeof document.exitFullscreen !== 'undefined') {
+               fullScreenApi.supportsFullScreen = true;
+       } else {
+               // check for fullscreen support by vendor prefix
+               for (var i = 0, il = browserPrefixes.length; i < il; i++ ) {
+                       fullScreenApi.prefix = browserPrefixes[i];
+                       if (typeof document[fullScreenApi.prefix + 'CancelFullScreen' ] !== 'undefined' ) {
+                               fullScreenApi.supportsFullScreen = true;
+                               break;
+                       }
+               }
+       }
+       
+       // update methods to do something useful
+       if (fullScreenApi.supportsFullScreen) {
+               fullScreenApi.fullScreenEventName = fullScreenApi.prefix + 'fullscreenchange';
+               fullScreenApi.isFullScreen = function() {
+                       switch (this.prefix) {  
+                               case '':
+                                       return document.fullScreen;
+                               case 'webkit':
+                                       return document.webkitIsFullScreen;
+                               default:
+                                       return document[this.prefix + 'FullScreen'];
+                       }
+               };
+               fullScreenApi.requestFullScreen = function(el) {
+                       return (this.prefix === '') ? el.requestFullscreen() : el[this.prefix + 'RequestFullScreen']();
+               };
+               fullScreenApi.cancelFullScreen = function(el) {
+                       return (this.prefix === '') ? document.exitFullscreen() : document[this.prefix + 'CancelFullScreen']();
+               };
+       }
+
+       // jQuery plugin
+       if (typeof jQuery !== 'undefined') {
+               jQuery.fn.requestFullScreen = function() {
+                       return this.each(function() {
+                               var el = jQuery(this);
+                               if (fullScreenApi.supportsFullScreen) {
+                                       fullScreenApi.requestFullScreen(el);
+                               }
+                       });
+               };
+       }
+
+       // export api
+       window.fullScreenApi = fullScreenApi;
+})();
diff --git a/www/plugins/gis/lib/leaflet/plugins/Control.MiniMap.js b/www/plugins/gis/lib/leaflet/plugins/Control.MiniMap.js
new file mode 100644 (file)
index 0000000..2baef61
--- /dev/null
@@ -0,0 +1,270 @@
+L.Control.MiniMap = L.Control.extend({
+       options: {
+               position: 'bottomright',
+               toggleDisplay: false,
+               zoomLevelOffset: -5,
+               zoomLevelFixed: false,
+               zoomAnimation: false,
+               autoToggleDisplay: false,
+               width: 150,
+               height: 150,
+               aimingRectOptions: {color: "#ff7800", weight: 1, clickable: false},
+               shadowRectOptions: {color: "#000000", weight: 1, clickable: false, opacity:0, fillOpacity:0}
+       },
+       
+       hideText: 'Hide MiniMap',
+       
+       showText: 'Show MiniMap',
+       
+       //layer is the map layer to be shown in the minimap
+       initialize: function (layer, options) {
+               L.Util.setOptions(this, options);
+               //Make sure the aiming rects are non-clickable even if the user tries to set them clickable (most likely by forgetting to specify them false)
+               this.options.aimingRectOptions.clickable = false;
+               this.options.shadowRectOptions.clickable = false;
+               this._layer = layer;
+       },
+       
+       onAdd: function (map) {
+
+               this._mainMap = map;
+
+               //Creating the container and stopping events from spilling through to the main map.
+               this._container = L.DomUtil.create('div', 'leaflet-control-minimap');
+               this._container.style.width = this.options.width + 'px';
+               this._container.style.height = this.options.height + 'px';
+               L.DomEvent.disableClickPropagation(this._container);
+               L.DomEvent.on(this._container, 'mousewheel', L.DomEvent.stopPropagation);
+
+
+               this._miniMap = new L.Map(this._container,
+               {
+                       attributionControl: false,
+                       zoomControl: false,
+                       zoomAnimation: this.options.zoomAnimation,
+                       autoToggleDisplay: this.options.autoToggleDisplay,
+                       touchZoom: !this.options.zoomLevelFixed,
+                       scrollWheelZoom: !this.options.zoomLevelFixed,
+                       doubleClickZoom: !this.options.zoomLevelFixed,
+                       boxZoom: !this.options.zoomLevelFixed,
+                       crs: map.options.crs
+               });
+
+               this._miniMap.addLayer(this._layer);
+
+               //These bools are used to prevent infinite loops of the two maps notifying each other that they've moved.
+               this._mainMapMoving = false;
+               this._miniMapMoving = false;
+
+               //Keep a record of this to prevent auto toggling when the user explicitly doesn't want it.
+               this._userToggledDisplay = false;
+               this._minimized = false;
+
+               if (this.options.toggleDisplay) {
+                       this._addToggleButton();
+               }
+
+               this._miniMap.whenReady(L.Util.bind(function () {
+                       this._aimingRect = L.rectangle(this._mainMap.getBounds(), this.options.aimingRectOptions).addTo(this._miniMap);
+                       this._shadowRect = L.rectangle(this._mainMap.getBounds(), this.options.shadowRectOptions).addTo(this._miniMap);
+                       this._mainMap.on('moveend', this._onMainMapMoved, this);
+                       this._mainMap.on('move', this._onMainMapMoving, this);
+                       this._miniMap.on('movestart', this._onMiniMapMoveStarted, this);
+                       this._miniMap.on('move', this._onMiniMapMoving, this);
+                       this._miniMap.on('moveend', this._onMiniMapMoved, this);
+               }, this));
+
+               return this._container;
+       },
+
+       addTo: function (map) {
+               L.Control.prototype.addTo.call(this, map);
+               this._miniMap.setView(this._mainMap.getCenter(), this._decideZoom(true));
+               this._setDisplay(this._decideMinimized());
+               return this;
+       },
+
+       onRemove: function (map) {
+               this._mainMap.off('moveend', this._onMainMapMoved, this);
+               this._mainMap.off('move', this._onMainMapMoving, this);
+               this._miniMap.off('moveend', this._onMiniMapMoved, this);
+
+               this._miniMap.removeLayer(this._layer);
+       },
+
+       _addToggleButton: function () {
+               this._toggleDisplayButton = this.options.toggleDisplay ? this._createButton(
+                               '', this.hideText, 'leaflet-control-minimap-toggle-display', this._container, this._toggleDisplayButtonClicked, this) : undefined;
+       },
+
+       _createButton: function (html, title, className, container, fn, context) {
+               var link = L.DomUtil.create('a', className, container);
+               link.innerHTML = html;
+               link.href = '#';
+               link.title = title;
+
+               var stop = L.DomEvent.stopPropagation;
+
+               L.DomEvent
+                       .on(link, 'click', stop)
+                       .on(link, 'mousedown', stop)
+                       .on(link, 'dblclick', stop)
+                       .on(link, 'click', L.DomEvent.preventDefault)
+                       .on(link, 'click', fn, context);
+
+               return link;
+       },
+
+       _toggleDisplayButtonClicked: function () {
+               this._userToggledDisplay = true;
+               if (!this._minimized) {
+                       this._minimize();
+                       this._toggleDisplayButton.title = this.showText;
+               }
+               else {
+                       this._restore();
+                       this._toggleDisplayButton.title = this.hideText;
+               }
+       },
+
+       _setDisplay: function (minimize) {
+               if (minimize != this._minimized) {
+                       if (!this._minimized) {
+                               this._minimize();
+                       }
+                       else {
+                               this._restore();
+                       }
+               }
+       },
+
+       _minimize: function () {
+               // hide the minimap
+               if (this.options.toggleDisplay) {
+                       this._container.style.width = '19px';
+                       this._container.style.height = '19px';
+                       this._toggleDisplayButton.className += ' minimized';
+               }
+               else {
+                       this._container.style.display = 'none';
+               }
+               this._minimized = true;
+       },
+
+       _restore: function () {
+               if (this.options.toggleDisplay) {
+                       this._container.style.width = this.options.width + 'px';
+                       this._container.style.height = this.options.height + 'px';
+                       this._toggleDisplayButton.className = this._toggleDisplayButton.className
+                                       .replace(/(?:^|\s)minimized(?!\S)/g, '');
+               }
+               else {
+                       this._container.style.display = 'block';
+               }
+               this._minimized = false;
+       },
+
+       _onMainMapMoved: function (e) {
+               if (!this._miniMapMoving) {
+                       this._mainMapMoving = true;
+                       this._miniMap.setView(this._mainMap.getCenter(), this._decideZoom(true));
+                       this._setDisplay(this._decideMinimized());
+               } else {
+                       this._miniMapMoving = false;
+               }
+               this._aimingRect.setBounds(this._mainMap.getBounds());
+       },
+
+       _onMainMapMoving: function (e) {
+               this._aimingRect.setBounds(this._mainMap.getBounds());
+       },
+
+       _onMiniMapMoveStarted:function (e) {
+               var lastAimingRect = this._aimingRect.getBounds();
+               var sw = this._miniMap.latLngToContainerPoint(lastAimingRect.getSouthWest());
+               var ne = this._miniMap.latLngToContainerPoint(lastAimingRect.getNorthEast());
+               this._lastAimingRectPosition = {sw:sw,ne:ne};
+       },
+
+       _onMiniMapMoving: function (e) {
+               if (!this._mainMapMoving && this._lastAimingRectPosition) {
+                       this._shadowRect.setBounds(new L.LatLngBounds(this._miniMap.containerPointToLatLng(this._lastAimingRectPosition.sw),this._miniMap.containerPointToLatLng(this._lastAimingRectPosition.ne)));
+                       this._shadowRect.setStyle({opacity:1,fillOpacity:0.3});
+               }
+       },
+
+       _onMiniMapMoved: function (e) {
+               if (!this._mainMapMoving) {
+                       this._miniMapMoving = true;
+                       this._mainMap.setView(this._miniMap.getCenter(), this._decideZoom(false));
+                       this._shadowRect.setStyle({opacity:0,fillOpacity:0});
+               } else {
+                       this._mainMapMoving = false;
+               }
+       },
+
+       _decideZoom: function (fromMaintoMini) {
+               if (!this.options.zoomLevelFixed) {
+                       if (fromMaintoMini)
+                               return this._mainMap.getZoom() + this.options.zoomLevelOffset;
+                       else {
+                               var currentDiff = this._miniMap.getZoom() - this._mainMap.getZoom();
+                               var proposedZoom = this._miniMap.getZoom() - this.options.zoomLevelOffset;
+                               var toRet;
+                               
+                               if (currentDiff > this.options.zoomLevelOffset && this._mainMap.getZoom() < this._miniMap.getMinZoom() - this.options.zoomLevelOffset) {
+                                       //This means the miniMap is zoomed out to the minimum zoom level and can't zoom any more.
+                                       if (this._miniMap.getZoom() > this._lastMiniMapZoom) {
+                                               //This means the user is trying to zoom in by using the minimap, zoom the main map.
+                                               toRet = this._mainMap.getZoom() + 1;
+                                               //Also we cheat and zoom the minimap out again to keep it visually consistent.
+                                               this._miniMap.setZoom(this._miniMap.getZoom() -1);
+                                       } else {
+                                               //Either the user is trying to zoom out past the mini map's min zoom or has just panned using it, we can't tell the difference.
+                                               //Therefore, we ignore it!
+                                               toRet = this._mainMap.getZoom();
+                                       }
+                               } else {
+                                       //This is what happens in the majority of cases, and always if you configure the min levels + offset in a sane fashion.
+                                       toRet = proposedZoom;
+                               }
+                               this._lastMiniMapZoom = this._miniMap.getZoom();
+                               return toRet;
+                       }
+               } else {
+                       if (fromMaintoMini)
+                               return this.options.zoomLevelFixed;
+                       else
+                               return this._mainMap.getZoom();
+               }
+       },
+
+       _decideMinimized: function () {
+               if (this._userToggledDisplay) {
+                       return this._minimized;
+               }
+
+               if (this.options.autoToggleDisplay) {
+                       if (this._mainMap.getBounds().contains(this._miniMap.getBounds())) {
+                               return true;
+                       }
+                       return false;
+               }
+
+               return this._minimized;
+       }
+});
+
+L.Map.mergeOptions({
+       miniMapControl: false
+});
+
+L.Map.addInitHook(function () {
+       if (this.options.miniMapControl) {
+               this.miniMapControl = (new L.Control.MiniMap()).addTo(this);
+       }
+});
+
+L.control.minimap = function (options) {
+       return new L.Control.MiniMap(options);
+};
diff --git a/www/plugins/gis/lib/leaflet/plugins/GPX.Speed.js b/www/plugins/gis/lib/leaflet/plugins/GPX.Speed.js
new file mode 100755 (executable)
index 0000000..9f4fbc9
--- /dev/null
@@ -0,0 +1,79 @@
+//#include 'GPX.js'
+
+(function() {
+
+function d2h(d) {
+       var hex = '0123456789ABCDEF';
+       var r = '';
+       d = Math.floor(d);
+       while (d !== 0) {
+               r = hex[d % 16] + r;
+               d = Math.floor(d / 16);
+       }
+       while (r.length < 2) r = '0' + r;
+       return r;
+}
+
+function gradient(color) {
+       // First arc (0, PI) in HSV colorspace
+       function f2h(d) { return d2h(256 * d); }
+       if (color < 0)
+               return '#FF0000';
+       else if (color < 1.0/3)
+               return '#FF' + f2h(3 * color) + '00';
+       else if (color < 2.0/3)
+               return '#' + f2h(2 - 3 * color) + 'FF00';
+       else if (color < 1)
+               return '#00FF' + f2h(3 * color - 2);
+       else
+               return '#00FFFF';
+}
+
+function gpx2time(s) {
+       // 2011-09-24T12:07:53Z
+       if (s.length !== 10 + 1 + 8 + 1)
+               return new Date();
+       return new Date(s);
+}
+
+L.GPX.include({
+       options: {
+               maxSpeed: 110,
+               chunks: 200
+       },
+
+       speedSplitEnable: function(options) {
+               L.Util.setOptions(this, options);
+               return this.on('addline', this.speed_split, this);
+       },
+
+       speedSplitDisable: function() {
+               return this.off('addline', this.speed_split, this);
+       },
+
+       speed_split: function(e) {
+               var l = e.line.pop(), ll = l.getLatLngs();
+               var chunk = Math.floor(ll.length / this.options.chunks);
+               if (chunk < 3) chunk = 3;
+               var p = null;
+               for (var i = 0; i < ll.length; i += chunk) {
+                       var d = 0, t = null;
+                       if (i + chunk > ll.length)
+                               chunk = ll.length - i;
+                       for (var j = 0; j < chunk; j++) {
+                               if (p) d += p.distanceTo(ll[i+j]);
+                               p = ll[i + j];
+                               if (!t) t = gpx2time(p.meta.time);
+                       }
+                       p = ll[i + chunk - 1];
+                       t = (gpx2time(p.meta.time) - t) / (3600 * 1000);
+                       var speed = 0.001 * d / t;
+                       var color = gradient(speed / this.options.maxSpeed);
+                       var poly = new L.Polyline(ll.slice(i, i+chunk+1), {color: color, weight: 2, opacity: 1});
+                       poly.bindPopup('Dist: ' + d.toFixed() + 'm; Speed: ' + speed.toFixed(2) + ' km/h');
+                       e.line.push(poly);
+               }
+       }
+
+});
+})();
diff --git a/www/plugins/gis/lib/leaflet/plugins/GPX.js b/www/plugins/gis/lib/leaflet/plugins/GPX.js
new file mode 100755 (executable)
index 0000000..e2e7d6a
--- /dev/null
@@ -0,0 +1,141 @@
+L.GPX = L.FeatureGroup.extend({
+       initialize: function(gpx, options) {
+               L.Util.setOptions(this, options);
+               this._gpx = gpx;
+               this._layers = {};
+               
+               if (gpx) {
+                       this.addGPX(gpx, options, this.options.async);
+               }
+       },
+       
+       loadXML: function(url, cb, options, async) {
+               if (async === undefined) async = this.options.async;
+               if (options === undefined) options = this.options;
+
+               var req = new window.XMLHttpRequest();
+               req.open('GET', url, async);
+               try {
+                       req.overrideMimeType('text/xml'); // unsupported by IE
+               } catch(e) {}
+               req.onreadystatechange = function() {
+                       if (req.readyState !== 4) return;
+                       if(req.status === 200) cb(req.responseXML, options);
+               };
+               req.send(null);
+       },
+
+       _humanLen: function(l) {
+               if (l < 2000)
+                       return l.toFixed(0) + ' m';
+               else
+                       return (l/1000).toFixed(1) + ' km';
+       },
+       
+       _polylineLen: function(line)//line is a L.Polyline()
+       {
+               var ll = line._latlngs;
+               var d = 0, p = null;
+               for (var i = 0; i < ll.length; i++)
+               {
+                       if(i && p)
+                               d += p.distanceTo(ll[i]);
+                       p = ll[i];
+               }
+               return d;
+       },
+
+       addGPX: function(url, options, async) {
+               var _this = this;
+               var cb = function(gpx, options) { _this._addGPX(gpx, options); };
+               this.loadXML(url, cb, options, async);
+       },
+
+       _addGPX: function(gpx, options) {
+               var layers = this.parseGPX(gpx, options);
+               if (!layers) return;
+               this.addLayer(layers);
+               this.fire('loaded');
+       },      
+
+       parseGPX: function(xml, options) {
+               var j, i, el, layers = [];
+               var named = false, tags = [['rte','rtept'], ['trkseg','trkpt']];
+
+               for (j = 0; j < tags.length; j++) {
+                       el = xml.getElementsByTagName(tags[j][0]);
+                       for (i = 0; i < el.length; i++) {
+                               var l = this.parse_trkseg(el[i], xml, options, tags[j][1]);
+                               for (var k = 0; k < l.length; k++) {
+                                       if (this.parse_name(el[i], l[k])) named = true;
+                                       layers.push(l[k]);
+                               }
+                       }
+               }
+
+               el = xml.getElementsByTagName('wpt');
+               if (options.display_wpt !== false) {
+                       for (i = 0; i < el.length; i++) {
+                               var marker = this.parse_wpt(el[i], xml, options);
+                               if (!marker) continue;
+                               if (this.parse_name(el[i], marker)) named = true;
+                               layers.push(marker);
+                       }
+               }
+
+               if (!layers.length) return;
+               var layer = layers[0];
+               if (layers.length > 1) 
+                       layer = new L.FeatureGroup(layers);
+               if (!named) this.parse_name(xml, layer);
+               return layer;
+       },
+
+       parse_name: function(xml, layer) {
+               var i, el, txt='', name, descr='', len=0;
+               el = xml.getElementsByTagName('name');
+               if (el.length)
+                       name = el[0].childNodes[0].nodeValue;
+               el = xml.getElementsByTagName('desc');
+               for (i = 0; i < el.length; i++) {
+                       for (var j = 0; j < el[i].childNodes.length; j++)
+                               descr = descr + el[i].childNodes[j].nodeValue;
+               }
+
+               if(layer instanceof L.Path)
+                       len = this._polylineLen(layer);
+
+               if (name) txt += '<h2>' + name + '</h2>' + descr;
+               if (len) txt += '<p>' + this._humanLen(len) + '</p>';
+               
+               if (layer && layer._popup === undefined) layer.bindPopup(txt);
+               return txt;
+       },
+
+       parse_trkseg: function(line, xml, options, tag) {
+               var el = line.getElementsByTagName(tag);
+               if (!el.length) return [];
+               var coords = [];
+               for (var i = 0; i < el.length; i++) {
+                       var ll = new L.LatLng(el[i].getAttribute('lat'),
+                                               el[i].getAttribute('lon'));
+                       ll.meta = {};
+                       for (var j in el[i].childNodes) {
+                               var e = el[i].childNodes[j];
+                               if (!e.tagName) continue;
+                               ll.meta[e.tagName] = e.textContent;
+                       }
+                       coords.push(ll);
+               }
+               var l = [new L.Polyline(coords, options)];
+               this.fire('addline', {line:l});
+               return l;
+       },
+
+       parse_wpt: function(e, xml, options) {
+               var m = new L.Marker(new L.LatLng(e.getAttribute('lat'),
+                                               e.getAttribute('lon')), options);
+               this.fire('addpoint', {point:m});
+               return m;
+       }
+});
diff --git a/www/plugins/gis/lib/leaflet/plugins/Google.js b/www/plugins/gis/lib/leaflet/plugins/Google.js
new file mode 100755 (executable)
index 0000000..c919618
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * Google layer using Google Maps API
+ */
+
+/* global google: true */
+
+L.Google = L.Class.extend({
+       includes: L.Mixin.Events,
+
+       options: {
+               minZoom: 0,
+               maxZoom: 18,
+               tileSize: 256,
+               subdomains: 'abc',
+               errorTileUrl: '',
+               attribution: '',
+               opacity: 1,
+               continuousWorld: false,
+               noWrap: false,
+               mapOptions: {
+                       backgroundColor: '#dddddd'
+               }
+       },
+
+       // Possible types: SATELLITE, ROADMAP, HYBRID, TERRAIN
+       initialize: function(type, options) {
+               L.Util.setOptions(this, options);
+
+               this._ready = google.maps.Map !== undefined;
+               if (!this._ready) L.Google.asyncWait.push(this);
+
+               this._type = type || 'SATELLITE';
+       },
+
+       onAdd: function(map, insertAtTheBottom) {
+               this._map = map;
+               this._insertAtTheBottom = insertAtTheBottom;
+
+               // create a container div for tiles
+               this._initContainer();
+               this._initMapObject();
+
+               // set up events
+               map.on('viewreset', this._resetCallback, this);
+
+               this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
+               map.on('move', this._update, this);
+
+               map.on('zoomanim', this._handleZoomAnim, this);
+
+               //20px instead of 1em to avoid a slight overlap with google's attribution
+               map._controlCorners.bottomright.style.marginBottom = '20px';
+
+               this._reset();
+               this._update();
+       },
+
+       onRemove: function(map) {
+               map._container.removeChild(this._container);
+
+               map.off('viewreset', this._resetCallback, this);
+
+               map.off('move', this._update, this);
+
+               map.off('zoomanim', this._handleZoomAnim, this);
+
+               map._controlCorners.bottomright.style.marginBottom = '0em';
+       },
+
+       getAttribution: function() {
+               return this.options.attribution;
+       },
+
+       setOpacity: function(opacity) {
+               this.options.opacity = opacity;
+               if (opacity < 1) {
+                       L.DomUtil.setOpacity(this._container, opacity);
+               }
+       },
+
+       setElementSize: function(e, size) {
+               e.style.width = size.x + 'px';
+               e.style.height = size.y + 'px';
+       },
+
+       _initContainer: function() {
+               var tilePane = this._map._container,
+                       first = tilePane.firstChild;
+
+               if (!this._container) {
+                       this._container = L.DomUtil.create('div', 'leaflet-google-layer leaflet-top leaflet-left');
+                       this._container.id = '_GMapContainer_' + L.Util.stamp(this);
+                       this._container.style.zIndex = 'auto';
+               }
+
+               tilePane.insertBefore(this._container, first);
+
+               this.setOpacity(this.options.opacity);
+               this.setElementSize(this._container, this._map.getSize());
+       },
+
+       _initMapObject: function() {
+               if (!this._ready) return;
+               this._google_center = new google.maps.LatLng(0, 0);
+               var map = new google.maps.Map(this._container, {
+                       center: this._google_center,
+                       zoom: 0,
+                       tilt: 0,
+                       mapTypeId: google.maps.MapTypeId[this._type],
+                       disableDefaultUI: true,
+                       keyboardShortcuts: false,
+                       draggable: false,
+                       disableDoubleClickZoom: true,
+                       scrollwheel: false,
+                       streetViewControl: false,
+                       styles: this.options.mapOptions.styles,
+                       backgroundColor: this.options.mapOptions.backgroundColor
+               });
+
+               var _this = this;
+               this._reposition = google.maps.event.addListenerOnce(map, 'center_changed',
+                       function() { _this.onReposition(); });
+               this._google = map;
+
+               google.maps.event.addListenerOnce(map, 'idle',
+                       function() { _this._checkZoomLevels(); });
+               google.maps.event.addListenerOnce(map, 'tilesloaded',
+                       function() { _this.fire('load'); });
+               //Reporting that map-object was initialized.
+               this.fire('MapObjectInitialized', { mapObject: map });
+       },
+
+       _checkZoomLevels: function() {
+               //setting the zoom level on the Google map may result in a different zoom level than the one requested
+               //(it won't go beyond the level for which they have data).
+               // verify and make sure the zoom levels on both Leaflet and Google maps are consistent
+               if (this._google.getZoom() !== this._map.getZoom()) {
+                       //zoom levels are out of sync. Set the leaflet zoom level to match the google one
+                       this._map.setZoom( this._google.getZoom() );
+               }
+       },
+
+       _resetCallback: function(e) {
+               this._reset(e.hard);
+       },
+
+       _reset: function(clearOldContainer) {
+               this._initContainer();
+       },
+
+       _update: function(e) {
+               if (!this._google) return;
+               this._resize();
+
+               var center = this._map.getCenter();
+               var _center = new google.maps.LatLng(center.lat, center.lng);
+
+               this._google.setCenter(_center);
+               this._google.setZoom(Math.round(this._map.getZoom()));
+
+               this._checkZoomLevels();
+       },
+
+       _resize: function() {
+               var size = this._map.getSize();
+               if (this._container.style.width === size.x &&
+                               this._container.style.height === size.y)
+                       return;
+               this.setElementSize(this._container, size);
+               this.onReposition();
+       },
+
+
+       _handleZoomAnim: function (e) {
+               var center = e.center;
+               var _center = new google.maps.LatLng(center.lat, center.lng);
+
+               this._google.setCenter(_center);
+               this._google.setZoom(Math.round(e.zoom));
+       },
+
+
+       onReposition: function() {
+               if (!this._google) return;
+               google.maps.event.trigger(this._google, 'resize');
+       }
+});
+
+L.Google.asyncWait = [];
+L.Google.asyncInitialize = function() {
+       var i;
+       for (i = 0; i < L.Google.asyncWait.length; i++) {
+               var o = L.Google.asyncWait[i];
+               o._ready = true;
+               if (o._container) {
+                       o._initMapObject();
+                       o._update();
+               }
+       }
+       L.Google.asyncWait = [];
+};
diff --git a/www/plugins/gis/lib/leaflet/plugins/KML.js b/www/plugins/gis/lib/leaflet/plugins/KML.js
new file mode 100755 (executable)
index 0000000..acdc4e8
--- /dev/null
@@ -0,0 +1,485 @@
+L.KML = L.FeatureGroup.extend({
+       options: {
+               async: true
+       },
+
+       initialize: function(kml, options) {
+               L.Util.setOptions(this, options);
+               this._kml = kml;
+               this._layers = {};
+
+               if (kml) {
+                       this.addKML(kml, options, this.options.async);
+               }
+       },
+
+       loadXML: function(url, cb, options, async) {
+               if (async === undefined) async = this.options.async;
+               if (options === undefined) options = this.options;
+
+               var req = new window.XMLHttpRequest();
+               
+               // Check for IE8 and IE9 Fix Cors for those browsers
+               if (req.withCredentials === undefined && typeof window.XDomainRequest !== 'undefined') {
+                       var xdr = new window.XDomainRequest();
+                       xdr.open('GET', url, async);
+                       xdr.onprogress = function () { };
+                       xdr.ontimeout = function () { };
+                       xdr.onerror = function () { };
+                       xdr.onload = function () {
+                               if (xdr.responseText) {
+                                       var xml = new window.ActiveXObject('Microsoft.XMLDOM');
+                                       xml.loadXML(xdr.responseText);
+                                       cb(xml, options);
+                               }
+                       };
+                       setTimeout(function () { xdr.send(); }, 0);
+               } else {
+                       req.open('GET', url, async);
+                       try {
+                               req.overrideMimeType('text/xml'); // unsupported by IE
+                       } catch (e) { }
+                       req.onreadystatechange = function () {
+                               if (req.readyState !== 4) return;
+                               if (req.status === 200) cb(req.responseXML, options);
+                       };
+                       req.send(null);
+               }
+       },
+
+       addKML: function(url, options, async) {
+               var _this = this;
+               var cb = function(gpx, options) { _this._addKML(gpx, options); };
+               this.loadXML(url, cb, options, async);
+       },
+
+       _addKML: function(xml, options) {
+               var layers = L.KML.parseKML(xml);
+               if (!layers || !layers.length) return;
+               for (var i = 0; i < layers.length; i++) {
+                       this.fire('addlayer', {
+                               layer: layers[i]
+                       });
+                       this.addLayer(layers[i]);
+               }
+               this.latLngs = L.KML.getLatLngs(xml);
+               this.fire('loaded');
+       },
+
+       latLngs: []
+});
+
+L.Util.extend(L.KML, {
+
+       parseKML: function (xml) {
+               var style = this.parseStyles(xml);
+               this.parseStyleMap(xml, style);
+               var el = xml.getElementsByTagName('Folder');
+               var layers = [], l;
+               for (var i = 0; i < el.length; i++) {
+                       if (!this._check_folder(el[i])) { continue; }
+                       l = this.parseFolder(el[i], style);
+                       if (l) { layers.push(l); }
+               }
+               el = xml.getElementsByTagName('Placemark');
+               for (var j = 0; j < el.length; j++) {
+                       if (!this._check_folder(el[j])) { continue; }
+                       l = this.parsePlacemark(el[j], xml, style);
+                       if (l) { layers.push(l); }
+               }
+               el = xml.getElementsByTagName('GroundOverlay');
+               for (var k = 0; k < el.length; k++) {
+                       l = this.parseGroundOverlay(el[k]);
+                       if (l) { layers.push(l); }
+               }
+               return layers;
+       },
+
+       // Return false if e's first parent Folder is not [folder]
+       // - returns true if no parent Folders
+       _check_folder: function (e, folder) {
+               e = e.parentNode;
+               while (e && e.tagName !== 'Folder')
+               {
+                       e = e.parentNode;
+               }
+               return !e || e === folder;
+       },
+
+       parseStyles: function(xml) {
+               var styles = {};
+               var sl = xml.getElementsByTagName('Style');
+               for (var i=0, len=sl.length; i<len; i++) {
+                       var style = this.parseStyle(sl[i]);
+                       if (style) {
+                               var styleName = '#' + style.id;
+                               styles[styleName] = style;
+                       }
+               }
+               return styles;
+       },
+
+       parseStyle: function (xml) {
+               var style = {}, poptions = {}, ioptions = {}, el, id;
+
+               var attributes = { color: true, width: true, Icon: true, href: true, hotSpot: true };
+
+               function _parse(xml) {
+                       var options = {};
+                       for (var i = 0; i < xml.childNodes.length; i++) {
+                               var e = xml.childNodes[i];
+                               var key = e.tagName;
+                               if (!attributes[key]) { continue; }
+                               if (key === 'hotSpot')
+                               {
+                                       for (var j = 0; j < e.attributes.length; j++) {
+                                               options[e.attributes[j].name] = e.attributes[j].nodeValue;
+                                       }
+                               } else {
+                                       var value = e.childNodes[0].nodeValue;
+                                       if (key === 'color') {
+                                               options.opacity = parseInt(value.substring(0, 2), 16) / 255.0;
+                                               options.color = '#' + value.substring(6, 8) + value.substring(4, 6) + value.substring(2, 4);
+                                       } else if (key === 'width') {
+                                               options.weight = value;
+                                       } else if (key === 'Icon') {
+                                               ioptions = _parse(e);
+                                               if (ioptions.href) { options.href = ioptions.href; }
+                                       } else if (key === 'href') {
+                                               options.href = value;
+                                       }
+                               }
+                       }
+                       return options;
+               }
+
+               el = xml.getElementsByTagName('LineStyle');
+               if (el && el[0]) { style = _parse(el[0]); }
+               el = xml.getElementsByTagName('PolyStyle');
+               if (el && el[0]) { poptions = _parse(el[0]); }
+               if (poptions.color) { style.fillColor = poptions.color; }
+               if (poptions.opacity) { style.fillOpacity = poptions.opacity; }
+               el = xml.getElementsByTagName('IconStyle');
+               if (el && el[0]) { ioptions = _parse(el[0]); }
+               if (ioptions.href) {
+                       style.icon = new L.KMLIcon({
+                               iconUrl: ioptions.href,
+                               shadowUrl: null,
+                               anchorRef: {x: ioptions.x, y: ioptions.y},
+                               anchorType:     {x: ioptions.xunits, y: ioptions.yunits}
+                       });
+               }
+               
+               id = xml.getAttribute('id');
+               if (id && style) {
+                       style.id = id;
+               }
+               
+               return style;
+       },
+       
+       parseStyleMap: function (xml, existingStyles) {
+               var sl = xml.getElementsByTagName('StyleMap');
+               
+               for (var i = 0; i < sl.length; i++) {
+                       var e = sl[i], el;
+                       var smKey, smStyleUrl;
+                       
+                       el = e.getElementsByTagName('key');
+                       if (el && el[0]) { smKey = el[0].textContent; }
+                       el = e.getElementsByTagName('styleUrl');
+                       if (el && el[0]) { smStyleUrl = el[0].textContent; }
+                       
+                       if (smKey === 'normal')
+                       {
+                               existingStyles['#' + e.getAttribute('id')] = existingStyles[smStyleUrl];
+                       }
+               }
+               
+               return;
+       },
+
+       parseFolder: function (xml, style) {
+               var el, layers = [], l;
+               el = xml.getElementsByTagName('Folder');
+               for (var i = 0; i < el.length; i++) {
+                       if (!this._check_folder(el[i], xml)) { continue; }
+                       l = this.parseFolder(el[i], style);
+                       if (l) { layers.push(l); }
+               }
+               el = xml.getElementsByTagName('Placemark');
+               for (var j = 0; j < el.length; j++) {
+                       if (!this._check_folder(el[j], xml)) { continue; }
+                       l = this.parsePlacemark(el[j], xml, style);
+                       if (l) { layers.push(l); }
+               }
+               el = xml.getElementsByTagName('GroundOverlay');
+               for (var k = 0; k < el.length; k++) {
+                       if (!this._check_folder(el[k], xml)) { continue; }
+                       l = this.parseGroundOverlay(el[k]);
+                       if (l) { layers.push(l); }
+               }
+               if (!layers.length) { return; }
+               if (layers.length === 1) { return layers[0]; }
+               return new L.FeatureGroup(layers);
+       },
+
+       parsePlacemark: function (place, xml, style) {
+               var h, i, j, k, el, il, options = {};
+
+               var multi = ['MultiGeometry', 'MultiTrack', 'gx:MultiTrack'];
+               for (h in multi) {
+                       el = place.getElementsByTagName(multi[h]);
+                       for (i = 0; i < el.length; i++) {
+                               return this.parsePlacemark(el[i], xml, style);
+                       }
+               }
+
+               el = place.getElementsByTagName('styleUrl');
+               for (i = 0; i < el.length; i++) {
+                       var url = el[i].childNodes[0].nodeValue;
+                       for (var a in style[url]) {
+                               options[a] = style[url][a];
+                       }
+               }
+               
+               il = place.getElementsByTagName('Style')[0];
+               if (il) {
+                       var inlineStyle = this.parseStyle(place);
+                       if (inlineStyle) {
+                               for (k in inlineStyle) {
+                                       options[k] = inlineStyle[k];
+                               }
+                       }
+               }
+               
+               var layers = [];
+
+               var parse = ['LineString', 'Polygon', 'Point', 'Track', 'gx:Track'];
+               for (j in parse) {
+                       var tag = parse[j];
+                       el = place.getElementsByTagName(tag);
+                       for (i = 0; i < el.length; i++) {
+                               var l = this['parse' + tag.replace(/gx:/, '')](el[i], xml, options);
+                               if (l) { layers.push(l); }
+                       }
+               }
+
+               if (!layers.length) {
+                       return;
+               }
+               var layer = layers[0];
+               if (layers.length > 1) {
+                       layer = new L.FeatureGroup(layers);
+               }
+
+               var name, descr = '';
+               el = place.getElementsByTagName('name');
+               if (el.length && el[0].childNodes.length) {
+                       name = el[0].childNodes[0].nodeValue;
+               }
+               el = place.getElementsByTagName('description');
+               for (i = 0; i < el.length; i++) {
+                       for (j = 0; j < el[i].childNodes.length; j++) {
+                               descr = descr + el[i].childNodes[j].nodeValue;
+                       }
+               }
+
+               if (name) {
+                       layer.on('add', function(e) {
+                               layer.bindPopup('<h2>' + name + '</h2>' + descr);
+                       });
+               }
+
+               return layer;
+       },
+
+       parseCoords: function (xml) {
+               var el = xml.getElementsByTagName('coordinates');
+               return this._read_coords(el[0]);
+       },
+
+       parseLineString: function (line, xml, options) {
+               var coords = this.parseCoords(line);
+               if (!coords.length) { return; }
+               return new L.Polyline(coords, options);
+       },
+
+       parseTrack: function (line, xml, options) {
+               var el = xml.getElementsByTagName('gx:coord');
+               if (el.length === 0) { el = xml.getElementsByTagName('coord'); }
+               var coords = [];
+               for (var j = 0; j < el.length; j++) {
+                       coords = coords.concat(this._read_gxcoords(el[j]));
+               }
+               if (!coords.length) { return; }
+               return new L.Polyline(coords, options);
+       },
+
+       parsePoint: function (line, xml, options) {
+               var el = line.getElementsByTagName('coordinates');
+               if (!el.length) {
+                       return;
+               }
+               var ll = el[0].childNodes[0].nodeValue.split(',');
+               return new L.KMLMarker(new L.LatLng(ll[1], ll[0]), options);
+       },
+
+       parsePolygon: function (line, xml, options) {
+               var el, polys = [], inner = [], i, coords;
+               el = line.getElementsByTagName('outerBoundaryIs');
+               for (i = 0; i < el.length; i++) {
+                       coords = this.parseCoords(el[i]);
+                       if (coords) {
+                               polys.push(coords);
+                       }
+               }
+               el = line.getElementsByTagName('innerBoundaryIs');
+               for (i = 0; i < el.length; i++) {
+                       coords = this.parseCoords(el[i]);
+                       if (coords) {
+                               inner.push(coords);
+                       }
+               }
+               if (!polys.length) {
+                       return;
+               }
+               if (options.fillColor) {
+                       options.fill = true;
+               }
+               if (polys.length === 1) {
+                       return new L.Polygon(polys.concat(inner), options);
+               }
+               return new L.MultiPolygon(polys, options);
+       },
+
+       getLatLngs: function (xml) {
+               var el = xml.getElementsByTagName('coordinates');
+               var coords = [];
+               for (var j = 0; j < el.length; j++) {
+                       // text might span many childNodes
+                       coords = coords.concat(this._read_coords(el[j]));
+               }
+               return coords;
+       },
+
+       _read_coords: function (el) {
+               var text = '', coords = [], i;
+               for (i = 0; i < el.childNodes.length; i++) {
+                       text = text + el.childNodes[i].nodeValue;
+               }
+               text = text.split(/[\s\n]+/);
+               for (i = 0; i < text.length; i++) {
+                       var ll = text[i].split(',');
+                       if (ll.length < 2) {
+                               continue;
+                       }
+                       coords.push(new L.LatLng(ll[1], ll[0]));
+               }
+               return coords;
+       },
+
+       _read_gxcoords: function (el) {
+               var text = '', coords = [];
+               text = el.firstChild.nodeValue.split(' ');
+               coords.push(new L.LatLng(text[1], text[0]));
+               return coords;
+       },
+
+       parseGroundOverlay: function (xml) {
+               var latlonbox = xml.getElementsByTagName('LatLonBox')[0];
+               var bounds = new L.LatLngBounds(
+                       [
+                               latlonbox.getElementsByTagName('south')[0].childNodes[0].nodeValue,
+                               latlonbox.getElementsByTagName('west')[0].childNodes[0].nodeValue
+                       ],
+                       [
+                               latlonbox.getElementsByTagName('north')[0].childNodes[0].nodeValue,
+                               latlonbox.getElementsByTagName('east')[0].childNodes[0].nodeValue
+                       ]
+               );
+               var attributes = {Icon: true, href: true, color: true};
+               function _parse(xml) {
+                       var options = {}, ioptions = {};
+                       for (var i = 0; i < xml.childNodes.length; i++) {
+                               var e = xml.childNodes[i];
+                               var key = e.tagName;
+                               if (!attributes[key]) { continue; }
+                               var value = e.childNodes[0].nodeValue;
+                               if (key === 'Icon') {
+                                       ioptions = _parse(e);
+                                       if (ioptions.href) { options.href = ioptions.href; }
+                               } else if (key === 'href') {
+                                       options.href = value;
+                               } else if (key === 'color') {
+                                       options.opacity = parseInt(value.substring(0, 2), 16) / 255.0;
+                                       options.color = '#' + value.substring(6, 8) + value.substring(4, 6) + value.substring(2, 4);
+                               }
+                       }
+                       return options;
+               }
+               var options = {};
+               options = _parse(xml);
+               if (latlonbox.getElementsByTagName('rotation')[0] !== undefined) {
+                       var rotation = latlonbox.getElementsByTagName('rotation')[0].childNodes[0].nodeValue;
+                       options.rotation = parseFloat(rotation);
+               }
+               return new L.RotatedImageOverlay(options.href, bounds, {opacity: options.opacity, angle: options.rotation});
+       }
+
+});
+
+L.KMLIcon = L.Icon.extend({
+       _setIconStyles: function (img, name) {
+               L.Icon.prototype._setIconStyles.apply(this, [img, name]);
+               var options = this.options;
+               this.options.popupAnchor = [0,(-0.83*img.height)];
+               if (options.anchorType.x === 'fraction')
+                       img.style.marginLeft = (-options.anchorRef.x * img.width) + 'px';
+               if (options.anchorType.y === 'fraction')
+                       img.style.marginTop  = ((-(1 - options.anchorRef.y) * img.height) + 1) + 'px';
+               if (options.anchorType.x === 'pixels')
+                       img.style.marginLeft = (-options.anchorRef.x) + 'px';
+               if (options.anchorType.y === 'pixels')
+                       img.style.marginTop  = (options.anchorRef.y - img.height + 1) + 'px';
+       }
+});
+
+
+L.KMLMarker = L.Marker.extend({
+       options: {
+               icon: new L.KMLIcon.Default()
+       }
+});
+
+// Inspired by https://github.com/bbecquet/Leaflet.PolylineDecorator/tree/master/src
+L.RotatedImageOverlay = L.ImageOverlay.extend({
+       options: {
+               angle: 0
+       },
+       _reset: function () {
+               L.ImageOverlay.prototype._reset.call(this);
+               this._rotate();
+       },
+       _animateZoom: function (e) {
+               L.ImageOverlay.prototype._animateZoom.call(this, e);
+               this._rotate();
+       },
+       _rotate: function () {
+        if (L.DomUtil.TRANSFORM) {
+            // use the CSS transform rule if available
+            this._image.style[L.DomUtil.TRANSFORM] += ' rotate(' + this.options.angle + 'deg)';
+        } else if(L.Browser.ie) {
+            // fallback for IE6, IE7, IE8
+            var rad = this.options.angle * (Math.PI / 180),
+                costheta = Math.cos(rad),
+                sintheta = Math.sin(rad);
+            this._image.style.filter += ' progid:DXImageTransform.Microsoft.Matrix(sizingMethod=\'auto expand\', M11=' + 
+                costheta + ', M12=' + (-sintheta) + ', M21=' + sintheta + ', M22=' + costheta + ')';                
+        }
+       },
+       getBounds: function() {
+               return this._bounds;
+       }
+});
+
diff --git a/www/plugins/gis/lib/leaflet/plugins/Marker.Rotate.js b/www/plugins/gis/lib/leaflet/plugins/Marker.Rotate.js
new file mode 100755 (executable)
index 0000000..174d76b
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Based on comments by @runanet and @coomsie 
+ * https://github.com/CloudMade/Leaflet/issues/386
+ *
+ * Wrapping function is needed to preserve L.Marker.update function
+ */
+(function () {
+       var _old__setPos = L.Marker.prototype._setPos;
+       L.Marker.include({
+               _updateImg: function(i, a, s) {
+                       a = L.point(s).divideBy(2)._subtract(L.point(a));
+                       var transform = '';
+                       transform += ' translate(' + -a.x + 'px, ' + -a.y + 'px)';
+                       transform += ' rotate(' + this.options.iconAngle + 'deg)';
+                       transform += ' translate(' + a.x + 'px, ' + a.y + 'px)';
+                       i.style[L.DomUtil.TRANSFORM] += transform;
+               },
+
+               setIconAngle: function (iconAngle) {
+                       this.options.iconAngle = iconAngle;
+                       if (this._map)
+                               this.update();
+               },
+
+               _setPos: function (pos) {
+                       if (this._icon)
+                               this._icon.style[L.DomUtil.TRANSFORM] = '';
+                       if (this._shadow)
+                               this._shadow.style[L.DomUtil.TRANSFORM] = '';
+
+                       _old__setPos.apply(this,[pos]);
+
+                       if (this.options.iconAngle) {
+                               var defaultIcon = new L.Icon.Default;
+                               var a = this.options.icon.options.iconAnchor || defaultIcon.options.iconAnchor;
+                               var s = this.options.icon.options.iconSize || defaultIcon.options.iconSize;
+                               var i;
+                               if (this._icon) {
+                                       i = this._icon;
+                                       this._updateImg(i, a, s);
+                               }
+                               if (this._shadow) {
+                                       if (this.options.icon.options.shadowAnchor)
+                                               a = this.options.icon.options.shadowAnchor;
+                                       s = this.options.icon.options.shadowSize;
+                                       i = this._shadow;
+                                       this._updateImg(i, a, s);
+                               }
+                       }
+               }
+       });
+}());
diff --git a/www/plugins/gis/lib/leaflet/plugins/images/icon-fullscreen-2x.png b/www/plugins/gis/lib/leaflet/plugins/images/icon-fullscreen-2x.png
new file mode 100644 (file)
index 0000000..7320d95
Binary files /dev/null and b/www/plugins/gis/lib/leaflet/plugins/images/icon-fullscreen-2x.png differ
diff --git a/www/plugins/gis/lib/leaflet/plugins/images/icon-fullscreen.png b/www/plugins/gis/lib/leaflet/plugins/images/icon-fullscreen.png
new file mode 100755 (executable)
index 0000000..fdb9a70
Binary files /dev/null and b/www/plugins/gis/lib/leaflet/plugins/images/icon-fullscreen.png differ
diff --git a/www/plugins/gis/lib/leaflet/plugins/images/toggle.png b/www/plugins/gis/lib/leaflet/plugins/images/toggle.png
new file mode 100755 (executable)
index 0000000..205e38b
Binary files /dev/null and b/www/plugins/gis/lib/leaflet/plugins/images/toggle.png differ
diff --git a/www/plugins/gis/lib/leaflet/plugins/leaflet-plugins.css b/www/plugins/gis/lib/leaflet/plugins/leaflet-plugins.css
new file mode 100755 (executable)
index 0000000..88c58fd
--- /dev/null
@@ -0,0 +1,16 @@
+/* L.Control.FullScreen */
+.fullscreen-icon { background-image: url(images/icon-fullscreen.png); }
+.leaflet-retina .fullscreen-icon { background-image: url(images/icon-fullscreen-2x.png); background-size: 26px 26px; }
+.leaflet-container:-webkit-full-screen { width: 100% !important; height: 100% !important; z-index: 99999; }
+.leaflet-pseudo-fullscreen { position: fixed !important; width: 100% !important; height: 100% !important; top: 0px !important; left: 0px !important; z-index: 99999; }
+
+/* MiniMap Plugin */
+.leaflet-control-minimap { border: solid rgba(255, 255, 255, 1.0) 4px; box-shadow: 0 1px 5px rgba(0,0,0,0.65); border-radius: 3px; background: #f8f8f9; transition: all .2s; }
+.leaflet-control-minimap a { background-color: rgba(255, 255, 255, 1.0); background-repeat: no-repeat; z-index: 99999; transition: all .2s; border-radius: 3px 0px 0px 0px; }
+.leaflet-control-minimap a.minimized { -webkit-transform: rotate(180deg); transform: rotate(180deg); border-radius: 0px; }
+.leaflet-control-minimap-toggle-display { background-image: url("images/toggle.png"); height: 19px; width: 19px; position: absolute; bottom: 0; right: 0; }
+/* Old IE */
+.leaflet-oldie .leaflet-control-minimap { border: 1px solid #999 }
+.leaflet-oldie .leaflet-control-minimap a { background-color: #fff }
+.leaflet-oldie .leaflet-control-minimap a.minimized { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2) }
+
diff --git a/www/plugins/gis/lib/leaflet/plugins/leaflet-providers.js b/www/plugins/gis/lib/leaflet/plugins/leaflet-providers.js
new file mode 100644 (file)
index 0000000..bc82990
--- /dev/null
@@ -0,0 +1,675 @@
+(function (root, factory) {
+       if (typeof define === 'function' && define.amd) {
+               // AMD. Register as an anonymous module.
+               define(['leaflet'], factory);
+       } else if (typeof modules === 'object' && module.exports) {
+               // define a Common JS module that relies on 'leaflet'
+               module.exports = factory(require('leaflet'));
+       } else {
+               // Assume Leaflet is loaded into global object L already
+               factory(L);
+       }
+}(this, function (L) {
+       'use strict';
+
+       L.TileLayer.Provider = L.TileLayer.extend({
+               initialize: function (arg, options) {
+                       var providers = L.TileLayer.Provider.providers;
+
+                       var parts = arg.split('.');
+
+                       var providerName = parts[0];
+                       var variantName = parts[1];
+
+                       if (!providers[providerName]) {
+                               throw 'No such provider (' + providerName + ')';
+                       }
+
+                       var provider = {
+                               url: providers[providerName].url,
+                               options: providers[providerName].options
+                       };
+
+                       // overwrite values in provider from variant.
+                       if (variantName && 'variants' in providers[providerName]) {
+                               if (!(variantName in providers[providerName].variants)) {
+                                       throw 'No such variant of ' + providerName + ' (' + variantName + ')';
+                               }
+                               var variant = providers[providerName].variants[variantName];
+                               var variantOptions;
+                               if (typeof variant === 'string') {
+                                       variantOptions = {
+                                               variant: variant
+                                       };
+                               } else {
+                                       variantOptions = variant.options;
+                               }
+                               provider = {
+                                       url: variant.url || provider.url,
+                                       options: L.Util.extend({}, provider.options, variantOptions)
+                               };
+                       } else if (typeof provider.url === 'function') {
+                               provider.url = provider.url(parts.splice(1, parts.length - 1).join('.'));
+                       }
+
+                       var forceHTTP = window.location.protocol === 'file:' || provider.options.forceHTTP;
+                       if (provider.url.indexOf('//') === 0 && forceHTTP) {
+                               provider.url = 'http:' + provider.url;
+                       }
+
+                       // If retina option is set
+                       if (provider.options.retina) {
+                               // Check retina screen
+                               if (options.detectRetina && L.Browser.retina) {
+                                       // The retina option will be active now
+                                       // But we need to prevent Leaflet retina mode
+                                       options.detectRetina = false;
+                               } else {
+                                       // No retina, remove option
+                                       provider.options.retina = '';
+                               }
+                       }
+
+                       // replace attribution placeholders with their values from toplevel provider attribution,
+                       // recursively
+                       var attributionReplacer = function (attr) {
+                               if (attr.indexOf('{attribution.') === -1) {
+                                       return attr;
+                               }
+                               return attr.replace(/\{attribution.(\w*)\}/,
+                                       function (match, attributionName) {
+                                               return attributionReplacer(providers[attributionName].options.attribution);
+                                       }
+                               );
+                       };
+                       provider.options.attribution = attributionReplacer(provider.options.attribution);
+
+                       // Compute final options combining provider options with any user overrides
+                       var layerOpts = L.Util.extend({}, provider.options, options);
+                       L.TileLayer.prototype.initialize.call(this, provider.url, layerOpts);
+               }
+       });
+
+       /**
+        * Definition of providers.
+        * see http://leafletjs.com/reference.html#tilelayer for options in the options map.
+        */
+
+       L.TileLayer.Provider.providers = {
+               OpenStreetMap: {
+                       url: '//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
+                       options: {
+                               maxZoom: 19,
+                               attribution:
+                                       '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
+                       },
+                       variants: {
+                               Mapnik: {},
+                               BlackAndWhite: {
+                                       url: 'http://{s}.tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png',
+                                       options: {
+                                               maxZoom: 18
+                                       }
+                               },
+                               DE: {
+                                       url: 'http://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png',
+                                       options: {
+                                               maxZoom: 18
+                                       }
+                               },
+                               France: {
+                                       url: 'http://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png',
+                                       options: {
+                                               attribution: '&copy; Openstreetmap France | {attribution.OpenStreetMap}'
+                                       }
+                               },
+                               HOT: {
+                                       url: 'http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png',
+                                       options: {
+                                               attribution: '{attribution.OpenStreetMap}, Tiles courtesy of <a href="http://hot.openstreetmap.org/" target="_blank">Humanitarian OpenStreetMap Team</a>'
+                                       }
+                               }
+                       }
+               },
+               OpenSeaMap: {
+                       url: 'http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png',
+                       options: {
+                               attribution: 'Map data: &copy; <a href="http://www.openseamap.org">OpenSeaMap</a> contributors'
+                       }
+               },
+               OpenTopoMap: {
+                       url: '//{s}.tile.opentopomap.org/{z}/{x}/{y}.png',
+                       options: {
+                               maxZoom: 16,
+                               attribution: 'Map data: {attribution.OpenStreetMap}, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: &copy; <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
+                       }
+               },
+               Thunderforest: {
+                       url: '//{s}.tile.thunderforest.com/{variant}/{z}/{x}/{y}.png',
+                       options: {
+                               attribution:
+                                       '&copy; <a href="http://www.thunderforest.com/">Thunderforest</a>, {attribution.OpenStreetMap}',
+                               variant: 'cycle'
+                       },
+                       variants: {
+                               OpenCycleMap: 'cycle',
+                               Transport: {
+                                       options: {
+                                               variant: 'transport',
+                                               maxZoom: 19
+                                       }
+                               },
+                               TransportDark: {
+                                       options: {
+                                               variant: 'transport-dark',
+                                               maxZoom: 19
+                                       }
+                               },
+                               SpinalMap: {
+                                       options: {
+                                               variant: 'spinal-map',
+                                               maxZoom: 11
+                                       }
+                               },
+                               Landscape: 'landscape',
+                               Outdoors: 'outdoors',
+                               Pioneer: 'pioneer'
+                       }
+               },
+               OpenMapSurfer: {
+                       url: 'http://korona.geog.uni-heidelberg.de/tiles/{variant}/x={x}&y={y}&z={z}',
+                       options: {
+                               maxZoom: 20,
+                               variant: 'roads',
+                               attribution: 'Imagery from <a href="http://giscience.uni-hd.de/">GIScience Research Group @ University of Heidelberg</a> &mdash; Map data {attribution.OpenStreetMap}'
+                       },
+                       variants: {
+                               Roads: 'roads',
+                               AdminBounds: {
+                                       options: {
+                                               variant: 'adminb',
+                                               maxZoom: 19
+                                       }
+                               },
+                               Grayscale: {
+                                       options: {
+                                               variant: 'roadsg',
+                                               maxZoom: 19
+                                       }
+                               }
+                       }
+               },
+               Hydda: {
+                       url: 'http://{s}.tile.openstreetmap.se/hydda/{variant}/{z}/{x}/{y}.png',
+                       options: {
+                               variant: 'full',
+                               attribution: 'Tiles courtesy of <a href="http://openstreetmap.se/" target="_blank">OpenStreetMap Sweden</a> &mdash; Map data {attribution.OpenStreetMap}'
+                       },
+                       variants: {
+                               Full: 'full',
+                               Base: 'base',
+                               RoadsAndLabels: 'roads_and_labels'
+                       }
+               },
+               MapQuestOpen: {
+                       /* Mapquest does support https, but with a different subdomain:
+                        * https://otile{s}-s.mqcdn.com/tiles/1.0.0/{type}/{z}/{x}/{y}.{ext}
+                        * which makes implementing protocol relativity impossible.
+                        */
+                       url: 'http://otile{s}.mqcdn.com/tiles/1.0.0/{type}/{z}/{x}/{y}.{ext}',
+                       options: {
+                               type: 'map',
+                               ext: 'jpg',
+                               attribution:
+                                       'Tiles Courtesy of <a href="http://www.mapquest.com/">MapQuest</a> &mdash; ' +
+                                       'Map data {attribution.OpenStreetMap}',
+                               subdomains: '1234'
+                       },
+                       variants: {
+                               OSM: {},
+                               Aerial: {
+                                       options: {
+                                               type: 'sat',
+                                               attribution:
+                                                       'Tiles Courtesy of <a href="http://www.mapquest.com/">MapQuest</a> &mdash; ' +
+                                                       'Portions Courtesy NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency'
+                                       }
+                               },
+                               HybridOverlay: {
+                                       options: {
+                                               type: 'hyb',
+                                               ext: 'png',
+                                               opacity: 0.9
+                                       }
+                               }
+                       }
+               },
+               MapBox: {
+                       url: '//api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}',
+                       options: {
+                               attribution:
+                                       'Imagery from <a href="http://mapbox.com/about/maps/">MapBox</a> &mdash; ' +
+                                       'Map data {attribution.OpenStreetMap}',
+                               subdomains: 'abcd'
+                       }
+               },
+               Stamen: {
+                       url: '//stamen-tiles-{s}.a.ssl.fastly.net/{variant}/{z}/{x}/{y}.{ext}',
+                       options: {
+                               attribution:
+                                       'Map tiles by <a href="http://stamen.com">Stamen Design</a>, ' +
+                                       '<a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> &mdash; ' +
+                                       'Map data {attribution.OpenStreetMap}',
+                               subdomains: 'abcd',
+                               minZoom: 0,
+                               maxZoom: 20,
+                               variant: 'toner',
+                               ext: 'png'
+                       },
+                       variants: {
+                               Toner: 'toner',
+                               TonerBackground: 'toner-background',
+                               TonerHybrid: 'toner-hybrid',
+                               TonerLines: 'toner-lines',
+                               TonerLabels: 'toner-labels',
+                               TonerLite: 'toner-lite',
+                               Watercolor: {
+                                       options: {
+                                               variant: 'watercolor',
+                                               minZoom: 1,
+                                               maxZoom: 16
+                                       }
+                               },
+                               Terrain: {
+                                       options: {
+                                               variant: 'terrain',
+                                               minZoom: 4,
+                                               maxZoom: 18,
+                                               bounds: [[22, -132], [70, -56]]
+                                       }
+                               },
+                               TerrainBackground: {
+                                       options: {
+                                               variant: 'terrain-background',
+                                               minZoom: 4,
+                                               maxZoom: 18,
+                                               bounds: [[22, -132], [70, -56]]
+                                       }
+                               },
+                               TopOSMRelief: {
+                                       options: {
+                                               variant: 'toposm-color-relief',
+                                               ext: 'jpg',
+                                               bounds: [[22, -132], [51, -56]]
+                                       }
+                               },
+                               TopOSMFeatures: {
+                                       options: {
+                                               variant: 'toposm-features',
+                                               bounds: [[22, -132], [51, -56]],
+                                               opacity: 0.9
+                                       }
+                               }
+                       }
+               },
+               Esri: {
+                       url: '//server.arcgisonline.com/ArcGIS/rest/services/{variant}/MapServer/tile/{z}/{y}/{x}',
+                       options: {
+                               variant: 'World_Street_Map',
+                               attribution: 'Tiles &copy; Esri'
+                       },
+                       variants: {
+                               WorldStreetMap: {
+                                       options: {
+                                               attribution:
+                                                       '{attribution.Esri} &mdash; ' +
+                                                       'Source: Esri, DeLorme, NAVTEQ, USGS, Intermap, iPC, NRCAN, Esri Japan, METI, Esri China (Hong Kong), Esri (Thailand), TomTom, 2012'
+                                       }
+                               },
+                               DeLorme: {
+                                       options: {
+                                               variant: 'Specialty/DeLorme_World_Base_Map',
+                                               minZoom: 1,
+                                               maxZoom: 11,
+                                               attribution: '{attribution.Esri} &mdash; Copyright: &copy;2012 DeLorme'
+                                       }
+                               },
+                               WorldTopoMap: {
+                                       options: {
+                                               variant: 'World_Topo_Map',
+                                               attribution:
+                                                       '{attribution.Esri} &mdash; ' +
+                                                       'Esri, DeLorme, NAVTEQ, TomTom, Intermap, iPC, USGS, FAO, NPS, NRCAN, GeoBase, Kadaster NL, Ordnance Survey, Esri Japan, METI, Esri China (Hong Kong), and the GIS User Community'
+                                       }
+                               },
+                               WorldImagery: {
+                                       options: {
+                                               variant: 'World_Imagery',
+                                               attribution:
+                                                       '{attribution.Esri} &mdash; ' +
+                                                       'Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
+                                       }
+                               },
+                               WorldTerrain: {
+                                       options: {
+                                               variant: 'World_Terrain_Base',
+                                               maxZoom: 13,
+                                               attribution:
+                                                       '{attribution.Esri} &mdash; ' +
+                                                       'Source: USGS, Esri, TANA, DeLorme, and NPS'
+                                       }
+                               },
+                               WorldShadedRelief: {
+                                       options: {
+                                               variant: 'World_Shaded_Relief',
+                                               maxZoom: 13,
+                                               attribution: '{attribution.Esri} &mdash; Source: Esri'
+                                       }
+                               },
+                               WorldPhysical: {
+                                       options: {
+                                               variant: 'World_Physical_Map',
+                                               maxZoom: 8,
+                                               attribution: '{attribution.Esri} &mdash; Source: US National Park Service'
+                                       }
+                               },
+                               OceanBasemap: {
+                                       options: {
+                                               variant: 'Ocean_Basemap',
+                                               maxZoom: 13,
+                                               attribution: '{attribution.Esri} &mdash; Sources: GEBCO, NOAA, CHS, OSU, UNH, CSUMB, National Geographic, DeLorme, NAVTEQ, and Esri'
+                                       }
+                               },
+                               NatGeoWorldMap: {
+                                       options: {
+                                               variant: 'NatGeo_World_Map',
+                                               maxZoom: 16,
+                                               attribution: '{attribution.Esri} &mdash; National Geographic, Esri, DeLorme, NAVTEQ, UNEP-WCMC, USGS, NASA, ESA, METI, NRCAN, GEBCO, NOAA, iPC'
+                                       }
+                               },
+                               WorldGrayCanvas: {
+                                       options: {
+                                               variant: 'Canvas/World_Light_Gray_Base',
+                                               maxZoom: 16,
+                                               attribution: '{attribution.Esri} &mdash; Esri, DeLorme, NAVTEQ'
+                                       }
+                               }
+                       }
+               },
+               OpenWeatherMap: {
+                       url: 'http://{s}.tile.openweathermap.org/map/{variant}/{z}/{x}/{y}.png',
+                       options: {
+                               maxZoom: 19,
+                               attribution: 'Map data &copy; <a href="http://openweathermap.org">OpenWeatherMap</a>',
+                               opacity: 0.5
+                       },
+                       variants: {
+                               Clouds: 'clouds',
+                               CloudsClassic: 'clouds_cls',
+                               Precipitation: 'precipitation',
+                               PrecipitationClassic: 'precipitation_cls',
+                               Rain: 'rain',
+                               RainClassic: 'rain_cls',
+                               Pressure: 'pressure',
+                               PressureContour: 'pressure_cntr',
+                               Wind: 'wind',
+                               Temperature: 'temp',
+                               Snow: 'snow'
+                       }
+               },
+               HERE: {
+                       /*
+                        * HERE maps, formerly Nokia maps.
+                        * These basemaps are free, but you need an API key. Please sign up at
+                        * http://developer.here.com/getting-started
+                        *
+                        * Note that the base urls contain '.cit' whichs is HERE's
+                        * 'Customer Integration Testing' environment. Please remove for production
+                        * envirionments.
+                        */
+                       url:
+                               '//{s}.{base}.maps.cit.api.here.com/maptile/2.1/' +
+                               '{type}/{mapID}/{variant}/{z}/{x}/{y}/{size}/{format}?' +
+                               'app_id={app_id}&app_code={app_code}&lg={language}',
+                       options: {
+                               attribution:
+                                       'Map &copy; 1987-2014 <a href="http://developer.here.com">HERE</a>',
+                               subdomains: '1234',
+                               mapID: 'newest',
+                               'app_id': '<insert your app_id here>',
+                               'app_code': '<insert your app_code here>',
+                               base: 'base',
+                               variant: 'normal.day',
+                               maxZoom: 20,
+                               type: 'maptile',
+                               language: 'eng',
+                               format: 'png8',
+                               size: '256'
+                       },
+                       variants: {
+                               normalDay: 'normal.day',
+                               normalDayCustom: 'normal.day.custom',
+                               normalDayGrey: 'normal.day.grey',
+                               normalDayMobile: 'normal.day.mobile',
+                               normalDayGreyMobile: 'normal.day.grey.mobile',
+                               normalDayTransit: 'normal.day.transit',
+                               normalDayTransitMobile: 'normal.day.transit.mobile',
+                               normalNight: 'normal.night',
+                               normalNightMobile: 'normal.night.mobile',
+                               normalNightGrey: 'normal.night.grey',
+                               normalNightGreyMobile: 'normal.night.grey.mobile',
+
+                               basicMap: {
+                                       options: {
+                                               type: 'basetile'
+                                       }
+                               },
+                               mapLabels: {
+                                       options: {
+                                               type: 'labeltile',
+                                               format: 'png'
+                                       }
+                               },
+                               trafficFlow: {
+                                       options: {
+                                               base: 'traffic',
+                                               type: 'flowtile'
+                                       }
+                               },
+                               carnavDayGrey: 'carnav.day.grey',
+                               hybridDay: {
+                                       options: {
+                                               base: 'aerial',
+                                               variant: 'hybrid.day'
+                                       }
+                               },
+                               hybridDayMobile: {
+                                       options: {
+                                               base: 'aerial',
+                                               variant: 'hybrid.day.mobile'
+                                       }
+                               },
+                               pedestrianDay: 'pedestrian.day',
+                               pedestrianNight: 'pedestrian.night',
+                               satelliteDay: {
+                                       options: {
+                                               base: 'aerial',
+                                               variant: 'satellite.day'
+                                       }
+                               },
+                               terrainDay: {
+                                       options: {
+                                               base: 'aerial',
+                                               variant: 'terrain.day'
+                                       }
+                               },
+                               terrainDayMobile: {
+                                       options: {
+                                               base: 'aerial',
+                                               variant: 'terrain.day.mobile'
+                                       }
+                               }
+                       }
+               },
+               FreeMapSK: {
+                       url: 'http://t{s}.freemap.sk/T/{z}/{x}/{y}.jpeg',
+                       options: {
+                               minZoom: 8,
+                               maxZoom: 16,
+                               subdomains: '1234',
+                               bounds: [[47.204642, 15.996093], [49.830896, 22.576904]],
+                               attribution:
+                                       '{attribution.OpenStreetMap}, vizualization CC-By-SA 2.0 <a href="http://freemap.sk">Freemap.sk</a>'
+                       }
+               },
+               MtbMap: {
+                       url: 'http://tile.mtbmap.cz/mtbmap_tiles/{z}/{x}/{y}.png',
+                       options: {
+                               attribution:
+                                       '{attribution.OpenStreetMap} &amp; USGS'
+                       }
+               },
+               CartoDB: {
+                       url: 'http://{s}.basemaps.cartocdn.com/{variant}/{z}/{x}/{y}.png',
+                       options: {
+                               attribution: '{attribution.OpenStreetMap} &copy; <a href="http://cartodb.com/attributions">CartoDB</a>',
+                               subdomains: 'abcd',
+                               maxZoom: 19,
+                               variant: 'light_all'
+                       },
+                       variants: {
+                               Positron: 'light_all',
+                               PositronNoLabels: 'light_nolabels',
+                               PositronOnlyLabels: 'light_only_labels',
+                               DarkMatter: 'dark_all',
+                               DarkMatterNoLabels: 'dark_nolabels',
+                               DarkMatterOnlyLabels: 'dark_only_labels'
+                       }
+               },
+               HikeBike: {
+                       url: 'http://{s}.tiles.wmflabs.org/{variant}/{z}/{x}/{y}.png',
+                       options: {
+                               maxZoom: 19,
+                               attribution: '{attribution.OpenStreetMap}',
+                               variant: 'hikebike'
+                       },
+                       variants: {
+                               HikeBike: {},
+                               HillShading: {
+                                       options: {
+                                               maxZoom: 15,
+                                               variant: 'hillshading'
+                                       }
+                               }
+                       }
+               },
+               BasemapAT: {
+                       url: '//maps{s}.wien.gv.at/basemap/{variant}/normal/google3857/{z}/{y}/{x}.{format}',
+                       options: {
+                               maxZoom: 19,
+                               attribution: 'Datenquelle: <a href="www.basemap.at">basemap.at</a>',
+                               subdomains: ['', '1', '2', '3', '4'],
+                               format: 'png',
+                               bounds: [[46.358770, 8.782379], [49.037872, 17.189532]],
+                               variant: 'geolandbasemap'
+                       },
+                       variants: {
+                               basemap: 'geolandbasemap',
+                               grau: 'bmapgrau',
+                               overlay: 'bmapoverlay',
+                               highdpi: {
+                                       options: {
+                                               variant: 'bmaphidpi',
+                                               format: 'jpeg'
+                                       }
+                               },
+                               orthofoto: {
+                                       options: {
+                                               variant: 'bmaporthofoto30cm',
+                                               format: 'jpeg'
+                                       }
+                               }
+                       }
+               },
+               NASAGIBS: {
+                       url: '//map1.vis.earthdata.nasa.gov/wmts-webmerc/{variant}/default/{time}/{tilematrixset}{maxZoom}/{z}/{y}/{x}.{format}',
+                       options: {
+                               attribution:
+                                       'Imagery provided by services from the Global Imagery Browse Services (GIBS), operated by the NASA/GSFC/Earth Science Data and Information System ' +
+                                       '(<a href="https://earthdata.nasa.gov">ESDIS</a>) with funding provided by NASA/HQ.',
+                               bounds: [[-85.0511287776, -179.999999975], [85.0511287776, 179.999999975]],
+                               minZoom: 1,
+                               maxZoom: 9,
+                               format: 'jpg',
+                               time: '',
+                               tilematrixset: 'GoogleMapsCompatible_Level'
+                       },
+                       variants: {
+                               ModisTerraTrueColorCR: 'MODIS_Terra_CorrectedReflectance_TrueColor',
+                               ModisTerraBands367CR: 'MODIS_Terra_CorrectedReflectance_Bands367',
+                               ViirsEarthAtNight2012: {
+                                       options: {
+                                               variant: 'VIIRS_CityLights_2012',
+                                               maxZoom: 8
+                                       }
+                               },
+                               ModisTerraLSTDay: {
+                                       options: {
+                                               variant: 'MODIS_Terra_Land_Surface_Temp_Day',
+                                               format: 'png',
+                                               maxZoom: 7,
+                                               opacity: 0.75
+                                       }
+                               },
+                               ModisTerraSnowCover: {
+                                       options: {
+                                               variant: 'MODIS_Terra_Snow_Cover',
+                                               format: 'png',
+                                               maxZoom: 8,
+                                               opacity: 0.75
+                                       }
+                               },
+                               ModisTerraAOD: {
+                                       options: {
+                                               variant: 'MODIS_Terra_Aerosol',
+                                               format: 'png',
+                                               maxZoom: 6,
+                                               opacity: 0.75
+                                       }
+                               },
+                               ModisTerraChlorophyll: {
+                                       options: {
+                                               variant: 'MODIS_Terra_Chlorophyll_A',
+                                               format: 'png',
+                                               maxZoom: 7,
+                                               opacity: 0.75
+                                       }
+                               }
+                       }
+               },
+               NLS: {
+                       // NLS maps are copyright National library of Scotland.
+                       // http://maps.nls.uk/projects/api/index.html
+                       // Please contact NLS for anything other than non-commercial low volume usage
+                       //
+                       // Map sources: Ordnance Survey 1:1m to 1:63K, 1920s-1940s
+                       //   z0-9  - 1:1m
+                       //  z10-11 - quarter inch (1:253440)
+                       //  z12-18 - one inch (1:63360)
+                       url: '//nls-{s}.tileserver.com/nls/{z}/{x}/{y}.jpg',
+                       options: {
+                               attribution: '<a href="http://geo.nls.uk/maps/">National Library of Scotland Historic Maps</a>',
+                               bounds: [[49.6, -12], [61.7, 3]],
+                               minZoom: 1,
+                               maxZoom: 18,
+                               subdomains: '0123',
+                       }
+               }
+       };
+
+       L.tileLayer.provider = function (provider, options) {
+               return new L.TileLayer.Provider(provider, options);
+       };
+
+       return L;
+}));
diff --git a/www/plugins/gis/lib/leaflet/plugins/leaflet.markercluster-src.js b/www/plugins/gis/lib/leaflet/plugins/leaflet.markercluster-src.js
new file mode 100644 (file)
index 0000000..4043316
--- /dev/null
@@ -0,0 +1,2163 @@
+/*
+ Leaflet.markercluster, Provides Beautiful Animated Marker Clustering functionality for Leaflet, a JS library for interactive maps.
+ https://github.com/Leaflet/Leaflet.markercluster
+ (c) 2012-2013, Dave Leaver, smartrak
+*/
+(function (window, document, undefined) {/*
+ * L.MarkerClusterGroup extends L.FeatureGroup by clustering the markers contained within
+ */
+
+L.MarkerClusterGroup = L.FeatureGroup.extend({
+
+       options: {
+               maxClusterRadius: 80, //A cluster will cover at most this many pixels from its center
+               iconCreateFunction: null,
+
+               spiderfyOnMaxZoom: true,
+               showCoverageOnHover: true,
+               zoomToBoundsOnClick: true,
+               singleMarkerMode: false,
+
+               disableClusteringAtZoom: null,
+
+               // Setting this to false prevents the removal of any clusters outside of the viewpoint, which
+               // is the default behaviour for performance reasons.
+               removeOutsideVisibleBounds: true,
+
+               //Whether to animate adding markers after adding the MarkerClusterGroup to the map
+               // If you are adding individual markers set to true, if adding bulk markers leave false for massive performance gains.
+               animateAddingMarkers: false,
+
+               //Increase to increase the distance away that spiderfied markers appear from the center
+               spiderfyDistanceMultiplier: 1,
+
+               // When bulk adding layers, adds markers in chunks. Means addLayers may not add all the layers in the call, others will be loaded during setTimeouts
+               chunkedLoading: false,
+               chunkInterval: 200, // process markers for a maximum of ~ n milliseconds (then trigger the chunkProgress callback)
+               chunkDelay: 50, // at the end of each interval, give n milliseconds back to system/browser
+               chunkProgress: null, // progress callback: function(processed, total, elapsed) (e.g. for a progress indicator)
+
+               //Options to pass to the L.Polygon constructor
+               polygonOptions: {}
+       },
+
+       initialize: function (options) {
+               L.Util.setOptions(this, options);
+               if (!this.options.iconCreateFunction) {
+                       this.options.iconCreateFunction = this._defaultIconCreateFunction;
+               }
+
+               this._featureGroup = L.featureGroup();
+               this._featureGroup.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);
+
+               this._nonPointGroup = L.featureGroup();
+               this._nonPointGroup.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);
+
+               this._inZoomAnimation = 0;
+               this._needsClustering = [];
+               this._needsRemoving = []; //Markers removed while we aren't on the map need to be kept track of
+               //The bounds of the currently shown area (from _getExpandedVisibleBounds) Updated on zoom/move
+               this._currentShownBounds = null;
+
+               this._queue = [];
+       },
+
+       addLayer: function (layer) {
+
+               if (layer instanceof L.LayerGroup) {
+                       var array = [];
+                       for (var i in layer._layers) {
+                               array.push(layer._layers[i]);
+                       }
+                       return this.addLayers(array);
+               }
+
+               //Don't cluster non point data
+               if (!layer.getLatLng) {
+                       this._nonPointGroup.addLayer(layer);
+                       return this;
+               }
+
+               if (!this._map) {
+                       this._needsClustering.push(layer);
+                       return this;
+               }
+
+               if (this.hasLayer(layer)) {
+                       return this;
+               }
+
+
+               //If we have already clustered we'll need to add this one to a cluster
+
+               if (this._unspiderfy) {
+                       this._unspiderfy();
+               }
+
+               this._addLayer(layer, this._maxZoom);
+
+               //Work out what is visible
+               var visibleLayer = layer,
+                       currentZoom = this._map.getZoom();
+               if (layer.__parent) {
+                       while (visibleLayer.__parent._zoom >= currentZoom) {
+                               visibleLayer = visibleLayer.__parent;
+                       }
+               }
+
+               if (this._currentShownBounds.contains(visibleLayer.getLatLng())) {
+                       if (this.options.animateAddingMarkers) {
+                               this._animationAddLayer(layer, visibleLayer);
+                       } else {
+                               this._animationAddLayerNonAnimated(layer, visibleLayer);
+                       }
+               }
+               return this;
+       },
+
+       removeLayer: function (layer) {
+
+               if (layer instanceof L.LayerGroup)
+               {
+                       var array = [];
+                       for (var i in layer._layers) {
+                               array.push(layer._layers[i]);
+                       }
+                       return this.removeLayers(array);
+               }
+
+               //Non point layers
+               if (!layer.getLatLng) {
+                       this._nonPointGroup.removeLayer(layer);
+                       return this;
+               }
+
+               if (!this._map) {
+                       if (!this._arraySplice(this._needsClustering, layer) && this.hasLayer(layer)) {
+                               this._needsRemoving.push(layer);
+                       }
+                       return this;
+               }
+
+               if (!layer.__parent) {
+                       return this;
+               }
+
+               if (this._unspiderfy) {
+                       this._unspiderfy();
+                       this._unspiderfyLayer(layer);
+               }
+
+               //Remove the marker from clusters
+               this._removeLayer(layer, true);
+
+               if (this._featureGroup.hasLayer(layer)) {
+                       this._featureGroup.removeLayer(layer);
+                       if (layer.setOpacity) {
+                               layer.setOpacity(1);
+                       }
+               }
+
+               return this;
+       },
+
+       //Takes an array of markers and adds them in bulk
+       addLayers: function (layersArray) {
+               var fg = this._featureGroup,
+                       npg = this._nonPointGroup,
+                       chunked = this.options.chunkedLoading,
+                       chunkInterval = this.options.chunkInterval,
+                       chunkProgress = this.options.chunkProgress,
+                       newMarkers, i, l, m;
+
+               if (this._map) {
+                       var offset = 0,
+                               started = (new Date()).getTime();
+                       var process = L.bind(function () {
+                               var start = (new Date()).getTime();
+                               for (; offset < layersArray.length; offset++) {
+                                       if (chunked && offset % 200 === 0) {
+                                               // every couple hundred markers, instrument the time elapsed since processing started:
+                                               var elapsed = (new Date()).getTime() - start;
+                                               if (elapsed > chunkInterval) {
+                                                       break; // been working too hard, time to take a break :-)
+                                               }
+                                       }
+
+                                       m = layersArray[offset];
+
+                                       //Not point data, can't be clustered
+                                       if (!m.getLatLng) {
+                                               npg.addLayer(m);
+                                               continue;
+                                       }
+
+                                       if (this.hasLayer(m)) {
+                                               continue;
+                                       }
+
+                                       this._addLayer(m, this._maxZoom);
+
+                                       //If we just made a cluster of size 2 then we need to remove the other marker from the map (if it is) or we never will
+                                       if (m.__parent) {
+                                               if (m.__parent.getChildCount() === 2) {
+                                                       var markers = m.__parent.getAllChildMarkers(),
+                                                               otherMarker = markers[0] === m ? markers[1] : markers[0];
+                                                       fg.removeLayer(otherMarker);
+                                               }
+                                       }
+                               }
+
+                               if (chunkProgress) {
+                                       // report progress and time elapsed:
+                                       chunkProgress(offset, layersArray.length, (new Date()).getTime() - started);
+                               }
+
+                               if (offset === layersArray.length) {
+                                       //Update the icons of all those visible clusters that were affected
+                                       this._featureGroup.eachLayer(function (c) {
+                                               if (c instanceof L.MarkerCluster && c._iconNeedsUpdate) {
+                                                       c._updateIcon();
+                                               }
+                                       });
+
+                                       this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
+                               } else {
+                                       setTimeout(process, this.options.chunkDelay);
+                               }
+                       }, this);
+
+                       process();
+               } else {
+                       newMarkers = [];
+                       for (i = 0, l = layersArray.length; i < l; i++) {
+                               m = layersArray[i];
+
+                               //Not point data, can't be clustered
+                               if (!m.getLatLng) {
+                                       npg.addLayer(m);
+                                       continue;
+                               }
+
+                               if (this.hasLayer(m)) {
+                                       continue;
+                               }
+
+                               newMarkers.push(m);
+                       }
+                       this._needsClustering = this._needsClustering.concat(newMarkers);
+               }
+               return this;
+       },
+
+       //Takes an array of markers and removes them in bulk
+       removeLayers: function (layersArray) {
+               var i, l, m,
+                       fg = this._featureGroup,
+                       npg = this._nonPointGroup;
+
+               if (!this._map) {
+                       for (i = 0, l = layersArray.length; i < l; i++) {
+                               m = layersArray[i];
+                               this._arraySplice(this._needsClustering, m);
+                               npg.removeLayer(m);
+                       }
+                       return this;
+               }
+
+               for (i = 0, l = layersArray.length; i < l; i++) {
+                       m = layersArray[i];
+
+                       if (!m.__parent) {
+                               npg.removeLayer(m);
+                               continue;
+                       }
+
+                       this._removeLayer(m, true, true);
+
+                       if (fg.hasLayer(m)) {
+                               fg.removeLayer(m);
+                               if (m.setOpacity) {
+                                       m.setOpacity(1);
+                               }
+                       }
+               }
+
+               //Fix up the clusters and markers on the map
+               this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
+
+               fg.eachLayer(function (c) {
+                       if (c instanceof L.MarkerCluster) {
+                               c._updateIcon();
+                       }
+               });
+
+               return this;
+       },
+
+       //Removes all layers from the MarkerClusterGroup
+       clearLayers: function () {
+               //Need our own special implementation as the LayerGroup one doesn't work for us
+
+               //If we aren't on the map (yet), blow away the markers we know of
+               if (!this._map) {
+                       this._needsClustering = [];
+                       delete this._gridClusters;
+                       delete this._gridUnclustered;
+               }
+
+               if (this._noanimationUnspiderfy) {
+                       this._noanimationUnspiderfy();
+               }
+
+               //Remove all the visible layers
+               this._featureGroup.clearLayers();
+               this._nonPointGroup.clearLayers();
+
+               this.eachLayer(function (marker) {
+                       delete marker.__parent;
+               });
+
+               if (this._map) {
+                       //Reset _topClusterLevel and the DistanceGrids
+                       this._generateInitialClusters();
+               }
+
+               return this;
+       },
+
+       //Override FeatureGroup.getBounds as it doesn't work
+       getBounds: function () {
+               var bounds = new L.LatLngBounds();
+
+               if (this._topClusterLevel) {
+                       bounds.extend(this._topClusterLevel._bounds);
+               }
+
+               for (var i = this._needsClustering.length - 1; i >= 0; i--) {
+                       bounds.extend(this._needsClustering[i].getLatLng());
+               }
+
+               bounds.extend(this._nonPointGroup.getBounds());
+
+               return bounds;
+       },
+
+       //Overrides LayerGroup.eachLayer
+       eachLayer: function (method, context) {
+               var markers = this._needsClustering.slice(),
+                       i;
+
+               if (this._topClusterLevel) {
+                       this._topClusterLevel.getAllChildMarkers(markers);
+               }
+
+               for (i = markers.length - 1; i >= 0; i--) {
+                       method.call(context, markers[i]);
+               }
+
+               this._nonPointGroup.eachLayer(method, context);
+       },
+
+       //Overrides LayerGroup.getLayers
+       getLayers: function () {
+               var layers = [];
+               this.eachLayer(function (l) {
+                       layers.push(l);
+               });
+               return layers;
+       },
+
+       //Overrides LayerGroup.getLayer, WARNING: Really bad performance
+       getLayer: function (id) {
+               var result = null;
+
+               this.eachLayer(function (l) {
+                       if (L.stamp(l) === id) {
+                               result = l;
+                       }
+               });
+
+               return result;
+       },
+
+       //Returns true if the given layer is in this MarkerClusterGroup
+       hasLayer: function (layer) {
+               if (!layer) {
+                       return false;
+               }
+
+               var i, anArray = this._needsClustering;
+
+               for (i = anArray.length - 1; i >= 0; i--) {
+                       if (anArray[i] === layer) {
+                               return true;
+                       }
+               }
+
+               anArray = this._needsRemoving;
+               for (i = anArray.length - 1; i >= 0; i--) {
+                       if (anArray[i] === layer) {
+                               return false;
+                       }
+               }
+
+               return !!(layer.__parent && layer.__parent._group === this) || this._nonPointGroup.hasLayer(layer);
+       },
+
+       //Zoom down to show the given layer (spiderfying if necessary) then calls the callback
+       zoomToShowLayer: function (layer, callback) {
+
+               var showMarker = function () {
+                       if ((layer._icon || layer.__parent._icon) && !this._inZoomAnimation) {
+                               this._map.off('moveend', showMarker, this);
+                               this.off('animationend', showMarker, this);
+
+                               if (layer._icon) {
+                                       callback();
+                               } else if (layer.__parent._icon) {
+                                       var afterSpiderfy = function () {
+                                               this.off('spiderfied', afterSpiderfy, this);
+                                               callback();
+                                       };
+
+                                       this.on('spiderfied', afterSpiderfy, this);
+                                       layer.__parent.spiderfy();
+                               }
+                       }
+               };
+
+               if (layer._icon && this._map.getBounds().contains(layer.getLatLng())) {
+                       //Layer is visible ond on screen, immediate return
+                       callback();
+               } else if (layer.__parent._zoom < this._map.getZoom()) {
+                       //Layer should be visible at this zoom level. It must not be on screen so just pan over to it
+                       this._map.on('moveend', showMarker, this);
+                       this._map.panTo(layer.getLatLng());
+               } else {
+                       var moveStart = function () {
+                               this._map.off('movestart', moveStart, this);
+                               moveStart = null;
+                       };
+
+                       this._map.on('movestart', moveStart, this);
+                       this._map.on('moveend', showMarker, this);
+                       this.on('animationend', showMarker, this);
+                       layer.__parent.zoomToBounds();
+
+                       if (moveStart) {
+                               //Never started moving, must already be there, probably need clustering however
+                               showMarker.call(this);
+                       }
+               }
+       },
+
+       //Overrides FeatureGroup.onAdd
+       onAdd: function (map) {
+               this._map = map;
+               var i, l, layer;
+
+               if (!isFinite(this._map.getMaxZoom())) {
+                       throw "Map has no maxZoom specified";
+               }
+
+               this._featureGroup.onAdd(map);
+               this._nonPointGroup.onAdd(map);
+
+               if (!this._gridClusters) {
+                       this._generateInitialClusters();
+               }
+
+               for (i = 0, l = this._needsRemoving.length; i < l; i++) {
+                       layer = this._needsRemoving[i];
+                       this._removeLayer(layer, true);
+               }
+               this._needsRemoving = [];
+
+               //Remember the current zoom level and bounds
+               this._zoom = this._map.getZoom();
+               this._currentShownBounds = this._getExpandedVisibleBounds();
+
+               this._map.on('zoomend', this._zoomEnd, this);
+               this._map.on('moveend', this._moveEnd, this);
+
+               if (this._spiderfierOnAdd) { //TODO FIXME: Not sure how to have spiderfier add something on here nicely
+                       this._spiderfierOnAdd();
+               }
+
+               this._bindEvents();
+
+               //Actually add our markers to the map:
+               l = this._needsClustering;
+               this._needsClustering = [];
+               this.addLayers(l);
+       },
+
+       //Overrides FeatureGroup.onRemove
+       onRemove: function (map) {
+               map.off('zoomend', this._zoomEnd, this);
+               map.off('moveend', this._moveEnd, this);
+
+               this._unbindEvents();
+
+               //In case we are in a cluster animation
+               this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '');
+
+               if (this._spiderfierOnRemove) { //TODO FIXME: Not sure how to have spiderfier add something on here nicely
+                       this._spiderfierOnRemove();
+               }
+
+
+
+               //Clean up all the layers we added to the map
+               this._hideCoverage();
+               this._featureGroup.onRemove(map);
+               this._nonPointGroup.onRemove(map);
+
+               this._featureGroup.clearLayers();
+
+               this._map = null;
+       },
+
+       getVisibleParent: function (marker) {
+               var vMarker = marker;
+               while (vMarker && !vMarker._icon) {
+                       vMarker = vMarker.__parent;
+               }
+               return vMarker || null;
+       },
+
+       //Remove the given object from the given array
+       _arraySplice: function (anArray, obj) {
+               for (var i = anArray.length - 1; i >= 0; i--) {
+                       if (anArray[i] === obj) {
+                               anArray.splice(i, 1);
+                               return true;
+                       }
+               }
+       },
+
+       //Internal function for removing a marker from everything.
+       //dontUpdateMap: set to true if you will handle updating the map manually (for bulk functions)
+       _removeLayer: function (marker, removeFromDistanceGrid, dontUpdateMap) {
+               var gridClusters = this._gridClusters,
+                       gridUnclustered = this._gridUnclustered,
+                       fg = this._featureGroup,
+                       map = this._map;
+
+               //Remove the marker from distance clusters it might be in
+               if (removeFromDistanceGrid) {
+                       for (var z = this._maxZoom; z >= 0; z--) {
+                               if (!gridUnclustered[z].removeObject(marker, map.project(marker.getLatLng(), z))) {
+                                       break;
+                               }
+                       }
+               }
+
+               //Work our way up the clusters removing them as we go if required
+               var cluster = marker.__parent,
+                       markers = cluster._markers,
+                       otherMarker;
+
+               //Remove the marker from the immediate parents marker list
+               this._arraySplice(markers, marker);
+
+               while (cluster) {
+                       cluster._childCount--;
+
+                       if (cluster._zoom < 0) {
+                               //Top level, do nothing
+                               break;
+                       } else if (removeFromDistanceGrid && cluster._childCount <= 1) { //Cluster no longer required
+                               //We need to push the other marker up to the parent
+                               otherMarker = cluster._markers[0] === marker ? cluster._markers[1] : cluster._markers[0];
+
+                               //Update distance grid
+                               gridClusters[cluster._zoom].removeObject(cluster, map.project(cluster._cLatLng, cluster._zoom));
+                               gridUnclustered[cluster._zoom].addObject(otherMarker, map.project(otherMarker.getLatLng(), cluster._zoom));
+
+                               //Move otherMarker up to parent
+                               this._arraySplice(cluster.__parent._childClusters, cluster);
+                               cluster.__parent._markers.push(otherMarker);
+                               otherMarker.__parent = cluster.__parent;
+
+                               if (cluster._icon) {
+                                       //Cluster is currently on the map, need to put the marker on the map instead
+                                       fg.removeLayer(cluster);
+                                       if (!dontUpdateMap) {
+                                               fg.addLayer(otherMarker);
+                                       }
+                               }
+                       } else {
+                               cluster._recalculateBounds();
+                               if (!dontUpdateMap || !cluster._icon) {
+                                       cluster._updateIcon();
+                               }
+                       }
+
+                       cluster = cluster.__parent;
+               }
+
+               delete marker.__parent;
+       },
+
+       _isOrIsParent: function (el, oel) {
+               while (oel) {
+                       if (el === oel) {
+                               return true;
+                       }
+                       oel = oel.parentNode;
+               }
+               return false;
+       },
+
+       _propagateEvent: function (e) {
+               if (e.layer instanceof L.MarkerCluster) {
+                       //Prevent multiple clustermouseover/off events if the icon is made up of stacked divs (Doesn't work in ie <= 8, no relatedTarget)
+                       if (e.originalEvent && this._isOrIsParent(e.layer._icon, e.originalEvent.relatedTarget)) {
+                               return;
+                       }
+                       e.type = 'cluster' + e.type;
+               }
+
+               this.fire(e.type, e);
+       },
+
+       //Default functionality
+       _defaultIconCreateFunction: function (cluster) {
+               var childCount = cluster.getChildCount();
+
+               var c = ' marker-cluster-';
+               if (childCount < 10) {
+                       c += 'small';
+               } else if (childCount < 100) {
+                       c += 'medium';
+               } else {
+                       c += 'large';
+               }
+
+               return new L.DivIcon({ html: '<div><span>' + childCount + '</span></div>', className: 'marker-cluster' + c, iconSize: new L.Point(40, 40) });
+       },
+
+       _bindEvents: function () {
+               var map = this._map,
+                   spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom,
+                   showCoverageOnHover = this.options.showCoverageOnHover,
+                   zoomToBoundsOnClick = this.options.zoomToBoundsOnClick;
+
+               //Zoom on cluster click or spiderfy if we are at the lowest level
+               if (spiderfyOnMaxZoom || zoomToBoundsOnClick) {
+                       this.on('clusterclick', this._zoomOrSpiderfy, this);
+               }
+
+               //Show convex hull (boundary) polygon on mouse over
+               if (showCoverageOnHover) {
+                       this.on('clustermouseover', this._showCoverage, this);
+                       this.on('clustermouseout', this._hideCoverage, this);
+                       map.on('zoomend', this._hideCoverage, this);
+               }
+       },
+
+       _zoomOrSpiderfy: function (e) {
+               var map = this._map;
+               if (map.getMaxZoom() === map.getZoom()) {
+                       if (this.options.spiderfyOnMaxZoom) {
+                               e.layer.spiderfy();
+                       }
+               } else if (this.options.zoomToBoundsOnClick) {
+                       e.layer.zoomToBounds();
+               }
+
+               // Focus the map again for keyboard users.
+               if (e.originalEvent && e.originalEvent.keyCode === 13) {
+                       map._container.focus();
+               }
+       },
+
+       _showCoverage: function (e) {
+               var map = this._map;
+               if (this._inZoomAnimation) {
+                       return;
+               }
+               if (this._shownPolygon) {
+                       map.removeLayer(this._shownPolygon);
+               }
+               if (e.layer.getChildCount() > 2 && e.layer !== this._spiderfied) {
+                       this._shownPolygon = new L.Polygon(e.layer.getConvexHull(), this.options.polygonOptions);
+                       map.addLayer(this._shownPolygon);
+               }
+       },
+
+       _hideCoverage: function () {
+               if (this._shownPolygon) {
+                       this._map.removeLayer(this._shownPolygon);
+                       this._shownPolygon = null;
+               }
+       },
+
+       _unbindEvents: function () {
+               var spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom,
+                       showCoverageOnHover = this.options.showCoverageOnHover,
+                       zoomToBoundsOnClick = this.options.zoomToBoundsOnClick,
+                       map = this._map;
+
+               if (spiderfyOnMaxZoom || zoomToBoundsOnClick) {
+                       this.off('clusterclick', this._zoomOrSpiderfy, this);
+               }
+               if (showCoverageOnHover) {
+                       this.off('clustermouseover', this._showCoverage, this);
+                       this.off('clustermouseout', this._hideCoverage, this);
+                       map.off('zoomend', this._hideCoverage, this);
+               }
+       },
+
+       _zoomEnd: function () {
+               if (!this._map) { //May have been removed from the map by a zoomEnd handler
+                       return;
+               }
+               this._mergeSplitClusters();
+
+               this._zoom = this._map._zoom;
+               this._currentShownBounds = this._getExpandedVisibleBounds();
+       },
+
+       _moveEnd: function () {
+               if (this._inZoomAnimation) {
+                       return;
+               }
+
+               var newBounds = this._getExpandedVisibleBounds();
+
+               this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, this._zoom, newBounds);
+               this._topClusterLevel._recursivelyAddChildrenToMap(null, this._map._zoom, newBounds);
+
+               this._currentShownBounds = newBounds;
+               return;
+       },
+
+       _generateInitialClusters: function () {
+               var maxZoom = this._map.getMaxZoom(),
+                       radius = this.options.maxClusterRadius,
+                       radiusFn = radius;
+       
+               //If we just set maxClusterRadius to a single number, we need to create
+               //a simple function to return that number. Otherwise, we just have to
+               //use the function we've passed in.
+               if (typeof radius !== "function") {
+                       radiusFn = function () { return radius; };
+               }
+
+               if (this.options.disableClusteringAtZoom) {
+                       maxZoom = this.options.disableClusteringAtZoom - 1;
+               }
+               this._maxZoom = maxZoom;
+               this._gridClusters = {};
+               this._gridUnclustered = {};
+       
+               //Set up DistanceGrids for each zoom
+               for (var zoom = maxZoom; zoom >= 0; zoom--) {
+                       this._gridClusters[zoom] = new L.DistanceGrid(radiusFn(zoom));
+                       this._gridUnclustered[zoom] = new L.DistanceGrid(radiusFn(zoom));
+               }
+
+               this._topClusterLevel = new L.MarkerCluster(this, -1);
+       },
+
+       //Zoom: Zoom to start adding at (Pass this._maxZoom to start at the bottom)
+       _addLayer: function (layer, zoom) {
+               var gridClusters = this._gridClusters,
+                   gridUnclustered = this._gridUnclustered,
+                   markerPoint, z;
+
+               if (this.options.singleMarkerMode) {
+                       layer.options.icon = this.options.iconCreateFunction({
+                               getChildCount: function () {
+                                       return 1;
+                               },
+                               getAllChildMarkers: function () {
+                                       return [layer];
+                               }
+                       });
+               }
+
+               //Find the lowest zoom level to slot this one in
+               for (; zoom >= 0; zoom--) {
+                       markerPoint = this._map.project(layer.getLatLng(), zoom); // calculate pixel position
+
+                       //Try find a cluster close by
+                       var closest = gridClusters[zoom].getNearObject(markerPoint);
+                       if (closest) {
+                               closest._addChild(layer);
+                               layer.__parent = closest;
+                               return;
+                       }
+
+                       //Try find a marker close by to form a new cluster with
+                       closest = gridUnclustered[zoom].getNearObject(markerPoint);
+                       if (closest) {
+                               var parent = closest.__parent;
+                               if (parent) {
+                                       this._removeLayer(closest, false);
+                               }
+
+                               //Create new cluster with these 2 in it
+
+                               var newCluster = new L.MarkerCluster(this, zoom, closest, layer);
+                               gridClusters[zoom].addObject(newCluster, this._map.project(newCluster._cLatLng, zoom));
+                               closest.__parent = newCluster;
+                               layer.__parent = newCluster;
+
+                               //First create any new intermediate parent clusters that don't exist
+                               var lastParent = newCluster;
+                               for (z = zoom - 1; z > parent._zoom; z--) {
+                                       lastParent = new L.MarkerCluster(this, z, lastParent);
+                                       gridClusters[z].addObject(lastParent, this._map.project(closest.getLatLng(), z));
+                               }
+                               parent._addChild(lastParent);
+
+                               //Remove closest from this zoom level and any above that it is in, replace with newCluster
+                               for (z = zoom; z >= 0; z--) {
+                                       if (!gridUnclustered[z].removeObject(closest, this._map.project(closest.getLatLng(), z))) {
+                                               break;
+                                       }
+                               }
+
+                               return;
+                       }
+
+                       //Didn't manage to cluster in at this zoom, record us as a marker here and continue upwards
+                       gridUnclustered[zoom].addObject(layer, markerPoint);
+               }
+
+               //Didn't get in anything, add us to the top
+               this._topClusterLevel._addChild(layer);
+               layer.__parent = this._topClusterLevel;
+               return;
+       },
+
+       //Enqueue code to fire after the marker expand/contract has happened
+       _enqueue: function (fn) {
+               this._queue.push(fn);
+               if (!this._queueTimeout) {
+                       this._queueTimeout = setTimeout(L.bind(this._processQueue, this), 300);
+               }
+       },
+       _processQueue: function () {
+               for (var i = 0; i < this._queue.length; i++) {
+                       this._queue[i].call(this);
+               }
+               this._queue.length = 0;
+               clearTimeout(this._queueTimeout);
+               this._queueTimeout = null;
+       },
+
+       //Merge and split any existing clusters that are too big or small
+       _mergeSplitClusters: function () {
+
+               //Incase we are starting to split before the animation finished
+               this._processQueue();
+
+               if (this._zoom < this._map._zoom && this._currentShownBounds.intersects(this._getExpandedVisibleBounds())) { //Zoom in, split
+                       this._animationStart();
+                       //Remove clusters now off screen
+                       this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, this._zoom, this._getExpandedVisibleBounds());
+
+                       this._animationZoomIn(this._zoom, this._map._zoom);
+
+               } else if (this._zoom > this._map._zoom) { //Zoom out, merge
+                       this._animationStart();
+
+                       this._animationZoomOut(this._zoom, this._map._zoom);
+               } else {
+                       this._moveEnd();
+               }
+       },
+
+       //Gets the maps visible bounds expanded in each direction by the size of the screen (so the user cannot see an area we do not cover in one pan)
+       _getExpandedVisibleBounds: function () {
+               if (!this.options.removeOutsideVisibleBounds) {
+                       return this._map.getBounds();
+               }
+
+               var map = this._map,
+                       bounds = map.getBounds(),
+                       sw = bounds._southWest,
+                       ne = bounds._northEast,
+                       latDiff = L.Browser.mobile ? 0 : Math.abs(sw.lat - ne.lat),
+                       lngDiff = L.Browser.mobile ? 0 : Math.abs(sw.lng - ne.lng);
+
+               return new L.LatLngBounds(
+                       new L.LatLng(sw.lat - latDiff, sw.lng - lngDiff, true),
+                       new L.LatLng(ne.lat + latDiff, ne.lng + lngDiff, true));
+       },
+
+       //Shared animation code
+       _animationAddLayerNonAnimated: function (layer, newCluster) {
+               if (newCluster === layer) {
+                       this._featureGroup.addLayer(layer);
+               } else if (newCluster._childCount === 2) {
+                       newCluster._addToMap();
+
+                       var markers = newCluster.getAllChildMarkers();
+                       this._featureGroup.removeLayer(markers[0]);
+                       this._featureGroup.removeLayer(markers[1]);
+               } else {
+                       newCluster._updateIcon();
+               }
+       }
+});
+
+L.MarkerClusterGroup.include(!L.DomUtil.TRANSITION ? {
+
+       //Non Animated versions of everything
+       _animationStart: function () {
+               //Do nothing...
+       },
+       _animationZoomIn: function (previousZoomLevel, newZoomLevel) {
+               this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, previousZoomLevel);
+               this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
+
+               //We didn't actually animate, but we use this event to mean "clustering animations have finished"
+               this.fire('animationend');
+       },
+       _animationZoomOut: function (previousZoomLevel, newZoomLevel) {
+               this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, previousZoomLevel);
+               this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
+
+               //We didn't actually animate, but we use this event to mean "clustering animations have finished"
+               this.fire('animationend');
+       },
+       _animationAddLayer: function (layer, newCluster) {
+               this._animationAddLayerNonAnimated(layer, newCluster);
+       }
+} : {
+
+       //Animated versions here
+       _animationStart: function () {
+               this._map._mapPane.className += ' leaflet-cluster-anim';
+               this._inZoomAnimation++;
+       },
+       _animationEnd: function () {
+               if (this._map) {
+                       this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '');
+               }
+               this._inZoomAnimation--;
+               this.fire('animationend');
+       },
+       _animationZoomIn: function (previousZoomLevel, newZoomLevel) {
+               var bounds = this._getExpandedVisibleBounds(),
+                   fg = this._featureGroup,
+                   i;
+
+               //Add all children of current clusters to map and remove those clusters from map
+               this._topClusterLevel._recursively(bounds, previousZoomLevel, 0, function (c) {
+                       var startPos = c._latlng,
+                               markers = c._markers,
+                               m;
+
+                       if (!bounds.contains(startPos)) {
+                               startPos = null;
+                       }
+
+                       if (c._isSingleParent() && previousZoomLevel + 1 === newZoomLevel) { //Immediately add the new child and remove us
+                               fg.removeLayer(c);
+                               c._recursivelyAddChildrenToMap(null, newZoomLevel, bounds);
+                       } else {
+                               //Fade out old cluster
+                               c.setOpacity(0);
+                               c._recursivelyAddChildrenToMap(startPos, newZoomLevel, bounds);
+                       }
+
+                       //Remove all markers that aren't visible any more
+                       //TODO: Do we actually need to do this on the higher levels too?
+                       for (i = markers.length - 1; i >= 0; i--) {
+                               m = markers[i];
+                               if (!bounds.contains(m._latlng)) {
+                                       fg.removeLayer(m);
+                               }
+                       }
+
+               });
+
+               this._forceLayout();
+
+               //Update opacities
+               this._topClusterLevel._recursivelyBecomeVisible(bounds, newZoomLevel);
+               //TODO Maybe? Update markers in _recursivelyBecomeVisible
+               fg.eachLayer(function (n) {
+                       if (!(n instanceof L.MarkerCluster) && n._icon) {
+                               n.setOpacity(1);
+                       }
+               });
+
+               //update the positions of the just added clusters/markers
+               this._topClusterLevel._recursively(bounds, previousZoomLevel, newZoomLevel, function (c) {
+                       c._recursivelyRestoreChildPositions(newZoomLevel);
+               });
+
+               //Remove the old clusters and close the zoom animation
+               this._enqueue(function () {
+                       //update the positions of the just added clusters/markers
+                       this._topClusterLevel._recursively(bounds, previousZoomLevel, 0, function (c) {
+                               fg.removeLayer(c);
+                               c.setOpacity(1);
+                       });
+
+                       this._animationEnd();
+               });
+       },
+
+       _animationZoomOut: function (previousZoomLevel, newZoomLevel) {
+               this._animationZoomOutSingle(this._topClusterLevel, previousZoomLevel - 1, newZoomLevel);
+
+               //Need to add markers for those that weren't on the map before but are now
+               this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
+               //Remove markers that were on the map before but won't be now
+               this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, previousZoomLevel, this._getExpandedVisibleBounds());
+       },
+       _animationZoomOutSingle: function (cluster, previousZoomLevel, newZoomLevel) {
+               var bounds = this._getExpandedVisibleBounds();
+
+               //Animate all of the markers in the clusters to move to their cluster center point
+               cluster._recursivelyAnimateChildrenInAndAddSelfToMap(bounds, previousZoomLevel + 1, newZoomLevel);
+
+               var me = this;
+
+               //Update the opacity (If we immediately set it they won't animate)
+               this._forceLayout();
+               cluster._recursivelyBecomeVisible(bounds, newZoomLevel);
+
+               //TODO: Maybe use the transition timing stuff to make this more reliable
+               //When the animations are done, tidy up
+               this._enqueue(function () {
+
+                       //This cluster stopped being a cluster before the timeout fired
+                       if (cluster._childCount === 1) {
+                               var m = cluster._markers[0];
+                               //If we were in a cluster animation at the time then the opacity and position of our child could be wrong now, so fix it
+                               m.setLatLng(m.getLatLng());
+                               if (m.setOpacity) {
+                                       m.setOpacity(1);
+                               }
+                       } else {
+                               cluster._recursively(bounds, newZoomLevel, 0, function (c) {
+                                       c._recursivelyRemoveChildrenFromMap(bounds, previousZoomLevel + 1);
+                               });
+                       }
+                       me._animationEnd();
+               });
+       },
+       _animationAddLayer: function (layer, newCluster) {
+               var me = this,
+                       fg = this._featureGroup;
+
+               fg.addLayer(layer);
+               if (newCluster !== layer) {
+                       if (newCluster._childCount > 2) { //Was already a cluster
+
+                               newCluster._updateIcon();
+                               this._forceLayout();
+                               this._animationStart();
+
+                               layer._setPos(this._map.latLngToLayerPoint(newCluster.getLatLng()));
+                               layer.setOpacity(0);
+
+                               this._enqueue(function () {
+                                       fg.removeLayer(layer);
+                                       layer.setOpacity(1);
+
+                                       me._animationEnd();
+                               });
+
+                       } else { //Just became a cluster
+                               this._forceLayout();
+
+                               me._animationStart();
+                               me._animationZoomOutSingle(newCluster, this._map.getMaxZoom(), this._map.getZoom());
+                       }
+               }
+       },
+
+       //Force a browser layout of stuff in the map
+       // Should apply the current opacity and location to all elements so we can update them again for an animation
+       _forceLayout: function () {
+               //In my testing this works, infact offsetWidth of any element seems to work.
+               //Could loop all this._layers and do this for each _icon if it stops working
+
+               L.Util.falseFn(document.body.offsetWidth);
+       }
+});
+
+L.markerClusterGroup = function (options) {
+       return new L.MarkerClusterGroup(options);
+};
+
+
+L.MarkerCluster = L.Marker.extend({
+       initialize: function (group, zoom, a, b) {
+
+               L.Marker.prototype.initialize.call(this, a ? (a._cLatLng || a.getLatLng()) : new L.LatLng(0, 0), { icon: this });
+
+
+               this._group = group;
+               this._zoom = zoom;
+
+               this._markers = [];
+               this._childClusters = [];
+               this._childCount = 0;
+               this._iconNeedsUpdate = true;
+
+               this._bounds = new L.LatLngBounds();
+
+               if (a) {
+                       this._addChild(a);
+               }
+               if (b) {
+                       this._addChild(b);
+               }
+       },
+
+       //Recursively retrieve all child markers of this cluster
+       getAllChildMarkers: function (storageArray) {
+               storageArray = storageArray || [];
+
+               for (var i = this._childClusters.length - 1; i >= 0; i--) {
+                       this._childClusters[i].getAllChildMarkers(storageArray);
+               }
+
+               for (var j = this._markers.length - 1; j >= 0; j--) {
+                       storageArray.push(this._markers[j]);
+               }
+
+               return storageArray;
+       },
+
+       //Returns the count of how many child markers we have
+       getChildCount: function () {
+               return this._childCount;
+       },
+
+       //Zoom to the minimum of showing all of the child markers, or the extents of this cluster
+       zoomToBounds: function () {
+               var childClusters = this._childClusters.slice(),
+                       map = this._group._map,
+                       boundsZoom = map.getBoundsZoom(this._bounds),
+                       zoom = this._zoom + 1,
+                       mapZoom = map.getZoom(),
+                       i;
+
+               //calculate how far we need to zoom down to see all of the markers
+               while (childClusters.length > 0 && boundsZoom > zoom) {
+                       zoom++;
+                       var newClusters = [];
+                       for (i = 0; i < childClusters.length; i++) {
+                               newClusters = newClusters.concat(childClusters[i]._childClusters);
+                       }
+                       childClusters = newClusters;
+               }
+
+               if (boundsZoom > zoom) {
+                       this._group._map.setView(this._latlng, zoom);
+               } else if (boundsZoom <= mapZoom) { //If fitBounds wouldn't zoom us down, zoom us down instead
+                       this._group._map.setView(this._latlng, mapZoom + 1);
+               } else {
+                       this._group._map.fitBounds(this._bounds);
+               }
+       },
+
+       getBounds: function () {
+               var bounds = new L.LatLngBounds();
+               bounds.extend(this._bounds);
+               return bounds;
+       },
+
+       _updateIcon: function () {
+               this._iconNeedsUpdate = true;
+               if (this._icon) {
+                       this.setIcon(this);
+               }
+       },
+
+       //Cludge for Icon, we pretend to be an icon for performance
+       createIcon: function () {
+               if (this._iconNeedsUpdate) {
+                       this._iconObj = this._group.options.iconCreateFunction(this);
+                       this._iconNeedsUpdate = false;
+               }
+               return this._iconObj.createIcon();
+       },
+       createShadow: function () {
+               return this._iconObj.createShadow();
+       },
+
+
+       _addChild: function (new1, isNotificationFromChild) {
+
+               this._iconNeedsUpdate = true;
+               this._expandBounds(new1);
+
+               if (new1 instanceof L.MarkerCluster) {
+                       if (!isNotificationFromChild) {
+                               this._childClusters.push(new1);
+                               new1.__parent = this;
+                       }
+                       this._childCount += new1._childCount;
+               } else {
+                       if (!isNotificationFromChild) {
+                               this._markers.push(new1);
+                       }
+                       this._childCount++;
+               }
+
+               if (this.__parent) {
+                       this.__parent._addChild(new1, true);
+               }
+       },
+
+       //Expand our bounds and tell our parent to
+       _expandBounds: function (marker) {
+               var addedCount,
+                   addedLatLng = marker._wLatLng || marker._latlng;
+
+               if (marker instanceof L.MarkerCluster) {
+                       this._bounds.extend(marker._bounds);
+                       addedCount = marker._childCount;
+               } else {
+                       this._bounds.extend(addedLatLng);
+                       addedCount = 1;
+               }
+
+               if (!this._cLatLng) {
+                       // when clustering, take position of the first point as the cluster center
+                       this._cLatLng = marker._cLatLng || addedLatLng;
+               }
+
+               // when showing clusters, take weighted average of all points as cluster center
+               var totalCount = this._childCount + addedCount;
+
+               //Calculate weighted latlng for display
+               if (!this._wLatLng) {
+                       this._latlng = this._wLatLng = new L.LatLng(addedLatLng.lat, addedLatLng.lng);
+               } else {
+                       this._wLatLng.lat = (addedLatLng.lat * addedCount + this._wLatLng.lat * this._childCount) / totalCount;
+                       this._wLatLng.lng = (addedLatLng.lng * addedCount + this._wLatLng.lng * this._childCount) / totalCount;
+               }
+       },
+
+       //Set our markers position as given and add it to the map
+       _addToMap: function (startPos) {
+               if (startPos) {
+                       this._backupLatlng = this._latlng;
+                       this.setLatLng(startPos);
+               }
+               this._group._featureGroup.addLayer(this);
+       },
+
+       _recursivelyAnimateChildrenIn: function (bounds, center, maxZoom) {
+               this._recursively(bounds, 0, maxZoom - 1,
+                       function (c) {
+                               var markers = c._markers,
+                                       i, m;
+                               for (i = markers.length - 1; i >= 0; i--) {
+                                       m = markers[i];
+
+                                       //Only do it if the icon is still on the map
+                                       if (m._icon) {
+                                               m._setPos(center);
+                                               m.setOpacity(0);
+                                       }
+                               }
+                       },
+                       function (c) {
+                               var childClusters = c._childClusters,
+                                       j, cm;
+                               for (j = childClusters.length - 1; j >= 0; j--) {
+                                       cm = childClusters[j];
+                                       if (cm._icon) {
+                                               cm._setPos(center);
+                                               cm.setOpacity(0);
+                                       }
+                               }
+                       }
+               );
+       },
+
+       _recursivelyAnimateChildrenInAndAddSelfToMap: function (bounds, previousZoomLevel, newZoomLevel) {
+               this._recursively(bounds, newZoomLevel, 0,
+                       function (c) {
+                               c._recursivelyAnimateChildrenIn(bounds, c._group._map.latLngToLayerPoint(c.getLatLng()).round(), previousZoomLevel);
+
+                               //TODO: depthToAnimateIn affects _isSingleParent, if there is a multizoom we may/may not be.
+                               //As a hack we only do a animation free zoom on a single level zoom, if someone does multiple levels then we always animate
+                               if (c._isSingleParent() && previousZoomLevel - 1 === newZoomLevel) {
+                                       c.setOpacity(1);
+                                       c._recursivelyRemoveChildrenFromMap(bounds, previousZoomLevel); //Immediately remove our children as we are replacing them. TODO previousBounds not bounds
+                               } else {
+                                       c.setOpacity(0);
+                               }
+
+                               c._addToMap();
+                       }
+               );
+       },
+
+       _recursivelyBecomeVisible: function (bounds, zoomLevel) {
+               this._recursively(bounds, 0, zoomLevel, null, function (c) {
+                       c.setOpacity(1);
+               });
+       },
+
+       _recursivelyAddChildrenToMap: function (startPos, zoomLevel, bounds) {
+               this._recursively(bounds, -1, zoomLevel,
+                       function (c) {
+                               if (zoomLevel === c._zoom) {
+                                       return;
+                               }
+
+                               //Add our child markers at startPos (so they can be animated out)
+                               for (var i = c._markers.length - 1; i >= 0; i--) {
+                                       var nm = c._markers[i];
+
+                                       if (!bounds.contains(nm._latlng)) {
+                                               continue;
+                                       }
+
+                                       if (startPos) {
+                                               nm._backupLatlng = nm.getLatLng();
+
+                                               nm.setLatLng(startPos);
+                                               if (nm.setOpacity) {
+                                                       nm.setOpacity(0);
+                                               }
+                                       }
+
+                                       c._group._featureGroup.addLayer(nm);
+                               }
+                       },
+                       function (c) {
+                               c._addToMap(startPos);
+                       }
+               );
+       },
+
+       _recursivelyRestoreChildPositions: function (zoomLevel) {
+               //Fix positions of child markers
+               for (var i = this._markers.length - 1; i >= 0; i--) {
+                       var nm = this._markers[i];
+                       if (nm._backupLatlng) {
+                               nm.setLatLng(nm._backupLatlng);
+                               delete nm._backupLatlng;
+                       }
+               }
+
+               if (zoomLevel - 1 === this._zoom) {
+                       //Reposition child clusters
+                       for (var j = this._childClusters.length - 1; j >= 0; j--) {
+                               this._childClusters[j]._restorePosition();
+                       }
+               } else {
+                       for (var k = this._childClusters.length - 1; k >= 0; k--) {
+                               this._childClusters[k]._recursivelyRestoreChildPositions(zoomLevel);
+                       }
+               }
+       },
+
+       _restorePosition: function () {
+               if (this._backupLatlng) {
+                       this.setLatLng(this._backupLatlng);
+                       delete this._backupLatlng;
+               }
+       },
+
+       //exceptBounds: If set, don't remove any markers/clusters in it
+       _recursivelyRemoveChildrenFromMap: function (previousBounds, zoomLevel, exceptBounds) {
+               var m, i;
+               this._recursively(previousBounds, -1, zoomLevel - 1,
+                       function (c) {
+                               //Remove markers at every level
+                               for (i = c._markers.length - 1; i >= 0; i--) {
+                                       m = c._markers[i];
+                                       if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
+                                               c._group._featureGroup.removeLayer(m);
+                                               if (m.setOpacity) {
+                                                       m.setOpacity(1);
+                                               }
+                                       }
+                               }
+                       },
+                       function (c) {
+                               //Remove child clusters at just the bottom level
+                               for (i = c._childClusters.length - 1; i >= 0; i--) {
+                                       m = c._childClusters[i];
+                                       if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
+                                               c._group._featureGroup.removeLayer(m);
+                                               if (m.setOpacity) {
+                                                       m.setOpacity(1);
+                                               }
+                                       }
+                               }
+                       }
+               );
+       },
+
+       //Run the given functions recursively to this and child clusters
+       // boundsToApplyTo: a L.LatLngBounds representing the bounds of what clusters to recurse in to
+       // zoomLevelToStart: zoom level to start running functions (inclusive)
+       // zoomLevelToStop: zoom level to stop running functions (inclusive)
+       // runAtEveryLevel: function that takes an L.MarkerCluster as an argument that should be applied on every level
+       // runAtBottomLevel: function that takes an L.MarkerCluster as an argument that should be applied at only the bottom level
+       _recursively: function (boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel) {
+               var childClusters = this._childClusters,
+                   zoom = this._zoom,
+                       i, c;
+
+               if (zoomLevelToStart > zoom) { //Still going down to required depth, just recurse to child clusters
+                       for (i = childClusters.length - 1; i >= 0; i--) {
+                               c = childClusters[i];
+                               if (boundsToApplyTo.intersects(c._bounds)) {
+                                       c._recursively(boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel);
+                               }
+                       }
+               } else { //In required depth
+
+                       if (runAtEveryLevel) {
+                               runAtEveryLevel(this);
+                       }
+                       if (runAtBottomLevel && this._zoom === zoomLevelToStop) {
+                               runAtBottomLevel(this);
+                       }
+
+                       //TODO: This loop is almost the same as above
+                       if (zoomLevelToStop > zoom) {
+                               for (i = childClusters.length - 1; i >= 0; i--) {
+                                       c = childClusters[i];
+                                       if (boundsToApplyTo.intersects(c._bounds)) {
+                                               c._recursively(boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel);
+                                       }
+                               }
+                       }
+               }
+       },
+
+       _recalculateBounds: function () {
+               var markers = this._markers,
+                       childClusters = this._childClusters,
+                       i;
+
+               this._bounds = new L.LatLngBounds();
+               delete this._wLatLng;
+
+               for (i = markers.length - 1; i >= 0; i--) {
+                       this._expandBounds(markers[i]);
+               }
+               for (i = childClusters.length - 1; i >= 0; i--) {
+                       this._expandBounds(childClusters[i]);
+               }
+       },
+
+
+       //Returns true if we are the parent of only one cluster and that cluster is the same as us
+       _isSingleParent: function () {
+               //Don't need to check this._markers as the rest won't work if there are any
+               return this._childClusters.length > 0 && this._childClusters[0]._childCount === this._childCount;
+       }
+});
+
+
+
+L.DistanceGrid = function (cellSize) {
+       this._cellSize = cellSize;
+       this._sqCellSize = cellSize * cellSize;
+       this._grid = {};
+       this._objectPoint = { };
+};
+
+L.DistanceGrid.prototype = {
+
+       addObject: function (obj, point) {
+               var x = this._getCoord(point.x),
+                   y = this._getCoord(point.y),
+                   grid = this._grid,
+                   row = grid[y] = grid[y] || {},
+                   cell = row[x] = row[x] || [],
+                   stamp = L.Util.stamp(obj);
+
+               this._objectPoint[stamp] = point;
+
+               cell.push(obj);
+       },
+
+       updateObject: function (obj, point) {
+               this.removeObject(obj);
+               this.addObject(obj, point);
+       },
+
+       //Returns true if the object was found
+       removeObject: function (obj, point) {
+               var x = this._getCoord(point.x),
+                   y = this._getCoord(point.y),
+                   grid = this._grid,
+                   row = grid[y] = grid[y] || {},
+                   cell = row[x] = row[x] || [],
+                   i, len;
+
+               delete this._objectPoint[L.Util.stamp(obj)];
+
+               for (i = 0, len = cell.length; i < len; i++) {
+                       if (cell[i] === obj) {
+
+                               cell.splice(i, 1);
+
+                               if (len === 1) {
+                                       delete row[x];
+                               }
+
+                               return true;
+                       }
+               }
+
+       },
+
+       eachObject: function (fn, context) {
+               var i, j, k, len, row, cell, removed,
+                   grid = this._grid;
+
+               for (i in grid) {
+                       row = grid[i];
+
+                       for (j in row) {
+                               cell = row[j];
+
+                               for (k = 0, len = cell.length; k < len; k++) {
+                                       removed = fn.call(context, cell[k]);
+                                       if (removed) {
+                                               k--;
+                                               len--;
+                                       }
+                               }
+                       }
+               }
+       },
+
+       getNearObject: function (point) {
+               var x = this._getCoord(point.x),
+                   y = this._getCoord(point.y),
+                   i, j, k, row, cell, len, obj, dist,
+                   objectPoint = this._objectPoint,
+                   closestDistSq = this._sqCellSize,
+                   closest = null;
+
+               for (i = y - 1; i <= y + 1; i++) {
+                       row = this._grid[i];
+                       if (row) {
+
+                               for (j = x - 1; j <= x + 1; j++) {
+                                       cell = row[j];
+                                       if (cell) {
+
+                                               for (k = 0, len = cell.length; k < len; k++) {
+                                                       obj = cell[k];
+                                                       dist = this._sqDist(objectPoint[L.Util.stamp(obj)], point);
+                                                       if (dist < closestDistSq) {
+                                                               closestDistSq = dist;
+                                                               closest = obj;
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+               }
+               return closest;
+       },
+
+       _getCoord: function (x) {
+               return Math.floor(x / this._cellSize);
+       },
+
+       _sqDist: function (p, p2) {
+               var dx = p2.x - p.x,
+                   dy = p2.y - p.y;
+               return dx * dx + dy * dy;
+       }
+};
+
+
+/* Copyright (c) 2012 the authors listed at the following URL, and/or
+the authors of referenced articles or incorporated external code:
+http://en.literateprograms.org/Quickhull_(Javascript)?action=history&offset=20120410175256
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Retrieved from: http://en.literateprograms.org/Quickhull_(Javascript)?oldid=18434
+*/
+
+(function () {
+       L.QuickHull = {
+
+               /*
+                * @param {Object} cpt a point to be measured from the baseline
+                * @param {Array} bl the baseline, as represented by a two-element
+                *   array of latlng objects.
+                * @returns {Number} an approximate distance measure
+                */
+               getDistant: function (cpt, bl) {
+                       var vY = bl[1].lat - bl[0].lat,
+                               vX = bl[0].lng - bl[1].lng;
+                       return (vX * (cpt.lat - bl[0].lat) + vY * (cpt.lng - bl[0].lng));
+               },
+
+               /*
+                * @param {Array} baseLine a two-element array of latlng objects
+                *   representing the baseline to project from
+                * @param {Array} latLngs an array of latlng objects
+                * @returns {Object} the maximum point and all new points to stay
+                *   in consideration for the hull.
+                */
+               findMostDistantPointFromBaseLine: function (baseLine, latLngs) {
+                       var maxD = 0,
+                               maxPt = null,
+                               newPoints = [],
+                               i, pt, d;
+
+                       for (i = latLngs.length - 1; i >= 0; i--) {
+                               pt = latLngs[i];
+                               d = this.getDistant(pt, baseLine);
+
+                               if (d > 0) {
+                                       newPoints.push(pt);
+                               } else {
+                                       continue;
+                               }
+
+                               if (d > maxD) {
+                                       maxD = d;
+                                       maxPt = pt;
+                               }
+                       }
+
+                       return { maxPoint: maxPt, newPoints: newPoints };
+               },
+
+
+               /*
+                * Given a baseline, compute the convex hull of latLngs as an array
+                * of latLngs.
+                *
+                * @param {Array} latLngs
+                * @returns {Array}
+                */
+               buildConvexHull: function (baseLine, latLngs) {
+                       var convexHullBaseLines = [],
+                               t = this.findMostDistantPointFromBaseLine(baseLine, latLngs);
+
+                       if (t.maxPoint) { // if there is still a point "outside" the base line
+                               convexHullBaseLines =
+                                       convexHullBaseLines.concat(
+                                               this.buildConvexHull([baseLine[0], t.maxPoint], t.newPoints)
+                                       );
+                               convexHullBaseLines =
+                                       convexHullBaseLines.concat(
+                                               this.buildConvexHull([t.maxPoint, baseLine[1]], t.newPoints)
+                                       );
+                               return convexHullBaseLines;
+                       } else {  // if there is no more point "outside" the base line, the current base line is part of the convex hull
+                               return [baseLine[0]];
+                       }
+               },
+
+               /*
+                * Given an array of latlngs, compute a convex hull as an array
+                * of latlngs
+                *
+                * @param {Array} latLngs
+                * @returns {Array}
+                */
+               getConvexHull: function (latLngs) {
+                       // find first baseline
+                       var maxLat = false, minLat = false,
+                               maxPt = null, minPt = null,
+                               i;
+
+                       for (i = latLngs.length - 1; i >= 0; i--) {
+                               var pt = latLngs[i];
+                               if (maxLat === false || pt.lat > maxLat) {
+                                       maxPt = pt;
+                                       maxLat = pt.lat;
+                               }
+                               if (minLat === false || pt.lat < minLat) {
+                                       minPt = pt;
+                                       minLat = pt.lat;
+                               }
+                       }
+                       var ch = [].concat(this.buildConvexHull([minPt, maxPt], latLngs),
+                                                               this.buildConvexHull([maxPt, minPt], latLngs));
+                       return ch;
+               }
+       };
+}());
+
+L.MarkerCluster.include({
+       getConvexHull: function () {
+               var childMarkers = this.getAllChildMarkers(),
+                       points = [],
+                       p, i;
+
+               for (i = childMarkers.length - 1; i >= 0; i--) {
+                       p = childMarkers[i].getLatLng();
+                       points.push(p);
+               }
+
+               return L.QuickHull.getConvexHull(points);
+       }
+});
+
+
+//This code is 100% based on https://github.com/jawj/OverlappingMarkerSpiderfier-Leaflet
+//Huge thanks to jawj for implementing it first to make my job easy :-)
+
+L.MarkerCluster.include({
+
+       _2PI: Math.PI * 2,
+       _circleFootSeparation: 25, //related to circumference of circle
+       _circleStartAngle: Math.PI / 6,
+
+       _spiralFootSeparation:  28, //related to size of spiral (experiment!)
+       _spiralLengthStart: 11,
+       _spiralLengthFactor: 5,
+
+       _circleSpiralSwitchover: 9, //show spiral instead of circle from this marker count upwards.
+                                                               // 0 -> always spiral; Infinity -> always circle
+
+       spiderfy: function () {
+               if (this._group._spiderfied === this || this._group._inZoomAnimation) {
+                       return;
+               }
+
+               var childMarkers = this.getAllChildMarkers(),
+                       group = this._group,
+                       map = group._map,
+                       center = map.latLngToLayerPoint(this._latlng),
+                       positions;
+
+               this._group._unspiderfy();
+               this._group._spiderfied = this;
+
+               //TODO Maybe: childMarkers order by distance to center
+
+               if (childMarkers.length >= this._circleSpiralSwitchover) {
+                       positions = this._generatePointsSpiral(childMarkers.length, center);
+               } else {
+                       center.y += 10; //Otherwise circles look wrong
+                       positions = this._generatePointsCircle(childMarkers.length, center);
+               }
+
+               this._animationSpiderfy(childMarkers, positions);
+       },
+
+       unspiderfy: function (zoomDetails) {
+               /// <param Name="zoomDetails">Argument from zoomanim if being called in a zoom animation or null otherwise</param>
+               if (this._group._inZoomAnimation) {
+                       return;
+               }
+               this._animationUnspiderfy(zoomDetails);
+
+               this._group._spiderfied = null;
+       },
+
+       _generatePointsCircle: function (count, centerPt) {
+               var circumference = this._group.options.spiderfyDistanceMultiplier * this._circleFootSeparation * (2 + count),
+                       legLength = circumference / this._2PI,  //radius from circumference
+                       angleStep = this._2PI / count,
+                       res = [],
+                       i, angle;
+
+               res.length = count;
+
+               for (i = count - 1; i >= 0; i--) {
+                       angle = this._circleStartAngle + i * angleStep;
+                       res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round();
+               }
+
+               return res;
+       },
+
+       _generatePointsSpiral: function (count, centerPt) {
+               var legLength = this._group.options.spiderfyDistanceMultiplier * this._spiralLengthStart,
+                       separation = this._group.options.spiderfyDistanceMultiplier * this._spiralFootSeparation,
+                       lengthFactor = this._group.options.spiderfyDistanceMultiplier * this._spiralLengthFactor,
+                       angle = 0,
+                       res = [],
+                       i;
+
+               res.length = count;
+
+               for (i = count - 1; i >= 0; i--) {
+                       angle += separation / legLength + i * 0.0005;
+                       res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round();
+                       legLength += this._2PI * lengthFactor / angle;
+               }
+               return res;
+       },
+
+       _noanimationUnspiderfy: function () {
+               var group = this._group,
+                       map = group._map,
+                       fg = group._featureGroup,
+                       childMarkers = this.getAllChildMarkers(),
+                       m, i;
+
+               this.setOpacity(1);
+               for (i = childMarkers.length - 1; i >= 0; i--) {
+                       m = childMarkers[i];
+
+                       fg.removeLayer(m);
+
+                       if (m._preSpiderfyLatlng) {
+                               m.setLatLng(m._preSpiderfyLatlng);
+                               delete m._preSpiderfyLatlng;
+                       }
+                       if (m.setZIndexOffset) {
+                               m.setZIndexOffset(0);
+                       }
+
+                       if (m._spiderLeg) {
+                               map.removeLayer(m._spiderLeg);
+                               delete m._spiderLeg;
+                       }
+               }
+
+               group._spiderfied = null;
+       }
+});
+
+L.MarkerCluster.include(!L.DomUtil.TRANSITION ? {
+       //Non Animated versions of everything
+       _animationSpiderfy: function (childMarkers, positions) {
+               var group = this._group,
+                       map = group._map,
+                       fg = group._featureGroup,
+                       i, m, leg, newPos;
+
+               for (i = childMarkers.length - 1; i >= 0; i--) {
+                       newPos = map.layerPointToLatLng(positions[i]);
+                       m = childMarkers[i];
+
+                       m._preSpiderfyLatlng = m._latlng;
+                       m.setLatLng(newPos);
+                       if (m.setZIndexOffset) {
+                               m.setZIndexOffset(1000000); //Make these appear on top of EVERYTHING
+                       }
+
+                       fg.addLayer(m);
+
+
+                       leg = new L.Polyline([this._latlng, newPos], { weight: 1.5, color: '#222' });
+                       map.addLayer(leg);
+                       m._spiderLeg = leg;
+               }
+               this.setOpacity(0.3);
+               group.fire('spiderfied');
+       },
+
+       _animationUnspiderfy: function () {
+               this._noanimationUnspiderfy();
+       }
+} : {
+       //Animated versions here
+       SVG_ANIMATION: (function () {
+               return document.createElementNS('http://www.w3.org/2000/svg', 'animate').toString().indexOf('SVGAnimate') > -1;
+       }()),
+
+       _animationSpiderfy: function (childMarkers, positions) {
+               var me = this,
+                       group = this._group,
+                       map = group._map,
+                       fg = group._featureGroup,
+                       thisLayerPos = map.latLngToLayerPoint(this._latlng),
+                       i, m, leg, newPos;
+
+               //Add markers to map hidden at our center point
+               for (i = childMarkers.length - 1; i >= 0; i--) {
+                       m = childMarkers[i];
+
+                       //If it is a marker, add it now and we'll animate it out
+                       if (m.setOpacity) {
+                               m.setZIndexOffset(1000000); //Make these appear on top of EVERYTHING
+                               m.setOpacity(0);
+                       
+                               fg.addLayer(m);
+
+                               m._setPos(thisLayerPos);
+                       } else {
+                               //Vectors just get immediately added
+                               fg.addLayer(m);
+                       }
+               }
+
+               group._forceLayout();
+               group._animationStart();
+
+               var initialLegOpacity = L.Path.SVG ? 0 : 0.3,
+                       xmlns = L.Path.SVG_NS;
+
+
+               for (i = childMarkers.length - 1; i >= 0; i--) {
+                       newPos = map.layerPointToLatLng(positions[i]);
+                       m = childMarkers[i];
+
+                       //Move marker to new position
+                       m._preSpiderfyLatlng = m._latlng;
+                       m.setLatLng(newPos);
+                       
+                       if (m.setOpacity) {
+                               m.setOpacity(1);
+                       }
+
+
+                       //Add Legs.
+                       leg = new L.Polyline([me._latlng, newPos], { weight: 1.5, color: '#222', opacity: initialLegOpacity });
+                       map.addLayer(leg);
+                       m._spiderLeg = leg;
+
+                       //Following animations don't work for canvas
+                       if (!L.Path.SVG || !this.SVG_ANIMATION) {
+                               continue;
+                       }
+
+                       //How this works:
+                       //http://stackoverflow.com/questions/5924238/how-do-you-animate-an-svg-path-in-ios
+                       //http://dev.opera.com/articles/view/advanced-svg-animation-techniques/
+
+                       //Animate length
+                       var length = leg._path.getTotalLength();
+                       leg._path.setAttribute("stroke-dasharray", length + "," + length);
+
+                       var anim = document.createElementNS(xmlns, "animate");
+                       anim.setAttribute("attributeName", "stroke-dashoffset");
+                       anim.setAttribute("begin", "indefinite");
+                       anim.setAttribute("from", length);
+                       anim.setAttribute("to", 0);
+                       anim.setAttribute("dur", 0.25);
+                       leg._path.appendChild(anim);
+                       anim.beginElement();
+
+                       //Animate opacity
+                       anim = document.createElementNS(xmlns, "animate");
+                       anim.setAttribute("attributeName", "stroke-opacity");
+                       anim.setAttribute("attributeName", "stroke-opacity");
+                       anim.setAttribute("begin", "indefinite");
+                       anim.setAttribute("from", 0);
+                       anim.setAttribute("to", 0.5);
+                       anim.setAttribute("dur", 0.25);
+                       leg._path.appendChild(anim);
+                       anim.beginElement();
+               }
+               me.setOpacity(0.3);
+
+               //Set the opacity of the spiderLegs back to their correct value
+               // The animations above override this until they complete.
+               // If the initial opacity of the spiderlegs isn't 0 then they appear before the animation starts.
+               if (L.Path.SVG) {
+                       this._group._forceLayout();
+
+                       for (i = childMarkers.length - 1; i >= 0; i--) {
+                               m = childMarkers[i]._spiderLeg;
+
+                               m.options.opacity = 0.5;
+                               m._path.setAttribute('stroke-opacity', 0.5);
+                       }
+               }
+
+               setTimeout(function () {
+                       group._animationEnd();
+                       group.fire('spiderfied');
+               }, 200);
+       },
+
+       _animationUnspiderfy: function (zoomDetails) {
+               var group = this._group,
+                       map = group._map,
+                       fg = group._featureGroup,
+                       thisLayerPos = zoomDetails ? map._latLngToNewLayerPoint(this._latlng, zoomDetails.zoom, zoomDetails.center) : map.latLngToLayerPoint(this._latlng),
+                       childMarkers = this.getAllChildMarkers(),
+                       svg = L.Path.SVG && this.SVG_ANIMATION,
+                       m, i, a;
+
+               group._animationStart();
+
+               //Make us visible and bring the child markers back in
+               this.setOpacity(1);
+               for (i = childMarkers.length - 1; i >= 0; i--) {
+                       m = childMarkers[i];
+
+                       //Marker was added to us after we were spidified
+                       if (!m._preSpiderfyLatlng) {
+                               continue;
+                       }
+
+                       //Fix up the location to the real one
+                       m.setLatLng(m._preSpiderfyLatlng);
+                       delete m._preSpiderfyLatlng;
+                       //Hack override the location to be our center
+                       if (m.setOpacity) {
+                               m._setPos(thisLayerPos);
+                               m.setOpacity(0);
+                       } else {
+                               fg.removeLayer(m);
+                       }
+
+                       //Animate the spider legs back in
+                       if (svg) {
+                               a = m._spiderLeg._path.childNodes[0];
+                               a.setAttribute('to', a.getAttribute('from'));
+                               a.setAttribute('from', 0);
+                               a.beginElement();
+
+                               a = m._spiderLeg._path.childNodes[1];
+                               a.setAttribute('from', 0.5);
+                               a.setAttribute('to', 0);
+                               a.setAttribute('stroke-opacity', 0);
+                               a.beginElement();
+
+                               m._spiderLeg._path.setAttribute('stroke-opacity', 0);
+                       }
+               }
+
+               setTimeout(function () {
+                       //If we have only <= one child left then that marker will be shown on the map so don't remove it!
+                       var stillThereChildCount = 0;
+                       for (i = childMarkers.length - 1; i >= 0; i--) {
+                               m = childMarkers[i];
+                               if (m._spiderLeg) {
+                                       stillThereChildCount++;
+                               }
+                       }
+
+
+                       for (i = childMarkers.length - 1; i >= 0; i--) {
+                               m = childMarkers[i];
+
+                               if (!m._spiderLeg) { //Has already been unspiderfied
+                                       continue;
+                               }
+
+
+                               if (m.setOpacity) {
+                                       m.setOpacity(1);
+                                       m.setZIndexOffset(0);
+                               }
+
+                               if (stillThereChildCount > 1) {
+                                       fg.removeLayer(m);
+                               }
+
+                               map.removeLayer(m._spiderLeg);
+                               delete m._spiderLeg;
+                       }
+                       group._animationEnd();
+               }, 200);
+       }
+});
+
+
+L.MarkerClusterGroup.include({
+       //The MarkerCluster currently spiderfied (if any)
+       _spiderfied: null,
+
+       _spiderfierOnAdd: function () {
+               this._map.on('click', this._unspiderfyWrapper, this);
+
+               if (this._map.options.zoomAnimation) {
+                       this._map.on('zoomstart', this._unspiderfyZoomStart, this);
+               }
+               //Browsers without zoomAnimation or a big zoom don't fire zoomstart
+               this._map.on('zoomend', this._noanimationUnspiderfy, this);
+
+               if (L.Path.SVG && !L.Browser.touch) {
+                       this._map._initPathRoot();
+                       //Needs to happen in the pageload, not after, or animations don't work in webkit
+                       //  http://stackoverflow.com/questions/8455200/svg-animate-with-dynamically-added-elements
+                       //Disable on touch browsers as the animation messes up on a touch zoom and isn't very noticable
+               }
+       },
+
+       _spiderfierOnRemove: function () {
+               this._map.off('click', this._unspiderfyWrapper, this);
+               this._map.off('zoomstart', this._unspiderfyZoomStart, this);
+               this._map.off('zoomanim', this._unspiderfyZoomAnim, this);
+
+               this._unspiderfy(); //Ensure that markers are back where they should be
+       },
+
+
+       //On zoom start we add a zoomanim handler so that we are guaranteed to be last (after markers are animated)
+       //This means we can define the animation they do rather than Markers doing an animation to their actual location
+       _unspiderfyZoomStart: function () {
+               if (!this._map) { //May have been removed from the map by a zoomEnd handler
+                       return;
+               }
+
+               this._map.on('zoomanim', this._unspiderfyZoomAnim, this);
+       },
+       _unspiderfyZoomAnim: function (zoomDetails) {
+               //Wait until the first zoomanim after the user has finished touch-zooming before running the animation
+               if (L.DomUtil.hasClass(this._map._mapPane, 'leaflet-touching')) {
+                       return;
+               }
+
+               this._map.off('zoomanim', this._unspiderfyZoomAnim, this);
+               this._unspiderfy(zoomDetails);
+       },
+
+
+       _unspiderfyWrapper: function () {
+               /// <summary>_unspiderfy but passes no arguments</summary>
+               this._unspiderfy();
+       },
+
+       _unspiderfy: function (zoomDetails) {
+               if (this._spiderfied) {
+                       this._spiderfied.unspiderfy(zoomDetails);
+               }
+       },
+
+       _noanimationUnspiderfy: function () {
+               if (this._spiderfied) {
+                       this._spiderfied._noanimationUnspiderfy();
+               }
+       },
+
+       //If the given layer is currently being spiderfied then we unspiderfy it so it isn't on the map anymore etc
+       _unspiderfyLayer: function (layer) {
+               if (layer._spiderLeg) {
+                       this._featureGroup.removeLayer(layer);
+
+                       layer.setOpacity(1);
+                       //Position will be fixed up immediately in _animationUnspiderfy
+                       layer.setZIndexOffset(0);
+
+                       this._map.removeLayer(layer._spiderLeg);
+                       delete layer._spiderLeg;
+               }
+       }
+});
+
+
+}(window, document));
\ No newline at end of file
diff --git a/www/plugins/gis/lib/leaflet/plugins/leaflet.markercluster.css b/www/plugins/gis/lib/leaflet/plugins/leaflet.markercluster.css
new file mode 100755 (executable)
index 0000000..789c8a3
--- /dev/null
@@ -0,0 +1,67 @@
+.leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow {
+       -webkit-transition: -webkit-transform 0.3s ease-out, opacity 0.3s ease-in;
+       -moz-transition: -moz-transform 0.3s ease-out, opacity 0.3s ease-in;
+       -o-transition: -o-transform 0.3s ease-out, opacity 0.3s ease-in;
+       transition: transform 0.3s ease-out, opacity 0.3s ease-in;
+       }
+
+.marker-cluster-small {
+       background-color: rgba(181, 226, 140, 0.6);
+       }
+.marker-cluster-small div {
+       background-color: rgba(110, 204, 57, 0.6);
+       }
+
+.marker-cluster-medium {
+       background-color: rgba(241, 211, 87, 0.6);
+       }
+.marker-cluster-medium div {
+       background-color: rgba(240, 194, 12, 0.6);
+       }
+
+.marker-cluster-large {
+       background-color: rgba(253, 156, 115, 0.6);
+       }
+.marker-cluster-large div {
+       background-color: rgba(241, 128, 23, 0.6);
+       }
+
+       /* IE 6-8 fallback colors */
+.leaflet-oldie .marker-cluster-small {
+       background-color: rgb(181, 226, 140);
+       }
+.leaflet-oldie .marker-cluster-small div {
+       background-color: rgb(110, 204, 57);
+       }
+
+.leaflet-oldie .marker-cluster-medium {
+       background-color: rgb(241, 211, 87);
+       }
+.leaflet-oldie .marker-cluster-medium div {
+       background-color: rgb(240, 194, 12);
+       }
+
+.leaflet-oldie .marker-cluster-large {
+       background-color: rgb(253, 156, 115);
+       }
+.leaflet-oldie .marker-cluster-large div {
+       background-color: rgb(241, 128, 23);
+}
+
+.marker-cluster {
+       background-clip: padding-box;
+       border-radius: 20px;
+       }
+.marker-cluster div {
+       width: 30px;
+       height: 30px;
+       margin-left: 5px;
+       margin-top: 5px;
+
+       text-align: center;
+       border-radius: 15px;
+       font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
+       }
+.marker-cluster span {
+       line-height: 30px;
+       }
\ No newline at end of file
index a9cc086..ee3d588 100644 (file)
@@ -10,6 +10,10 @@ Parametres possibles :
 - lon|lonxit|longitude = -4.7      longitude du centre de la carte\r
 - zoom = 5                         zoom de la carte\r
 - maxZoom = 13                     zoom maximum autorisé\r
+- minZoom = 13                     zoom minimum autorisé\r
+\r
+- default_layer = acetate                   nom de la couche affichée par défaut\r
+- affiche_layers = openmapsurfer/acetate    noms des couches proposées séparés par des /\r
 \r
 - sw_lat = lat - 10°               latitude du sud-ouest de la bounding box\r
 - sw_lon = lon - 10°               longitude du sud-ouest de la bounding box\r
@@ -22,19 +26,22 @@ Parametres possibles :
 \r
 - fullscreen = oui                     afficher un bouton pour passer la carte en plein écran\r
 - zoom_molette|zoom_wheel = non        désactiver le zoom avec la molette de la souris, actif par defaut\r
-- control_type|controle_type = non     ne pas afficher les controles de changement de type\r
-- no_control|aucun_controle = oui      ne pas afficher les controles de la carte\r
+- control_type|controle_type = non     ne pas afficher le contrôle de changement de couche\r
+- control_type_collapsed = non         afficher le contrôle de changement de couche replié (oui par défaut)\r
+- no_control|aucun_controle = oui      ne pas afficher les contrôles de la carte\r
 - scale = oui                          afficher l'échelle de la carte\r
 - overview = oui                       afficher une mini carte de situation\r
 \r
 - autocenterandzoom|centrer_auto = oui        centrer et zoomer la carte automatiquement pour afficher tous les marqueurs\r
 - localize_visitor|localiser_visiteur = oui   centrer la carte sur la position du visiteur (API geolocation HTML5)\r
+- localize_visitor_zoom = 12                  niveau de zoom sur la position du visiteur (par défaut la valeur de zoom de la carte) \r
 - id_a_ouvrir                                 id_gis de l'infobulle à afficher au chargement(marqueur uniquement)\r
 \r
 - objets = gis                     type d'objets à afficher (fichier json/gis_xx qui génère la source de donnees)\r
 - limit|limite = 500               nombre max de marqueurs à afficher, 500 par defaut\r
 - kml = 12                         kml à superposer à la carte (id_document ou url ou liste d'url)\r
 - gpx = 12                         gpx à superposer à la carte (id_document ou url ou liste d'url)\r
+- geojson = 12                     geojson à superposer à la carte (id_document ou url  ou liste d'url)\r
 - centrer_fichier = non            permet de ne pas centrer la carte automatiquement sur les fichiers kml/gpx surperposés\r
 - point = non                      si elle vaut "non" cette option n'affichera pas de points du tout (utile pour n'afficher qu'un kml par exemple)\r
 \r
@@ -48,9 +55,11 @@ Uniquement si objets = point_libre :
 - description                      description du point\r
 \r
 Clustering (regroupement de points proches) :\r
-- cluster = oui                    Active le clustering\r
-- clusterMaxZoom = 11              Regroupe les points jusque à ce zoom, mais pas au delà\r
-- clusterShowCoverageOnHover = 0   1 pour afficher un dessin au survol de la zone couvertes par les points regroupés\r
+- cluster = oui                      Active le clustering\r
+- clusterMaxZoom = 11                Regroupe les points jusque à ce zoom, mais pas au delà\r
+- clusterShowCoverageOnHover = oui   Affiche au survol du cluster le contour de la zone couverte par les points regroupés\r
+- maxClusterRadius = 80              Le rayon maximal (en pixels) qu'un cluster couvrira (80 pixels par defaut)\r
+- clusterSpiderfyOnMaxZoom = oui     Active l'effet d'éclatement pour afficher les points qui se chevauchent\r
 ]\r
 \r
 [(#SET{width,#ENV{width,#ENV{largeur,100%}}})]\r
@@ -74,58 +83,72 @@ Clustering (regroupement de points proches) :
 \r
 <script type="text/javascript">/*<!\[CDATA\[*/\r
 var map[(#GET{id})];\r
-if (typeof map_cfg=="undefined") var map_cfg = {};\r
-map_cfg["[(#GET{id})]"] = {\r
-"mapid":"map[(#GET{id})]",\r
-"scrollWheelZoom": [(#ENV{zoom_molette,#ENV{zoom_wheel}}|=={non}|?{false,true})],\r
-"zoomControl": [(#ENV{no_control,#ENV{aucun_controle}}|!={oui}|?{true,false})][,\r
-"maxZoom": (#ENV{maxZoom})],\r
-"utiliser_bb":[(#GET{utiliser_bb}|?{true,false})],\r
-"lat":[(#GET{lat})][,\r
-"sw_lat":(#GET{sw_lat})][,\r
-"ne_lat":(#GET{ne_lat})],\r
-"lon":[(#GET{lon})][,\r
-"sw_lon":(#GET{sw_lon})][,\r
-"ne_lon":(#GET{ne_lon})],\r
-"zoom":[(#ENV{zoom,#CONFIG{gis/zoom,0}})],\r
-"default_layer":"[(#REM|gis_layer_defaut)]",\r
-"layers":#EVAL{json_encode($GLOBALS['gis_layers'])},\r
-"affiche_layers":[(#CONFIG{gis/layers,#ARRAY}|json_encode)],\r
-"control_type":[(#ENV{control_type,#ENV{controle_type}}|=={non}|?{false,true})],\r
-"no_control":[(#ENV{no_control,#ENV{aucun_controle}}|=={oui}|?{true,false})],\r
-"scale":[(#ENV{scale}|=={oui}|?{true,false})],\r
-"overview":[(#ENV{overview}|=={oui}|?{true,false})],\r
-"fullscreen":[(#ENV{fullscreen}|=={oui}|?{true,false})],\r
-"cluster":[(#ENV{cluster}|=={oui}|?{true,false})],\r
-"clusterMaxZoom":[(#ENV{clusterMaxZoom, #ENV{maxZoom}|?{#ENV{maxZoom}|moins{2},0}})],\r
-"clusterShowCoverageOnHover":[(#ENV{clusterShowCoverageOnHover}|?{true,false})],\r
-"path_styles":[(#ENV*{path_styles}|json_encode)],\r
-"autocenterandzoom":[(#ENV{autocenterandzoom,#ENV{centrer_auto}}|?{true,false})],\r
-"open_id":"[(#ENV{id_a_ouvrir,''})]",\r
-"affiche_points":[(#ENV{point,''}|=={non}|?{false,true})],\r
-"json_points":{\r
-       "url":"[(#URL_PAGE{gis_json}|url_absolue)]"[,\r
-       "objets":"(#ENV{objets,#ENV{class}}|trim)"],\r
-       "limit":[(#ENV{limit,#ENV{limite,500}}|trim)],\r
-       "env":[(#ENV*{args,#ENV*}|gis_modele_url_json_env|json_encode)][,\r
-       "titre" : (#ENV{titre}|json_encode)][,\r
-       "description" : (#ENV{description}|json_encode)][,\r
-       "icone" : (#ENV{icone}|json_encode)]\r
-       },\r
-"localize_visitor":[(#ENV{localize_visitor,#ENV{localiser_visiteur}}|?{true,false})],\r
-"centrer_fichier":[(#ENV{centrer_fichier,oui}|=={oui}|?{true,false})],\r
-"kml":[(#ENV{kml,''}|?{[(#ENV{kml}|is_array|?{#ENV{kml},#LISTE{#ENV{kml}}}|gis_kml_to_urls|json_encode)],false})],\r
-"gpx":[(#ENV{gpx,''}|?{[(#ENV{gpx}|is_array|?{#ENV{gpx},#LISTE{#ENV{gpx}}}|gis_kml_to_urls|json_encode)],false})]\r
-};\r
-\r
-// Charger le javascript de GIS et initialiser la carte (des que DOM ready)\r
-jQuery.getScript('[(#PRODUIRE{fond=javascript/gis.js,cluster=#ENV{cluster,non}})]',function(){\r
-       jQuery(function(){\r
-               if (typeof(callback_map[(#GET{id})]) === "function") {\r
-                       map_cfg["[(#GET{id})]"]['callback']=callback_map[(#GET{id})];\r
+var jQgisloader;\r
+(function (){\r
+       if (typeof jQuery.ajax == "undefined"){jQuery(init_gis);}else {init_gis();}\r
+       function init_gis(){\r
+               // Charger le javascript de GIS une seule fois si plusieurs carte\r
+               if (typeof jQgisloader=="undefined"){\r
+                       jQgisloader = jQuery.ajax({url: '[(#PRODUIRE{fond=javascript/gis.js})]', dataType: 'script', cache: true});\r
                }\r
-               gis_init_map(map_cfg["[(#GET{id})]"]);\r
-       });\r
-});\r
+               // et initialiser la carte (des que js GIS charge et des que DOM ready)\r
+               jQgisloader.done(function(){\r
+                       jQuery(function(){\r
+                               map[(#GET{id})] = new L.Map.Gis('map[(#GET{id})]',{\r
+                                       mapId: '[(#GET{id})]',\r
+                                       callback: (typeof(callback_map[(#GET{id})]) === "function") ? callback_map[(#GET{id})] : false,\r
+                                       center: [#GET{lat},#GET{lon}],\r
+                                       zoom: [(#ENV{zoom,#CONFIG{gis/zoom,0}})][,\r
+                                       maxZoom: (#ENV{maxZoom})][,\r
+                                       minZoom: (#ENV{minZoom})][,\r
+                                       default_layer: '(#ENV{default_layer})'][,\r
+                                       affiche_layers: (#ENV{affiche_layers}|?{[(#ENV{affiche_layers}|explode{/}|json_encode)],''})],\r
+                                       scrollWheelZoom: [(#ENV{zoom_molette,#ENV{zoom_wheel}}|=={non}|?{false,true})],\r
+                                       zoomControl: [(#ENV{no_control,#ENV{aucun_controle}}|!={oui}|?{true,false})],\r
+                                       fullscreenControl: [(#ENV{fullscreen}|=={oui}|?{true,false})],\r
+                                       scaleControl: [(#ENV{scale}|=={oui}|?{true,false})],\r
+                                       overviewControl:[(#ENV{overview}|=={oui}|?{true,false})],\r
+                                       layersControl: [(#ENV{control_type,#ENV{controle_type}}|=={non}|?{false,true})],\r
+                                       layersControlOptions: {\r
+                                               collapsed: [(#ENV{control_type_collapsed,#ENV{control_type_collapsed}}|=={non}|?{false,true})]\r
+                                       },\r
+                                       noControl: [(#ENV{no_control,#ENV{aucun_controle}}|=={oui}|?{true,false})],\r
+                                       utiliser_bb: [(#GET{utiliser_bb}|?{true,false})][,\r
+                                       sw_lat: (#GET{sw_lat})][,\r
+                                       ne_lat: (#GET{ne_lat})][,\r
+                                       sw_lon: (#GET{sw_lon})][,\r
+                                       ne_lon: (#GET{ne_lon})],\r
+                                       affiche_points: [(#ENV{point,''}|=={non}|?{false,true})],\r
+                                       json_points:{\r
+                                               url: '[(#URL_PAGE{gis_json}|url_absolue)]'[,\r
+                                               objets: '(#ENV{objets,#ENV{class}}|trim)'],\r
+                                               limit: [(#ENV{limit,#ENV{limite,500}}|trim)],\r
+                                               env: [(#ENV*{args,#ENV*}|gis_modele_url_json_env|json_encode)][,\r
+                                               titre: (#ENV{titre}|?{#ENV{titre},''}|json_encode)][,\r
+                                               description: (#ENV{description}|?{#ENV{description},''}|json_encode)][,\r
+                                               icone: (#ENV{icone}|?{#ENV{icone},''}|json_encode)]\r
+                                       },\r
+                                       cluster: [(#ENV{cluster}|=={oui}|?{true,false})],\r
+                                       clusterOptions: {\r
+                                               disableClusteringAtZoom: [(#ENV{clusterMaxZoom,0})],\r
+                                               showCoverageOnHover: [(#ENV{clusterShowCoverageOnHover}|?{true,false})],\r
+                                               spiderfyOnMaxZoom: [(#ENV{clusterSpiderfyOnMaxZoom}|?{true,false})],\r
+                                               maxClusterRadius: [(#ENV{maxClusterRadius,80})]\r
+                                       },\r
+                                       pathStyles: [(#ENV*{path_styles}|json_encode)],\r
+                                       autocenterandzoom: [(#ENV{autocenterandzoom,#ENV{centrer_auto}}|?{true,false})],\r
+                                       openId: [(#ENV{id_a_ouvrir,false})],\r
+                                       localize_visitor: [(#ENV{localize_visitor,#ENV{localiser_visiteur}}|?{true,false})],\r
+                                       localize_visitor_zoom: [(#ENV{localize_visitor_zoom,#ENV{zoom,#CONFIG{gis/zoom,0}}})],\r
+                                       centrer_fichier: [(#ENV{centrer_fichier,oui}|=={oui}|?{true,false})],\r
+                                       kml: [(#ENV{kml,''}|?{[(#ENV{kml}|is_array|?{#ENV{kml},#LISTE{#ENV{kml}}}|gis_kml_to_urls|json_encode)],false})],\r
+                                       gpx: [(#ENV{gpx,''}|?{[(#ENV{gpx}|is_array|?{#ENV{gpx},#LISTE{#ENV{gpx}}}|gis_kml_to_urls|json_encode)],false})],\r
+                                       geojson: [(#ENV{geojson,''}|?{[(#ENV{geojson}|is_array|?{#ENV{geojson},#LISTE{#ENV{geojson}}}|gis_kml_to_urls|json_encode)],false})]\r
+                               });\r
+                       });\r
+               });\r
+       }\r
+\r
+})()\r
 /*\]\]>*/\r
 </script>\r
diff --git a/www/plugins/gis/modeles/carte_gis_objet.html b/www/plugins/gis/modeles/carte_gis_objet.html
new file mode 100644 (file)
index 0000000..abc2d72
--- /dev/null
@@ -0,0 +1,27 @@
+[(#REM)
+       S'il y a au moins un point GIS *ou* au moins un fichier de tracé, on affiche une carte.
+       Celle-ci contiendra tous les points liés à l'objet demandé, et tous les tracés joints quelque soit leur format.
+]
+
+#SET{carte,''}
+#SET{point,non}
+<BOUCLE_gis(GIS){objet}{id_objet}{0,1}>
+#SET{carte,oui}
+#SET{point,oui}
+</BOUCLE_gis>
+#SET{kml,#LISTE} #SET{gpx,#LISTE} #SET{json,#LISTE}
+<BOUCLE_trace(DOCUMENTS){objet}{id_objet}{extension IN gpx,kml,json}>
+#SET{#EXTENSION, #GET{#EXTENSION}|push{#ID_DOCUMENT}}
+#SET{carte,oui}
+</BOUCLE_trace>
+
+[(#GET{carte}|oui)
+       [(#INCLURE{fond=modeles/carte_gis,
+               env,
+               objet=#ENV{objet},
+               id_objet=#ENV{id_objet},
+               point=#GET{point},
+               geojson=#GET{json},
+               gpx=#GET{gpx},
+               kml=#GET{kml}})]
+]
index dac6e20..52f1193 100755 (executable)
@@ -1,8 +1,8 @@
 <BOUCLE_gis(GIS){objet ?}{id_objet ?}{id_gis ?}{0,1}>\r
 <div id="map_preview" class="carte_gis" style="width: 100%; height: 150px;"></div>\r
-\r
+#SET{autocenter,''}\r
 <script type="text/javascript">\r
-<!--\r
+/*<![CDATA[*/\r
 (function($){\r
        var init_map_preview = function() {\r
                var map_preview;\r
@@ -17,7 +17,7 @@
                map_preview.addLayer(base_layer);\r
                \r
                map_preview.setView(new L.LatLng([(#LAT)],[(#LON)]),[(#ZOOM|sinon{#CONFIG{gis/zoom,0}})]);\r
-               
+
                [(#REM) On ajoute les points ou le point unique suivant les params ]\r
                <B_points>\r
                var data = {\r
                                        "id":"#ID_GIS",\r
                                        "properties": {\r
                                                "title":[(#TITRE|supprimer_numero|json_encode)],\r
-                                               "description":[(#DESCRIPTIF|json_encode)][,(#LOGO_GIS|oui)\r
-                                               [(#SET{logo_doc,#LOGO_GIS|image_passe_partout{28,28}|image_recadre{28,28}|image_recadre{32,32,center}|image_aplatir{jpg,ffffff}})]\r
-                                               #SET{icon_w,#GET{logo_doc}|extraire_attribut{src}|largeur}\r
-                                               #SET{icon_h,#GET{logo_doc}|extraire_attribut{src}|hauteur}\r
-                                               ["icon": (#GET{logo_doc}|extraire_attribut{src}|url_absolue|json_encode)],\r
-                                               "icon_size": \[#GET{icon_w},#GET{icon_h}\],\r
-                                               "icon_anchor": \[[(#GET{icon_w}|div{2})],[(#GET{icon_h})]\],\r
-                                               "popup_anchor": \[1,[-(#GET{icon_h}|div{1.2})]\]]\r
+                                               "description":[(#DESCRIPTIF|json_encode)][\r
+                                               (#LOGO_GIS|gis_icon_properties)]\r
                                        }\r
                                }\r
                </BOUCLE_points>\r
                                        "id":"#ID_GIS",\r
                                        "properties": {\r
                                                "title":[(#TITRE|supprimer_numero|json_encode)],\r
-                                               "description":[(#DESCRIPTIF|json_encode)][,(#LOGO_GIS|oui)\r
-                                               [(#SET{logo_doc,#LOGO_GIS|image_passe_partout{28,28}|image_recadre{28,28}|image_recadre{32,32,center}|image_aplatir{jpg,ffffff}})]\r
-                                               #SET{icon_w,#GET{logo_doc}|extraire_attribut{src}|largeur}\r
-                                               #SET{icon_h,#GET{logo_doc}|extraire_attribut{src}|hauteur}\r
-                                               ["icon": (#GET{logo_doc}|extraire_attribut{src}|url_absolue|json_encode)],\r
-                                               "icon_size": \[#GET{icon_w},#GET{icon_h}\],\r
-                                               "icon_anchor": \[[(#GET{icon_w}|div{2})],[(#GET{icon_h})]\],\r
-                                               "popup_anchor": \[1,[-(#GET{icon_h}|div{1.2})]\]]\r
+                                               "description":[(#DESCRIPTIF|json_encode)][\r
+                                               (#LOGO_GIS|gis_icon_properties)]\r
                                        }\r
                                }]\r
                };\r
                        }).addTo(map_preview);\r
                        geojson.addData(data);\r
                        [(#GET{autocenter}|oui)\r
-                       map_preview.fitBounds(geojson.getBounds());]\r
+                       if(data.features.length > 1)\r
+                               map_preview.fitBounds(geojson.getBounds());]\r
                }\r
                [(#REM) On ajoute les KML attachés à l'objet ]\r
                <BOUCLE_kml(DOCUMENTS){tous}{objet}{id_objet}{extension=kml}>\r
                map_preview.addLayer(new L.KML('[(#URL_DOCUMENT|url_absolue)]', {async: true}));\r
                </BOUCLE_kml>\r
                [(#REM) On ajoute les GPX attachés à l'objet ]\r
-               <BOUCLE_gpx(DOCUMENTS){tous}{objet}{id_objet}{extension=kml}>\r
+               <BOUCLE_gpx(DOCUMENTS){tous}{objet}{id_objet}{extension=gpx}>\r
                map_preview.addLayer(new L.GPX('[(#URL_DOCUMENT|url_absolue)]', {async: true}));\r
                </BOUCLE_gpx>\r
        }\r
        });\r
 \r
 })(jQuery);\r
--->\r
+/*]]>*/\r
 </script>\r
 </BOUCLE_gis>\r
index 620ffd5..2397680 100644 (file)
@@ -1,10 +1,10 @@
 <paquet
        prefix="gis"
        categorie="divers"
-       version="4.8.9"
-       schema="2.0.3"
+       version="4.32.5"
+       schema="2.0.8"
        etat="stable"
-       compatibilite="[3.0.0;3.0.*]"
+       compatibilite="[3.0.0;3.1.*]"
        logo="images/gis.png"
        documentation="http://contrib.spip.net/4189"
 >
        <credit lien="https://github.com/leaflet-extras/leaflet-providers">Leaflet providers</credit>
        <credit lien="https://github.com/brunob/leaflet.fullscreen">Leaflet fullscreen</credit>
        <credit lien="https://github.com/Norkart/Leaflet-MiniMap">Leaflet minimap</credit>
-       <copyright>2011-2013</copyright>
-       <licence>GPL v3</licence>
+       <credit lien="http://wiki.openstreetmap.org/wiki/Nominatim">Nominatim</credit>
        <credit lien="http://mattrich.deviantart.com/art/Picnic-101256405">Icône de mattrich sous licence CC BY-NC-SA</credit>
+       <copyright>2011-2015</copyright>
+       <licence>GPL v3</licence>
 
        <traduire module="gis" reference="fr" gestionnaire="salvatore" />
        <traduire module="paquet-gis" reference="fr" gestionnaire="salvatore" />
 
-       <utilise nom="selecteur_generique" compatibilite="[1.12;]" />
-       <necessite nom="saisies" compatibilite="[1.19.0;]" />
-       <lib nom="leaflet-gis-4.8.7" lien="http://contrib.spip.net/IMG/zip/leaflet-gis-4.8.7.zip" />
+       <utilise nom="selecteurgenerique" compatibilite="[0.8.6;]" />
+       <necessite nom="saisies" compatibilite="[2.4.0;]" />
 
+       <pipeline
+               nom="gis_modele_parametres_autorises"
+       />
        <pipeline
                nom="declarer_tables_interfaces"
                inclure="base/gis.php"
@@ -64,7 +67,7 @@
                inclure="gis_pipelines.php"
        />
        <pipeline
-               nom="taches_generales_cron"
+               nom="optimiser_base_disparus"
                inclure="gis_pipelines.php"
        />
        <pipeline
        <pipeline
                nom="xmlrpc_server_class"
                inclure="gis_pipelines.php"
-       />      
+       />
+       <pipeline
+               nom="formulaire_traiter"
+               inclure="gis_pipelines.php"
+       />
        <menu nom="gis_tous" titre="gis:icone_gis_tous" parent="menu_edition" icone="images/gis-16.png" />
-       <menu nom="configurer_gis" titre="gis:cfg_titre_gis" parent="menu_configuration" icone="images/gis-16.png" />
        <menu nom="gis_nouveau" titre="gis:editer_gis_nouveau" parent="outils_rapides" icone="images/gis-new-16.png" action="gis_edit" parametres="new=oui" />
 </paquet>
index dc6ab9b..32332bb 100644 (file)
@@ -1,4 +1,5 @@
 <div class="ajax">
+#SET{gis_defaut,''}
 <BOUCLE_test(GIS){objet}{id_objet}{0,1}> </BOUCLE_test>
 #SET{gis_defaut,nouveau}
 #SET{gis_defaut,glop}
index 5fcdb96..50766c6 100644 (file)
@@ -8,6 +8,9 @@
        <div class="champ contenu_region[(#REGION|non)vide]">
                #REGION
        </div>
+       <div class="champ contenu_departement[(#DEPARTEMENT|non)vide]">
+               #DEPARTEMENT
+       </div>
        <div class="champ contenu_code_postal[(#CODE_POSTAL|non)vide]">
                #CODE_POSTAL
        </div>
index 0a1a982..b7856c9 100644 (file)
@@ -1,18 +1,16 @@
 <BOUCLE_gis(GIS){id_gis=#ENV{id}}>
+<div class='infos'>
+       <div class='numero'><:gis:info_numero_gis:><p>#ID_GIS</p></div>
 
-       <div class='infos'>
-               <div class='numero'><:gis:info_numero_gis:><p>#ID_GIS</p></div>
+       [(#INCLURE{fond=prive/objets/liste/objets_gis_simple,id_gis=#ID_GIS,env})]
 
-               #INCLURE{fond=prive/objets/liste/objets_gis_simple, id_gis=#ID_GIS, env}
-
-        [(#AUTORISER{supprimer,gis,#ID_GIS}|oui)
-            [(#URL_ACTION_AUTEUR{
-                supprimer_gis,
-                #ID_GIS,
-                #URL_ECRIRE{gis_tous}
-                               }|icone_horizontale{<:gis:bouton_supprimer_gis:>,gis-24,del}
-            )]
-        ]
-
-    </div>
+       [(#AUTORISER{supprimer,gis,#ID_GIS}|oui)
+               [(#URL_ACTION_AUTEUR{
+                       supprimer_gis,
+                       #ID_GIS,
+                       #URL_ECRIRE{gis_tous}
+                       }|icone_horizontale{<:gis:bouton_supprimer_gis:>,gis-24,del}
+               )]
+       ]
+</div>
 </BOUCLE_gis>
index a4f6e6c..202f829 100644 (file)
        <tbody>
        <BOUCLE_liste_gis(GIS){id_gis ?}{id_rubrique ?}{id_article ?}{id_breve ?}{id_document ?}{id_mot ?}{id_auteur ?}{id_syndic ?}{where?}{recherche?}{tri #ENV{par,multi titre},#GET{defaut_tri}}{pagination #ENV{nb,10}}>
                <tr class="[(#COMPTEUR_BOUCLE|alterner{row_odd,row_even})][(#ID_GIS|=={#ENV{id_lien_ajoute}}|oui)append]">
-                       <td class='titre principale'>[(#LOGO_GIS|image_reduire{20,20})]<a href="[(#ID_GIS|generer_url_entite{gis})]"
+                       <td class='#EDIT{titre} titre principale'>[(#LOGO_GIS|image_reduire{20,20})]<a href="[(#ID_GIS|generer_url_entite{gis})]"
                                title="<:gis:texte_voir_gis:>">[(#TITRE|sinon{<:ecrire:info_sans_titre:>})]</a></td>
-                       <td class='pays'>#PAYS</td>
-                       <td class='ville'>#VILLE</td>
-                       <td class='id_gis'><a href="[(#ID_GIS|generer_url_entite{gis})]"
+                       <td class='#EDIT{pays} pays'>#PAYS</td>
+                       <td class='#EDIT{ville} ville'>#VILLE</td>
+                       <td class='id_gis'><a href="[(#URL_ECRIRE{gis_edit,id_gis=#ID_GIS})]"
                                title="<:gis:texte_voir_gis:>">#ID_GIS</a></td>
                </tr>
        </BOUCLE_liste_gis>
index ff8a6f7..1770b5e 100644 (file)
@@ -34,7 +34,7 @@ a mis a jour la valeur avec la page reelle]
                        <td class='picto'>[(#CHEMIN_IMAGE{gis-16.png}|balise_img)]</td>
                        <td class='titre principale'>[(#LOGO_GIS|image_reduire{20,20})]<a href="[(#ID_GIS|generer_url_entite{gis})]"
                                title="<:gis:texte_voir_gis:>">[(#TITRE|sinon{<:ecrire:info_sans_titre:>})]</a></td>
-                       <td class='action'><button class="button" name="ajouter_lien[gis-#ID_GIS-#OBJET-#ID_OBJET]" value="+"><:gis:lien_ajouter_gis:> [(#CHEMIN_IMAGE{ajouter-12.png}|balise_img{'+'})]</button></td>
+                       <td class='action'><button type="submit" class="button" name="ajouter_lien[gis-#ID_GIS-#OBJET-#ID_OBJET]" value="+"><:gis:lien_ajouter_gis:> [(#CHEMIN_IMAGE{ajouter-12.png}|balise_img{'+'})]</button></td>
                </tr>
        </BOUCLE_liste_gis>
        </tbody>
index e73d30c..d4950cf 100644 (file)
@@ -34,7 +34,7 @@
        </tbody>
 </table>
 [<p class='pagination'>(#PAGINATION{prive})</p>]
-[(#GRAND_TOTAL|>{3}|oui)<div class="action"><button class="button link" name="supprimer_lien#EVAL{chr(91)}gis-*-#OBJET-#ID_OBJET#EVAL{chr(93)}" value="X"><:gis:info_supprimer_liens:> [(#CHEMIN_IMAGE{supprimer-12.png}|balise_img{'X'})]</button></div>]
+[(#GRAND_TOTAL|>{3}|oui)<div class="action"><button type="submit" class="button link" name="supprimer_lien#EVAL{chr(91)}gis-*-#OBJET-#ID_OBJET#EVAL{chr(93)}" value="X"><:gis:info_supprimer_liens:> [(#CHEMIN_IMAGE{supprimer-12.png}|balise_img{'X'})]</button></div>]
 [(#INCLURE{fond=modeles/carte_gis_preview,id_objet,objet})]
 </div>
 </B_liste_gis>
diff --git a/www/plugins/gis/prive/objets/liste/gis_tous.html b/www/plugins/gis/prive/objets/liste/gis_tous.html
deleted file mode 100644 (file)
index c5eee52..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-<B_gis>
-[(#ANCRE_PAGINATION)]
-<div class="menu points">
-<ul class="liste_items">
-       <BOUCLE_gis(GIS){par titre}{pagination #ENV{nb,5}}>
-       <li class="item">
-               <h3 class="titre">
-                       <a href="[(#URL_ECRIRE{gis,id_gis=#ID_GIS})]">#TITRE</a>
-                       [<span class="numero">N° (#ID_GIS)</span>]
-               </h3>
-               [<div class="description">
-                       (#DESCRIPTIF|couper{200})
-               </div>]
-               <div class="geo">
-                       [<abbr class="latitude" title="(#LAT)">[[(#LAT|>{0}|?{'N','S'})] (#LAT|dec_to_dms)]</abbr> - ]
-                       [<abbr class="longitude" title="(#LON)">[[(#LON|>{0}|?{'E','W'})] (#LON|dec_to_dms)]</abbr>]
-               </div>
-               <p class='actions'>
-                       <span class="afficher" id="afficher_[(#ID_GIS)]">&#91;<a href="[(#URL_ECRIRE{gis,id_gis=#ID_GIS})]">Afficher</a>&#93;</span>
-                       [(#AUTORISER{modifier,gis,#ID_GIS}|oui)
-                       <span class='lien_modifier'>&#91;<a href='[(#URL_ECRIRE{gis_edit}|parametre_url{id_gis,#ID_GIS}|parametre_url{redirect,#SELF|url_absolue})]'><:gis:editer_gis_editer:></a>&#93;</span>]
-               </p>
-       </li>
-       </BOUCLE_gis>
-</ul>
-[<p class="pagination">(#PAGINATION)</p>]
-<script type="text/javascript">
-(function($){
-       $(function(){
-               $('.afficher').css('cursor','pointer').click(function(){
-                       var id_marker = $(this).attr('id').replace('afficher_','');
-                       gis_focus_marker(id_marker,'_all');
-                       return false;
-               });
-       });
-})(jQuery);
-</script>
-</B_gis>
-</div>
index d0a22cb..71af35c 100644 (file)
@@ -17,7 +17,7 @@
                </tr>
        </thead>
        <tbody>
-       <BOUCLE_liste_objets(GIS gis_liens){id_gis}{tri #ENV{order,objet},#GET{defaut_tri}}{pagination #ENV{nb,10}}{!lang_select}>
+       <BOUCLE_liste_objets(GIS_LIENS){id_gis}{tri #ENV{order,objet},#GET{defaut_tri}}{pagination #ENV{nb,10}}{!lang_select}>
                <tr class="[(#COMPTEUR_BOUCLE|alterner{row_odd,row_even})]">
                        <td class='picto'>[(#OBJET|objet_icone{16})]</td>
                        <td class='objet'>[(#OBJET|objet_info{texte_objet}|_T)]</td>
index 688e540..2a0635b 100644 (file)
@@ -1,11 +1,11 @@
+#SET{defaut_tri,#ARRAY{}}
 <B_liste_objets>
-
 #ANCRE_PAGINATION
 <div class="liste-elements objets_gis">
 #SET{total,#GRAND_TOTAL}
 <h3>[(#GET{total}|singulier_ou_pluriel{gis:info_1_objet_gis,gis:info_nb_objets_gis})]</h3>
 <ul class="liste_items">
-    <BOUCLE_liste_objets(GIS gis_liens){id_gis}{tri #ENV{order,objet},#GET{defaut_tri}}{pagination #ENV{nb,10}}{!lang_select}>
+    <BOUCLE_liste_objets(GIS_LIENS){id_gis}{tri #ENV{order,objet},#GET{defaut_tri}}{pagination #ENV{nb,10}}{!lang_select}>
         <li class="item #OBJET">
             <a href="[(#ID_OBJET|generer_url_entite{#OBJET})]">[(#INFO_TITRE{#OBJET,#ID_OBJET}) ]([(#OBJET|objet_info{texte_objet}|_T)] <:info_numero_abbreviation|attribut_html:>#ID_OBJET)</a>
         </li>
index e6322cb..101a4a1 100644 (file)
@@ -9,7 +9,7 @@
 [(#INCLURE{fond=modeles/carte_gis_preview,id_gis})]
 
 <div id="wysiwyg">
-<INCLURE{fond=prive/objets/contenu/gis,id=#ID_GIS} />
+<INCLURE{fond=prive/objets/contenu/gis,id=#ID_GIS,id_gis=#ID_GIS} />
 </div>
 
 <INCLURE{fond=prive/objets/liste/objets_gis,sinon=<:gis:aucun_objet:>,env,ajax} />
 
 #PIPELINE{afficher_complement_objet,#ARRAY{args,#ARRAY{type,gis,id,#ID_GIS},data,'<div class="nettoyeur"></div>'}}
 
+[(#EVAL{_AJAX}|oui)
+<script type="text/javascript">/*<!\[CDATA\[*/reloadExecPage('#ENV{exec}','#navigation,#chemin');/*\]\]>*/</script>
+]
+
 </BOUCLE_gis>
 [(#ENV**{exec}|=={gis_edit}|?{#INCLURE{fond=prive/squelettes/contenu/gis_edit,redirect='',env,retourajax=oui},#REM|sinon_interdire_acces})]
 <//B_gis>
index 6f0c67f..ca63efe 100644 (file)
@@ -3,7 +3,7 @@
 ][(#ID_GIS|non)
        [(#AUTORISER{creer,gis,#ID_GIS,'','',#ARRAY{associer_objet,#ENV{associer_objet}}}|sinon_interdire_acces)]
 ]
-#SET{redirect,#ENV{redirect,#ENV{id_gis}|?{#URL_ECRIRE{gis,id_gis=#ID_GIS},#URL_ECRIRE{gis_tout}}}}
+#SET{redirect,#ENV{redirect,#ENV{id_gis}|?{#URL_ECRIRE{gis,id_gis=#ID_GIS},#URL_ECRIRE{gis_tous}}}}
 
 #SET{objet, #ENV{objet, ''}}
 #SET{id_objet, #ENV{id_objet, 0}}
index bb5621a..9d6a4a4 100644 (file)
@@ -1,15 +1,23 @@
-<h1 class="grostitre"><:gis:editer_gis_titre:></h1>
+<div class="onglets_simple clearfix">
+       <ul>
+               [<li>(#URL_ECRIRE{gis_tous}|parametre_url{afficher,carte}|lien_ou_expose{Carte,#ENV{afficher,carte}|=={carte},ajax})</li>]
+               [<li>(#URL_ECRIRE{gis_tous}|parametre_url{afficher,liste}|lien_ou_expose{Liste,#ENV{afficher,carte}|=={liste},ajax})</li>]
+       </ul>
+       [(#FORMULAIRE_RECHERCHE_ECRIRE{#SELF,ajax})]
+</div>
 
-[(#REM)<p><:gis:editer_gis_explication:></p>]
+[(#ENV{afficher,carte}|=={carte}|oui)
+       [(#INCLURE{fond=modeles/carte_gis,
+               id_carte_gis=_all,
+               objets=tous_avec_liens_espace_prive,
+               recherche}
+       )]
+]
 
-#FORMULAIRE_RECHERCHE_ECRIRE{#SELF,ajax}
-<div class="nettoyeur"></div>
+[(#ENV{afficher,carte}|=={liste}|oui)
+       [(#INCLURE{fond=prive/objets/liste/gis,env})]
+]
 
-<BOUCLE_gis(GIS){recherche ?}{pagination 1}>
-<h3>[(#GRAND_TOTAL|singulier_ou_pluriel{gis:info_1_gis,gis:info_nb_gis})]</h3>
-</BOUCLE_gis>
-<h3><:gis:info_aucun_gis:></h3>
-<//B_gis>
-
-
-[(#INCLURE{fond=modeles/carte_gis,id_carte_gis=_all,recherche,objets=tous_avec_liens_espace_prive})]
+[(#AUTORISER{creer,gis})
+       [(#URL_ECRIRE{gis_edit,new=oui}|parametre_url{redirect,#URL_ECRIRE{gis_tous}}|icone_verticale{<:gis:editer_gis_nouveau:>,gis,new,right})]
+]
diff --git a/www/plugins/gis/prive/squelettes/navigation/gis_tous.html b/www/plugins/gis/prive/squelettes/navigation/gis_tous.html
deleted file mode 100644 (file)
index 66c8916..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-#INCLURE{fond=prive/objets/liste/gis_tous,env}
-
-#BOITE_OUVRIR{'',raccourcis}
-[(#URL_ECRIRE{gis_edit,nouveau=oui}|icone_horizontale{<:gis:editer_gis_nouveau:>,gis,new})]
-#BOITE_FERMER
diff --git a/www/plugins/gis/prive/squelettes/top/gis_tous.html b/www/plugins/gis/prive/squelettes/top/gis_tous.html
new file mode 100644 (file)
index 0000000..b210f49
--- /dev/null
@@ -0,0 +1,2 @@
+<h1 class="invisible"><:gis:editer_gis_titre:></h1>
+#LARGEUR_ECRAN{pleine_largeur}
\ No newline at end of file
index 130a35d..eb7ea22 100644 (file)
@@ -9,12 +9,4 @@
 
 /* liste des points exec=gis_tous */
 
-.points .liste_items .titre { font-size: small; }
-.points .liste_items .titre .numero { float: right; }
-.points .liste_items .description,
-.points .liste_items .geo { margin: 5px 0 0; color: #666; }
-.points .liste_items .geo abbr { border: 0; }
-.points .liste_items p.actions { font-size: 0.9em; margin: 5px 0 0; }
-
-/* Avec OpenLayer, le menu deroulant de spip passe dessous la carte... */
-.olMap .olMapViewport {z-index:0;}
+.gis_tous .onglets_simple .formulaire_recherche { margin-bottom: 0; }
index e86d9ec..954486b 100644 (file)
        #SET{init_ne_lon,#ENV{ne_lon,#GET{lon}|plus{10}}}
 ]
 
-<li class="pleine_largeur editer editer_[(#ENV{nom})][ (#ENV{li_class})][ saisie_(#ENV{type_saisie})]"[ data-id="(#ENV{id_saisie})"]>
+<[(#VAL{li}|saisie_balise_structure_formulaire)] class="pleine_largeur editer editer_[(#ENV{nom})][ (#ENV{li_class})][ saisie_(#ENV{type_saisie})]"[ data-id="(#ENV{id_saisie})"]>
 #ENV*{inserer_debut}
 <div id="map_[(#ENV{nom})]" name="formMap" class="formMap" style="width: #ENV{largeur,100%}; height: #ENV{hauteur,350px}"></div>
 <script type="text/javascript">
-<!--
-var form_map;
-var annuler_geocoder = 0;
+/*<![CDATA[*/
+var form_map,
+       annuler_geocoder = 0;
 [(#ENV{recherche}|!={non}|oui|et{#CONFIG{gis/geocoder}|oui})
 [(#SET{geocoder,oui})]
 var geocoder;]
 
 (function($){
-       var champ_lat = $('#champ_#ENV{champ_lat,lat}');
-       var champ_lon = $('#champ_#ENV{champ_lon,lon}');
-       var champ_zoom = $('#champ_#ENV{champ_zoom,zoom}');
-       var champ_adresse = $('#champ_#ENV{champ_adresse,adresse}');
-       var champ_code_postal = $('##ENV{champ_code_postal,code_postal}');
-       var champ_ville = $('#champ_#ENV{champ_ville,ville}');
-       var champ_region = $('#champ_#ENV{champ_region,region}');
-       var champ_pays = $('#champ_#ENV{champ_pays,pays}');
-       
        var marker;
-       
+
        var maj_inputs = function(map,data,action) {
                [(#GET{geocoder}|oui)
                if (action != 'geocoding') {
                        var f = geocoder.geocode(data);
                }]
                var zoom = map.getZoom();
+               if(data.lng <= -180) data.lng = data.lng+360
+               else if(data.lng > 180) data.lng = data.lng-360;
                $('#champ_#ENV{champ_zoom,zoom}').val(zoom);
                if(action == 'click'){
                        $('#champ_#ENV{champ_lat,lat}').val(data.lat);
@@ -83,37 +76,47 @@ var geocoder;]
                if (!marker._map)
                        form_map.addLayer(marker);
        }
-       
+
        [(#GET{geocoder}|oui)
        function geocode(query) {
-               $('#champ_#ENV{champ_adresse,adresse}').val(query.street);
-               $('#champ_#ENV{champ_code_postal,code_postal}').val(query.postcode);
-               $('#champ_#ENV{champ_ville,ville}').val(query.locality);
-               $('#champ_#ENV{champ_region,region}').val(query.region);
-               $('#champ_#ENV{champ_pays,pays}').val(query.country);
-               maj_inputs(form_map,query,'geocoding');
+               if(! query.error){
+                       $('#champ_#ENV{champ_adresse,adresse}').val(query.street).change();
+                       $('#champ_#ENV{champ_code_postal,code_postal}').val(query.postcode).change();
+                       $('#champ_#ENV{champ_ville,ville}').val(query.locality).change();
+                       $('#champ_#ENV{champ_departement,departement}').val(query.departement).change();
+                       $('#champ_#ENV{champ_region,region}').val(query.region).change();
+                       $('#champ_#ENV{champ_pays,pays}').val(query.country).change();
+                       $('#champ_#ENV{champ_code_pays,code_pays}').val(query.country_code).change();
+                       maj_inputs(form_map,query,'geocoding');
+               }else{
+                       alert('<:gis:erreur_geocoder:> '+query.search);
+               }
        }]
-       
+
        var init_map = function(callback) {
                // creer la carte
                var map_container = 'map_[(#ENV{nom})]';
                form_map = new L.Map(map_container);
-               
+
+               // affecter sur l'objet du DOM
+               jQuery("#"+map_container).get(0).map=form_map;
+
                // appeler l'éventuelle fonction de callback
                if (callback && typeof(callback) === "function") {
                        form_map.on('load',function(e){
                                callback(e.target);
                        });
                }
+
                form_map.attributionControl.setPrefix('');
-               
+
                marker = new L.Marker(new L.LatLng(#ENV{lat,0}, #ENV{lon,0}), {draggable: true});
-               
+
                //default layer
                #SET{layer_defaut,#REM|gis_layer_defaut} #SET{layers,#EVAL{$GLOBALS['gis_layers']}}
                var [(#GET{layer_defaut})] = [new (#GET{layers}|table_valeur{#GET{layer_defaut}/layer})];
                form_map.addLayer([(#GET{layer_defaut})]);
-               
+
                <B_layers>
                var layers_control = new L.Control.Layers();
                layers_control.addBaseLayer([(#GET{layer_defaut})],["(#GET{layers}|table_valeur{#GET{layer_defaut}/nom})"]);
@@ -127,9 +130,9 @@ var geocoder;]
                // classe noajax sur le layer_control pour éviter l'ajout de hidden par SPIP
                $(layers_control._form).addClass('noajax');
                </B_layers>
-               
+
                [(#GET{utiliser_bb}|non)
-               form_map.setView(new L.LatLng([(#GET{init_lat})], [(#GET{init_lon})]), [(#GET{init_zoom})]); 
+               form_map.setView(new L.LatLng([(#GET{init_lat})], [(#GET{init_lon})]), [(#GET{init_zoom})]);
                ]
                [(#GET{utiliser_bb}|oui)
                form_map.fitBounds(
@@ -143,12 +146,12 @@ var geocoder;]
                form_map.on('load', function(e) {
                        $('#champ_#ENV{champ_lat,lat}').val(e.latlng.lat);
                        $('#champ_#ENV{champ_lon,lon}').val(e.latlng.lon);
-               });             
+               });
                ]
-               
+
                [(#GET{geocoder}|oui)
-               geocoder = new L.Geocoder(geocode);]
-               
+               geocoder = new L.Geocoder(geocode,{acceptLanguage:'#ENV{lang}'});]
+
                [(#REM) Ici on utilise #ENV{lat} et #ENV{lon}, et pas les valeurs
                        d'initialisation #GET{init_lat} et #GET{init_lon} qui sont toujours remplies]
                [(#ENV{lat}|et{#ENV{lon}}|oui)
@@ -160,18 +163,12 @@ var geocoder;]
                                        "id":"#ENV{id_gis,oui}",
                                        "properties": {
                                                "title":[(#ENV{titre,''}|supprimer_numero|json_encode)],
-                                               "description":[(#ENV{descriptif,''}|json_encode)][,(#LOGO_GIS|oui)
-                                               [(#SET{logo_doc,#LOGO_GIS|image_passe_partout{32,32}|image_recadre{32,32}})]
-                                               #SET{icon_w,#GET{logo_doc}|extraire_attribut{src}|largeur}
-                                               #SET{icon_h,#GET{logo_doc}|extraire_attribut{src}|hauteur}
-                                               ["icon": (#GET{logo_doc}|extraire_attribut{src}|url_absolue|json_encode)],
-                                               "icon_size": \[#GET{icon_w},#GET{icon_h}\],
-                                               "icon_anchor": \[[(#GET{icon_w}|div{2})],[(#GET{icon_h})]\],
-                                               "popup_anchor": \[1,[-(#GET{icon_h}|div{1.2})]\]]
+                                               "description":[(#ENV{descriptif,''}|json_encode)][
+                                               (#LOGO_GIS|gis_icon_properties)]
                                        }
                                }\]
                }
-               
+
                var geojson = new L.geoJson('', {
                        onEachFeature: function (feature, layer) {
                                marker = layer;
@@ -190,26 +187,26 @@ var geocoder;]
                        }
                }).addTo(form_map);
                geojson.addData(data);]
-               
+
                // mettre a jour les coordonnees quand on clique la carte
                form_map.on('click', function(e) {
                        annuler_geocoder = 0;
                        maj_inputs(form_map,e.latlng,'click');
                });
-               
+
                marker.on("dragend", function(e){
                        maj_inputs(form_map,e.target._latlng,'click');
                });
-               
+
                // mettre à jour le zoom quand on le modifie
                form_map.on('zoomend', function(e) {
                        $('#champ_#ENV{champ_zoom,zoom}').val(e.target._zoom);
                });
-               
+
                [(#GET{geocoder}|oui)
                // geocoder si clic...
                $('a##ENV{nom}_rechercher_geocodage').css("cursor","pointer").click(function(){
-                       var address = $("#champ_#ENV{nom}_geocoder").attr("value");
+                       var address = $("#champ_#ENV{nom}_geocoder").val();
                        annuler_geocoder = 0;
                        geocoder.geocode(address);
                });
@@ -221,10 +218,10 @@ var geocoder;]
                                return false;
                        }
                });]
-               
+
                [(#ENV{id_gis}|non|ou{#ENV{id_gis}|=={oui}}|et{#CONFIG{gis/geolocaliser_user_html5}|=={on}}|oui)
                form_map.locate({setView: true, maxZoom: [(#GET{init_zoom})]});]
-               
+
        };
 
        $(function(){
@@ -236,15 +233,15 @@ var geocoder;]
                        }
                });
        });
-       
+
 })(jQuery);
--->
+/*]]>*/
 </script>
 #ENV*{inserer_fin}
-</li>
+</[(#VAL{li}|saisie_balise_structure_formulaire)]>
 [(#GET{geocoder}|oui)
-<li class="rechercher_adresse editer_[(#ENV{nom})]">
+<[(#VAL{li}|saisie_balise_structure_formulaire)] class="rechercher_adresse editer editer_[(#ENV{nom})]">
        <label for="champ_#ENV{nom}_geocoder"><:gis:label_rechercher_address:></label>
        <input type="text" class="text" name="champ_#ENV{nom}_geocoder" id="champ_#ENV{nom}_geocoder" value="" />
        <a id="#ENV{nom}_rechercher_geocodage"><:info_rechercher:></a>
-</li>]
+</[(#VAL{li}|saisie_balise_structure_formulaire)]>]
index 15e4891..fef9aed 100644 (file)
@@ -1,10 +1,10 @@
 <svn_revision>
 <text_version>
 Origine: file:///home/svn/repository/spip-zone/_plugins_/gis/trunk
-Revision: 73835
-Dernier commit: 2013-06-24 20:00:11 +0200 
+Revision: 95041
+Dernier commit: 2016-02-09 10:02:40 +0100 
 </text_version>
 <origine>file:///home/svn/repository/spip-zone/_plugins_/gis/trunk</origine>
-<revision>73835</revision>
-<commit>2013-06-24 20:00:11 +0200 </commit>
+<revision>95041</revision>
+<commit>2016-02-09 10:02:40 +0100 </commit>
 </svn_revision>
\ No newline at end of file
index a28e532..5bee2b1 100644 (file)
@@ -1,10 +1,21 @@
 <?php
 
+/**
+ * Gestion de l'action déplacer saisie.
+ *
+ * @package SPIP\Saisies\Action
+ */
 if (!defined("_ECRIRE_INC_VERSION")) return;
 
+/**
+ * Action de déplacement de saisies dans le constructeur de formulaires
+ *
+ * @return void
+**/
 function action_deplacer_saisie_dist() {
        include_spip('inc/session');
-       
+
        $session         = _request('session');
        $identifiant = _request('saisie');
        $ou          = _request('ou');
@@ -35,4 +46,3 @@ function action_deplacer_saisie_dist() {
        $formulaire_actuel = session_set($session, $formulaire_actuel);
 }
 
-?>
index 33902c1..f362eed 100644 (file)
@@ -1,28 +1,44 @@
-<?php 
+<?php
+/**
+ * Déclaration de la balise `#CONFIGURER_SAISIE`
+ *
+ * @package SPIP\Saisies\Balises
+ */
 
 // Sécurité
 if (!defined("_ECRIRE_INC_VERSION")) return;
 
+/**
+ * Compile la balise `#CONFIGURER_SAISIE`
+ *
+ * @uses Pile::recuperer_et_supprimer_argument_balise()
+ * @uses Pile::creer_et_ajouter_argument_balise()
+ * @see balise_INCLURE_dist()
+ *  
+ * @param Champ $p
+ * @return Champ
+**/
 function balise_CONFIGURER_SAISIE_dist($p){
 
        // On recupere le premier argument : le nom de la saisie
        $saisie = Pile::recuperer_et_supprimer_argument_balise(1, $p);
-       
+
        // On ajoute le squelette a inclure dans les parametres
        $p = Pile::creer_et_ajouter_argument_balise($p, 'fond', 'inclure/configurer_saisie');
-       
+
        // On ajoute l'environnement
        $p = Pile::creer_et_ajouter_argument_balise($p, 'env');
-       
+
        // On ajoute le nom recupere
        $p = Pile::creer_et_ajouter_argument_balise($p, 'saisie', $saisie);
-       
+
        // On redirige vers la balise INCLURE
-       if(function_exists('balise_INCLURE'))
+       if (function_exists('balise_INCLURE')) {
                return balise_INCLURE($p);
-       else
-               return balise_INCLURE_dist($p); 
+       } else {
+               return balise_INCLURE_dist($p);
+       }
 
 }
 
-?>
+
index 83e4401..6a37f12 100644 (file)
@@ -1,7 +1,7 @@
 <?php 
 
 /**
- * Gestion de la balise GENERER_SAISIES
+ * Gestion de la balise `#GENERER_SAISIES`
  *
  * @package SPIP\Saisies\Balises
  */
 if (!defined("_ECRIRE_INC_VERSION")) return;
 
 /**
- * Compile la balise GENERER_SAISIES
+ * Compile la balise `#GENERER_SAISIES` qui retourne le code HTML des saisies de formulaire,
+ * à partir du tableau des saisies transmises
  *
  * La balise accepte 1 paramètre qui est une liste de descriptions de saisies
  * dont on veut générer le HTML affichant les champs du formulaires
  *
  * Cette balise est un raccourcis :
- * - #GENERER_SAISIES{#TABLEAU_DE_SAISIES} est équivalent à
- * - #INCLURE{fond=generer_saisies,env,saisies=#TABLEAU_DE_SAISIES}
+ * - `#GENERER_SAISIES{#TABLEAU_DE_SAISIES}` est équivalent à
+ * - `#INCLURE{fond=inclure/generer_saisies,env,saisies=#TABLEAU_DE_SAISIES}`
  *
+ * @syntaxe `#GENERER_SAISIE{#TABLEAU_DE_SAISIES}`
+ * @uses Pile::recuperer_et_supprimer_argument_balise()
+ * @uses Pile::creer_et_ajouter_argument_balise()
+ * @see balise_INCLURE_dist()
+ * 
  * @param Champ $p
  *     Pile au niveau de la balise
  * @return Champ
@@ -28,22 +34,22 @@ function balise_GENERER_SAISIES_dist($p){
 
        // On recupere le premier (et seul) argument : le tableau decrivant ce qu'on veut generer
        $config = Pile::recuperer_et_supprimer_argument_balise(1, $p);
-       
+
        // On ajoute le squelette a inclure dans les parametres
        $p = Pile::creer_et_ajouter_argument_balise($p, 'fond', 'inclure/generer_saisies');
-       
+
        // On ajoute l'environnement
        $p = Pile::creer_et_ajouter_argument_balise($p, 'env');
-       
+
        // On ajoute le tableau recupere
        $p = Pile::creer_et_ajouter_argument_balise($p, 'saisies', $config);
-       
+
        // On redirige vers la balise INCLURE
-       if(function_exists('balise_INCLURE'))
+       if (function_exists('balise_INCLURE')) {
                return balise_INCLURE($p);
-       else
-               return balise_INCLURE_dist($p); 
+       } else {
+               return balise_INCLURE_dist($p);
+       }
 
 }
 
-?>
index c891e55..9149b8c 100644 (file)
@@ -1,29 +1,61 @@
 <?php
 
+/**
+ * Déclaration de la classe `Pile` et de la balise `#SAISIE`
+ *
+ * @package SPIP\Saisies\Balises
+**/
+
 if (!defined("_ECRIRE_INC_VERSION")) return;
 
 // pour ne pas interferer avec d'eventuelles futures fonctions du core
-// on met le tout dans un namespace ; les fonctions sont autonomes.
+// on met le tout dans une classe ; les fonctions sont autonomes.
 
+/**
+ * Conteneur pour modifier les arguments d'une balise SPIP (de classe Champ) à compiler
+ *
+ * @note
+ *     Ces fonctions visent à modifier l'AST (Arbre de Syntaxe Abstraite) issues
+ *     de l'analyse du squelette. Très utile pour créer des balises qui
+ *     transmettent des arguments supplémentaires automatiquement, à des balises
+ *     déjà existantes.
+ *     Voir un exemple d'utilisation dans `balise_SAISIE_dist()`.
+ * 
+ * @note
+ *     Les arguments sont stockés sont dans l'entree 0 de la propriété `param`
+ *     dans l'objet Champ (représenté par `$p`), donc dans `$p->param[0]`.
+ * 
+ *     `param[0][0]` vaut toujours '' (ou presque ?)
+ *
+ * @see balise_SAISIE_dist() Pour un exemple d'utilisation
+**/
 class Pile {
 
 
-       // les arguments sont dans l'entree 0 du tableau param.
-       // param[0][0] vaut toujours '' (ou presque ?)
+       /**
+        * Récupère un argument de balise
+        * 
+        * @param int $pos
+        * @param Champ $p
+        * @return mixed|null Élément de l'AST représentant l'argument s'il existe
+       **/
        static function recuperer_argument_balise($pos, $p) {
                if (!isset($p->param[0])) {
                        return null;
                }
                if (!isset($p->param[0][$pos])) {
                        return null;
-               }       
+               }
                return $p->param[0][$pos];
        }
-       
-       
-       
-       // les arguments sont dans l'entree 0 du tableau param.
-       // param[0][0] vaut toujours '' (ou presque ?)
+
+       /**
+        * Supprime un argument de balise
+        *
+        * @param int $pos
+        * @param Champ $p
+        * @return Champ
+       **/
        static function supprimer_argument_balise($pos, $p) {
                if (!isset($p->param[0])) {
                        return null;
@@ -37,23 +69,37 @@ class Pile {
                        $debut = array_slice($p->param[0], 0, $pos);
                        $fin   = array_slice($p->param[0], $pos+1);
                        $p->param[0] = array_merge($debut, $fin);
-               }               
+               }
                return $p;
-       }       
-       
-       
-       
+       }
+
+
+       /**
+        * Retourne un argument de balise, et le supprime de la liste des arguments
+        *
+        * @uses Pile::recuperer_argument_balise()
+        * @uses Pile::supprimer_argument_balise()
+        * 
+        * @param int $pos
+        * @param Champ $p
+        * @return mixed|null Élément de l'AST représentant l'argument s'il existe
+       **/
        static function recuperer_et_supprimer_argument_balise($pos, &$p) {
                $arg = Pile::recuperer_argument_balise($pos, $p);
                $p   = Pile::supprimer_argument_balise($pos, $p);
                return $arg;
        }
-       
-       
-       
-       
-       // les arguments sont dans l'entree 0 du tableau param.
-       // param[0][0] vaut toujours '' (ou presque ?)
+
+
+       /**
+        * Ajoute un argument de balise
+        *
+        * Empile l'argument à la suite des arguments déjà existants pour la balise
+        * 
+        * @param mixed $element Élément de l'AST représentant l'argument à ajouter
+        * @param Champ $p
+        * @return Champ
+       **/
        static function ajouter_argument_balise($element, $p) {
                if (isset($p->param[0][0])) {
                        $zero = array_shift($p->param[0]);
@@ -67,12 +113,26 @@ class Pile {
                }
                return $p;
        }
-       
-       
-       
-       // creer_argument_balise(nom) = {nom}
-       // creer_argument_balise(nom, 'coucou') = {nom=coucou}
-       // creer_argument_balise(nom, $balise) = {nom=#BALISE}
+
+
+       /**
+        * Crée l'élément de l'AST représentant un argument de balise.
+        *
+        * @example
+        *     ```
+        *     $nom = Pile::creer_argument_balise(nom);           // {nom}
+        *     $nom = Pile::creer_argument_balise(nom, 'coucou'); // {nom=coucou}
+        *     
+        *     $balise = Pile::creer_balise('BALISE');
+        *     $nom = Pile::creer_argument_balise(nom, $balise);  // {nom=#BALISE}
+        *     ```
+        * 
+        * @param string $nom
+        *     Nom de l'argument
+        * @param string|object $valeur
+        *     Valeur de l'argument. Peut être une chaîne de caractère ou un autre élément d'AST
+        * @return array
+       **/
        static function creer_argument_balise($nom, $valeur = null) {
                include_spip('public/interfaces');
                $s = new Texte;
@@ -91,7 +151,7 @@ class Pile {
                        $res = array($s);
                } 
                // {nom=coucou}
-               elseif (is_string($valeur)) {                   
+               elseif (is_string($valeur)) {
                        $s->texte .= "=$valeur";
                        $res = array($s);
                }
@@ -103,9 +163,21 @@ class Pile {
 
                return $res;
        }
-       
-       
-       
+
+
+       /**
+        * Crée et ajoute un argument à une balise
+        *
+        * @uses Pile::creer_argument_balise()
+        * @uses Pile::ajouter_argument_balise()
+        * 
+        * @param Champ $p
+        * @param string $nom
+        *     Nom de l'argument
+        * @param string|object $valeur
+        *     Valeur de l'argument. Peut être une chaîne de caractère ou un autre élément d'AST
+        * @return Champ
+       **/
        static function creer_et_ajouter_argument_balise($p, $nom, $valeur = null) {
                $new = Pile::creer_argument_balise($nom, $valeur); 
                return Pile::ajouter_argument_balise($new, $p);
@@ -113,15 +185,28 @@ class Pile {
 
 
 
-       // creer une balise
-       static function creer_balise($nom, $opt) {
+       /**
+        * Crée l'AST d'une balise
+        *
+        * @example
+        *     ```
+        *     // Crée : #ENV*{titre}
+        *     $titre = Pile::recuperer_argument_balise(1, $p); // $titre, 1er argument de la balise actuelle 
+        *     $env = Pile::creer_balise('ENV', array('param' => array($titre), 'etoile' => '*'));
+        *     ```
+        * 
+        * @param string $nom
+        *     Nom de la balise
+        * @param array $opt
+        *     Options (remplira les propriétés correspondantes de l'objet Champ)
+        * @return Champ
+       **/
+       static function creer_balise($nom, $opt = array()) {
                include_spip('public/interfaces');
                $b = new Champ;
                $b->nom_champ = strtoupper($nom);
-               $vars = get_class_vars('Champ'); // property_exists($b, $o); est en php 5
                foreach ($opt as $o=>$val) {
-                       #if (property_exists($b,$o)) {
-                       if (array_key_exists($o, $vars)) {
+                       if (property_exists($b,$o)) {
                                if ($o == 'param') {
                                        array_unshift($val, '');
                                        $b->$o = array($val);
@@ -136,19 +221,31 @@ class Pile {
 
 
 
-/* 
- * #saisie{type,nom} : champs obligatoires
- * 
- * collecte des arguments en fonctions du parametre "nom"
- * ajoute des arguments
- * appelle #INCLURE avec les arguments collectes en plus
- * 
- * il faudrait en faire une balise dynamique (?)
- * pour avoir un code plus propre
- * mais je n'ai pas reussi a trouver comment recuperer "valeur=#ENV{$nom}"
+/**
+ * Compile la balise `#SAISIE` qui retourne le code HTML de la saisie de formulaire indiquée.
+ *
+ * Cette balise incluera le squelette `saisies/_base.html` et lui-même `saisies/{type}.html`
+ *
+ * La balise `#SAISIE` est un raccourci pour une écriture plus compliquée de la balise `#INCLURE`.
+ * La balise calcule une série de paramètre récupérer et à transmettre à `#INCLURE`,
+ * en fonction des valeurs des 2 premiers paramètres transmis.
  * 
+ * Les autres arguments sont transmis tels quels à la balise `#INCLURE`.
+ *
+ * Ainsi `#SAISIE{input,nom,label=Nom,...}` exécutera l'équivalent de
+ * `#INCLURE{nom=nom,valeur=#ENV{nom},type_saisie=input,erreurs,fond=saisies/_base,label=Nom,...}`
+ *
+ * @syntaxe `#SAISIE{type,nom[,option=xx,...]}`
+ *
+ * @uses Pile::recuperer_et_supprimer_argument_balise()
+ * @uses Pile::creer_balise()
+ * @uses Pile::creer_et_ajouter_argument_balise()
+ * @see balise_INCLURE_dist()
+ *
+ * @param Champ $p
+ * @return Champ
  */
-function balise_SAISIE_dist ($p) {
+function balise_SAISIE_dist($p) {
 
        // on recupere les parametres sans les traduire en code d'execution php
        $type_saisie = Pile::recuperer_et_supprimer_argument_balise(1, $p); // $type
@@ -167,14 +264,11 @@ function balise_SAISIE_dist ($p) {
 
        // on appelle la balise #INCLURE
        // avec les arguments ajoutes
-       if(function_exists('balise_INCLURE'))
+       if (function_exists('balise_INCLURE')) {
                return balise_INCLURE($p);
-       else
-               return balise_INCLURE_dist($p); 
-               
+       } else {
+               return balise_INCLURE_dist($p);
+       }
 }
 
 
-
-
-?>
index 3498901..8ed9f94 100644 (file)
@@ -1,21 +1,34 @@
 <?php
 
+/**
+ * Déclaration de la balise `#VOIR_SAISIE`
+ *
+ * @package SPIP\Saisies\Balises
+**/
+
+
 // Sécurité
 if (!defined("_ECRIRE_INC_VERSION")) return;
 
-/* 
- * #VOIR_SAISIE{type,nom} : champs obligatoires
+/**
+ * Compile la balise `#VOIR_SAISIE` qui retourne le code HTML de la vue d'une saisie indiquée
+ *
+ * Cette balise incluera le squelette `saisies-vues/_base.html` et lui-même `saisies-vues/{type}.html`
  * 
- * collecte des arguments en fonctions du parametre "nom"
- * ajoute des arguments
- * appelle #INCLURE avec les arguments collectes en plus
+ * @syntaxe `#VOIR_SAISIE{type,nom[,option=valeur,...]}`
+ * @uses Pile::recuperer_et_supprimer_argument_balise()
+ * @uses Pile::creer_et_ajouter_argument_balise()
+ * @uses Pile::creer_balise()
+ * @see balise_INCLURE_dist()
  * 
+ * @param Champ $p
+ * @return Champ
  */
 function balise_VOIR_SAISIE_dist ($p) {
 
        // on recupere les parametres sans les traduire en code d'execution php
        $type_saisie = Pile::recuperer_et_supprimer_argument_balise(1, $p);
-       $nom       = Pile::recuperer_et_supprimer_argument_balise(1, $p);
+       $nom         = Pile::recuperer_et_supprimer_argument_balise(1, $p);
 
        // creer #ENV*{$titre} (* pour les cas de tableau serialises par exemple, que l'on veut reutiliser)
        $env_nom   = Pile::creer_balise('ENV', array('param' => array($nom), 'etoile' => '*')); // #ENV*{nom}
@@ -29,11 +42,10 @@ function balise_VOIR_SAISIE_dist ($p) {
 
        // on appelle la balise #INCLURE
        // avec les arguments ajoutes
-       if(function_exists('balise_INCLURE'))
+       if(function_exists('balise_INCLURE')) {
                return balise_INCLURE($p);
-       else
-               return balise_INCLURE_dist($p); 
-               
-}
+       } else {
+               return balise_INCLURE_dist($p);
+       }
 
-?>
+}
index 609742d..482c418 100644 (file)
@@ -1,30 +1,55 @@
 <?php 
+/**
+ * Déclaration de la balise `#VOIR_SAISIES`
+ *
+ * @package SPIP\Saisies\Balises
+**/
 
 // Sécurité
 if (!defined("_ECRIRE_INC_VERSION")) return;
 
+/**
+ * Compile la balise `#VOIR_SAISIES` qui retourne le code HTML des vues de formulaire,
+ * à partir du tableau des saisies transmises
+ *
+ * La balise accepte 1 paramètre qui est une liste de descriptions de saisies
+ * dont on veut générer le HTML affichant les vues du formulaires
+ *
+ * Cette balise est un raccourcis :
+ * - `#VOIR_SAISIES{#TABLEAU_DE_SAISIES}` est équivalent à
+ * - `#INCLURE{fond=inclure/voir_saisies,env,saisies=#TABLEAU_DE_SAISIES}`
+ * 
+ * @syntaxe `#VOIR_SAISIE{#TABLEAU_DE_SAISIES}`
+ * @uses Pile::recuperer_et_supprimer_argument_balise()
+ * @uses Pile::creer_et_ajouter_argument_balise()
+ * @see balise_INCLURE_dist()
+ * 
+ * @param Champ $p
+ *     Pile au niveau de la balise
+ * @return Champ
+ *     Pile complété du code à générer
+**/
 function balise_VOIR_SAISIES_dist($p){
 
        // On recupere les arguments : les tableaux decrivant ce qu'on veut generer + les reponses
        $saisies = Pile::recuperer_et_supprimer_argument_balise(1, $p);
        $valeurs = Pile::recuperer_et_supprimer_argument_balise(1, $p);
-       
+
        // On ajoute le squelette a inclure dans les parametres
        $p = Pile::creer_et_ajouter_argument_balise($p, 'fond', 'inclure/voir_saisies');
-       
+
        // On ajoute l'environnement
        $p = Pile::creer_et_ajouter_argument_balise($p, 'env');
-       
+
        // On ajoute les tableaux recuperes
        $p = Pile::creer_et_ajouter_argument_balise($p, 'saisies', $saisies);
        $p = Pile::creer_et_ajouter_argument_balise($p, 'valeurs', $valeurs);
-       
+
        // On redirige vers la balise INCLURE
-       if(function_exists('balise_INCLURE'))
+       if(function_exists('balise_INCLURE')) {
                return balise_INCLURE($p);
-       else
-               return balise_INCLURE_dist($p); 
+       } else {
+               return balise_INCLURE_dist($p);
+       }
 
 }
-
-?>
diff --git a/www/plugins/saisies/contenu/page-saisies_cvt.html b/www/plugins/saisies/contenu/page-saisies_cvt.html
deleted file mode 100644 (file)
index 0d7a487..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-<h1>CVT automatique avec Saisies</h1>
-<p>Démonstration d'un formulaire CVT généré uniquement à partir d'une déclaration de Saisies</p>
-
-#FORMULAIRE_SAISIES_CVT
index 94415fa..4048651 100644 (file)
@@ -5,67 +5,24 @@
 }
 #deplacable .ui-state-highlight { height: 5em; line-height: 1.2em; }
 #deplacable .ui-sortable {min-height:3em;}
-/*
-.formulaire_construire_formulaire{
-       padding:1em;
-}
-.formulaire_construire_formulaire ul li label{
-       display:block;
-       margin:0;
-       width:auto;
-       float:none;
-       clear:both;
-       line-height:1.7em;
-}
-.formulaire_construire_formulaire input.text, .formulaire_construire_formulaire input.password, .formulaire_construire_formulaire textarea, .formulaire_construire_formulaire select{
-       width:auto;
-}
-*/
-.formulaire_construire_formulaire li.actions_formulaire{
+
+.formulaire_construire_formulaire .actions_formulaire{
        margin:0;
        padding:1em;
        text-align:center;
        border:0;
 }
-.formulaire_construire_formulaire li.actions_formulaire img{
+.formulaire_construire_formulaire .actions_formulaire img{
        vertical-align:middle;
 }
-/*
-.formulaire_construire_formulaire li.configurable{
-       position:relative;
-       padding:1em;
-       margin:0.5em 0;
-       background:transparent;
-       border:1px dashed transparent;
-       border-radius:5px;
-       -moz-border-radius:5px;
-       -webkit-border-radius:5px;
-}
-.formulaire_construire_formulaire li.configurable.hover{
-       border-color:#999;
-}
-*/
-.formulaire_construire_formulaire li.en_configuration{
+
+.formulaire_construire_formulaire .en_configuration{
        border:5px solid #999;
        border-radius:5px;
        margin:.5em;
 }
-.formulaire_construire_formulaire li.fieldset.configurable>fieldset>ul {margin-left:30px;}
-/*
-.formulaire_construire_formulaire li.fieldset.configurable{
-       padding:0;
-}
-.formulaire_construire_formulaire li.fieldset h3.legend{
-       margin:0;
-}
-.formulaire_construire_formulaire li.fieldset.configurable > fieldset{
-       border:1px solid #ddd;
-       padding:0;
-}
-.formulaire_construire_formulaire li.fieldset.configurable > fieldset > ul{
-       padding:0 1em;
-}
-*/
+.formulaire_construire_formulaire .fieldset.configurable>fieldset>.editer-groupe {margin-left:30px;}
+
 .formulaire_construire_formulaire .formulaire_configurer{
        border-top:3px dashed #999;
        margin: 1em -8px 0 -138px;
        margin: 1em -8px 0;
 }
 
-/*
-.formulaire_construire_formulaire li.fieldset.configurable > .formulaire_configurer{
-       margin:1em 0 0 0;
-}
-.formulaire_construire_formulaire .formulaire_configurer fieldset{
-       border:0;
-       padding:0;
-}
-.formulaire_construire_formulaire .formulaire_configurer-contenus li.editer{
-       border:0;
-       margin:0.5em 0;
-       padding:0.5em 1em;
-}
-.formulaire_construire_formulaire .formulaire_configurer li.formulaire_configurer-contenu{
-       background:white;
-       border-top:1px solid #ddd;
-       border-radius:0 0 5px 5px;
-       -moz-border-radius:0 0 5px 5px;
-       -webkit-border-radius:0 0 5px 5px;
-}
-*/
 .formulaire_construire_formulaire .formulaire_configurer .formulaire_configurer-onglets {
        overflow:auto;
 }
 }
 .formulaire_construire_formulaire .formulaire_configurer .boutons { margin-bottom: -20px; }
 
+.formulaire_construire_formulaire .editer.obligatoire .formulaire_configurer label {
+    color: #666;
+    font-weight: normal;
+}
+.formulaire_construire_formulaire .editer.obligatoire .formulaire_configurer .obligatoire label {
+    color: black;
+    font-weight: bold;
+}
+
+
 .formulaire_configurer-contenus > .fieldset > fieldset:first-child {border-top:0;}
+.formulaire_configurer-contenus > .fieldset > fieldset {padding: 0}
 
-.formulaire_construire_formulaire li.editer,
-.formulaire_construire_formulaire li.explication,
-.formulaire_construire_formulaire li.fieldset {padding-top:30px; position:relative;}
-.formulaire_construire_formulaire li.explication { padding-left:140px; background:transparent; }
-.formulaire_construire_formulaire li.explication > p { margin-left:-130px; }
-.formulaire_construire_formulaire .formulaire_configurer li.fieldset {padding-top:0px;}
-.formulaire_construire_formulaire .formulaire_configurer fieldset fieldset>ul>li.editer:first-child {padding-top:0px;}
+.formulaire_construire_formulaire .editer-groupe>.configurable {padding-top:30px; position:relative;}
+.formulaire_construire_formulaire .editer.saisie_explication > .explication { position:initial; }
+.formulaire_construire_formulaire .editer.pleine_largeur .formulaire_configurer { margin-left: 0px; }
+.formulaire_construire_formulaire .editer.pleine_largeur .formulaire_configurer > .formulaire_configurer-contenus { margin-left: -138px; }
+.formulaire_construire_formulaire .formulaire_configurer .fieldset {padding-top:0px;}
+.formulaire_construire_formulaire .formulaire_configurer fieldset fieldset>.editer-groupe>.editer:first-child {padding-top:0px;}
 
-.formulaire_construire_formulaire li.hover {background-color:transparent;}
+/* SPIP 3.0 compat avec li.selecteur_item */
+.formulaire_construire_formulaire ul.editer-groupe > li.selecteur_item { background:transparent; padding-left:140px; }
+.formulaire_construire_formulaire ul.editer-groupe > li.selecteur_item label { margin-left:-130px; }
+.formulaire_construire_formulaire ul.editer-groupe > li.selecteur_item .choix label { margin-left:5px; }
+
+.formulaire_construire_formulaire .editer.hover {background-color:transparent;}
 
 .formulaire_construire_formulaire .actions{
        position:absolute;
        cursor:move;
 }
 
-
-
-.formulaire_construire_formulaire li.saisies_disponibles {
-       /*padding:1em;*/
-}
-
 .formulaire_construire_formulaire .ajouter_saisie{
        width:45%;
        margin:5px;
diff --git a/www/plugins/saisies/demo/page-saisies_cvt.html b/www/plugins/saisies/demo/page-saisies_cvt.html
new file mode 100644 (file)
index 0000000..0d7a487
--- /dev/null
@@ -0,0 +1,4 @@
+<h1>CVT automatique avec Saisies</h1>
+<p>Démonstration d'un formulaire CVT généré uniquement à partir d'une déclaration de Saisies</p>
+
+#FORMULAIRE_SAISIES_CVT
diff --git a/www/plugins/saisies/extra-vues/pays.html b/www/plugins/saisies/extra-vues/pays.html
deleted file mode 100644 (file)
index f45fbd1..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<B_pays>
-<div class="#ENV{champ_extra}">
-       <strong>#ENV{label_extra}</strong>
-       <BOUCLE_pays(GEO_PAYS){id_pays=#ENV*{valeur_extra}}>
-               #NOM
-       </BOUCLE_pays>
-</div>
-</B_pays>
\ No newline at end of file
index 123ab32..388ca47 100644 (file)
@@ -1,5 +1,5 @@
-[(#ENV{erreurs}|table_valeur{positionner}|oui)
-       <a name="ajax_ancre" href="[(#ENV{erreurs}|table_valeur{positionner})]"></a>
+[(#ENV{erreurs/positionner}|oui)
+       <a name="ajax_ancre" href="[(#ENV{erreurs/positionner})]"></a>
 ]
 <div class="formulaire_spip formulaire_editer formulaire_#ENV{form}[ (#ENV{formulaire_modifie}|oui) modifie]">
        [<p class="reponse_formulaire reponse_formulaire_ok">(#ENV*{message_ok})</p>]
                #ACTION_FORMULAIRE{#ENV{action}}
                <input style="display:none;" type="submit" class="submit" name="enregistrer" value="<:bouton_enregistrer:>" />
 
-               <ul id="deplacable">
+               <[(#VAL{ul}|saisie_balise_structure_formulaire)] class="editer-groupe" id="deplacable">
                        
-                       <li id="reinitialiser" class="actions_formulaire">
+                       <[(#VAL{li}|saisie_balise_structure_formulaire)] id="reinitialiser" class="actions_formulaire">
                                <button type="submit" class="submit" name="reinitialiser" value="oui" onclick="return confirm('<:saisies:construire_reinitialiser_confirmer:>')">
                                        <img src="#CHEMIN{images/formulaire-reinitialiser-24.png}" alt="" />
                                        <:saisies:construire_reinitialiser:>
                                </button>
-                       </li>
+                       </[(#VAL{li}|saisie_balise_structure_formulaire)]>
 
-                       <BOUCLE_contenu(POUR){tableau #ENV{_contenu}}>
+                       [(#REM)<!-- les choix de saisies -->]
+                       <BOUCLE_contenu(DATA){source tableau, #ENV{_contenu}}>
                        [(#VAL{saisie}|array_key_exists{#VALEUR}|oui)
                                [(#VALEUR**|formidable_generer_saisie_configurable{#ENV**|unserialize})]
                        ]
                        </BOUCLE_contenu>
-                       <li class="aucun"><em class="attention"><:saisies:construire_aucun_champs:></em></li>
+                       <[(#VAL{li}|saisie_balise_structure_formulaire)] class="aucun"><em class="attention"><:saisies:construire_aucun_champs:></em></[(#VAL{li}|saisie_balise_structure_formulaire)]>
                        <//B_contenu>
                        
                        <B_saisies_disponibles>
-                       <li class="editer haut saisies_disponibles" id="attrapable">
+                       <[(#VAL{li}|saisie_balise_structure_formulaire)] class="editer haut saisies_disponibles" id="attrapable">
                                <label><:saisies:construire_ajouter_champ:></label>
-                               <BOUCLE_saisies_disponibles(POUR){tableau #ENV{_saisies_disponibles}}{par cle}>
-                               <button type="submit" name="ajouter_saisie" value="#CLE" class="submit ajouter_saisie"[ title="(#VALEUR|table_valeur{description})"] [style="background-image:url((#VALEUR|table_valeur{icone}|sinon{#CHEMIN{images/formulaire-saisie-defaut.png}}))"]>
-                                       <span>[(#VALEUR|table_valeur{titre})]</span>
+                               <BOUCLE_saisies_disponibles(DATA){source tableau, #ENV{_saisies_disponibles}}{par cle}>
+                               <button type="submit" name="ajouter_saisie" value="#CLE" class="submit ajouter_saisie"[ title="(#DESCRIPTION)"] [style="background-image:url((#ICONE|sinon{#CHEMIN{images/formulaire-saisie-defaut.png}}))"]>
+                                       <span>#TITRE</span>
                                </button>
                                </BOUCLE_saisies_disponibles>
-                       </li>
+                       </[(#VAL{li}|saisie_balise_structure_formulaire)]>
                        </B_saisies_disponibles>
-               </ul>
+                       
+                       <B_saisies_groupes_disponibles>
+                       <[(#VAL{li}|saisie_balise_structure_formulaire)] class="editer haut saisies_groupes_disponibles" id="attrapable_bis">
+                               <label><:saisies:construire_ajouter_groupe:></label>
+                               <BOUCLE_saisies_groupes_disponibles(DATA){source tableau, #ENV{_saisies_groupes_disponibles}}{par cle}>
+                               <button type="submit" name="ajouter_groupe_saisie" value="#CLE" class="submit ajouter_saisie"[ title="(#DESCRIPTION)"] [style="background-image:url((#ICONE|sinon{#CHEMIN{images/formulaire-saisie-defaut.png}}))"]>
+                                       <span>#TITRE</span>
+                               </button>
+                               </BOUCLE_saisies_groupes_disponibles>
+                       </[(#VAL{li}|saisie_balise_structure_formulaire)]>
+                       </B_saisies_groupes_disponibles>
+                       
+               </[(#VAL{ul}|saisie_balise_structure_formulaire)]>
 
                
                [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ]
                .modifie .message_reinitialiser,.modifie #reinitialiser {display: block}
        </style>
        <script type="text/javascript">
-               jQuery(function(){
-                       jQuery('.formulaire_construire_formulaire li.configurable')
-                               .hover(
-                                       function(){
-                                               jQuery(this)
-                                                       .addClass('hover')
+       (function($){
+
+               function formulaire_configurer_onglets() {
+                       var $formulaire_configurer = $('.formulaire_configurer');
+                       var $onglets = $('<ul class="formulaire_configurer-onglets"></ul>');
+                       var $contenus = $formulaire_configurer.find('> .editer-groupe > .fieldset');
+
+                       // On ajoute le conteneur des onglets
+                       $formulaire_configurer.prepend($onglets);
+
+                       // On parcourt les contenus pour générer les onglets
+                       $contenus.each(function(i){
+                               // On ajoute un identifiant et une classe
+                               $(this)
+                                       .attr('id', 'formulaire_configurer-contenu-'+i)
+                                       .addClass('formulaire_configurer-contenu');
+                               // On récupère le titre (en le cachant au passage)
+                               var titre = $(this).find('[(#GLOBALS{debut_intertitre,<h3>}|replace{"<(\S*).*>",$1,i})]').eq(0).hide().text();
+                               // On crée un onglet
+                               var $onglet = $('<li><a href="#formulaire_configurer-contenu-'+i+'">'+titre+'</a></li>');
+                               $onglet
+                                       .click(function(){
+                                               $contenus.hide();
+                                               $(
+                                                       $(this)
+                                                               .siblings()
+                                                                       .removeClass('actif')
+                                                               .end()
+                                                               .addClass('actif')
+                                                               .find('a')
+                                                                       .attr('href')
+                                               ).show();
+                                               return false;
+                                       });
+
+                               // On active le premier onglet au démarrage
+                               if (i == 0) {
+                                       $onglet.addClass('actif');
+                               }
+
+                               // S'il y a des erreurs dans cette partie du contenu, on met une classe "erreurs" à l'onglet aussi
+                               if ($(this).find('.editer.erreur').length > 0) {
+                                       $onglet.addClass('erreur');
+                               }
+
+                               // On ajoute l'onglet
+                               $onglets.append($onglet);
+                       })
+                       .hide()
+                       .eq(0)
+                               .show();
+               }
+
+               /* enlever les required */
+               $('.formulaire_#FORM .editer.obligatoire').find('input, textarea, select').each(function(){
+                       if ($(this).prop('required')) {
+                               $(this).prop('required', false);
+                       }
+               });
+
+               $('.formulaire_#FORM .configurable')
+                       .hover(
+                               function(){
+                                       $(this)
+                                               .addClass('hover')
+                                               .find('> .actions')
+                                                       .show()
+                                               .end()
+                                               .parents('li.configurable:not(.en_configuration)')
+                                                       .mouseout();
+                               },
+                               function(){
+                                       if (!$(this).is('.en_configuration'))
+                                               $(this)
+                                                       .removeClass('hover')
                                                        .find('> .actions')
-                                                               .show()
+                                                               .hide()
                                                        .end()
-                                                       .parents('li.configurable:not(.en_configuration)')
-                                                               .mouseout();
-                                       },
-                                       function(){
-                                               if (!jQuery(this).is('.en_configuration'))
-                                                       jQuery(this)
-                                                               .removeClass('hover')
-                                                               .find('> .actions')
-                                                                       .hide()
-                                                               .end()
-                                                               .parents('li.configurable').eq('0')
-                                                                       .mouseover();
-                                       }
-                               )
-                               .filter(':not(.en_configuration)')
-                                       .find('> .actions')
-                                               .hide()
-                                       .end()
-                               .end();
-                       
-                       // On lance la création des onglets
-                       formulaire_configurer_onglets();
-                       
-                       // Gérer la liste des vérifications
-                       jQuery('.liste_verifications')
-                               .each(function(){
-                                       var options = jQuery(this).siblings('.options_verifier').hide();
-                                       var select = jQuery(this).find('select');
-                                       
-                                       select
-                                               .change(function(){
-                                                       var montrer = jQuery(this).val() ? jQuery(this).val() : 'soigfeg';
-                                                       options.hide().filter('.'+montrer).show();
-                                               })
-                                               .change();
-                               });
-                       
-                       // On déplie toujours les fieldsets plés par défaut
-                       jQuery('li.fieldset.plie')
-                               .each(function(){
-                                       jQuery(this)
-                                               .removeClass('plie')
-                                               .find('> fieldset > ul').show();
-                               });
-                               
-                       [(#ENV{erreurs}|non|et{#ENV{_chemin_ui}})
-                               $.getScript("#CHEMIN{#ENV{_chemin_ui}jquery.ui.core.js}", function(){
-                               $.getScript("#CHEMIN{#ENV{_chemin_ui}jquery.ui.widget.js}", function(){
-                               $.getScript("#CHEMIN{#ENV{_chemin_ui}jquery.ui.mouse.js}", function(){
-                                       $.getScript("#CHEMIN{#ENV{_chemin_ui}jquery.ui.sortable.js}", function(){
-                                               if ($.fn.sortable) {
-                                                       jQuery( "#deplacable, #deplacable ul" ).sortable({
-                                                               revert: true,
-                                                               containment: '#deplacable',
-                                                               connectWith: "#deplacable, #deplacable ul",
-                                                               placeholder: "ui-state-highlight",
-                                                               handle: '>.actions .deplacer_saisie',
-                                                               start: function(event, ui) {
-                                                                       jQuery('.ui-state-highlight')
-                                                                               .css('height', ui.item.css('height'))
-                                                                               .css('height', '+=20px');
-                                                               },
-                                                               update: function(event, ui) {
-                                                                       id = ui.item.data('id');
-                                                                       ou = ui.item.next().data('id');
-                                                                       // avant le suivant
-                                                                       if (!ou) {
-                                                                               // sinon dans le parent
-                                                                               ou = ui.item.closest('.fieldset').data('id');
-                                                                               if (ou) {
-                                                                                       ou = '[(#VAL{91}|chr)]' + ou + '[(#VAL{93}|chr)]';
-                                                                               }
+                                                       .parents('.configurable').eq('0')
+                                                               .mouseover();
+                               }
+                       )
+                       .filter(':not(.en_configuration)')
+                               .find('> .actions')
+                                       .hide()
+                               .end()
+                       .end();
+
+               // On lance la création des onglets
+               formulaire_configurer_onglets();
+
+               // Gérer la liste des vérifications
+               $('.liste_verifications').each(function(){
+                       var $options = $(this).siblings('.options_verifier').hide();
+                       var $select  = $(this).find('select');
+
+                       $select
+                               .change(function(){
+                                       var montrer = $(this).val() ? $(this).val() : 'soigfeg';
+                                       $options.hide().filter('.'+montrer).show();
+                               })
+                               .change();
+               });
+
+               // On déplie toujours les fieldsets plés par défaut
+               $('.fieldset.plie').each(function(){
+                       $(this)
+                               .removeClass('plie')
+                               .find('> fieldset > .editer-groupe').show();
+               });
+
+               [(#ENV{erreurs}|non|et{#ENV{_chemin_ui}})
+                       $.getScript("#CHEMIN{#ENV{_chemin_ui}core.js}", function(){
+                       $.getScript("#CHEMIN{#ENV{_chemin_ui}widget.js}", function(){
+                       $.getScript("#CHEMIN{#ENV{_chemin_ui}mouse.js}", function(){
+                               $.getScript("#CHEMIN{#ENV{_chemin_ui}sortable.js}", function(){
+                                       if ($.fn.sortable) {
+                                               $( "#deplacable, #deplacable .editer-groupe" ).sortable({
+                                                       revert: true,
+                                                       containment: '#deplacable',
+                                                       connectWith: "#deplacable, #deplacable .editer-groupe",
+                                                       placeholder: "ui-state-highlight",
+                                                       handle: '>.actions .deplacer_saisie',
+                                                       start: function(event, ui) {
+                                                               $('.ui-state-highlight')
+                                                                       .css('height', ui.item.css('height'))
+                                                                       .css('height', '+=20px');
+                                                       },
+                                                       update: function(event, ui) {
+                                                               id = ui.item.data('id');
+                                                               ou = ui.item.next().data('id');
+                                                               // avant le suivant
+                                                               if (!ou) {
+                                                                       // sinon dans le parent
+                                                                       ou = ui.item.closest('.fieldset').data('id');
+                                                                       if (ou) {
+                                                                               ou = '\[' + ou + '\]';
                                                                        }
-                                                                       url = "#URL_ECRIRE";
-                                                                       $.get(url, {
-                                                                               session: '#ENV{_identifiant_session}',
-                                                                               action: 'deplacer_saisie',
-                                                                               saisie: id,
-                                                                               ou: ou
-                                                                       }, function() {
-                                                                               //jQuery('input.vide').submit();
-                                                                               jQuery('.formulaire_#ENV{form}').addClass('modifie').trigger('modifsaisies');
-                                                                       });
                                                                }
-                                                       });
-                                               }
-                                       });
-                                                       /*
-                                       $.getScript("#CHEMIN{#ENV{_chemin_ui}jquery.ui.draggable.js}", function(){
-                                               if ($.fn.draggable) {
-                                                       jQuery( "#attrapable" ).draggable({
-                                                               connectToSortable: "#deplacable, #deplacable ul",
-                                                               helper: "clone"
-                                                       });
-                                               }
-                                       });
-                                                       */
-                               });});});
-                       ]
-               });
-               
-               function formulaire_configurer_onglets(){
-                       var formulaire_configurer = jQuery('.formulaire_configurer');
-                       var onglets = jQuery('<ul class="formulaire_configurer-onglets"></ul>');
-                       var contenus = formulaire_configurer.find('> ul > li.fieldset');
-                       
-                       // On ajoute le conteneur des onglets
-                       formulaire_configurer
-                               .prepend(onglets);
-                       
-                       // On parcourt les contenus pour générer les onglets
-                       contenus
-                               .each(function(i){
-                                       // On ajoute un identifiant et une classe
-                                       jQuery(this)
-                                               .attr('id', 'formulaire_configurer-contenu-'+i)
-                                               .addClass('formulaire_configurer-contenu');
-                                       // On récupère le titre (en le cachant au passage)
-                                       var titre = jQuery(this).find('h3').eq(0).hide().text();
-                                       // On crée un onglet
-                                       var onglet = jQuery('<li><a href="#formulaire_configurer-contenu-'+i+'">'+titre+'</a></li>');
-                                       onglet
-                                               .click(function(){
-                                                       contenus.hide();
-                                                       jQuery(
-                                                               jQuery(this)
-                                                                       .siblings()
-                                                                               .removeClass('actif')
-                                                                       .end()
-                                                                       .addClass('actif')
-                                                                       .find('a')
-                                                                               .attr('href')
-                                                       ).show();
-                                                       return false;
+                                                               url = "#URL_ECRIRE";
+                                                               $.get(url, {
+                                                                       session: '#ENV{_identifiant_session}',
+                                                                       action: 'deplacer_saisie',
+                                                                       saisie: id,
+                                                                       ou: ou
+                                                               }, function() {
+                                                                       //$('input.vide').submit();
+                                                                       $('.formulaire_#ENV{form}').addClass('modifie').trigger('modifsaisies');
+                                                               });
+                                                       }
                                                });
-                                       
-                                       // On active le premier onglet au démarrage
-                                       if (i == 0) onglet.addClass('actif');
-                                       
-                                       // S'il y a des erreurs dans cette partie du contenu, on met une classe "erreurs" à l'onglet aussi
-                                       if (jQuery(this).find('li.erreur').length > 0)
-                                               onglet.addClass('erreur');
-                                       
-                                       // On ajoute l'onglet
-                                       onglets
-                                               .append(onglet);
-                               })
-                               .hide()
-                               .eq(0)
-                                       .show();
-               }
+                                       }
+                               });
+                               /*
+                               $.getScript("#CHEMIN{#ENV{_chemin_ui}draggable.js}", function(){
+                                       if ($.fn.draggable) {
+                                               $( "#attrapable" ).draggable({
+                                                       connectToSortable: "#deplacable, #deplacable ul",
+                                                       helper: "clone"
+                                               });
+                                       }
+                               });
+                               */
+                       });});});
+               ]
+
+       })(jQuery);
        </script>
        </BOUCLE_editable>
 </div>
index 6255abb..21b934e 100644 (file)
@@ -40,25 +40,25 @@ function formulaires_construire_formulaire_charger($identifiant, $formulaire_ini
        $saisies_disponibles = saisies_lister_disponibles();
        $contexte['_saisies_disponibles'] = $saisies_disponibles;
        
+       // La liste des groupes de saisies
+       $saisies_groupes_disponibles = saisies_groupes_lister_disponibles("saisies/groupes");
+       $contexte['_saisies_groupes_disponibles'] = $saisies_groupes_disponibles;
+       
        $contexte['fond_generer'] = 'formulaires/inc-generer_saisies_configurables';
        
        // On cherche jquery UI pour savoir si on pourra glisser-déplacer
-       // SPIP 3 - jquery_ui
-       if (find_in_path('javascript/ui/jquery.ui.sortable.js') and find_in_path('javascript/ui/jquery.ui.draggable.js')){
+       // SPIP 3.1 - jquery_ui
+       if (find_in_path('javascript/ui/sortable.js') and find_in_path('javascript/ui/draggable.js')){
                $contexte['_chemin_ui'] = 'javascript/ui/';
        }
-       // plugin jquery_ui >= 1.8
-       elseif (find_in_path('javascript/jquery-ui/ui/jquery.ui.sortable.js') and find_in_path('javascript/jquery-ui/ui/jquery.ui.draggable.js')){
-               $contexte['_chemin_ui'] = 'javascript/jquery-ui/ui/';
-       }
-       // plugin jquery_ui < 1.8
-       elseif (find_in_path('javascript/jquery-ui-1.8.16/ui/jquery.ui.sortable.js') and find_in_path('javascript/jquery-ui-1.8.16/ui/jquery.ui.draggable.js')){
-               $contexte['_chemin_ui'] = 'javascript/jquery-ui-1.8.16/ui/';
+       // SPIP 3 - jquery_ui
+       elseif (find_in_path('javascript/ui/jquery.ui.sortable.js') and find_in_path('javascript/ui/jquery.ui.draggable.js')){
+               $contexte['_chemin_ui'] = 'javascript/ui/jquery.ui.';
        }
        else{
                $contexte['_chemin_ui'] = false;
        }
-       
+
        return $contexte;
 }
 
@@ -164,7 +164,7 @@ function formulaires_construire_formulaire_verifier($identifiant, $formulaire_in
                                'nom' => "saisie_modifiee_${nom}[verifier][type]",
                                'label' => _T('saisies:construire_verifications_label'),
                                'option_intro' => _T('saisies:construire_verifications_aucune'),
-                               'li_class' => 'liste_verifications',
+                               'conteneur_class' => 'liste_verifications',
                                'datas' => array()
                        )
                );
@@ -178,7 +178,7 @@ function formulaires_construire_formulaire_verifier($identifiant, $formulaire_in
                                        'options' => array(
                                                'nom' => 'options',
                                                'label' => $verif['titre'],
-                                               'li_class' => "$type_verif options_verifier"
+                                               'conteneur_class' => "$type_verif options_verifier"
                                        ),
                                        'saisies' => $verif['options']
                                );
@@ -212,7 +212,7 @@ function formulaires_construire_formulaire_verifier($identifiant, $formulaire_in
                        $vraies_erreurs = array_merge($vraies_erreurs, saisies_verifier($verif_options[$type_verif]['saisies']));
                }
        }
-       
+
        // On insère chaque saisie des options de verification
        if ($verif_options){
                foreach ($verif_options as $saisie_verif){
@@ -221,14 +221,17 @@ function formulaires_construire_formulaire_verifier($identifiant, $formulaire_in
        }
        $erreurs['configurer_'.$nom] = $formulaire_config;
        $erreurs['positionner'] = '#configurer_'.$nom;
-       
+
        if ($enregistrer_saisie) {
-               if ($vraies_erreurs)
+               if ($vraies_erreurs) {
                        $erreurs = array_merge($erreurs, $vraies_erreurs);
-               else
+               } else {
                        $erreurs = array();
+               }
+       } else {
+               $erreurs['message_erreur'] = ''; // on ne veut pas du message_erreur automatique
        }
-       
+
        return $erreurs;
 }
 
@@ -242,6 +245,11 @@ function formulaires_construire_formulaire_traiter($identifiant, $formulaire_ini
        // On récupère le formulaire à son état actuel
        $formulaire_actuel = session_get($identifiant);
        
+       // Si on demande à ajouter un groupe
+       if ($ajouter_saisie = _request('ajouter_groupe_saisie')){ 
+               $formulaire_actuel = saisies_groupe_inserer($formulaire_actuel, $ajouter_saisie);
+       }
+       
        // Si on demande à ajouter une saisie
        if ($ajouter_saisie = _request('ajouter_saisie')){
                $nom = saisies_generer_nom($formulaire_actuel, $ajouter_saisie);
@@ -352,18 +360,18 @@ function formulaires_construire_formulaire_traiter($identifiant, $formulaire_ini
                // On modifie enfin
                $formulaire_actuel = saisies_modifier($formulaire_actuel, $nom, $saisie_modifiee);
        }
-       
+
        // Si on demande à réinitialiser
        if (_request('reinitialiser') == 'oui'){
                $formulaire_actuel = $formulaire_initial;
        }
-       
+
        // On enregistre en session la nouvelle version du formulaire
        session_set($identifiant, $formulaire_actuel);
-       
+
        // Le formulaire reste éditable
        $retours['editable'] = true;
-       
+
        return $retours;
 }
 
@@ -379,17 +387,22 @@ function formidable_transformer_nom(&$valeur, $cle, $transformation){
 function formidable_generer_saisie_configurable($saisie, $env){
        // On récupère le nom
        $nom = $saisie['options']['nom'];
-       $identifiant = $saisie['identifiant'];
+       $identifiant = isset($saisie['identifiant']) ? $saisie['identifiant'] : "";
        // On cherche si ya un formulaire de config
        $formulaire_config = isset($env['erreurs']['configurer_'.$nom]) ? $env['erreurs']['configurer_'.$nom] : "";
        // On ajoute une classe
-       if (!isset($saisie['options']['li_class'])) {
-               $saisie['options']['li_class'] = ''; // initialisation
+       if (!isset($saisie['options']['conteneur_class'])) {
+               $saisie['options']['conteneur_class'] = ''; // initialisation
+       }
+       // Compat ancien nom li_class
+       if (isset($saisie['options']['li_class'])) {
+               $saisie['options']['conteneur_class'] .= " " . $saisie['options']['li_class']; // initialisation
        }
-       $saisie['options']['li_class'] .= ' configurable';
+       $saisie['options']['conteneur_class'] .= ' configurable';
+
        // On ajoute l'option "tout_afficher"
        $saisie['options']['tout_afficher'] = 'oui';
-       
+
        // On ajoute les boutons d'actions, mais seulement s'il n'y a pas de configuration de lancée
        if (!$env['erreurs']) {
                $saisie = saisies_inserer_html(
@@ -406,21 +419,21 @@ function formidable_generer_saisie_configurable($saisie, $env){
                        'debut'
                );
        }
-       
+
        // On ajoute une ancre pour s'y déplacer
        $saisie = saisies_inserer_html(
                $saisie,
                "\n<a id=\"configurer_$nom\"></a>\n",
                'debut'
        );
-       
+
        // Si ya un form de config on l'ajoute à la fin
        if (is_array($formulaire_config)){
                // On double l'environnement
                $env2 = $env;
                // On ajoute une classe
-               $saisie['options']['li_class'] .= ' en_configuration';
-               
+               $saisie['options']['conteneur_class'] .= ' en_configuration';
+
                // Si possible on met en readonly
                $saisie['options']['readonly'] = 'oui';
                
@@ -477,10 +490,13 @@ function formidable_generer_saisie_configurable($saisie, $env){
                $erreurs_test = $env['erreurs'];
                unset($erreurs_test['configurer_'.$nom]);
                unset($erreurs_test['positionner']);
+               unset($erreurs_test['message_erreur']);
+
                if ($erreurs_test){
                        // Là aussi on désinfecte à la main
-                       if (is_array($env2["saisie_modifiee_$nom"]['options']))
+                       if (isset($env2["saisie_modifiee_$nom"]['options']) and is_array($env2["saisie_modifiee_$nom"]['options'])) {
                                spip_desinfecte($env2["saisie_modifiee_$nom"]['options']);
+                       }
                }
                else{
                        $env2["saisie_modifiee_$nom"] = $env2['_saisies_par_nom'][$nom];
@@ -494,17 +510,17 @@ function formidable_generer_saisie_configurable($saisie, $env){
                $env2['fond_generer'] = 'inclure/generer_saisies';
                $saisie = saisies_inserer_html(
                        $saisie,
-                       '<div class="formulaire_configurer"><ul class="formulaire_configurer-contenus">'
+                       '<div class="formulaire_configurer"><'.saisie_balise_structure_formulaire('ul').' class="editer-groupe formulaire_configurer-contenus">'
                        .recuperer_fond(
                                'inclure/generer_saisies',
                                $env2
                        )
-                       .'<li class="boutons">
+                       .'<'.saisie_balise_structure_formulaire('li').' class="boutons">
                                <input type="hidden" name="enregistrer_saisie" value="'.$nom.'" />
                                <button type="submit" class="submit link" name="enregistrer_saisie" value="">'._T('bouton_annuler').'</button>
                                <input type="submit" class="submit" name="enregistrer" value="'._T('bouton_valider').'" />
-                       </li>'
-                       .'</ul></div>',
+                       </'.saisie_balise_structure_formulaire('li').'>'
+                       .'</'.saisie_balise_structure_formulaire('ul').'></div>',
                        'fin'
                );
        }
@@ -530,4 +546,31 @@ function saisie_option_contenu_vide($var) {
        }
        return true;
 }
-?>
+
+function saisies_groupe_inserer($formulaire_actuel, $saisie){
+       include_spip('inclure/configurer_saisie_fonctions');
+       
+               //le groupe de saisies
+               $saisies_charger_infos = saisies_charger_infos($saisie,$saisies_repertoire = "saisies/groupes");
+               
+               //le tableau est-il en options ou en saisies ?
+               $classique_yaml=count($saisies_charger_infos['options']);
+               $formidable_yaml=count($saisies_charger_infos['saisies']);
+               if($classique_yaml>0) {
+                       $champ_options = 'options';
+               }
+               if($formidable_yaml>0) { 
+                       $champ_options = 'saisies';
+               }
+               
+               //les champs du groupe 
+               foreach($saisies_charger_infos[$champ_options] as $info_saisie){
+                       unset($info_saisie['identifiant']);
+                       $saisies_disponibles = saisies_lister_disponibles();
+                       $construire_nom = $info_saisie[$champ_options]['nom'] ? $info_saisie[$champ_options]['nom'] : $info_saisie['saisie'];
+                       $nom = $info_saisie[$champ_options]['nom'] = saisies_generer_nom($formulaire_actuel,$construire_nom);
+                       
+                       $formulaire_actuel = saisies_inserer($formulaire_actuel, $info_saisie);
+               }
+               return $formulaire_actuel;
+}
index b87cb54..eb23c21 100644 (file)
@@ -8,9 +8,9 @@
                parametre : url d'action ]\r
                #ACTION_FORMULAIRE{#ENV{action}}\r
                \r
-               <ul>\r
+               <[(#VAL{ul}|saisie_balise_structure_formulaire)] class="editer-groupe">\r
                        #GENERER_SAISIES{#ENV{_saisies}}\r
-               </ul>\r
+               </[(#VAL{ul}|saisie_balise_structure_formulaire)]>\r
                \r
                [(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ]\r
                <!--extra-->\r
index bca21ae..53853ef 100644 (file)
@@ -3,7 +3,7 @@
 /**\r
  * Gestion de l'affichage des saisies\r
  *\r
- * @return SPIP\Saisies\Saisies\r
+ * @package SPIP\Saisies\Saisies\r
 **/\r
 \r
 // Sécurité\r
@@ -40,10 +40,11 @@ include_spip('inc/saisies_manipuler');
 // Les outils pour afficher les saisies et leur vue\r
 include_spip('inc/saisies_afficher');\r
 \r
-/*\r
+/**\r
  * Cherche la description des saisies d'un formulaire CVT dont on donne le nom\r
  *\r
  * @param string $form Nom du formulaire dont on cherche les saisies\r
+ * @param array $args Tableau d'arguments du formulaire\r
  * @return array Retourne les saisies du formulaire sinon false\r
  */\r
 function saisies_chercher_formulaire($form, $args){\r
@@ -68,7 +69,7 @@ function saisies_chercher_formulaire($form, $args){
        }\r
 }\r
 \r
-/*\r
+/**\r
  * Cherche une saisie par son id, son nom ou son chemin et renvoie soit la saisie, soit son chemin\r
  *\r
  * @param array $saisies Un tableau décrivant les saisies\r
@@ -136,12 +137,12 @@ function saisies_generer_nom($formulaire, $type_saisie){
        return $type_saisie.'_'.$compteur;\r
 }\r
 \r
-/*\r
+/**\r
  * Crée un identifiant Unique\r
  * pour toutes les saisies donnees qui n'en ont pas \r
  *\r
  * @param Array $saisies Tableau de saisies\r
- * @param Bool $regenerer_id Régénère un nouvel identifiant pour toutes les saisies ?\r
+ * @param Bool $regenerer Régénère un nouvel identifiant pour toutes les saisies ?\r
  * @return Array Tableau de saisies complété des identifiants\r
  */\r
 function saisies_identifier($saisies, $regenerer = false) {\r
@@ -160,7 +161,7 @@ function saisies_identifier($saisies, $regenerer = false) {
  * (et pour ses sous saisies éventuels)\r
  *\r
  * @param Array $saisie Tableau d'une saisie\r
- * @param Bool $regenerer_id Régénère un nouvel identifiant pour la saisie ?\r
+ * @param Bool $regenerer Régénère un nouvel identifiant pour la saisie ?\r
  * @return Array Tableau de la saisie complété de l'identifiant\r
 **/\r
 function saisie_identifier($saisie, $regenerer = false) {\r
@@ -175,7 +176,7 @@ function saisie_identifier($saisie, $regenerer = false) {
        return $saisie;\r
 }\r
 \r
-/*\r
+/**\r
  * Vérifier tout un formulaire tel que décrit avec les Saisies\r
  *\r
  * @param array $formulaire Le contenu d'un formulaire décrit dans un tableau de Saisies\r
@@ -250,41 +251,44 @@ function saisies_verifier($formulaire, $saisies_masquees_nulles=true){
        return $erreurs;\r
 }\r
 \r
-/*\r
+/**\r
  * Applatie une description tabulaire\r
- * @param string $tab, le tableau à aplatir\r
+ * @param string $tab Le tableau à aplatir\r
  * @return $nouveau_tab\r
  */\r
 function saisies_aplatir_tableau($tab){\r
-    $nouveau_tab = array();\r
-    foreach($tab as $entree=>$contenu){\r
-        if (is_array($contenu)){\r
-            foreach ($contenu as $cle => $valeur){\r
-                $nouveau_tab[$cle] = $valeur;\r
-                }\r
-            }\r
-        else{\r
-            $nouveau_tab[$entree] = $contenu;\r
-            }\r
-        }\r
-    return $nouveau_tab;\r
+       $nouveau_tab = array();\r
+       foreach($tab as $entree=>$contenu){\r
+               if (is_array($contenu)) {\r
+                       foreach ($contenu as $cle => $valeur) {\r
+                               $nouveau_tab[$cle] = $valeur;\r
+                       }\r
+               } else {\r
+                       $nouveau_tab[$entree] = $contenu;\r
+               }\r
+       }\r
+       return $nouveau_tab;\r
 }\r
 \r
-/*\r
+/**\r
  * Applatie une description chaînée, en supprimant les sous-groupes.\r
- * @param string $chaine, la chaîne à aplatir\r
+ * @param string $chaine La chaîne à aplatir\r
  * @return $chaine\r
  */\r
 function saisies_aplatir_chaine($chaine){\r
-    return trim(preg_replace("#(?:^|\n)(\*(?:.*)|/\*)\n#i","\n",$chaine));\r
-    }\r
-/*\r
+       return trim(preg_replace("#(?:^|\n)(\*(?:.*)|/\*)\n#i","\n",$chaine));\r
+}\r
+\r
+/**\r
  * Transforme une chaine en tableau avec comme principe :\r
+ * \r
  * - une ligne devient une case\r
  * - si la ligne est de la forme truc|bidule alors truc est la clé et bidule la valeur\r
  * - si la ligne commence par * alors on commence un sous-tableau\r
  * - si la ligne est égale à /*, alors on fini le sous-tableau\r
+ * \r
  * @param string $chaine Une chaine à transformer\r
+ * @param string $separateur Séparateur utilisé\r
  * @return array Retourne un tableau PHP\r
  */\r
 function saisies_chaine2tableau($chaine, $separateur="\n"){\r
@@ -298,36 +302,34 @@ function saisies_chaine2tableau($chaine, $separateur="\n"){
                        // Si ce n'est pas une ligne sans rien\r
                        if ($ligne !== ''){\r
                                // si ca commence par * c'est qu'on va faire un sous tableau\r
-                               if (strpos($ligne,"*")===0){\r
+                               if (strpos($ligne,"*")===0) {\r
                                        $soustab=True;\r
                                        $soustab_cle    = _T_ou_typo(substr($ligne,1), 'multi');\r
                                        if (!isset($tableau[$soustab_cle])){\r
                                                $tableau[$soustab_cle] = array();\r
                                        }\r
                                }\r
-                               elseif ($ligne=="/*"){//si on finit sous tableau\r
+                               elseif ($ligne=="/*") {//si on finit sous tableau\r
                                        $soustab=False;\r
                                }\r
                                else{//sinon c'est une entrée normale\r
                                // Si on trouve un découpage dans la ligne on fait cle|valeur\r
-                                       if (strpos($ligne, '|') !== false){\r
+                                       if (strpos($ligne, '|') !== false) {\r
                                                list($cle,$valeur) = explode('|', $ligne, 2);\r
                                                // permettre les traductions de valeurs au passage\r
                                                if ($soustab == True){\r
                                                        $tableau[$soustab_cle][$cle] = _T_ou_typo($valeur, 'multi');\r
-                                                       }\r
-                                               else{\r
+                                               } else {\r
                                                        $tableau[$cle] = _T_ou_typo($valeur, 'multi');\r
-                                                       }\r
+                                               }\r
                                        }\r
                                // Sinon on génère la clé\r
                                        else{\r
-                                               if ($soustab == True){\r
+                                               if ($soustab == True) {\r
                                                        $tableau[$soustab_cle][$i] = _T_ou_typo($ligne,'multi');\r
-                                                       }\r
-                                               else{\r
+                                               } else {\r
                                                        $tableau[$i] = _T_ou_typo($ligne,'multi');\r
-                                                       }\r
+                                               }\r
                                        }\r
                                }\r
                        }\r
@@ -337,18 +339,21 @@ function saisies_chaine2tableau($chaine, $separateur="\n"){
        // Si c'est déjà un tableau on lui applique _T_ou_typo (qui fonctionne de manière récursive avant de le renvoyer\r
        elseif (is_array($chaine)){\r
                return _T_ou_typo($chaine, 'multi');\r
-       }\r
-       else{\r
+       } else {\r
                return array();\r
        }\r
 }\r
 \r
-/*\r
+/**\r
  * Transforme un tableau en chaine de caractères avec comme principe :\r
+ * \r
  * - une case de vient une ligne de la chaine\r
  * - chaque ligne est générée avec la forme cle|valeur\r
  * - si une entrée du tableau est elle même un tableau, on met une ligne de la forme *clef\r
  * - pour marquer que l'on quitte un sous-tableau, on met une ligne commencant par /*, sauf si on bascule dans un autre sous-tableau.\r
+ *\r
+ * @param array $tableau Tableau à transformer\r
+ * @return string Texte représentant les données du tableau\r
  */\r
 function saisies_tableau2chaine($tableau){\r
        if ($tableau and is_array($tableau)){\r
@@ -387,27 +392,20 @@ function saisies_tableau2chaine($tableau){
 \r
 \r
 /**\r
- * Passe une valeur en tableau d'élements si ce n'en est pas une\r
- *\r
- * entrée :\r
- * cle|valeur\r
- * cle|valeur\r
- *\r
- * Sinon :\r
- * valeur,valeur\r
+ * Transforme une valeur en tableau d'élements si ce n'est pas déjà le cas\r
  *\r
  * @param mixed $valeur\r
  * @return array Tableau de valeurs\r
 **/\r
-function saisies_valeur2tableau($valeur, $sinon_separateur="") {\r
+function saisies_valeur2tableau($valeur) {\r
        if (is_array($valeur)) {\r
                return $valeur;\r
        }\r
-       \r
+\r
        if (!strlen($valeur)) {\r
                return array();\r
        }\r
-       \r
+\r
        $t = saisies_chaine2tableau($valeur);\r
        if (count($t) > 1) {\r
                return $t;\r
@@ -418,20 +416,45 @@ function saisies_valeur2tableau($valeur, $sinon_separateur="") {
        if (isset($t[0])) {\r
                $t = saisies_chaine2tableau($t[0], ',');\r
        }\r
-       \r
+\r
        return $t;\r
 }\r
 \r
 \r
+/**\r
+ * Pour les saisies multiples (type checkbox) proposant un choix alternatif,\r
+ * retrouve à partir des data de choix proposés\r
+ * et des valeurs des choix enregistrés\r
+ * le texte enregistré pour le choix alternatif.\r
+ *\r
+ * @param array $data\r
+ * @param array $valeur\r
+ * @return string choix_alternatif\r
+**/\r
+function saisies_trouver_choix_alternatif($data,$valeur) {\r
+       if (!is_array($valeur)) {\r
+               $valeur = saisies_chaine2tableau($valeur) ;\r
+       }\r
+       if (!is_array($data)) {\r
+               $data = saisies_chaine2tableau($data) ;\r
+       }\r
+       $choix_theorique = array_keys($data);\r
+       $choix_alternatif = array_values(array_diff($valeur,$choix_theorique));\r
+       return $choix_alternatif[0];//on suppose que personne ne s'est amusé à proposer deux choix alternatifs\r
+}\r
 \r
-\r
-/*\r
+/**\r
  * Génère une page d'aide listant toutes les saisies et leurs options\r
+ *\r
+ * Retourne le résultat du squelette `inclure/saisies_aide` auquel\r
+ * on a transmis toutes les saisies connues.\r
+ * \r
+ * @return string Code HTML\r
  */\r
 function saisies_generer_aide(){\r
        // On a déjà la liste par saisie\r
        $saisies = saisies_lister_disponibles();\r
-       \r
+\r
        // On construit une liste par options\r
        $options = array();\r
        foreach ($saisies as $type_saisie=>$saisie){\r
@@ -448,7 +471,7 @@ function saisies_generer_aide(){
                $saisies[$type_saisie]['options'] = $options_saisie;\r
        }\r
        ksort($options);\r
-       \r
+\r
        return recuperer_fond(\r
                'inclure/saisies_aide',\r
                array(\r
@@ -458,7 +481,7 @@ function saisies_generer_aide(){
        );\r
 }\r
 \r
-/*\r
+/**\r
  * Le tableau de saisies a-t-il une option afficher_si ?\r
  *\r
  * @param array $saisies Un tableau de saisies\r
@@ -476,7 +499,7 @@ function saisies_afficher_si($saisies) {
 }\r
 \r
 \r
-/*\r
+/**\r
  * Le tableau de saisies a-t-il une option afficher_si_remplissage ?\r
  *\r
  * @param array $saisies Un tableau de saisies\r
@@ -491,4 +514,4 @@ function saisies_afficher_si_remplissage($saisies) {
        }\r
        return false;\r
 }\r
-?>\r
+\r
index 9c15248..4e7e348 100644 (file)
-<?php\r
-\r
-/**\r
- * Gestion de l'affichage des saisies\r
- *\r
- * @return SPIP\Saisies\Afficher\r
-**/\r
-\r
-// Sécurité\r
-if (!defined('_ECRIRE_INC_VERSION')) return;\r
-\r
-/**\r
- * Indique si une saisie peut être affichée.\r
- *\r
- * On s'appuie sur l'éventuelle clé "editable" du $champ.\r
- * Si editable vaut :\r
- *    - absent : le champ est éditable\r
- *    - 1, le champ est éditable\r
- *    - 0, le champ n'est pas éditable\r
- *    - -1, le champ est éditable s'il y a du contenu dans le champ (l'environnement)\r
- *         ou dans un de ses enfants (fieldsets)\r
- *\r
- * @param array $champ\r
- *     Tableau de description de la saisie\r
- * @param array $env\r
- *     Environnement transmis à la saisie, certainement l'environnement du formulaire\r
- * @param bool $utiliser_editable\r
- *     - false pour juste tester le cas -1\r
- *\r
- * @return bool\r
- *     Retourne un booléen indiquant l'état éditable ou pas :\r
- *     - true si la saisie est éditable (peut être affichée)\r
- *     - false sinon\r
- */\r
-function saisie_editable($champ, $env, $utiliser_editable=true) {\r
-       if ($utiliser_editable) {\r
-               // si le champ n'est pas éditable, on sort.\r
-               if (!isset($champ['editable'])) {\r
-                       return true;\r
-               }\r
-               $editable = $champ['editable'];\r
-\r
-               if ($editable > 0) {\r
-                       return true;\r
-               }\r
-               if ($editable == 0) {\r
-                       return false;\r
-               }\r
-       }\r
-\r
-       // cas -1\r
-       // name de la saisie\r
-       if (isset($champ['options']['nom'])) {\r
-               // si on a le name dans l'environnement, on le teste\r
-               $nom = $champ['options']['nom'];\r
-               if (isset($env[$nom])) {\r
-                       return $env[$nom] ? true : false ;\r
-               }\r
-       }\r
-       // sinon, si on a des sous saisies\r
-       if (isset($champ['saisies']) and is_array($champ['saisies'])) {\r
-               foreach($champ['saisies'] as $saisie) {\r
-                       if (saisie_editable($saisie, $env, false)) {\r
-                               return true;\r
-                       }\r
-               }\r
-       }\r
-\r
-       // aucun des paramètres demandés n'avait de contenu\r
-       return false;\r
-}\r
-\r
-/**\r
- * Génère une saisie à partir d'un tableau la décrivant et de l'environnement\r
- *\r
- * @param array $champ\r
- *     Description de la saisie.\r
- *     Le tableau doit être de la forme suivante :\r
- *     array(\r
- *                     'saisie' => 'input',\r
- *                     'options' => array(\r
- *                             'nom' => 'le_name',\r
- *                             'label' => 'Un titre plus joli',\r
- *                             'obligatoire' => 'oui',\r
- *                             'explication' => 'Remplissez ce champ en utilisant votre clavier.'\r
- *                     )\r
- *     )\r
- * @param array $env\r
- *     Environnement du formulaire\r
- *     Permet de savoir les valeurs actuelles des contenus des saisies,\r
- *     les erreurs eventuelles présentes...\r
- * @return string\r
- *     Code HTML des saisies de formulaire\r
- */\r
-function saisies_generer_html($champ, $env=array()){\r
-       // Si le parametre n'est pas bon, on genere du vide\r
-       if (!is_array($champ))\r
-               return '';\r
-\r
-       // Si la saisie n'est pas editable, on sort aussi.\r
-       if (!saisie_editable($champ, $env)) {\r
-               return '';\r
-       }\r
-\r
-       $contexte = array();\r
-\r
-       // On sélectionne le type de saisie\r
-       $contexte['type_saisie'] = $champ['saisie'];\r
-       // Identifiant unique de saisie, si present\r
-       if (isset($champ['identifiant'])) {\r
-               $contexte['id_saisie'] = $champ['identifiant'];\r
-       }\r
-\r
-       // Peut-être des transformations à faire sur les options textuelles\r
-       $options = $champ['options'];\r
-       foreach ($options as $option => $valeur){\r
-               if ($option == 'datas') {\r
-                       // exploser une chaine datas en tableau (applique _T_ou_typo sur chaque valeur)\r
-                       $options[$option] = saisies_chaine2tableau($valeur);\r
-               } else {\r
-                       $options[$option] = _T_ou_typo($valeur, 'multi');\r
-               }\r
-       }\r
-\r
-       // On ajoute les options propres à la saisie\r
-       $contexte = array_merge($contexte, $options);\r
-\r
-       // Si env est définie dans les options ou qu'il y a des enfants, on ajoute tout l'environnement\r
-       if (isset($contexte['env']) or (isset($champ['saisies']) AND is_array($champ['saisies']))) {\r
-               unset($contexte['env']);\r
-\r
-               // on sauve l'ancien environnement\r
-               // car les sous-saisies ne doivent pas être affectees\r
-               // par les modification sur l'environnement servant à generer la saisie mère\r
-               $contexte['_env'] = $env;\r
-\r
-               // À partir du moment où on passe tout l'environnement, il faut enlever certains éléments qui ne doivent absolument provenir que des options\r
-               unset($env['inserer_debut']);\r
-               unset($env['inserer_fin']);\r
-               $saisies_disponibles = saisies_lister_disponibles();\r
-               if (isset($saisies_disponibles[$contexte['type_saisie']]) and is_array($saisies_disponibles[$contexte['type_saisie']]['options'])) {\r
-                       $options_a_supprimer = saisies_lister_champs($saisies_disponibles[$contexte['type_saisie']]['options']);\r
-                       foreach ($options_a_supprimer as $option_a_supprimer){\r
-                               unset($env[$option_a_supprimer]);\r
-                       }\r
-               }\r
-\r
-               $contexte = array_merge($env, $contexte);\r
-       }\r
-       // Sinon on ne sélectionne que quelques éléments importants\r
-       else{\r
-               // On récupère la liste des erreurs\r
-               $contexte['erreurs'] = $env['erreurs'];\r
-               // On ajoute toujours le bon self\r
-               $contexte['self'] = self();\r
-       }\r
-\r
-       // Dans tous les cas on récupère de l'environnement la valeur actuelle du champ\r
-       // Si le nom du champ est un tableau indexé, il faut parser !\r
-       if (preg_match('/([\w]+)((\[[\w]+\])+)/', $contexte['nom'], $separe)){\r
-               $contexte['valeur'] = $env[$separe[1]];\r
-               preg_match_all('/\[([\w]+)\]/', $separe[2], $index);\r
-               // On va chercher au fond du tableau\r
-               foreach($index[1] as $cle){\r
-                       $contexte['valeur'] = isset($contexte['valeur'][$cle]) ? $contexte['valeur'][$cle] : null;\r
-               }\r
-       }\r
-       // Sinon la valeur est juste celle du nom\r
-       else {\r
-               $contexte['valeur'] = (isset($env[$contexte['nom']]) ? $env[$contexte['nom']] : null);\r
-       }\r
-\r
-       // Si ya des enfants on les remonte dans le contexte\r
-       if (isset($champ['saisies']) and is_array($champ['saisies']))\r
-               $contexte['saisies'] = $champ['saisies'];\r
-\r
-       // On génère la saisie\r
-       return recuperer_fond(\r
-               'saisies/_base',\r
-               $contexte\r
-       );\r
-}\r
-\r
-/**\r
- * Génère une vue d'une saisie à partir d'un tableau la décrivant\r
- *\r
- * @see saisies_generer_html()\r
- * @param array $saisie\r
- *     Tableau de description d'une saisie\r
- * @param array $env\r
- *     L'environnement, contenant normalement la réponse à la saisie\r
- * @param array $env_obligatoire\r
- *     ???\r
- * @return string\r
- *     Code HTML de la vue de la saisie\r
- */\r
-function saisies_generer_vue($saisie, $env=array(), $env_obligatoire=array()){\r
-       // Si le paramètre n'est pas bon, on génère du vide\r
-       if (!is_array($saisie))\r
-               return '';\r
-\r
-       $contexte = array();\r
-\r
-       // On sélectionne le type de saisie\r
-       $contexte['type_saisie'] = $saisie['saisie'];\r
-\r
-       // Peut-être des transformations à faire sur les options textuelles\r
-       $options = $saisie['options'];\r
-       foreach ($options as $option => $valeur){\r
-               if ($option == 'datas') {\r
-                       // exploser une chaine datas en tableau (applique _T_ou_typo sur chaque valeur)\r
-                       $options[$option] = saisies_chaine2tableau($valeur);\r
-               } else {\r
-                       $options[$option] = _T_ou_typo($valeur, 'multi');\r
-               }\r
-       }\r
-\r
-       // On ajoute les options propres à la saisie\r
-       $contexte = array_merge($contexte, $options);\r
-\r
-       // Si env est définie dans les options ou qu'il y a des enfants, on ajoute tout l'environnement\r
-       if (isset($contexte['env']) or (isset($saisie['saisies']) AND is_array($saisie['saisies']))){\r
-               unset($contexte['env']);\r
-\r
-               // on sauve l'ancien environnement\r
-               // car les sous-saisies ne doivent pas être affectees\r
-               // par les modification sur l'environnement servant à generer la saisie mère\r
-               $contexte['_env'] = $env;\r
-\r
-               // À partir du moment où on passe tout l'environnement, il faut enlever\r
-               // certains éléments qui ne doivent absolument provenir que des options\r
-               $saisies_disponibles = saisies_lister_disponibles();\r
-               if (is_array($saisies_disponibles[$contexte['type_saisie']]['options'])){\r
-                       $options_a_supprimer = saisies_lister_champs($saisies_disponibles[$contexte['type_saisie']]['options']);\r
-                       foreach ($options_a_supprimer as $option_a_supprimer){\r
-                               unset($env[$option_a_supprimer]);\r
-                       }\r
-               }\r
-\r
-               $contexte = array_merge($env, $contexte);\r
-       }\r
-\r
-       // Dans tous les cas on récupère de l'environnement la valeur actuelle du champ\r
-\r
-       // On regarde en priorité s'il y a un tableau listant toutes les valeurs\r
-       if ($env['valeurs'] and is_array($env['valeurs']) and isset($env['valeurs'][$contexte['nom']])){\r
-               $contexte['valeur'] = $env['valeurs'][$contexte['nom']];\r
-       }\r
-       // Si le nom du champ est un tableau indexé, il faut parser !\r
-       elseif (preg_match('/([\w]+)((\[[\w]+\])+)/', $contexte['nom'], $separe)){\r
-               $contexte['valeur'] = $env[$separe[1]];\r
-               preg_match_all('/\[([\w]+)\]/', $separe[2], $index);\r
-               // On va chercher au fond du tableau\r
-               foreach($index[1] as $cle){\r
-                       $contexte['valeur'] = $contexte['valeur'][$cle];\r
-               }\r
-       }\r
-       // Sinon la valeur est juste celle du nom\r
-       else {\r
-               // certains n'ont pas de nom (fieldset)\r
-               $contexte['valeur'] = isset($env[$contexte['nom']]) ? $env[$contexte['nom']] : '';\r
-       }\r
-\r
-       // Si ya des enfants on les remonte dans le contexte\r
-       if (isset($saisie['saisies']) AND is_array($saisie['saisies']))\r
-               $contexte['saisies'] = $saisie['saisies'];\r
-\r
-       if (is_array($env_obligatoire)) {\r
-               $contexte = array_merge($contexte, $env_obligatoire);\r
-       }\r
-\r
-       // On génère la saisie\r
-       return recuperer_fond(\r
-               'saisies-vues/_base',\r
-               $contexte\r
-       );\r
-}\r
-\r
-/**\r
- * Génère, à partir d'un tableau de saisie le code javascript ajouté à la fin de #GENERER_SAISIES\r
- * pour produire un affichage conditionnel des saisies ayant une option afficher_si ou afficher_si_remplissage.\r
- *\r
- * @param array $saisies\r
- *     Tableau de descriptions des saisies\r
- * @param string $id_form\r
- *     Identifiant unique pour le formulaire\r
- * @return text\r
- *     Code javascript\r
- */\r
-function saisies_generer_js_afficher_si($saisies,$id_form){\r
-       $i = 0;\r
-       $saisies = saisies_lister_par_nom($saisies,true);\r
-       $code = '';\r
-       $code .= '(function($){';\r
-       $code .= '$(document).ready(function(){chargement=true;';\r
-               $code .= 'verifier_saisies_'.$id_form." = function(form){\n";\r
-                               foreach ($saisies as $saisie) {\r
-                                       // on utilise comme selecteur l'identifiant de saisie en priorite s'il est connu\r
-                                       // parce que li_class = 'tableau[nom][option]' ne fonctionne evidement pas\r
-                                       // lorsque le name est un tableau\r
-                                       if (isset($saisie['options']['afficher_si']) or isset($saisie['options']['afficher_si_remplissage'])) {\r
-                                               $i++;\r
-                                               // retrouver la classe css probable\r
-                                               switch ($saisie['saisie']) {\r
-                                                       case 'fieldset':\r
-                                                               $class_li = 'fieldset_'.$saisie['options']['nom'];\r
-                                                               break;\r
-                                                       case 'explication':\r
-                                                               $class_li = 'explication_'.$saisie['options']['nom'];\r
-                                                               break;\r
-                                                       default:\r
-                                                               $class_li = 'editer_'.$saisie['options']['nom'];\r
-                                               }\r
-                                               $afficher_si = isset($saisie['options']['afficher_si']) ? $saisie['options']['afficher_si'] : '';\r
-                                               $afficher_si_remplissage = isset($saisie['options']['afficher_si_remplissage']) ? $saisie['options']['afficher_si_remplissage'] : '';\r
-                                               $condition = join("\n", array_filter(array($afficher_si, $afficher_si_remplissage)));\r
-                                               // retrouver l'identifiant\r
-                                               $identifiant = '';\r
-                                               if (isset($saisie['identifiant']) and $saisie['identifiant']) {\r
-                                                       $identifiant = $saisie['identifiant'];\r
-                                               }\r
-                                               // On gère le cas @plugin:non_plugin@\r
-                                               preg_match_all('#@plugin:(.+)@#U', $condition, $matches);\r
-                                               foreach ($matches[1] as $plug) {\r
-                                                       if (defined('_DIR_PLUGIN_'.strtoupper($plug)))\r
-                                                               $condition = preg_replace('#@plugin:'.$plug.'@#U', 'true', $condition);\r
-                                                       else\r
-                                                               $condition = preg_replace('#@plugin:'.$plug.'@#U', 'false', $condition);\r
-                                               }\r
-                                               // On gère le cas @config:plugin:meta@ suivi d'un test\r
-                                               preg_match_all('#@config:(.+):(.+)@#U', $condition, $matches);\r
-                                               foreach ($matches[1] as $plugin) {\r
-                                                       $config = lire_config($plugin);\r
-                                                       $condition = preg_replace('#@config:'.$plugin.':'.$matches[2][0].'@#U', '"'.$config[$matches[2][0]].'"', $condition);\r
-                                               }\r
-                                               // On transforme en une condition valide\r
-                                               preg_match_all('#@(.+)@#U', $condition, $matches);\r
-                                               foreach ($matches[1] as $nom) {\r
-                                                       switch($saisies[$nom]['saisie']) {\r
-                                                               case 'radio':\r
-                                                               case 'oui_non':\r
-                                                                       $condition = preg_replace('#@'.preg_quote($nom).'@#U', '$(form).find("[name=\''.$nom.'\']:checked").val()', $condition);\r
-                                                                       break;\r
-                                                               case 'case':\r
-                                                                       $condition = preg_replace('#@'.preg_quote($nom).'@#U', '($(form).find(".checkbox[name=\''.$nom.'\']").is(":checked") ? $(form).find(".checkbox[name=\''.$nom.'\']").val() : "")', $condition);\r
-                                                                       break;\r
-                                                               case 'checkbox':\r
-                                                                       preg_match_all('#@(.+)@ == "(.*)"$#U', $condition, $matches2);\r
-                                                                       foreach ($matches2[2] as $value) {\r
-                                                                               $condition = preg_replace('#@'.preg_quote($nom).'@#U', '($(form).find(".checkbox[name=\''.$nom.'[]\'][value='.$value.']").is(":checked") ? $(form).find(".checkbox[name=\''.$nom.'[]\'][value='.$value.']").val() : "")', $condition);\r
-                                                                               }\r
-                                                                       break;\r
-                                                               default:\r
-                                                                       $condition = preg_replace('#@'.preg_quote($nom).'@#U', '$(form).find("[name=\''.$nom.'\']").val()', $condition);\r
-                                                       }\r
-                                               }\r
-                                               if ($identifiant) {\r
-                                                       $sel = "li[data-id='$identifiant']";\r
-                                               } else {\r
-                                                       $sel = "li.$class_li";\r
-                                               }\r
-                                               $code .= "\tif (".$condition.') {$(form).find("'.$sel.'").show(400);} '."\n\t";\r
-                                               $code .= 'else {if (chargement==true) {$(form).find("'.$sel.'").hide(400).css("display","none");} else {$(form).find("'.$sel.'").hide(400);};} '."\n";\r
-                                       }\r
-                               }\r
-               $code .= "};";\r
-               $code .= '$("li#afficher_si_'.$id_form.'").parents("form").each(function(){verifier_saisies_'.$id_form.'(this);});';\r
-               $code .= '$("li#afficher_si_'.$id_form.'").parents("form").change(function(){verifier_saisies_'.$id_form.'(this);});';\r
-       $code .= 'chargement=false;})';\r
-       $code .= '})(jQuery);';\r
-       return $i>0 ? $code : '';\r
-}\r
-\r
-/**\r
- * Lorsque l'on affiche les saisies (#VOIR_SAISIES), les saisies ayant une option afficher_si\r
- * et dont les conditions ne sont pas remplies doivent être retirées du tableau de saisies.\r
- *\r
- * Cette fonction sert aussi lors de la vérification des saisies avec saisies_verifier().\r
- * À ce moment là, les saisies non affichées sont retirées de _request\r
- * (on passe leur valeur à NULL).\r
- *\r
- * @param array $saisies\r
- *     Tableau de descriptions de saisies\r
- * @param array|null $env\r
- *     Tableau d'environnement transmis dans inclure/voi_saisies.html,\r
- *     NULL si on doit rechercher dans _request (pour saisies_verifier()).\r
- * @return array\r
- *     Tableau de descriptions de saisies\r
- */\r
-function saisies_verifier_afficher_si($saisies, $env=NULL) {\r
-       // eviter une erreur par maladresse d'appel :)\r
-       if (!is_array($saisies)) {\r
-               return array();\r
-       }\r
-       foreach ($saisies as $cle => $saisie) {\r
-               if (isset($saisie['options']['afficher_si'])) {\r
-                       $condition = $saisie['options']['afficher_si'];\r
-                       // On gère le cas @plugin:non_plugin@\r
-                       preg_match_all('#@plugin:(.+)@#U', $condition, $matches);\r
-                       foreach ($matches[1] as $plug) {\r
-                               if (defined('_DIR_PLUGIN_'.strtoupper($plug)))\r
-                                       $condition = preg_replace('#@plugin:'.$plug.'@#U', 'true', $condition);\r
-                               else\r
-                                       $condition = preg_replace('#@plugin:'.$plug.'@#U', 'false', $condition);\r
-                       }\r
-                       // On gère le cas @config:plugin:meta@ suivi d'un test\r
-                       preg_match_all('#@config:(.+):(.+)@#U', $condition, $matches);\r
-                       foreach ($matches[1] as $plugin) {\r
-                               $config = lire_config($plugin);\r
-                               $condition = preg_replace('#@config:'.$plugin.':'.$matches[2][0].'@#U', '"'.$config[$matches[2][0]].'"', $condition);\r
-                       }\r
-                       // On transforme en une condition valide\r
-                       if (is_null($env))\r
-                               $condition = preg_replace('#@(.+)@#U', '_request(\'$1\')', $condition);\r
-                       else\r
-                               $condition = preg_replace('#@(.+)@#U', '$env["valeurs"][\'$1\']', $condition);\r
-                       eval('$ok = '.$condition.';');\r
-                       if (!$ok) {\r
-                               unset($saisies[$cle]);\r
-                               if (is_null($env)) set_request($saisie['options']['nom'],NULL);\r
-                       }\r
-               }\r
-               if (isset($saisies[$cle]['saisies'])) // S'il s'agit d'un fieldset ou equivalent, verifier les sous-saisies\r
-                       $saisies[$cle]['saisies'] = saisies_verifier_afficher_si($saisies[$cle]['saisies'], $env);\r
-       }\r
-       return $saisies;\r
-}\r
-\r
-?>\r
+<?php
+
+/**
+ * Gestion de l'affichage des saisies.
+ *
+ * @return SPIP\Saisies\Afficher
+ **/
+
+// Sécurité
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
+
+/**
+ * Indique si une saisie peut être affichée.
+ *
+ * On s'appuie sur l'éventuelle clé "editable" du $champ.
+ * Si editable vaut :
+ *    - absent : le champ est éditable
+ *    - 1, le champ est éditable
+ *    - 0, le champ n'est pas éditable
+ *    - -1, le champ est éditable s'il y a du contenu dans le champ (l'environnement)
+ *         ou dans un de ses enfants (fieldsets)
+ *
+ * @param array $champ
+ *                                 Tableau de description de la saisie
+ * @param array $env
+ *                                 Environnement transmis à la saisie, certainement l'environnement du formulaire
+ * @param bool  $utiliser_editable
+ *                                 - false pour juste tester le cas -1
+ *
+ * @return bool
+ *              Retourne un booléen indiquant l'état éditable ou pas :
+ *              - true si la saisie est éditable (peut être affichée)
+ *              - false sinon
+ */
+function saisie_editable($champ, $env, $utiliser_editable = true) {
+       if ($utiliser_editable) {
+               // si le champ n'est pas éditable, on sort.
+               if (!isset($champ['editable'])) {
+                       return true;
+               }
+               $editable = $champ['editable'];
+
+               if ($editable > 0) {
+                       return true;
+               }
+               if ($editable == 0) {
+                       return false;
+               }
+       }
+
+       // cas -1
+       // name de la saisie
+       if (isset($champ['options']['nom'])) {
+               // si on a le name dans l'environnement, on le teste
+               $nom = $champ['options']['nom'];
+               if (isset($env[$nom])) {
+                       return $env[$nom] ? true : false;
+               }
+       }
+       // sinon, si on a des sous saisies
+       if (isset($champ['saisies']) and is_array($champ['saisies'])) {
+               foreach ($champ['saisies'] as $saisie) {
+                       if (saisie_editable($saisie, $env, false)) {
+                               return true;
+                       }
+               }
+       }
+
+       // aucun des paramètres demandés n'avait de contenu
+       return false;
+}
+
+/**
+ * Génère une saisie à partir d'un tableau la décrivant et de l'environnement.
+ *
+ * @param array $champ
+ *                     Description de la saisie.
+ *                     Le tableau doit être de la forme suivante :
+ *                     array(
+ *                     'saisie' => 'input',
+ *                     'options' => array(
+ *                     'nom' => 'le_name',
+ *                     'label' => 'Un titre plus joli',
+ *                     'obligatoire' => 'oui',
+ *                     'explication' => 'Remplissez ce champ en utilisant votre clavier.'
+ *                     )
+ *                     )
+ * @param array $env
+ *                     Environnement du formulaire
+ *                     Permet de savoir les valeurs actuelles des contenus des saisies,
+ *                     les erreurs eventuelles présentes...
+ *
+ * @return string
+ *                Code HTML des saisies de formulaire
+ */
+function saisies_generer_html($champ, $env = array()) {
+       // Si le parametre n'est pas bon, on genere du vide
+       if (!is_array($champ)) {
+               return '';
+       }
+
+       // Si la saisie n'est pas editable, on sort aussi.
+       if (!saisie_editable($champ, $env)) {
+               return '';
+       }
+
+       $contexte = array();
+
+       // On sélectionne le type de saisie
+       $contexte['type_saisie'] = $champ['saisie'];
+       // Identifiant unique de saisie, si present
+       if (isset($champ['identifiant'])) {
+               $contexte['id_saisie'] = $champ['identifiant'];
+       }
+
+       // Peut-être des transformations à faire sur les options textuelles
+       $options = isset($champ['options']) ? $champ['options'] : array();
+       foreach ($options as $option => $valeur) {
+               if ($option == 'datas') {
+                       // exploser une chaine datas en tableau (applique _T_ou_typo sur chaque valeur)
+                       $options[$option] = saisies_chaine2tableau($valeur);
+               } else {
+                       $options[$option] = _T_ou_typo($valeur, 'multi');
+               }
+       }
+
+       // compatibilité li_class > conteneur_class
+       if (!empty($options['li_class'])) {
+               $options['conteneur_class'] = $options['li_class'];
+       }
+
+       // On ajoute les options propres à la saisie
+       $contexte = array_merge($contexte, $options);
+
+       // Si env est définie dans les options ou qu'il y a des enfants, on ajoute tout l'environnement
+       if (isset($contexte['env']) or (isset($champ['saisies']) and is_array($champ['saisies']))) {
+               unset($contexte['env']);
+
+               // on sauve l'ancien environnement
+               // car les sous-saisies ne doivent pas être affectees
+               // par les modification sur l'environnement servant à generer la saisie mère
+               $contexte['_env'] = $env;
+
+               // À partir du moment où on passe tout l'environnement, il faut enlever certains éléments qui ne doivent absolument provenir que des options
+               unset($env['inserer_debut']);
+               unset($env['inserer_fin']);
+               $saisies_disponibles = saisies_lister_disponibles();
+               if (isset($saisies_disponibles[$contexte['type_saisie']]) and is_array($saisies_disponibles[$contexte['type_saisie']]['options'])) {
+                       $options_a_supprimer = saisies_lister_champs($saisies_disponibles[$contexte['type_saisie']]['options']);
+                       foreach ($options_a_supprimer as $option_a_supprimer) {
+                               unset($env[$option_a_supprimer]);
+                       }
+               }
+
+               $contexte = array_merge($env, $contexte);
+       }
+       // Sinon on ne sélectionne que quelques éléments importants
+       else {
+               // On récupère la liste des erreurs
+               $contexte['erreurs'] = $env['erreurs'];
+               // On récupère la langue de l'objet si existante
+               if (isset($env['langue'])) {
+                       $contexte['langue'] = $env['langue'];
+               }
+               // On ajoute toujours le bon self
+               $contexte['self'] = self();
+       }
+
+       // Dans tous les cas on récupère de l'environnement la valeur actuelle du champ
+       // Si le nom du champ est un tableau indexé, il faut parser !
+       if (isset($contexte['nom']) and preg_match('/([\w]+)((\[[\w]+\])+)/', $contexte['nom'], $separe)) {
+               $contexte['valeur'] = $env[$separe[1]];
+               preg_match_all('/\[([\w]+)\]/', $separe[2], $index);
+               // On va chercher au fond du tableau
+               foreach ($index[1] as $cle) {
+                       $contexte['valeur'] = isset($contexte['valeur'][$cle]) ? $contexte['valeur'][$cle] : null;
+               }
+       }
+       // Sinon la valeur est juste celle du nom si elle existe
+       elseif (isset($contexte['nom']) and isset($env[$contexte['nom']])) {
+               $contexte['valeur'] = $env[$contexte['nom']];
+       }
+       // Sinon rien
+       else {
+               $contexte['valeur'] = null;
+       }
+
+       // Si ya des enfants on les remonte dans le contexte
+       if (isset($champ['saisies']) and is_array($champ['saisies'])) {
+               $contexte['saisies'] = $champ['saisies'];
+       }
+
+       // On génère la saisie
+       return recuperer_fond(
+               'saisies/_base',
+               $contexte
+       );
+}
+
+/**
+ * Génère une vue d'une saisie à partir d'un tableau la décrivant.
+ *
+ * @see saisies_generer_html()
+ *
+ * @param array $saisie
+ *                               Tableau de description d'une saisie
+ * @param array $env
+ *                               L'environnement, contenant normalement la réponse à la saisie
+ * @param array $env_obligatoire
+ *                               ???
+ *
+ * @return string
+ *                Code HTML de la vue de la saisie
+ */
+function saisies_generer_vue($saisie, $env = array(), $env_obligatoire = array()) {
+       // Si le paramètre n'est pas bon, on génère du vide
+       if (!is_array($saisie)) {
+               return '';
+       }
+
+       $contexte = array();
+
+       // On sélectionne le type de saisie
+       $contexte['type_saisie'] = $saisie['saisie'];
+
+       // Peut-être des transformations à faire sur les options textuelles
+       $options = $saisie['options'];
+       foreach ($options as $option => $valeur) {
+               if ($option == 'datas') {
+                       // exploser une chaine datas en tableau (applique _T_ou_typo sur chaque valeur)
+                       $options[$option] = saisies_chaine2tableau($valeur);
+               } else {
+                       $options[$option] = _T_ou_typo($valeur, 'multi');
+               }
+       }
+
+       // On ajoute les options propres à la saisie
+       $contexte = array_merge($contexte, $options);
+
+       // Si env est définie dans les options ou qu'il y a des enfants, on ajoute tout l'environnement
+       if (isset($contexte['env']) or (isset($saisie['saisies']) and is_array($saisie['saisies']))) {
+               unset($contexte['env']);
+
+               // on sauve l'ancien environnement
+               // car les sous-saisies ne doivent pas être affectees
+               // par les modification sur l'environnement servant à generer la saisie mère
+               $contexte['_env'] = $env;
+
+               // À partir du moment où on passe tout l'environnement, il faut enlever
+               // certains éléments qui ne doivent absolument provenir que des options
+               $saisies_disponibles = saisies_lister_disponibles();
+
+               if (isset($saisies_disponibles[$contexte['type_saisie']]['options'])
+                       and is_array($saisies_disponibles[$contexte['type_saisie']]['options'])) {
+                       $options_a_supprimer = saisies_lister_champs($saisies_disponibles[$contexte['type_saisie']]['options']);
+                       foreach ($options_a_supprimer as $option_a_supprimer) {
+                               unset($env[$option_a_supprimer]);
+                       }
+               }
+
+               $contexte = array_merge($env, $contexte);
+       }
+
+       // Dans tous les cas on récupère de l'environnement la valeur actuelle du champ
+
+       // On regarde en priorité s'il y a un tableau listant toutes les valeurs
+       if ($env['valeurs'] and is_array($env['valeurs']) and isset($env['valeurs'][$contexte['nom']])) {
+               $contexte['valeur'] = $env['valeurs'][$contexte['nom']];
+       }
+       // Si le nom du champ est un tableau indexé, il faut parser !
+       elseif (preg_match('/([\w]+)((\[[\w]+\])+)/', $contexte['nom'], $separe)) {
+               $contexte['valeur'] = $env[$separe[1]];
+               preg_match_all('/\[([\w]+)\]/', $separe[2], $index);
+               // On va chercher au fond du tableau
+               foreach ($index[1] as $cle) {
+                       $contexte['valeur'] = $contexte['valeur'][$cle];
+               }
+       }
+       // Sinon la valeur est juste celle du nom
+       else {
+               // certains n'ont pas de nom (fieldset)
+               $contexte['valeur'] = isset($env[$contexte['nom']]) ? $env[$contexte['nom']] : '';
+       }
+
+       // Si ya des enfants on les remonte dans le contexte
+       if (isset($saisie['saisies']) and is_array($saisie['saisies'])) {
+               $contexte['saisies'] = $saisie['saisies'];
+       }
+
+       if (is_array($env_obligatoire)) {
+               $contexte = array_merge($contexte, $env_obligatoire);
+       }
+
+       // On génère la saisie
+       return recuperer_fond(
+               'saisies-vues/_base',
+               $contexte
+       );
+}
+
+/**
+ * Génère, à partir d'un tableau de saisie le code javascript ajouté à la fin de #GENERER_SAISIES
+ * pour produire un affichage conditionnel des saisies ayant une option afficher_si ou afficher_si_remplissage.
+ *
+ * @param array  $saisies
+ *                        Tableau de descriptions des saisies
+ * @param string $id_form
+ *                        Identifiant unique pour le formulaire
+ *
+ * @return text
+ *              Code javascript
+ */
+function saisies_generer_js_afficher_si($saisies, $id_form) {
+       $i = 0;
+       $saisies = saisies_lister_par_nom($saisies, true);
+       $code = '';
+       $code .= '(function($){';
+       $code .= '$(document).ready(function(){chargement=true;';
+       $code .= 'verifier_saisies_'.$id_form." = function(form){\n";
+       foreach ($saisies as $saisie) {
+               // on utilise comme selecteur l'identifiant de saisie en priorite s'il est connu
+                                       // parce que conteneur_class = 'tableau[nom][option]' ne fonctionne evidement pas
+                                       // lorsque le name est un tableau
+                                       if (isset($saisie['options']['afficher_si']) or isset($saisie['options']['afficher_si_remplissage'])) {
+                                               ++$i;
+                                               // retrouver la classe css probable
+                                               switch ($saisie['saisie']) {
+                                                       case 'fieldset':
+                                                               $class_li = 'fieldset_'.$saisie['options']['nom'];
+                                                               break;
+                                                       case 'explication':
+                                                               $class_li = 'explication_'.$saisie['options']['nom'];
+                                                               break;
+                                                       default:
+                                                               $class_li = 'editer_'.$saisie['options']['nom'];
+                                               }
+                                               $afficher_si = isset($saisie['options']['afficher_si']) ? $saisie['options']['afficher_si'] : '';
+                                               $afficher_si_remplissage = isset($saisie['options']['afficher_si_remplissage']) ? $saisie['options']['afficher_si_remplissage'] : '';
+                                               $condition = implode("\n", array_filter(array($afficher_si, $afficher_si_remplissage)));
+                                               // retrouver l'identifiant
+                                               $identifiant = '';
+                                               if (isset($saisie['identifiant']) and $saisie['identifiant']) {
+                                                       $identifiant = $saisie['identifiant'];
+                                               }
+                                               // On gère le cas @plugin:non_plugin@
+                                               preg_match_all('#@plugin:(.+)@#U', $condition, $matches);
+                                               foreach ($matches[1] as $plug) {
+                                                       if (defined('_DIR_PLUGIN_'.strtoupper($plug))) {
+                                                               $condition = preg_replace('#@plugin:'.$plug.'@#U', 'true', $condition);
+                                                       } else {
+                                                               $condition = preg_replace('#@plugin:'.$plug.'@#U', 'false', $condition);
+                                                       }
+                                               }
+                                               // On gère le cas @config:plugin:meta@ suivi d'un test
+                                               preg_match_all('#@config:(.+):(.+)@#U', $condition, $matches);
+                                               foreach ($matches[1] as $plugin) {
+                                                       $config = lire_config($plugin);
+                                                       $condition = preg_replace('#@config:'.$plugin.':'.$matches[2][0].'@#U', '"'.$config[$matches[2][0]].'"', $condition);
+                                               }
+                                               // On transforme en une condition valide
+                                               preg_match_all('#@(.+)@#U', $condition, $matches);
+                                               foreach ($matches[1] as $nom) {
+                                                       switch ($saisies[$nom]['saisie']) {
+                                                               case 'radio':
+                                                               case 'oui_non':
+                                                               case 'true_false':
+                                                                       $condition = preg_replace('#@'.preg_quote($nom).'@#U', '$(form).find("[name=\''.$nom.'\']:checked").val()', $condition);
+                                                                       break;
+                                                               case 'case':
+                                                                       $condition = preg_replace('#@'.preg_quote($nom).'@#U', '($(form).find(".checkbox[name=\''.$nom.'\']").is(":checked") ? $(form).find(".checkbox[name=\''.$nom.'\']").val() : "")', $condition);
+                                                                       break;
+                                                               case 'checkbox':
+                                                                       preg_match_all('#@(.+)@\s*==\s*"(.*)"$#U', $condition, $matches2);
+                                                                       foreach ($matches2[2] as $value) {
+                                                                               $condition = preg_replace('#@'.preg_quote($nom).'@#U', '($(form).find(".checkbox[name=\''.$nom.'[]\'][value='.$value.']").is(":checked") ? $(form).find(".checkbox[name=\''.$nom.'[]\'][value='.$value.']").val() : "")', $condition);
+                                                                       }
+                                                                       break;
+                                                               default:
+                                                                       $condition = preg_replace('#@'.preg_quote($nom).'@#U', '$(form).find("[name=\''.$nom.'\']").val()', $condition);
+                                                       }
+                                               }
+                                               if ($identifiant) {
+                                                       $sel = "[data-id='$identifiant']";
+                                               } else {
+                                                       $sel = ".$class_li";
+                                               }
+                                               $code .= "\tif (".$condition.') {$(form).find("'.$sel.'").show(400);} '."\n\t";
+                                               $code .= 'else {if (chargement==true) {$(form).find("'.$sel.'").hide(400).css("display","none");} else {$(form).find("'.$sel.'").hide(400);};} '."\n";
+                                       }
+       }
+       $code .= '};';
+       $code .= '$("#afficher_si_'.$id_form.'").parents("form").each(function(){verifier_saisies_'.$id_form.'(this);});';
+       $code .= '$("#afficher_si_'.$id_form.'").parents("form").change(function(){verifier_saisies_'.$id_form.'(this);});';
+       $code .= 'chargement=false;})';
+       $code .= '})(jQuery);';
+
+       return $i > 0 ? $code : '';
+}
+
+/**
+ * Lorsque l'on affiche les saisies (#VOIR_SAISIES), les saisies ayant une option afficher_si
+ * et dont les conditions ne sont pas remplies doivent être retirées du tableau de saisies.
+ *
+ * Cette fonction sert aussi lors de la vérification des saisies avec saisies_verifier().
+ * À ce moment là, les saisies non affichées sont retirées de _request
+ * (on passe leur valeur à NULL).
+ *
+ * @param array      $saisies
+ *                            Tableau de descriptions de saisies
+ * @param array|null $env
+ *                            Tableau d'environnement transmis dans inclure/voi_saisies.html,
+ *                            NULL si on doit rechercher dans _request (pour saisies_verifier()).
+ *
+ * @return array
+ *               Tableau de descriptions de saisies
+ */
+function saisies_verifier_afficher_si($saisies, $env = null) {
+       // eviter une erreur par maladresse d'appel :)
+       if (!is_array($saisies)) {
+               return array();
+       }
+       foreach ($saisies as $cle => $saisie) {
+               if (isset($saisie['options']['afficher_si'])) {
+                       $condition = $saisie['options']['afficher_si'];
+                       // On gère le cas @plugin:non_plugin@
+                       preg_match_all('#@plugin:(.+)@#U', $condition, $matches);
+                       foreach ($matches[1] as $plug) {
+                               if (defined('_DIR_PLUGIN_'.strtoupper($plug))) {
+                                       $condition = preg_replace('#@plugin:'.$plug.'@#U', 'true', $condition);
+                               } else {
+                                       $condition = preg_replace('#@plugin:'.$plug.'@#U', 'false', $condition);
+                               }
+                       }
+                       // On gère le cas @config:plugin:meta@ suivi d'un test
+                       preg_match_all('#@config:(.+):(.+)@#U', $condition, $matches);
+                       foreach ($matches[1] as $plugin) {
+                               $config = lire_config($plugin);
+                               $condition = preg_replace('#@config:'.$plugin.':'.$matches[2][0].'@#U', '"'.$config[$matches[2][0]].'"', $condition);
+                       }
+                       // On transforme en une condition valide
+                       if (is_null($env)) {
+                               $condition = preg_replace('#@(.+)@#U', '_request(\'$1\')', $condition);
+                       } else {
+                               $condition = preg_replace('#@(.+)@#U', '$env["valeurs"][\'$1\']', $condition);
+                       }
+                       eval('$ok = '.$condition.';');
+                       if (!$ok) {
+                               unset($saisies[$cle]);
+                               if (is_null($env)) {
+                                       set_request($saisie['options']['nom'], null);
+                               }
+                       }
+               }
+               if (isset($saisies[$cle]['saisies'])) { // S'il s'agit d'un fieldset ou equivalent, verifier les sous-saisies
+                       $saisies[$cle]['saisies'] = saisies_verifier_afficher_si($saisies[$cle]['saisies'], $env);
+               }
+       }
+
+       return $saisies;
+}
index 76892f7..6dc3b80 100644 (file)
@@ -1,24 +1,33 @@
 <?php
 
+/**
+ * Gestion de listes des saisies.
+ *
+ * @return SPIP\Saisies\Listes
+ **/
+
 // Sécurité
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
-/*
+/**
  * Prend la description complète du contenu d'un formulaire et retourne
  * les saisies "à plat" classées par identifiant unique.
  *
- * @param array $contenu Le contenu d'un formulaire
- * @param bool $avec_conteneur Indique si on renvoie aussi les saisies ayant des enfants, comme les fieldsets
+ * @param array $contenu        Le contenu d'un formulaire
+ * @param bool  $avec_conteneur Indique si on renvoie aussi les saisies ayant des enfants, comme les fieldsets
+ *
  * @return array Un tableau avec uniquement les saisies
  */
-function saisies_lister_par_identifiant($contenu, $avec_conteneur=true){
+function saisies_lister_par_identifiant($contenu, $avec_conteneur = true) {
        $saisies = array();
 
-       if (is_array($contenu)){
-               foreach ($contenu as $ligne){
+       if (is_array($contenu)) {
+               foreach ($contenu as $ligne) {
                        if (is_array($ligne)) {
                                $enfants_presents = (isset($ligne['saisies']) and is_array($ligne['saisies']));
-                               if (array_key_exists('saisie', $ligne) and (!$enfants_presents or $avec_conteneur)){
+                               if (array_key_exists('saisie', $ligne) and (!$enfants_presents or $avec_conteneur)) {
                                        $saisies[$ligne['identifiant']] = $ligne;
                                }
                                if ($enfants_presents) {
@@ -27,45 +36,47 @@ function saisies_lister_par_identifiant($contenu, $avec_conteneur=true){
                        }
                }
        }
-       
+
        return $saisies;
 }
 
-/*
+/**
  * Prend la description complète du contenu d'un formulaire et retourne
  * les saisies "à plat" classées par nom.
  *
- * @param array $contenu Le contenu d'un formulaire
- * @param bool $avec_conteneur Indique si on renvoie aussi les saisies ayant des enfants, comme les fieldset
+ * @param array $contenu        Le contenu d'un formulaire
+ * @param bool  $avec_conteneur Indique si on renvoie aussi les saisies ayant des enfants, comme les fieldset
+ *
  * @return array Un tableau avec uniquement les saisies
  */
-function saisies_lister_par_nom($contenu, $avec_conteneur=true){
+function saisies_lister_par_nom($contenu, $avec_conteneur = true) {
        $saisies = array();
-       
-       if (is_array($contenu)){
-               foreach ($contenu as $ligne){
-                       if (is_array($ligne)){
-                               if (array_key_exists('saisie', $ligne) and (!isset($ligne['saisies']) OR !is_array($ligne['saisies']) or $avec_conteneur)){
+
+       if (is_array($contenu)) {
+               foreach ($contenu as $ligne) {
+                       if (is_array($ligne)) {
+                               if (array_key_exists('saisie', $ligne) and (!isset($ligne['saisies']) or !is_array($ligne['saisies']) or $avec_conteneur) and isset($ligne['options'])) {
                                        $saisies[$ligne['options']['nom']] = $ligne;
                                }
-                               if (isset($ligne['saisies']) AND is_array($ligne['saisies'])){
+                               if (isset($ligne['saisies']) and is_array($ligne['saisies'])) {
                                        $saisies = array_merge($saisies, saisies_lister_par_nom($ligne['saisies']));
                                }
                        }
                }
        }
-       
+
        return $saisies;
 }
 
-/*
+/**
  * Liste les saisies ayant une option X
- * # saisies_lister_avec_option('sql', $saisies);
- *  
+ * # saisies_lister_avec_option('sql', $saisies);.
+ *
+ *
+ * @param String $option  Nom de l'option cherchée
+ * @param Array  $saisies Liste de saisies
+ * @param String $tri     tri par défaut des résultats (s'ils ne sont pas deja triés) ('nom', 'identifiant')
  *
- * @param String $option Nom de l'option cherchée
- * @param Array $saisies Liste de saisies
- * @param String $tri tri par défaut des résultats (s'ils ne sont pas deja triés) ('nom', 'identifiant')
  * @return liste de ces saisies triees par nom ayant une option X définie
  */
 function saisies_lister_avec_option($option, $saisies, $tri = 'nom') {
@@ -73,7 +84,7 @@ function saisies_lister_avec_option($option, $saisies, $tri = 'nom') {
        // tri par nom si ce n'est pas le cas
        $s = array_keys($saisies);
        if (is_int(array_shift($s))) {
-               $trier = 'saisies_lister_par_' . $tri;
+               $trier = 'saisies_lister_par_'.$tri;
                $saisies = $trier($saisies);
        }
        foreach ($saisies as $nom_ou_id => $saisie) {
@@ -85,110 +96,121 @@ function saisies_lister_avec_option($option, $saisies, $tri = 'nom') {
        return $saisies_option;
 }
 
-/*
- * Liste les saisies ayant une definition SQL
+/**
+ * Liste les saisies ayant une definition SQL.
+ *
+ * @param Array  $saisies liste de saisies
+ * @param String $tri     tri par défaut des résultats (s'ils ne sont pas deja triés) ('nom', 'identifiant')
  *
- * @param Array $saisies liste de saisies
- * @param String $tri tri par défaut des résultats (s'ils ne sont pas deja triés) ('nom', 'identifiant')
  * @return liste de ces saisies triees par nom ayant une option sql définie
  */
 function saisies_lister_avec_sql($saisies, $tri = 'nom') {
        return saisies_lister_avec_option('sql', $saisies, $tri);
 }
 
-/*
+/**
  * Prend la description complète du contenu d'un formulaire et retourne
  * les saisies "à plat" classées par type de saisie.
- * $saisie['input']['input_1'] = $saisie
+ * $saisie['input']['input_1'] = $saisie.
  *
  * @param array $contenu Le contenu d'un formulaire
+ *
  * @return array Un tableau avec uniquement les saisies
  */
 function saisies_lister_par_type($contenu) {
        $saisies = array();
-       
-       if (is_array($contenu)){
-               foreach ($contenu as $ligne){
-                       if (is_array($ligne)){
-                               if (array_key_exists('saisie', $ligne) and (!is_array($ligne['saisies']))){
+
+       if (is_array($contenu)) {
+               foreach ($contenu as $ligne) {
+                       if (is_array($ligne)) {
+                               if (array_key_exists('saisie', $ligne) and (!is_array($ligne['saisies']))) {
                                        $saisies[ $ligne['saisie'] ][ $ligne['options']['nom'] ] = $ligne;
                                }
-                               if (is_array($ligne['saisies'])){
-                                       $saisies = array_merge($saisies, saisies_lister_par_type($ligne['saisies']));
+                               if (is_array($ligne['saisies'])) {
+                                       $saisies = array_merge_recursive($saisies, saisies_lister_par_type($ligne['saisies']));
                                }
                        }
                }
        }
-       
+
        return $saisies;
 }
 
-/*
+/**
  * Prend la description complète du contenu d'un formulaire et retourne
  * une liste des noms des champs du formulaire.
  *
- * @param array $contenu Le contenu d'un formulaire
+ * @param array $contenu        Le contenu d'un formulaire
+ * @param bool  $avec_conteneur Indique si on renvoie aussi les saisies ayant des enfants, comme les fieldset
+ *
  * @return array Un tableau listant les noms des champs
  */
-function saisies_lister_champs($contenu, $avec_conteneur=true){
+function saisies_lister_champs($contenu, $avec_conteneur = true) {
        $saisies = saisies_lister_par_nom($contenu, $avec_conteneur);
+
        return array_keys($saisies);
 }
 
-/*
+/**
  * A utiliser dans une fonction charger d'un formulaire CVT,
  * cette fonction renvoie le tableau de contexte correspondant
- * de la forme $contexte['nom_champ'] = ''
+ * de la forme $contexte['nom_champ'] = ''.
  *
  * @param array $contenu Le contenu d'un formulaire (un tableau de saisies)
+ *
  * @return array Un tableau de contexte
  */
 function saisies_charger_champs($contenu) {
        // array_fill_keys est disponible uniquement avec PHP >= 5.2.0
        // return array_fill_keys(saisies_lister_champs($contenu, false), '');
        $champs = array();
-       foreach (saisies_lister_champs($contenu, false) as $champ)
+       foreach (saisies_lister_champs($contenu, false) as $champ) {
                $champs[$champ] = '';
+       }
+
        return $champs;
 }
 
-/*
+/**
  * Prend la description complète du contenu d'un formulaire et retourne
  * une liste des valeurs par défaut des champs du formulaire.
  *
  * @param array $contenu Le contenu d'un formulaire
+ *
  * @return array Un tableau renvoyant la valeur par défaut de chaque champs
  */
-function saisies_lister_valeurs_defaut($contenu){
+function saisies_lister_valeurs_defaut($contenu) {
        $contenu = saisies_lister_par_nom($contenu, false);
        $defauts = array();
-       foreach ($contenu as $nom => $saisie){
+       foreach ($contenu as $nom => $saisie) {
                // Si le nom du champ est un tableau indexé, il faut parser !
-               if (preg_match('/([\w]+)((\[[\w]+\])+)/', $nom, $separe)){
+               if (preg_match('/([\w]+)((\[[\w]+\])+)/', $nom, $separe)) {
                        $nom = $separe[1];
                        // Dans ce cas on ne récupère que le nom, la valeur par défaut du tableau devra être renseigné autre part
                        $defauts[$nom] = array();
-               }
-               else{
+               } else {
                        $defauts[$nom] = isset($saisie['options']['defaut']) ? $saisie['options']['defaut'] : '';
                }
        }
+
        return $defauts;
 }
 
-/*
- * Compare deux tableaux de saisies pour connaitre les différences
- * @param array $saisies_anciennes Un tableau décrivant des saisies
- * @param array $saisies_nouvelles Un autre tableau décrivant des saisies
- * @param bool $avec_conteneur Indique si on veut prendre en compte dans la comparaison les conteneurs comme les fieldsets
- * @param string $tri Comparer selon quel tri ? 'nom' / 'identifiant'
+/**
+ * Compare deux tableaux de saisies pour connaitre les différences.
+ *
+ * @param array  $saisies_anciennes Un tableau décrivant des saisies
+ * @param array  $saisies_nouvelles Un autre tableau décrivant des saisies
+ * @param bool   $avec_conteneur    Indique si on veut prendre en compte dans la comparaison les conteneurs comme les fieldsets
+ * @param string $tri               Comparer selon quel tri ? 'nom' / 'identifiant'
+ *
  * @return array Retourne le tableau des saisies supprimées, ajoutées et modifiées
  */
-function saisies_comparer($saisies_anciennes, $saisies_nouvelles, $avec_conteneur=true, $tri = 'nom') {
+function saisies_comparer($saisies_anciennes, $saisies_nouvelles, $avec_conteneur = true, $tri = 'nom') {
        $trier = "saisies_lister_par_$tri";
        $saisies_anciennes = $trier($saisies_anciennes, $avec_conteneur);
        $saisies_nouvelles = $trier($saisies_nouvelles, $avec_conteneur);
-       
+
        // Les saisies supprimées sont celles qui restent dans les anciennes quand on a enlevé toutes les nouvelles
        $saisies_supprimees = array_diff_key($saisies_anciennes, $saisies_nouvelles);
        // Les saisies ajoutées, c'est le contraire
@@ -200,51 +222,61 @@ function saisies_comparer($saisies_anciennes, $saisies_nouvelles, $avec_conteneu
        #$saisies_modifiees = array_udiff($saisies_nouvelles, $saisies_restantes, 'saisies_comparer_rappel');
        // Et enfin les saisies qui ont le même nom et la même valeur
        $saisies_identiques = array_diff_key($saisies_restantes, $saisies_modifiees);
-       
+
        return array(
                'supprimees' => $saisies_supprimees,
                'ajoutees' => $saisies_ajoutees,
                'modifiees' => $saisies_modifiees,
-               'identiques' => $saisies_identiques
+               'identiques' => $saisies_identiques,
        );
 }
 
-/*
- * Compare deux saisies et indique si elles sont égales ou pas
+/**
+ * Compare deux saisies et indique si elles sont égales ou pas.
+ *
+ * @param array $a Une description de saisie
+ * @param array $b Une autre description de saisie
+ *
+ * @return int Retourne 0 si les saisies sont identiques, 1 sinon.
  */
-function saisies_comparer_rappel($a, $b){
-       if ($a === $b) return 0;
-       else return 1;
+function saisies_comparer_rappel($a, $b) {
+       if ($a === $b) {
+               return 0;
+       } else {
+               return 1;
+       }
 }
 
-/*
+/**
  * Compare deux tableaux de saisies pour connaitre les différences
- * en s'appuyant sur les identifiants de saisies
+ * en s'appuyant sur les identifiants de saisies.
  *
  * @see saisies_comparer()
+ *
  * @param array $saisies_anciennes Un tableau décrivant des saisies
  * @param array $saisies_nouvelles Un autre tableau décrivant des saisies
- * @param bool $avec_conteneur Indique si on veut prendre en compte dans la comparaison les conteneurs comme les fieldsets
+ * @param bool  $avec_conteneur    Indique si on veut prendre en compte dans la comparaison les conteneurs comme les fieldsets
+ *
  * @return array Retourne le tableau des saisies supprimées, ajoutées et modifiées
  */
-function saisies_comparer_par_identifiant($saisies_anciennes, $saisies_nouvelles, $avec_conteneur=true) {
+function saisies_comparer_par_identifiant($saisies_anciennes, $saisies_nouvelles, $avec_conteneur = true) {
        return saisies_comparer($saisies_anciennes, $saisies_nouvelles, $avec_conteneur, $tri = 'identifiant');
 }
 
-/*
- * Liste toutes les saisies configurables (ayant une description)
+/**
+ * Liste toutes les saisies configurables (ayant une description).
  *
  * @return array Un tableau listant des saisies et leurs options
  */
-function saisies_lister_disponibles(){
+function saisies_lister_disponibles($saisies_repertoire = "saisies") {
        static $saisies = null;
-       
-       if (is_null($saisies)){
+
+       if (is_null($saisies)) {
                $saisies = array();
-               $liste = find_all_in_path('saisies/', '.+[.]yaml$');
-               
-               if (count($liste)){
-                       foreach ($liste as $fichier=>$chemin){
+               $liste = find_all_in_path("$saisies_repertoire/", '.+[.]yaml$');
+
+               if (count($liste)) {
+                       foreach ($liste as $fichier => $chemin) {
                                $type_saisie = preg_replace(',[.]yaml$,i', '', $fichier);
                                $dossier = str_replace($fichier, '', $chemin);
                                // On ne garde que les saisies qui ont bien le HTML avec !
@@ -252,73 +284,101 @@ function saisies_lister_disponibles(){
                                        and (
                                                is_array($saisie = saisies_charger_infos($type_saisie))
                                        )
-                               ){
+                               ) {
                                        $saisies[$type_saisie] = $saisie;
                                }
                        }
                }
        }
-       
+
        return $saisies;
 }
 
-/*
- * Lister les saisies existantes ayant une définition SQL 
+/**
+ * Liste tous les groupes de saisies configurables (ayant une description).
  *
  * @return array Un tableau listant des saisies et leurs options
  */
-function saisies_lister_disponibles_sql() {
+function saisies_groupes_lister_disponibles($saisies_repertoire = "saisies") {
+       static $saisies = null;
+
+       if (is_null($saisies)) {
+               $saisies = array();
+               $liste = find_all_in_path("$saisies_repertoire/", '.+[.]yaml$');
+
+               if (count($liste)) {
+                       foreach ($liste as $fichier => $chemin) {
+                               $type_saisie = preg_replace(',[.]yaml$,i', '', $fichier);
+                               $dossier = str_replace($fichier, '', $chemin);
+                               // On ne garde que les saisies qui ont bien le HTML avec !
+                               if (is_array($saisie = saisies_charger_infos($type_saisie,$saisies_repertoire))) {
+                                       $saisies[$type_saisie] = $saisie;
+                               }
+                       }
+               }
+       }
+       return $saisies;
+}
+
+/**
+ * Lister les saisies existantes ayant une définition SQL.
+ *
+ * @return array Un tableau listant des saisies et leurs options
+ */
+function saisies_lister_disponibles_sql($saisies_repertoire = "saisies") {
        $saisies = array();
-       $saisies_disponibles = saisies_lister_disponibles();
-       foreach ($saisies_disponibles as $type=>$saisie) {
+       $saisies_disponibles = saisies_lister_disponibles($saisies_repertoire);
+       foreach ($saisies_disponibles as $type => $saisie) {
                if (isset($saisie['defaut']['options']['sql']) and $saisie['defaut']['options']['sql']) {
                        $saisies[$type] = $saisie;
                }
        }
+
        return $saisies;
 }
 
-/*
- * Charger les informations contenues dans le YAML d'une saisie
+/**
+ * Charger les informations contenues dans le YAML d'une saisie.
  *
  * @param string $type_saisie Le type de la saisie
+ *
  * @return array Un tableau contenant le YAML décodé
  */
-function saisies_charger_infos($type_saisie){
-       if(defined('_DIR_PLUGIN_YAML')){
+function saisies_charger_infos($type_saisie,$saisies_repertoire = "saisies") {
+       if (defined('_DIR_PLUGIN_YAML')) {
                include_spip('inc/yaml');
-               $fichier = find_in_path("saisies/$type_saisie.yaml");
+               $fichier = find_in_path("$saisies_repertoire/$type_saisie.yaml");
                $saisie = yaml_decode_file($fichier);
-               if (is_array($saisie)){
-                       $saisie['titre'] = (isset($saisie['titre']) AND $saisie['titre'])
+               if (is_array($saisie)) {
+                       $saisie['titre'] = (isset($saisie['titre']) and $saisie['titre'])
                                ? _T_ou_typo($saisie['titre']) : $type_saisie;
-                       $saisie['description'] = (isset($saisie['description']) AND $saisie['description'])
+                       $saisie['description'] = (isset($saisie['description']) and $saisie['description'])
                                ? _T_ou_typo($saisie['description']) : '';
-                       $saisie['icone'] = (isset($saisie['icone']) AND $saisie['icone'])
+                       $saisie['icone'] = (isset($saisie['icone']) and $saisie['icone'])
                                ? find_in_path($saisie['icone']) : '';
                }
-       }else
+       } else {
                $saisie = array();
+       }
+
        return $saisie;
 }
 
-/*
- * Quelles sont les saisies qui se débrouillent toutes seules, sans le _base commun
+/**
+ * Quelles sont les saisies qui se débrouillent toutes seules, sans le _base commun.
  *
  * @return array Retourne un tableau contenant les types de saisies qui ne doivent pas utiliser le _base.html commun
  */
-function saisies_autonomes(){
+function saisies_autonomes() {
        $saisies_autonomes = pipeline(
                'saisies_autonomes',
                array(
                        'fieldset',
                        'hidden',
-                       'destinataires', 
-                       'explication'
+                       'destinataires',
+                       'explication',
                )
        );
-       
+
        return $saisies_autonomes;
 }
-
-?>
index 9ccffda..e498390 100644 (file)
 <?php
 
 /**
- * Gestion de l'affichage des saisies
+ * Gestion de l'affichage des saisies.
  *
  * @return SPIP\Saisies\Manipuler
-**/
+ **/
 
 // Sécurité
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 /**
- * Supprimer une saisie dont on donne l'identifiant, le nom ou le chemin
+ * Supprimer une saisie dont on donne l'identifiant, le nom ou le chemin.
  *
- * @param array $saisies
- *     Tableau des descriptions de saisies
+ * @param array        $saisies             Tableau des descriptions de saisies
  * @param string|array $id_ou_nom_ou_chemin
  *     L'identifiant unique
  *     ou le nom de la saisie à supprimer
  *     ou son chemin sous forme d'une liste de clés
+ *
  * @return array
- *     Tableau modifié décrivant les saisies
+ *               Tableau modifié décrivant les saisies
  */
-function saisies_supprimer($saisies, $id_ou_nom_ou_chemin){
+function saisies_supprimer($saisies, $id_ou_nom_ou_chemin) {
        // Si la saisie n'existe pas, on ne fait rien
-       if ($chemin = saisies_chercher($saisies, $id_ou_nom_ou_chemin, true)){
+       if ($chemin = saisies_chercher($saisies, $id_ou_nom_ou_chemin, true)) {
                // La position finale de la saisie
                $position = array_pop($chemin);
-       
+
                // On va chercher le parent par référence pour pouvoir le modifier
-               $parent =$saisies;
-               foreach($chemin as $cle){
-                       $parent =$parent[$cle];
+               $parent = &$saisies;
+               foreach ($chemin as $cle) {
+                       $parent = &$parent[$cle];
                }
-               
+
                // On supprime et réordonne
                unset($parent[$position]);
                $parent = array_values($parent);
        }
-       
+
        return $saisies;
 }
 
 /**
- * Insère une saisie à une position donnée
+ * Insère une saisie à une position donnée.
  *
- * @param array $saisies
- *     Tableau des descriptions de saisies
- * @param array $saisie
- *     Description de la saisie à insérer
+ * @param array $saisies     Tableau des descriptions de saisies
+ * @param array $saisie     Description de la saisie à insérer
  * @param array $chemin
  *     Position complète où insérer la saisie.
  *     En absence, insère la saisie à la fin.
+ *
  * @return array
  *     Tableau des saisies complété de la saisie insérée
  */
-function saisies_inserer($saisies, $saisie, $chemin=array()){
+function saisies_inserer($saisies, $saisie, $chemin = array()) {
        // On vérifie quand même que ce qu'on veut insérer est correct
-       if ($saisie['saisie'] and $saisie['options']['nom']){
+       if ($saisie['saisie'] and $saisie['options']['nom']) {
                // ajouter un identifiant
                $saisie = saisie_identifier($saisie);
-               
+
                // Par défaut le parent c'est la racine
-               $parent =$saisies;
+               $parent = &$saisies;
                // S'il n'y a pas de position, on va insérer à la fin du formulaire
-               if (!$chemin){
+               if (!$chemin) {
                        $position = count($parent);
-               }
-               elseif (is_array($chemin)){
+               } elseif (is_array($chemin)) {
                        $position = array_pop($chemin);
-                       foreach ($chemin as $cle){
+                       foreach ($chemin as $cle) {
                                // Si la clé est un conteneur de saisies "saisies" et qu'elle n'existe pas encore, on la crée
-                               if ($cle == 'saisies' and !isset($parent[$cle]))
+                               if ($cle == 'saisies' and !isset($parent[$cle])) {
                                        $parent[$cle] = array();
-                               $parent =& $parent[$cle];
+                               }
+                               $parent = &$parent[$cle];
                        }
                        // On vérifie maintenant que la position est cohérente avec le parent
-                       if ($position < 0) $position = 0;
-                       elseif ($position > count($parent)) $position = count($parent);
+                       if ($position < 0) {
+                               $position = 0;
+                       } elseif ($position > count($parent)) {
+                               $position = count($parent);
+                       }
                }
                // Et enfin on insère
                array_splice($parent, $position, 0, array($saisie));
        }
-       
+
        return $saisies;
 }
 
-/*
+/**
  * Duplique une saisie (ou groupe de saisies)
  * en placant la copie à la suite de la saisie d'origine.
- * Modifie automatiquement les identifiants des saisies
+ * Modifie automatiquement les identifiants des saisies.
  *
- * @param array $saisies Un tableau décrivant les saisies
+ * @param array        $saisies             Un tableau décrivant les saisies
  * @param unknown_type $id_ou_nom_ou_chemin L'identifiant unique ou le nom ou le chemin de la saisie a dupliquer
+ *
  * @return array Retourne le tableau modifié des saisies
  */
-function saisies_dupliquer($saisies, $id_ou_nom_ou_chemin){
+function saisies_dupliquer($saisies, $id_ou_nom_ou_chemin) {
        // On récupère le contenu de la saisie à déplacer
        $saisie = saisies_chercher($saisies, $id_ou_nom_ou_chemin);
        if ($saisie) {
@@ -102,98 +107,101 @@ function saisies_dupliquer($saisies, $id_ou_nom_ou_chemin){
                // insertion apres quoi ?
                $chemin_validation = saisies_chercher($saisies, $id_ou_nom_ou_chemin, true);
                // 1 de plus pour mettre APRES le champ trouve
-               $chemin_validation[count($chemin_validation)-1]++;
+               ++$chemin_validation[count($chemin_validation) - 1];
                // On ajoute "copie" après le label du champs
                $clone['options']['label'] .= ' '._T('saisies:construire_action_dupliquer_copie');
 
                // Création de nouveau identifiants pour le clone
                $clone = saisie_identifier($clone, true);
-               
+
                $saisies = saisies_inserer($saisies, $clone, $chemin_validation);
        }
 
        return $saisies;
 }
 
-/*
- * Déplace une saisie existante autre part
+/**
+ * Déplace une saisie existante autre part.
  *
- * @param array $saisies Un tableau décrivant les saisies
+ * @param array        $saisies             Un tableau décrivant les saisies
  * @param unknown_type $id_ou_nom_ou_chemin L'identifiant unique ou le nom ou le chemin de la saisie à déplacer
- * @param string $ou Le nom de la saisie devant laquelle on déplacera OU le nom d'un conteneur entre crochets [conteneur]
+ * @param string       $ou                  Le nom de la saisie devant laquelle on déplacera OU le nom d'un conteneur entre crochets [conteneur]
+ *
  * @return array Retourne le tableau modifié des saisies
  */
-function saisies_deplacer($saisies, $id_ou_nom_ou_chemin, $ou){
+function saisies_deplacer($saisies, $id_ou_nom_ou_chemin, $ou) {
        // On récupère le contenu de la saisie à déplacer
        $saisie = saisies_chercher($saisies, $id_ou_nom_ou_chemin);
 
        // Si on l'a bien trouvé
-       if ($saisie){
+       if ($saisie) {
                // On cherche l'endroit où la déplacer
                // Si $ou est vide, c'est à la fin de la racine
-               if (!$ou){
+               if (!$ou) {
                        $saisies = saisies_supprimer($saisies, $id_ou_nom_ou_chemin);
                        $chemin = array(count($saisies));
                }
                // Si l'endroit est entre crochet, c'est un conteneur
-               elseif (preg_match('/^\[(@?[\w]*)\]$/', $ou, $match)){
+               elseif (preg_match('/^\[(@?[\w]*)\]$/', $ou, $match)) {
                        $parent = $match[1];
                        // Si dans les crochets il n'y a rien, on met à la fin du formulaire
-                       if (!$parent){
+                       if (!$parent) {
                                $saisies = saisies_supprimer($saisies, $id_ou_nom_ou_chemin);
                                $chemin = array(count($saisies));
                        }
                        // Sinon on vérifie que ce conteneur existe
-                       elseif (saisies_chercher($saisies, $parent, true)){
+                       elseif (saisies_chercher($saisies, $parent, true)) {
                                // S'il existe on supprime la saisie et on recherche la nouvelle position
                                $saisies = saisies_supprimer($saisies, $id_ou_nom_ou_chemin);
                                $parent = saisies_chercher($saisies, $parent, true);
                                $chemin = array_merge($parent, array('saisies', 1000000));
-                       }
-                       else
+                       } else {
                                $chemin = false;
+                       }
                }
                // Sinon ça sera devant un champ
-               else{
+               else {
                        // On vérifie que le champ existe
-                       if (saisies_chercher($saisies, $ou, true)){
+                       if (saisies_chercher($saisies, $ou, true)) {
                                // S'il existe on supprime la saisie
                                $saisies = saisies_supprimer($saisies, $id_ou_nom_ou_chemin);
                                // Et on recherche la nouvelle position qui n'est plus forcément la même maintenant qu'on a supprimé une saisie
                                $chemin = saisies_chercher($saisies, $ou, true);
-                       }
-                       else
+                       } else {
                                $chemin = false;
+                       }
                }
-               
+
                // Si seulement on a bien trouvé un nouvel endroit où la placer, alors on déplace
-               if ($chemin)
+               if ($chemin) {
                        $saisies = saisies_inserer($saisies, $saisie, $chemin);
+               }
        }
-       
+
        return $saisies;
 }
 
-/*
- * Modifie une saisie
+/**
+ * Modifie une saisie.
  *
- * @param array $saisies Un tableau décrivant les saisies
+ * @param array        $saisies             Un tableau décrivant les saisies
  * @param unknown_type $id_ou_nom_ou_chemin L'identifiant unique ou le nom ou le chemin de la saisie à modifier
- * @param array $modifs Le tableau des modifications à apporter à la saisie
+ * @param array        $modifs              Le tableau des modifications à apporter à la saisie
+ *
  * @return Retourne le tableau décrivant les saisies, mais modifié
  */
-function saisies_modifier($saisies, $id_ou_nom_ou_chemin, $modifs){
+function saisies_modifier($saisies, $id_ou_nom_ou_chemin, $modifs) {
        $chemin = saisies_chercher($saisies, $id_ou_nom_ou_chemin, true);
        $position = array_pop($chemin);
-       $parent =$saisies;
-       foreach ($chemin as $cle){
-               $parent =$parent[$cle];
+       $parent = &$saisies;
+       foreach ($chemin as $cle) {
+               $parent = &$parent[$cle];
        }
-       
+
        // On récupère le type tel quel
        $modifs['saisie'] = $parent[$position]['saisie'];
        // On récupère le nom s'il n'y est pas
-       if (!isset($modifs['options']['nom'])){
+       if (!isset($modifs['options']['nom'])) {
                $modifs['options']['nom'] = $parent[$position]['options']['nom'];
        }
        // On récupère les enfants tels quels s'il n'y a pas des enfants dans la modif
@@ -201,7 +209,7 @@ function saisies_modifier($saisies, $id_ou_nom_ou_chemin, $modifs){
                !isset($modifs['saisies'])
                and isset($parent[$position]['saisies'])
                and is_array($parent[$position]['saisies'])
-       ){
+       ) {
                $modifs['saisies'] = $parent[$position]['saisies'];
        }
 
@@ -211,38 +219,35 @@ function saisies_modifier($saisies, $id_ou_nom_ou_chemin, $modifs){
                $modifs['saisie'] = $type;
                unset($modifs['options']['nouveau_type_saisie']);
        }
-                       
+
        // On remplace tout
        $parent[$position] = $modifs;
-       
+
        // Cette méthode ne marche pas trop
        //$parent[$position] = array_replace_recursive($parent[$position], $modifs);
-       
+
        return $saisies;
 }
 
 /**
- * Transforme tous les noms du formulaire avec un preg_replace
+ * Transforme tous les noms du formulaire avec un preg_replace.
  *
- * @param array $saisies
- *     Un tableau décrivant les saisies
- * @param string $masque
- *     Ce que l'on doit chercher dans le nom
- * @param string $remplacement
- *     Ce par quoi on doit remplacer
- * @return array
- *     Retourne le tableau modifié des saisies
+ * @param array  $saisies      Un tableau décrivant les saisies
+ * @param string $masque       Ce que l'on doit chercher dans le nom
+ * @param string $remplacement Ce par quoi on doit remplacer
+ *
+ * @return array               Retourne le tableau modifié des saisies
  */
-function saisies_transformer_noms($saisies, $masque, $remplacement){
-       if (is_array($saisies)){
-               foreach ($saisies as $cle => $saisie){
+function saisies_transformer_noms($saisies, $masque, $remplacement) {
+       if (is_array($saisies)) {
+               foreach ($saisies as $cle => $saisie) {
                        $saisies[$cle]['options']['nom'] = preg_replace($masque, $remplacement, $saisie['options']['nom']);
                        if (isset($saisie['saisies']) and is_array($saisie['saisies'])) {
                                $saisies[$cle]['saisies'] = saisies_transformer_noms($saisie['saisies'], $masque, $remplacement);
                        }
                }
        }
-       
+
        return $saisies;
 }
 
@@ -250,17 +255,15 @@ function saisies_transformer_noms($saisies, $masque, $remplacement){
  * Transforme les noms d'une liste de saisies pour qu'ils soient
  * uniques dans le formulaire donné.
  *
- * @param array $formulaire
- *     Le formulaire à analyser 
- * @param array $saisies
- *     Un tableau décrivant les saisies.
+ * @param array $formulaire  Le formulaire à analyser
+ * @param array $saisies     Un tableau décrivant les saisies.
+ *
  * @return array
  *     Retourne le tableau modifié des saisies
  */
-function saisies_transformer_noms_auto($formulaire, $saisies){
-
-       if (is_array($saisies)){
-               foreach ($saisies as $cle => $saisie){
+function saisies_transformer_noms_auto($formulaire, $saisies) {
+       if (is_array($saisies)) {
+               foreach ($saisies as $cle => $saisie) {
                        $saisies[$cle]['options']['nom'] = saisies_generer_nom($formulaire, $saisie['saisie']);
                        // il faut prendre en compte dans $formulaire les saisies modifiees
                        // sinon on aurait potentiellement 2 champs successifs avec le meme nom.
@@ -268,36 +271,37 @@ function saisies_transformer_noms_auto($formulaire, $saisies){
                        $new = $saisies[$cle];
                        unset($new['saisies']);
                        $formulaire[] = $new;
-                       
-                       if (is_array($saisie['saisies']))
+
+                       if (is_array($saisie['saisies'])) {
                                $saisies[$cle]['saisies'] = saisies_transformer_noms_auto($formulaire, $saisie['saisies']);
+                       }
                }
        }
 
        return $saisies;
 }
 
-/*
- * Insère du HTML au début ou à la fin d'une saisie
+/**
+ * Insère du HTML au début ou à la fin d'une saisie.
  *
- * @param array $saisie La description d'une seule saisie
- * @param string $insertion Du code HTML à insérer dans la saisie 
- * @param string $ou L'endroit où insérer le HTML : "debut" ou "fin"
- * @return array Retourne la description de la saisie modifiée
+ * @param array  $saisie    La description d'une seule saisie
+ * @param string $insertion Du code HTML à insérer dans la saisie
+ * @param string $ou        L'endroit où insérer le HTML : "debut" ou "fin"
+ *
+ * @return array            Retourne la description de la saisie modifiée
  */
-function saisies_inserer_html($saisie, $insertion, $ou='fin'){
-       if (!in_array($ou, array('debut', 'fin')))
+function saisies_inserer_html($saisie, $insertion, $ou = 'fin') {
+       if (!in_array($ou, array('debut', 'fin'))) {
                $ou = 'fin';
-       
+       }
+
        if ($ou == 'debut') {
                $saisie['options']['inserer_debut'] =
-                       $insertion . (isset($saisie['options']['inserer_debut']) ? $saisie['options']['inserer_debut'] : '');
+                       $insertion.(isset($saisie['options']['inserer_debut']) ? $saisie['options']['inserer_debut'] : '');
        } elseif ($ou == 'fin') {
                $saisie['options']['inserer_fin'] =
-                       (isset($saisie['options']['inserer_fin']) ? $saisie['options']['inserer_fin'] : '') . $insertion;
+                       (isset($saisie['options']['inserer_fin']) ? $saisie['options']['inserer_fin'] : '').$insertion;
        }
-       
+
        return $saisie;
 }
-
-?>
index 5285b93..da20293 100644 (file)
@@ -35,4 +35,3 @@ function construire_configuration_saisie($saisie, $avec_nom='non'){
        return $configuration_saisie;
 }
 
-?>
diff --git a/www/plugins/saisies/inclure/fieldset_legend.html b/www/plugins/saisies/inclure/fieldset_legend.html
new file mode 100644 (file)
index 0000000..2b47f93
--- /dev/null
@@ -0,0 +1,8 @@
+[(#ENV{icone}|oui)
+[(#REM) en SPIP 3 on cherche avant tout dans le chemin des images
+]#SET{icone,''}[(#SPIP_VERSION|version_compare{2.9, >}|oui)#SET{icone,#CHEMIN_IMAGE{#ENV{icone}}]
+[<img src="(#GET{icone}|sinon{#CHEMIN{#ENV{icone}}}
+                                        |image_reduire{#ENV{taille_icone}|sinon{16}}
+                                        |extraire_attribut{src})" alt="#ENV*{label}" /> ]
+]
+<span>#ENV*{label}</span>
index 6866cbe..d684086 100644 (file)
@@ -1,7 +1,7 @@
 [(#REM) Ajout d'un marqueur unique pour identifier le formulaire]\r
 [(#SET{id_unique,#EVAL{'rand();'}})]\r
-<li id="afficher_si_#GET{id_unique}" style="display:none;" >\r
+\r
+<[(#VAL{li}|saisie_balise_structure_formulaire)] id="afficher_si_#GET{id_unique}" style="display:none;" >\r
 <script type="text/javascript">// <![CDATA[\r
 [(#ENV**{saisies}|saisies_generer_js_afficher_si{#GET{id_unique}})]\r
- //]]></script>\r
- </li>
\ No newline at end of file
+ //]]></script> </[(#VAL{li}|saisie_balise_structure_formulaire)]>\r
index 4f3363b..c3b6061 100644 (file)
@@ -14,11 +14,11 @@ Sauter à : <a href="#liste_saisies">Toutes les saisies</a>, <a href="#liste_opt
        </tr>
 </thead>
 <tbody>
-       <BOUCLE_options_tableau(POUR){tableau #ENV{options}}>
+       <BOUCLE_options_tableau(DATA){source tableau, #ENV{options}}>
        <tr class="tr_liste [(#COMPTEUR_BOUCLE|alterner{row_odd,row_even})]">
                <th><a href="#option_#CLE">#CLE</a></th>
-               <BOUCLE_saisies_tableau(POUR){tableau #ENV{saisies}}>
-               <td>[(#CLE|in_array{[(#_options_tableau:VALEUR|table_valeur{utilisee_par})]}|?{'X','-'})]</td>
+               <BOUCLE_saisies_tableau(DATA){source tableau, #ENV{saisies}}>
+               <td>[(#CLE|in_array{#_options_tableau:UTILISEE_PAR}|?{'X','-'})]</td>
                </BOUCLE_saisies_tableau>
        </tr>
        </BOUCLE_options_tableau>
@@ -28,35 +28,35 @@ Sauter à : <a href="#liste_saisies">Toutes les saisies</a>, <a href="#liste_opt
 </B_options_tableau>
 
 <h2 class="h2 spip" id="liste_saisies">Toutes les saisies</h2>
-<BOUCLE_saisies(POUR){tableau #ENV{saisies}}>
-<h3 class="h3 spip" id="saisie_#CLE">[(#VALEUR|table_valeur{titre})] (#CLE)</h3>
+<BOUCLE_saisies(DATA){source tableau, #ENV{saisies}}>
+<h3 class="h3 spip" id="saisie_#CLE">#TITRE (#CLE)</h3>
 <p class="description">
-       <strong>Description :</strong> [(#VALEUR|table_valeur{description})]
+       <strong>Description :</strong> #DESCRIPTION
 </p>
 <p class="options">
        <strong>Options :</strong>
-       <BOUCLE_options_saisie(POUR){tableau #VALEUR|table_valeur{options}}{", "}><a href="#option_#CLE">#CLE</a></BOUCLE_options_saisie>
+       <BOUCLE_options_saisie(DATA){source tableau, #OPTIONS}{", "}><a href="#option_#CLE">#CLE</a></BOUCLE_options_saisie>
 </p>
 </BOUCLE_saisies>
 
 <h2 class="h2 spip" id="liste_options_saisies">Toutes les options</h2>
-<BOUCLE_options(POUR){tableau #ENV{options}}>
-[(#SET{label, [(#VALEUR|table_valeur{label}|sinon{[(#VALEUR|table_valeur{label_case})]})]})]
+<BOUCLE_options(DATA){source tableau, #ENV{options}}>
+[(#SET{label, #LABEL|sinon{#LABEL_CASE}})]
 <h3 class="h3 spip" id="option_#CLE">[(#GET{label}|?{#GET{label} [ ((#CLE))], #CLE})]</h3>
 [<p class="description">
-       <strong>Description :</strong> (#VALEUR|table_valeur{explication}|sinon{[(#VALEUR|table_valeur{label}|?{[(#VALEUR|table_valeur{label_case})]})]})
+       <strong>Description :</strong> (#EXPLICATION|sinon{#LABEL|?{#LABEL_CASE}})
 </p>]
 <B_utilisee_par>
 <p class="utilisee_par">
        <strong>Utilisée par :</strong>
-       <BOUCLE_utilisee_par(POUR){tableau #VALEUR|table_valeur{utilisee_par}}{", "}><a href="#saisie_#VALEUR">#VALEUR</a></BOUCLE_utilisee_par>
+       <BOUCLE_utilisee_par(DATA){source tableau, #UTILISEE_PAR}{", "}><a href="#saisie_#VALEUR">#VALEUR</a></BOUCLE_utilisee_par>
 </p>
 </B_utilisee_par>
 <B_choix>
 <p class="choix_possibles">
        <strong>Choix possibles :</strong>
        <ul class="spip">
-               <BOUCLE_choix(POUR){tableau #VALEUR|table_valeur{datas}}>
+               <BOUCLE_choix(DATA){source tableau, #DATAS}>
                <li>"#CLE" : #VALEUR</li>
                </BOUCLE_choix>
        </ul>
index 9cdd288..d3f0e6e 100644 (file)
@@ -5,27 +5,48 @@ jQuery(function(){
 
 function saisies_fieldset_pliable(){
        // On cherche les groupes de champs pliables
-       jQuery('li.fieldset.pliable')
+       jQuery('.fieldset.pliable')
                .each(function(){
-                       var li = jQuery(this);
-                       var ul = jQuery(this).find('> fieldset > ul');
+                       var fieldset = jQuery(this);
+                       var groupe = jQuery(this).find('> fieldset > .editer-groupe');
                        var legend = jQuery(this).find('> fieldset > .legend');
-                       
+
                        // S'il est déjà plié on cache le contenu
-                       if (li.is('.plie'))
-                               ul.hide();
-                       
+                       if (fieldset.is('.plie'))
+                               groupe.hide();
+
                        // Ensuite on ajoute une action sur le titre
                        legend
                                .unbind('click')
                                .click(
                                        function(){
-                                               li.toggleClass('plie');
-                                               if (ul.is(':hidden'))
-                                                       ul.show();
+                                               fieldset.toggleClass('plie');
+                                               if (groupe.is(':hidden'))
+                                                       groupe.show();
                                                else
-                                                       ul.hide();
+                                                       groupe.hide();
                                        }
                                );
                });
 };
+
+function saisies_date_jour_mois_annee_changer_date(me, datetime) {
+       var champ = jQuery(me);
+       var li = champ.closest('.editer');
+       var     jour = jQuery.trim(li.find('.date_jour').val());
+       var     mois = jQuery.trim(li.find('.date_mois').val());
+       var     annee = jQuery.trim(li.find('.date_annee').val());
+       var     date = jQuery.trim(li.find('.datetime').val());
+       
+       while(jour.length < 2) {jour = '0' + jour;}
+       while(mois.length < 2) {mois = '0' + mois;}
+       while(annee.length < 4) {annee = '0' + annee;}
+       
+       if (datetime == 'oui') {
+               date = annee + '-' + mois + '-' + jour + date.substring(10);
+       }
+       else {
+               date = annee + '-' + mois + '-' + jour;
+       }
+       li.find('.datetime').attr('value', date);
+}
index bcdac9f..9999515 100644 (file)
@@ -1,4 +1,4 @@
-<traduction module="paquet-saisies" gestionnaire="salvatore" url="http://trad.spip.net" source="svn://zone.spip.org/spip-zone/_plugins_/saisies/lang/" reference="fr">
+<traduction module="paquet-saisies" gestionnaire="salvatore" url="http://trad.spip.net" source="svn://zone.spip.org/spip-zone/_plugins_/saisies/trunk/lang/" reference="fr">
        <langue code="ar" url="http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=ar" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
                <traducteur nom="George" lien="http://trad.spip.net/auteur/جورج-قندلفت" />
        </langue>
@@ -18,6 +18,9 @@
        <langue code="nl" url="http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=nl" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
                <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
        </langue>
+       <langue code="pt_br" url="http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=pt_br" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+               <traducteur nom="Ricardo Porto" lien="http://trad.spip.net/auteur/ricardo-porto" />
+       </langue>
        <langue code="ru" url="http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=ru" total="4" traduits="4" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
                <traducteur nom="Serge Markitanenko" lien="http://trad.spip.net/auteur/serge-markitanenko" />
        </langue>
index bab403a..138397a 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=ar
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
index 4fb90f5..7ba1e40 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=de
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
index ac252f3..4da860b 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=en
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
index eb06ed8..4e4684b 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=es
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
index 8a2c065..4107b51 100644 (file)
@@ -1,7 +1,9 @@
 <?php
 // This is a SPIP language file  --  Ceci est un fichier langue de SPIP
-// Fichier source, a modifier dans svn://zone.spip.org/spip-zone/_plugins_/saisies/lang/
-if (!defined('_ECRIRE_INC_VERSION')) return;
+// Fichier source, a modifier dans svn://zone.spip.org/spip-zone/_plugins_/saisies/trunk/lang/
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
index f0dc892..a6c0317 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=fr_tu
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
index 13b832b..e16d00f 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=nl
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
diff --git a/www/plugins/saisies/lang/paquet-saisies_pt_br.php b/www/plugins/saisies/lang/paquet-saisies_pt_br.php
new file mode 100644 (file)
index 0000000..057c563
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=pt_br
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // S
+       'saisies_description' => 'Este plugin permite facilitar a escrita de campos de formulários, propondo uma tag #SAISIE. O HTML gerado é compatível com a nomenclatura dos formulários propostos pelo SPIP > 2.0 e com o plugin de configuração CFG.',
+       'saisies_nom' => 'Entradas para formulários',
+       'saisies_slogan' => 'Facilitar a entrada de dados em campos de formulários.',
+       'saisies_titre' => 'Entrada de dados para formulários'
+);
+
+?>
index 1fc60c0..b0ed597 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=ru
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
index 406bad6..c8e2287 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/paquet-saisies?lang_cible=sk
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
index 1f3cbd7..d5b0a84 100644 (file)
@@ -1,39 +1,47 @@
-<traduction module="saisies" gestionnaire="salvatore" url="http://trad.spip.net" source="svn://zone.spip.org/spip-zone/_plugins_/saisies/lang/" reference="fr">
-       <langue code="ca" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=ca" total="162" traduits="85" relire="0" modifs="9" nouveaux="68" pourcent="52.47">
+<traduction module="saisies" gestionnaire="salvatore" url="http://trad.spip.net" source="svn://zone.spip.org/spip-zone/_plugins_/saisies/trunk/lang/" reference="fr">
+       <langue code="ca" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=ca" total="170" traduits="85" relire="0" modifs="9" nouveaux="76" pourcent="50.00">
        </langue>
-       <langue code="de" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=de" total="162" traduits="142" relire="0" modifs="3" nouveaux="17" pourcent="87.65">
+       <langue code="de" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=de" total="170" traduits="145" relire="0" modifs="3" nouveaux="22" pourcent="85.29">
                <traducteur nom="klaus++" lien="http://trad.spip.net/auteur/klaus" />
+               <traducteur nom="Torsten Willmann" lien="http://trad.spip.net/auteur/torsten-willmann" />
        </langue>
-       <langue code="en" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=en" total="162" traduits="162" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       <langue code="en" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=en" total="170" traduits="169" relire="0" modifs="0" nouveaux="1" pourcent="99.41">
+               <traducteur nom="George" lien="http://trad.spip.net/auteur/جورج-قندلفت" />
                <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
+               <traducteur nom="jack31" lien="http://trad.spip.net/auteur/jack31" />
                <traducteur nom="kent1" lien="http://trad.spip.net/auteur/kent1" />
                <traducteur nom="Paolo" lien="http://trad.spip.net/auteur/paolo" />
        </langue>
-       <langue code="es" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=es" total="162" traduits="162" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       <langue code="es" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=es" total="170" traduits="162" relire="0" modifs="0" nouveaux="8" pourcent="95.29">
                <traducteur nom="kent1" lien="http://trad.spip.net/auteur/kent1" />
                <traducteur nom="Raquel S. Bujaldón" lien="http://trad.spip.net/auteur/raquel-s-bujaldon" />
                <traducteur nom="severo" lien="http://trad.spip.net/auteur/severo" />
        </langue>
-       <langue code="fa" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=fa" total="162" traduits="107" relire="0" modifs="3" nouveaux="52" pourcent="66.05">
+       <langue code="fa" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=fa" total="170" traduits="107" relire="0" modifs="3" nouveaux="60" pourcent="62.94">
                <traducteur nom="Davood Hossein" lien="http://trad.spip.net/auteur/davood-hossein" />
        </langue>
-       <langue code="fr" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=fr" total="162" traduits="162" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       <langue code="fr" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=fr" total="170" traduits="170" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
        </langue>
-       <langue code="fr_tu" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=fr_tu" total="162" traduits="159" relire="0" modifs="0" nouveaux="3" pourcent="98.15">
+       <langue code="fr_tu" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=fr_tu" total="170" traduits="159" relire="0" modifs="0" nouveaux="11" pourcent="93.53">
                <traducteur nom="beatnick" lien="http://trad.spip.net/auteur/beatnick" />
                <traducteur nom="Cerf" lien="http://trad.spip.net/auteur/cerf" />
        </langue>
-       <langue code="it" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=it" total="162" traduits="139" relire="0" modifs="3" nouveaux="20" pourcent="85.80">
+       <langue code="it" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=it" total="170" traduits="139" relire="0" modifs="3" nouveaux="28" pourcent="81.76">
        </langue>
-       <langue code="nl" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=nl" total="162" traduits="162" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       <langue code="nl" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=nl" total="170" traduits="165" relire="0" modifs="0" nouveaux="5" pourcent="97.06">
                <traducteur nom="Hanjo" lien="http://trad.spip.net/auteur/hanjo" />
                <traducteur nom="mpossoz" lien="http://trad.spip.net/auteur/mpossoz" />
        </langue>
-       <langue code="ru" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=ru" total="162" traduits="109" relire="0" modifs="36" nouveaux="17" pourcent="67.28">
+       <langue code="pt_br" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=pt_br" total="170" traduits="169" relire="0" modifs="0" nouveaux="1" pourcent="99.41">
+               <traducteur nom="placido" lien="http://trad.spip.net/auteur/placido" />
+               <traducteur nom="Ricardo Porto" lien="http://trad.spip.net/auteur/ricardo-porto" />
+       </langue>
+       <langue code="ru" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=ru" total="170" traduits="148" relire="0" modifs="10" nouveaux="12" pourcent="87.06">
                <traducteur nom="nazar" lien="http://trad.spip.net/auteur/nazar" />
+               <traducteur nom="olly" lien="http://trad.spip.net/auteur/olly" />
                <traducteur nom="Serge Markitanenko" lien="http://trad.spip.net/auteur/serge-markitanenko" />
        </langue>
-       <langue code="sk" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=sk" total="162" traduits="162" relire="0" modifs="0" nouveaux="0" pourcent="100.00">
+       <langue code="sk" url="http://trad.spip.net/tradlang_module/saisies?lang_cible=sk" total="170" traduits="162" relire="0" modifs="0" nouveaux="8" pourcent="95.29">
                <traducteur nom="jaro" lien="http://trad.spip.net/auteur/jaro" />
        </langue>
 </traduction>
index 829f1d5..959fc0b 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/saisies?lang_cible=ca
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
index 10a80af..c2d8dfe 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/saisies?lang_cible=de
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
@@ -83,6 +85,7 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'option_groupe_description' => 'Beschreibung',
        'option_groupe_utilisation' => 'Verwendung',
        'option_groupe_validation' => 'Bestätigung',
+       'option_id_groupe_label' => 'Schlagwortgruppe',
        'option_info_obligatoire_explication' => 'Sie können die Standardbezeichnung für Pflichtfelder ändern:<i>[Pflichtfeld]</i>.',
        'option_info_obligatoire_label' => 'Pflichtfeld-Anzeige',
        'option_inserer_barre_choix_edition' => 'Vollständige Symbolleiste "Bearbeiten"',
@@ -142,6 +145,8 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'saisie_hidden_titre' => 'Verborgenes Feld',
        'saisie_input_explication' => 'Eine einfache Textzeile, kann angezeigt oder ausgeblendet werden (Passwort)',
        'saisie_input_titre' => 'Textzeile',
+       'saisie_mot_explication' => 'Ein oder mehrere Schlagwörter einer Gruppe',
+       'saisie_mot_titre' => 'Schlagwort',
        'saisie_oui_non_explication' => 'Ja oder nein, alle klar ? :)',
        'saisie_oui_non_titre' => 'Ja oder nein',
        'saisie_radio_defaut_choix1' => 'Eins',
index 6e8cd5a..b4818be 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/saisies?lang_cible=en
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
@@ -115,6 +117,7 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'option_option_destinataire_intro_label' => 'Label of first choice empty (in list format)',
        'option_option_intro_label' => 'Label for the first empty choice',
        'option_option_statut_label' => 'Show the status',
+       'option_placeholder_label' => 'Placeholder',
        'option_pliable_label' => 'Expandable',
        'option_pliable_label_case' => 'The group of fields can be expanded or shrunk.',
        'option_plie_label' => 'Already shrunk',
@@ -135,6 +138,10 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'option_type_label' => 'Field type',
        'option_type_password' => 'Text, hidden during input (eg. password)',
        'option_type_text' => 'Normal',
+       'option_valeur_non_explication' => 'Posted value if the checkbox is not selected',
+       'option_valeur_non_label' => 'Value No',
+       'option_valeur_oui_explication' => 'Posted value if the checkbox is selected',
+       'option_valeur_oui_label' => 'Value Yes',
 
        // S
        'saisie_auteurs_explication' => 'Allows you to select one or more authors',
@@ -168,6 +175,8 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'saisie_radio_titre' => 'Radio buttons',
        'saisie_selecteur_article' => 'Display an article selection browser',
        'saisie_selecteur_article_titre' => 'Article selector',
+       'saisie_selecteur_document' => 'Display a document selector',
+       'saisie_selecteur_document_titre' => 'Document selector',
        'saisie_selecteur_rubrique' => 'Display a section selector browser',
        'saisie_selecteur_rubrique_article' => 'Display an article or section selector browser',
        'saisie_selecteur_rubrique_article_titre' => 'Article or section selector',
index c37fa17..0ecef17 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/saisies?lang_cible=es
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
index 5f3a866..34d2550 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/saisies?lang_cible=fa
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
index 567369d..ed7e220 100644 (file)
@@ -1,7 +1,9 @@
 <?php
 // This is a SPIP language file  --  Ceci est un fichier langue de SPIP
-// Fichier source, a modifier dans svn://zone.spip.org/spip-zone/_plugins_/saisies/lang/
-if (!defined('_ECRIRE_INC_VERSION')) return;
+// Fichier source, a modifier dans svn://zone.spip.org/spip-zone/_plugins_/saisies/trunk/lang/
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
@@ -19,6 +21,7 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'construire_action_dupliquer_copie' => '(copie)',
        'construire_action_supprimer' => 'Supprimer',
        'construire_ajouter_champ' => 'Ajouter un champ',
+       'construire_ajouter_groupe' => 'Ajouter un groupe',
        'construire_attention_enregistrer' => 'N’oubliez pas d’enregistrer vos modifications !',
        'construire_attention_modifie' => 'Le formulaire ci-dessous est différent du formulaire initial. Vous avez la possibilité de le réinitialiser à son état avant vos modifications.',
        'construire_attention_supprime' => 'Vos modifications comportent des suppressions de champs. Veuillez confirmer l’enregistrement de cette nouvelle version du formulaire.',
@@ -113,6 +116,7 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'option_option_destinataire_intro_label' => 'Label du premier choix vide (lorsque sous forme de liste)',
        'option_option_intro_label' => 'Label du premier choix vide',
        'option_option_statut_label' => 'Afficher les statuts',
+       'option_placeholder_label' => 'Placeholder',
        'option_pliable_label' => 'Pliable',
        'option_pliable_label_case' => 'Le groupe de champs pourra être replié.',
        'option_plie_label' => 'Déjà plié',
@@ -133,6 +137,10 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'option_type_label' => 'Type du champ',
        'option_type_password' => 'Texte masqué lors de la saisie (ex : mot de passe)',
        'option_type_text' => 'Normal',
+       'option_valeur_non_explication' => 'Valeur postée si le checkbox n’est pas sélectionné',
+       'option_valeur_non_label' => 'Valeur non',
+       'option_valeur_oui_explication' => 'Valeur postée si le checkbox est sélectionné',
+       'option_valeur_oui_label' => 'Valeur oui',
 
        // S
        'saisie_auteurs_explication' => 'Permet de sélectionner un ou plusieurs auteurs',
@@ -166,6 +174,8 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'saisie_radio_titre' => 'Boutons radios',
        'saisie_selecteur_article' => 'Affiche un navigateur de sélection d’article',
        'saisie_selecteur_article_titre' => 'Sélecteur d’article',
+       'saisie_selecteur_document' => 'Affiche un sélecteur de document',
+       'saisie_selecteur_document_titre' => 'Sélecteur de document',
        'saisie_selecteur_rubrique' => 'Affiche un navigateur de sélection de rubrique',
        'saisie_selecteur_rubrique_article' => 'Affiche un navigateur de sélection d’article ou de rubrique',
        'saisie_selecteur_rubrique_article_titre' => 'Sélecteur d’article ou rubrique',
index 1dbdbc1..cb888bc 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/saisies?lang_cible=fr_tu
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
index de77118..1b24b97 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/saisies?lang_cible=it
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
index 76d61d5..2678a15 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/saisies?lang_cible=nl
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
@@ -115,6 +117,7 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'option_option_destinataire_intro_label' => 'Label voor de eerste lege keuze (wanneer in lijstvorm)',
        'option_option_intro_label' => 'Label voor de eerste lege keuze',
        'option_option_statut_label' => 'Toon de status',
+       'option_placeholder_label' => 'Placeholder',
        'option_pliable_label' => 'Uitvouwbaar',
        'option_pliable_label_case' => 'De group velden kan worden uit- en ingevouwen.',
        'option_plie_label' => 'Al ingevouwen',
@@ -168,6 +171,8 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'saisie_radio_titre' => 'Radio knop',
        'saisie_selecteur_article' => 'Toon een artikelkeuze',
        'saisie_selecteur_article_titre' => 'Artikelkeuze',
+       'saisie_selecteur_document' => 'Toont een documentkeuze',
+       'saisie_selecteur_document_titre' => 'Documentkiezer',
        'saisie_selecteur_rubrique' => 'Toon een rubriekkeuze',
        'saisie_selecteur_rubrique_article' => 'Toon een artikel- of rubriekkeuze',
        'saisie_selecteur_rubrique_article_titre' => 'Artikel- of rubriekkeuze',
diff --git a/www/plugins/saisies/lang/saisies_pt_br.php b/www/plugins/saisies/lang/saisies_pt_br.php
new file mode 100644 (file)
index 0000000..315dbd4
--- /dev/null
@@ -0,0 +1,204 @@
+<?php
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+// extrait automatiquement de http://trad.spip.net/tradlang_module/saisies?lang_cible=pt_br
+// ** ne pas modifier le fichier **
+
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+
+       // B
+       'bouton_parcourir_docs_article' => 'Procurar na matéria',
+       'bouton_parcourir_docs_breve' => 'Procurar na nota',
+       'bouton_parcourir_docs_rubrique' => 'Procurar na seção',
+       'bouton_parcourir_mediatheque' => 'Procurar na mídiateca',
+
+       // C
+       'construire_action_annuler' => 'Cancelar',
+       'construire_action_configurer' => 'Configurar',
+       'construire_action_deplacer' => 'Mover',
+       'construire_action_dupliquer' => 'Duplicar',
+       'construire_action_dupliquer_copie' => '(cópia)',
+       'construire_action_supprimer' => 'Excluir',
+       'construire_ajouter_champ' => 'Incluir um campo',
+       'construire_attention_enregistrer' => 'Lembre-se de gravar as suas alterações!',
+       'construire_attention_modifie' => 'O formulário abaixo é diferente do formulário inicial.Você tem a possibilidade de revertê-lo ao estado em que estava, antes das suas alterações.',
+       'construire_attention_supprime' => 'As suas alterações incluem exclusões de campos. Por favor, confirme a gravação desta nova versão do formulário.',
+       'construire_aucun_champs' => 'No momento, não há nenhum campo no formulário.',
+       'construire_confirmer_supprimer_champ' => 'Você quer realmente excluir este campo?',
+       'construire_info_nb_champs_masques' => '@nb@ campo(s) invisível(eis) o tempo de configurar o grupo.',
+       'construire_position_explication' => 'Indique qual campo este deve preceder.',
+       'construire_position_fin_formulaire' => 'No fim do formulário',
+       'construire_position_fin_groupe' => 'No fim do grupo @groupe@',
+       'construire_position_label' => 'Posição do campo',
+       'construire_reinitialiser' => 'Reverter o formulário',
+       'construire_reinitialiser_confirmer' => 'Você perderá todas as suas modificações. Quer realmente reverter à versão inicial do formulário?',
+       'construire_verifications_aucune' => 'Nenhuma',
+       'construire_verifications_label' => 'Tipo de verificação a ser usada',
+
+       // E
+       'erreur_generique' => 'Há erros nos campos abaixo. Por favor, verifique as informações digitadas',
+       'erreur_option_nom_unique' => 'Este nome já está sendo usado por outro campo e deve ser único, neste formulário.',
+
+       // I
+       'info_configurer_saisies' => 'Página de teste das entradas de dados',
+
+       // L
+       'label_annee' => 'Ano',
+       'label_jour' => 'Dia',
+       'label_mois' => 'Mês',
+
+       // O
+       'option_aff_art_interface_explication' => 'Exibir somente as matérias do idioma do usuário',
+       'option_aff_art_interface_label' => 'Exibição multilíngue',
+       'option_aff_langue_explication' => 'Exibe o idioma da matéria ou da seção selecionada antes do titulo',
+       'option_aff_langue_label' => 'Exibir o idioma',
+       'option_aff_rub_interface_explication' => 'Exibir apenas as seções do idioma do usuário',
+       'option_aff_rub_interface_label' => 'Exibição multilíngue',
+       'option_afficher_si_explication' => 'Informe as condições para exibir o campo, em função do valor de outros campos. O identificador dos outros campos deve ser inserido entre <code>@</code>.<br />
+Exemplo: code>@selection_1@=="Toto"</code> condiciona a exibição do campo a que o campo  <code>selection_1</code> tenha o valor <code>Toto</code>.',
+       'option_afficher_si_label' => 'Exibição condicional',
+       'option_afficher_si_remplissage_explication' => 'Ao contrário da opção anterior,  esta só condiciona a exibição enquanto o formulário está sendo respondido e não quando o resultado é exibido. Sua sintaxe é a mesma.',
+       'option_afficher_si_remplissage_label' => 'Exibição condicional durante o preenchimento',
+       'option_attention_explication' => 'Uma mensagem mais importante que a explicação.',
+       'option_attention_label' => 'Aviso',
+       'option_autocomplete_defaut' => 'Deixar por padrão',
+       'option_autocomplete_explication' => 'Ao carregar a página, o seu navegador pode preencher previamente o campo em função do seu histórico',
+       'option_autocomplete_label' => 'Preenchimento prévio do campo',
+       'option_autocomplete_off' => 'Desativar',
+       'option_autocomplete_on' => 'Ativar',
+       'option_cacher_option_intro_label' => 'Esconder a primeira opção em branco.',
+       'option_choix_alternatif_label' => 'Permitir a proposição de opção alternativa',
+       'option_choix_alternatif_label_defaut' => 'Outra opção',
+       'option_choix_alternatif_label_label' => 'Rótulo desta outra opção',
+       'option_choix_destinataires_explication' => 'Um ou mais autores que o usuário possa escolher. Se nada for selecionado, será selecionado o autor que instalou o site.',
+       'option_choix_destinataires_label' => 'Destinatários possíveis',
+       'option_class_label' => 'Classes CSS adicionais',
+       'option_cols_explication' => 'Largura do bloco (em números de caracteres). Este opção não é sempre aplicável, já que os estilos CSS do seu site podem se sobrepor.',
+       'option_cols_label' => 'Largura',
+       'option_datas_explication' => 'Você deve informar uma opção por linha,  no formato "chave|Rótulo da escolha"',
+       'option_datas_label' => 'Lista de opções aceitáveis',
+       'option_datas_sous_groupe_explication' => 'Você deve indicar uma opção por linha, no formato "chave|Rótulo" da opção.<br /> 
+Você pode indicar o início de um subgrupo, no formato "*Título do subgrupo". Para encerrar um subgrupo, você pode iniciar um outro ou inserir uma linha contendo apenas "/*".',
+       'option_defaut_label' => 'Valor padrão',
+       'option_disable_avec_post_explication' => 'Igual na opção anterior, mas envia ainda o valor dentro um campo escondido.',
+       'option_disable_avec_post_label' => 'Desativar mas enviar',
+       'option_disable_explication' => 'O campo não pode mais obter foco.',
+       'option_disable_label' => 'Desativar o campo',
+       'option_erreur_obligatoire_explication' => 'Você pode personalizar a mensagem de erro exibida para indicar a obrigatoriedade (se não, deixe em branco).',
+       'option_erreur_obligatoire_label' => 'Mensagem de obrigatoriedade',
+       'option_explication_explication' => 'Se necessário, uma frase curta descrevendo o objeto do campo.',
+       'option_explication_label' => 'Explicação',
+       'option_groupe_affichage' => 'Exibição',
+       'option_groupe_description' => 'Descrição',
+       'option_groupe_utilisation' => 'Utilização',
+       'option_groupe_validation' => 'Validação',
+       'option_heure_pas_explication' => 'Ao usar o horário, é exibido um menu para ajudar na entrada de horas e minutos. Você pode escolher o intervalo de tempo entre cada opção (30 min por padrão)',
+       'option_heure_pas_label' => 'Intervalo de minutos no menu de apoio à entrada de dados',
+       'option_horaire_label' => 'Horário',
+       'option_horaire_label_case' => 'Permitir informar também o horário',
+       'option_id_groupe_label' => 'Grupo de palavras',
+       'option_info_obligatoire_explication' => 'Você pode alterar o valor padrão da indicação de obrigatoriedade: <i>[Obrigatório]</i>.',
+       'option_info_obligatoire_label' => 'Indicação de obrigatoriedade',
+       'option_inserer_barre_choix_edition' => 'barra de formatação completa',
+       'option_inserer_barre_choix_forum' => 'barra dos fóruns',
+       'option_inserer_barre_explication' => 'Inserir uma barra de ferramentas da Pena, se o plugin estiver ativo.',
+       'option_inserer_barre_label' => 'Inserir uma barra de ferramentas ',
+       'option_label_case_label' => 'Rótulo localizado ao lado do checkbox',
+       'option_label_explication' => 'O titulo que será exibido.',
+       'option_label_label' => 'Rótulo',
+       'option_maxlength_explication' => 'O usuário não poderá digitar mais do que esse número de caracteres.',
+       'option_maxlength_label' => 'Número máximo de caracteres.',
+       'option_multiple_explication' => 'O usuário poderá selecionar vários valores.',
+       'option_multiple_label' => 'Seleção múltipla',
+       'option_nom_explication' => 'Um nome que identificará o campo.  Só pode conter letras minúsculas, números e o caracter "_".',
+       'option_nom_label' => 'Nome do campo',
+       'option_obligatoire_label' => 'Campo obrigatório',
+       'option_option_destinataire_intro_label' => 'Rótulo da primeira opção em branco (quando em formato de lista)',
+       'option_option_intro_label' => 'Rótulo da primeira opção em branco',
+       'option_option_statut_label' => 'Exibir os status',
+       'option_placeholder_label' => 'Marcador de posição',
+       'option_pliable_label' => 'Expansível',
+       'option_pliable_label_case' => 'O grupo de campos poderá ser expandido',
+       'option_plie_label' => 'Já retraído',
+       'option_plie_label_case' => 'Se o grupo de campos é expansível, ele já estará contraído na exibição do formulário.',
+       'option_previsualisation_explication' => 'Si o plugin Pena estiver ativo, adiciona uma aba para visualizar o texto digitado.',
+       'option_previsualisation_label' => 'Ativar a visualização',
+       'option_readonly_explication' => 'O campo pode ser lido, selecionado, mas não alterado.',
+       'option_readonly_label' => 'Só leitura',
+       'option_rows_explication' => 'Altura do bloco em número de linhas. Esta opção não é sempre aplicável, já que os estilos CSS do seu site poderão sobrepor-se.',
+       'option_rows_label' => 'Número de linhas',
+       'option_size_explication' => 'Largura do campo em número de caractéres. Esta opção não é sempre aplicável, já que os estilos CSS do seu site poderão sobrepor-se.',
+       'option_size_label' => 'Tamanho do campo',
+       'option_type_choix_plusieurs' => 'Permitir que o usuário escolha <strong>diversos</strong> destinatários.',
+       'option_type_choix_tous' => 'Incluir <strong>todos</strong> estes autores como destinatários. O usuário não terá nenhuma escolha.',
+       'option_type_choix_un' => 'Permitir ao usuário escolher <strong>um único</strong> destinatário (no formato de lista).',
+       'option_type_choix_un_radio' => 'Permite ao usuário escolher <strong>um único</strong> destinatário (no formato de checkboxes).',
+       'option_type_explication' => 'Em modo "mascarado", o conteúdo do campo não será mostrado.',
+       'option_type_label' => 'Tipo do campo',
+       'option_type_password' => 'Texto mascarado durante o preenchimento (ex: senha).',
+       'option_type_text' => 'Normal',
+       'option_valeur_non_explication' => 'Valor postado se o checkbox não estiver selecionado',
+       'option_valeur_non_label' => 'Valor não',
+       'option_valeur_oui_explication' => 'VAlor postado se o checkbox estiver selecionado',
+       'option_valeur_oui_label' => 'Valor sim',
+
+       // S
+       'saisie_auteurs_explication' => 'Permite selecionar um ou mais autores',
+       'saisie_auteurs_titre' => 'Autores',
+       'saisie_case_explication' => 'Permite ativar ou desativar algo.',
+       'saisie_case_titre' => 'Checkbox único',
+       'saisie_checkbox_explication' => 'Permite escolher varias opções com checkboxes.',
+       'saisie_checkbox_titre' => 'Checkboxes',
+       'saisie_date_explication' => 'Permite informar uma data com a ajuda do calendário.',
+       'saisie_date_titre' => 'Data',
+       'saisie_destinataires_explication' => 'Permite escolher um ou mais destinatários entre autores pré-selecionados.',
+       'saisie_destinataires_titre' => 'Destinatários',
+       'saisie_explication_explication' => 'Um texto explicativo geral.',
+       'saisie_explication_titre' => 'Explicação',
+       'saisie_fieldset_explication' => 'Uma área que poderá englobar vários campos.',
+       'saisie_fieldset_titre' => 'Grupo de campos',
+       'saisie_file_explication' => 'Envio de um arquivo',
+       'saisie_file_titre' => 'Arquivo',
+       'saisie_hidden_explication' => 'Um campo preenchido previamente, que o usuário não poderá ver.',
+       'saisie_hidden_titre' => 'Campo invisível',
+       'saisie_input_explication' => 'Uma simples linha de texto podendo ser visível ou mascarada (senha).',
+       'saisie_input_titre' => 'Linha de texto',
+       'saisie_mot_explication' => 'Uma ou mais palavras-chave de um grupo de palavras',
+       'saisie_mot_titre' => 'Palavra-chave',
+       'saisie_oui_non_explication' => 'Sim ou não, está claro? ;)',
+       'saisie_oui_non_titre' => 'Sim ou não',
+       'saisie_radio_defaut_choix1' => 'Um',
+       'saisie_radio_defaut_choix2' => 'Dois',
+       'saisie_radio_defaut_choix3' => 'Três',
+       'saisie_radio_explication' => 'Permite escolher uma opção entre várias disponíveis.',
+       'saisie_radio_titre' => 'Botões rádio',
+       'saisie_selecteur_article' => 'Exibe um navegador de seleção de matéria',
+       'saisie_selecteur_article_titre' => 'Seletor de matéria',
+       'saisie_selecteur_document' => 'Exibe um seletor de documento',
+       'saisie_selecteur_document_titre' => 'Seletor de documento',
+       'saisie_selecteur_rubrique' => 'Exibe um navegador de seleção de seção',
+       'saisie_selecteur_rubrique_article' => 'Exibe um navegador de seleção de matéria ou de seção',
+       'saisie_selecteur_rubrique_article_titre' => 'Seletor de matéria ou seção',
+       'saisie_selecteur_rubrique_titre' => 'Seletor de seção',
+       'saisie_selection_explication' => 'Escolher uma opção em uma lista',
+       'saisie_selection_multiple_explication' => 'Permite escolher várias opções em uma lista',
+       'saisie_selection_multiple_titre' => 'Seleção múltipla',
+       'saisie_selection_titre' => 'Lista',
+       'saisie_textarea_explication' => 'Um campo de texto em várias linhas.',
+       'saisie_textarea_titre' => 'Bloco de texto',
+
+       // T
+       'tous_visiteurs' => 'Todos os visitantes (mesmo os não registrados)',
+       'tout_selectionner' => 'Selecionar tudo',
+
+       // V
+       'vue_sans_reponse' => '<i>Sem resposta</i>',
+
+       // Z
+       'z' => 'zzz'
+);
+
+?>
index e1634f0..6594de7 100644 (file)
@@ -3,15 +3,17 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/saisies?lang_cible=ru
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
        // B
-       'bouton_parcourir_docs_article' => 'Посмотреть статью', # MODIF
-       'bouton_parcourir_docs_breve' => 'Посмотреть новость', # MODIF
-       'bouton_parcourir_docs_rubrique' => 'Посмотреть раздел', # MODIF
-       'bouton_parcourir_mediatheque' => 'Изменить библиотеку мультимедиа', # MODIF
+       'bouton_parcourir_docs_article' => 'Посмотреть статью',
+       'bouton_parcourir_docs_breve' => 'Посмотреть новость',
+       'bouton_parcourir_docs_rubrique' => 'Посмотреть раздел',
+       'bouton_parcourir_mediatheque' => 'Изменить библиотеку мультимедиа',
 
        // C
        'construire_action_annuler' => 'Отменить',
@@ -22,11 +24,11 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'construire_action_supprimer' => 'Удалить',
        'construire_ajouter_champ' => 'Добавить поле',
        'construire_attention_enregistrer' => 'Обязательно нажмите кнопку СОХРАНИТЬ, если вы что то изменяли!',
-       'construire_attention_modifie' => 'Ð\9dа Ñ\8dÑ\82ой Ñ\81Ñ\82Ñ\80аниÑ\86е Ñ\83 Ð²Ð°Ñ\81 ÐµÑ\81Ñ\82Ñ\8c Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾Ñ\81Ñ\82Ñ\8c Ñ\80едакÑ\82иÑ\80оваÑ\82Ñ\8c Ñ\84оÑ\80мÑ\83. Ð\94лÑ\8f Ñ\82ого, Ñ\87Ñ\82обÑ\8b Ð²ÐµÑ\80нÑ\83Ñ\82Ñ\8cÑ\81Ñ\8f Ðº Ñ\81пиÑ\81кÑ\83 Ð²Ñ\8bбоÑ\80а Ñ\84оÑ\80м, Ð½Ð°Ð¶Ð¼Ð¸Ñ\82е Ð½Ð° ÐºÐ½Ð¾Ð¿ÐºÑ\83 Ð½Ð¸Ð¶Ðµ', # MODIF
-       'construire_attention_supprime' => 'Изменения включают удаление полей. Пожалуйста, подтвердите регистрацию новой формы.', # MODIF
-       'construire_aucun_champs' => 'На данный момент поля в форме отсутствуют', # MODIF
+       'construire_attention_modifie' => 'Ð\9dа Ñ\8dÑ\82ой Ñ\81Ñ\82Ñ\80аниÑ\86е Ñ\83 Ð²Ð°Ñ\81 ÐµÑ\81Ñ\82Ñ\8c Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾Ñ\81Ñ\82Ñ\8c Ñ\80едакÑ\82иÑ\80оваÑ\82Ñ\8c Ñ\84оÑ\80мÑ\83. Ð§Ñ\82обÑ\8b Ð²ÐµÑ\80нÑ\83Ñ\82Ñ\8cÑ\81Ñ\8f Ðº Ñ\81пиÑ\81кÑ\83 Ð²Ñ\8bбоÑ\80а Ñ\84оÑ\80м, Ð½Ð°Ð¶Ð¼Ð¸Ñ\82е Ð½Ð° ÐºÐ½Ð¾Ð¿ÐºÑ\83 Ð½Ð¸Ð¶Ðµ',
+       'construire_attention_supprime' => 'Изменения включают удаление полей. Пожалуйста, подтвердите регистрацию новой формы.',
+       'construire_aucun_champs' => 'На данный момент поля в форме отсутствуют',
        'construire_confirmer_supprimer_champ' => 'Вы действительно хотите удалить это поле?',
-       'construire_info_nb_champs_masques' => '@nb@ скрытых полей', # MODIF
+       'construire_info_nb_champs_masques' => '@nb@ скрытых полей',
        'construire_position_explication' => 'На месте какого поля показывать?',
        'construire_position_fin_formulaire' => 'В самом конце',
        'construire_position_fin_groupe' => 'После группы @groupe@',
@@ -41,7 +43,7 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'erreur_option_nom_unique' => 'Такое название поля уже используется.',
 
        // I
-       'info_configurer_saisies' => 'Тестовая страница формы', # MODIF
+       'info_configurer_saisies' => 'Тестовая страница формы',
 
        // L
        'label_annee' => 'Год',
@@ -49,22 +51,28 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'label_mois' => 'Месяц',
 
        // O
-       'option_aff_art_interface_explication' => 'Отображать только статьи в настройках языка пользователя', # MODIF
-       'option_aff_art_interface_label' => 'Многоязычное отображение', # MODIF
-       'option_aff_langue_explication' => 'Показать выбранный язык статьи или раздела перед названием', # MODIF
+       'option_aff_art_interface_explication' => 'Отображать только статьи в настройках языка пользователя',
+       'option_aff_art_interface_label' => 'Многоязычное отображение',
+       'option_aff_langue_explication' => 'Показать выбранный язык статьи или раздела перед названием',
        'option_aff_langue_label' => 'Вывод языка статьи',
        'option_aff_rub_interface_explication' => 'Отображать только разделы в языковых настройках пользователя.', # MODIF
-       'option_aff_rub_interface_label' => 'Многоязычное отображение', # MODIF
-       'option_attention_explication' => 'Сообщение, которое является более важным нежели комментарий.', # MODIF
+       'option_aff_rub_interface_label' => 'Многоязычное отображение',
+       'option_afficher_si_label' => 'Отображение по условию',
+       'option_afficher_si_remplissage_explication' => 'В отличие от предыдущего варианта, этот определяет отображение при заполнении формы, а не при просмотре результатов. Синтаксис такой же.',
+       'option_afficher_si_remplissage_label' => 'Условное отображение при заполнении',
+       'option_attention_explication' => 'Сообщение, которое является более важным, нежели комментарий.',
        'option_attention_label' => 'Предупреждение',
        'option_autocomplete_defaut' => 'Оставить по умолчанию',
-       'option_autocomplete_explication' => 'При загрузке страницы браузер может предварительно заполнить поля на основании истории', # MODIF
+       'option_autocomplete_explication' => 'При загрузке страницы браузер может предварительно заполнить поля на основании истории',
        'option_autocomplete_label' => 'Предварительное заполнение поля',
        'option_autocomplete_off' => 'Отключить',
        'option_autocomplete_on' => 'Включить',
-       'option_cacher_option_intro_label' => 'Не отображать первый пустой вариант', # MODIF
-       'option_choix_destinataires_explication' => 'Один или несколько авторов, среди которых пользователь может сделать свой ​​выбор. Если ничего не выбрано, то то будет выбран автор, который сейчас на сайте.', # MODIF
-       'option_choix_destinataires_label' => 'Возможные получатели', # MODIF
+       'option_cacher_option_intro_label' => 'Скрыть первый пустой вариант',
+       'option_choix_alternatif_label' => 'Предложить альтернативный выбор',
+       'option_choix_alternatif_label_defaut' => 'Другой выбор',
+       'option_choix_alternatif_label_label' => 'Подпись для этого альтернативного выбора',
+       'option_choix_destinataires_explication' => 'Один или несколько авторов, среди которых пользователь может сделать свой ​​выбор. Если ничего не выбрано, то будет выбран автор по умолчанию (активный при инсталляции сайта).',
+       'option_choix_destinataires_label' => 'Возможные получатели',
        'option_class_label' => 'Дополнительные CSS классы',
        'option_cols_explication' => 'Длина поля в символах. Эта опция не всегда работает, так CSS стили сайта могут отменять заданное значение.',
        'option_cols_label' => 'Ширина',
@@ -83,6 +91,9 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'option_groupe_description' => 'Основное',
        'option_groupe_utilisation' => 'Свойства',
        'option_groupe_validation' => 'Проверка',
+       'option_horaire_label' => 'Расписание',
+       'option_horaire_label_case' => 'Разрешить заполнить время',
+       'option_id_groupe_label' => 'Группа ключей',
        'option_info_obligatoire_explication' => 'Вы можете изменить стандартные настройки обязательного заполнения полей.. ', # MODIF
        'option_info_obligatoire_label' => 'Обязательное заполнение полей', # MODIF
        'option_inserer_barre_choix_edition' => 'добавить полную панель',
@@ -101,9 +112,9 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'option_obligatoire_label' => 'Обязательное поле',
        'option_option_intro_label' => 'Название первого элемента списка',
        'option_option_statut_label' => 'Показать статус',
-       'option_pliable_label' => 'Расширяемая', # MODIF
-       'option_pliable_label_case' => 'Группа полей может быть развернута или сжата.', # MODIF
-       'option_plie_label' => 'Уже сжато', # MODIF
+       'option_pliable_label' => 'Расширяемая',
+       'option_pliable_label_case' => 'Группа полей может быть развернута или сжата.',
+       'option_plie_label' => 'Уже сжато',
        'option_plie_label_case' => 'Если группу полей можно расширить или сжать, тогда эта опция их сожмет с отображением полей.', # MODIF
        'option_previsualisation_explication' => 'Добавить вкладку предварительного просмотра?',
        'option_previsualisation_label' => 'Предварительный просмотр',
@@ -113,12 +124,12 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'option_rows_label' => 'Количество строк',
        'option_size_explication' => 'Длина поля в символах. Эта опция не всегда работает, так CSS стили сайта могут отменять заданное значение.',
        'option_size_label' => 'Размер поля',
-       'option_type_choix_plusieurs' => 'Позволяет выбрать <strong>несколько</strong> получателей.', # MODIF
-       'option_type_choix_tous' => 'Отметить <strong>всех</strong> авторов как получателей. Пользователю выбор не предоставляется.', # MODIF
-       'option_type_choix_un' => 'Сделать возможным выбор только одного получателя.', # MODIF
-       'option_type_explication' => 'Если выбран "ввод пароля", то символы в поле будут превращаться в звездочки',
+       'option_type_choix_plusieurs' => 'Позволяет выбрать <strong>несколько</strong> получателей.',
+       'option_type_choix_tous' => 'Отметить <strong>всех</strong> авторов как получателей. Пользователю выбор не предоставляется.',
+       'option_type_choix_un' => 'Сделать возможным выбор <strong>только одного</strong> получателя.',
+       'option_type_explication' => 'Если выбран «ввод пароля», то символы в поле будут превращаться в звездочки',
        'option_type_label' => 'Тип поля',
-       'option_type_password' => 'Ð\92вод Ð¿Ð°Ñ\80олÑ\8f', # MODIF
+       'option_type_password' => 'ТекÑ\81Ñ\82, Ñ\81кÑ\80Ñ\8bваемÑ\8bй Ð²Ð¾ Ð²Ñ\80емÑ\8f Ð²Ð²Ð¾Ð´Ð° (напÑ\80имеÑ\80, Ð¿Ð°Ñ\80олÑ\8c)',
        'option_type_text' => 'Обычный',
 
        // S
@@ -142,6 +153,8 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'saisie_hidden_titre' => 'Невидимое (скрытое) поле',
        'saisie_input_explication' => 'Основное поле для ввода информации',
        'saisie_input_titre' => 'Текстовая строка',
+       'saisie_mot_explication' => 'Один или больше ключей из группы',
+       'saisie_mot_titre' => 'Ключи',
        'saisie_oui_non_explication' => 'Выбор только одного варианта ответа: ДА или НЕТ',
        'saisie_oui_non_titre' => 'Да или Нет',
        'saisie_radio_defaut_choix1' => 'Один',
@@ -151,6 +164,8 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'saisie_radio_titre' => 'Радио кнопка',
        'saisie_selecteur_article' => 'Позволяет выбратью статью из структуры сайта',
        'saisie_selecteur_article_titre' => 'Выбор статьи',
+       'saisie_selecteur_document' => 'Показать селектор документа',
+       'saisie_selecteur_document_titre' => 'Селектор документа',
        'saisie_selecteur_rubrique' => 'Позволяет выбратью раздел из структуры сайта',
        'saisie_selecteur_rubrique_article' => 'Позволяет выбратью статью или раздел из структуры сайта',
        'saisie_selecteur_rubrique_article_titre' => 'Выбор статьи или раздела',
@@ -163,14 +178,14 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
        'saisie_textarea_titre' => 'Текстовое поле',
 
        // T
-       'tous_visiteurs' => 'Все посетители (в том числе не зарегистрированы)',
+       'tous_visiteurs' => 'Все посетители (в том числе не зарегистрированные)',
        'tout_selectionner' => 'Выбрать все',
 
        // V
        'vue_sans_reponse' => '<i>ничего не задано</i>',
 
        // Z
-       'z' => 'zzz' # MODIF
+       'z' => 'zzz'
 );
 
 ?>
index b8504d2..37b305b 100644 (file)
@@ -3,7 +3,9 @@
 // extrait automatiquement de http://trad.spip.net/tradlang_module/saisies?lang_cible=sk
 // ** ne pas modifier le fichier **
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 $GLOBALS[$GLOBALS['idx_lang']] = array(
 
index 56b740b..579e54b 100644 (file)
@@ -1,29 +1,29 @@
-<paquet\r
-       prefix="saisies"\r
-       categorie="outil"\r
-       version="1.41.2"\r
-       etat="stable"\r
-       compatibilite="[2.0.0;3.0.*]"\r
-       logo="images/logo_saisie_48.png"\r
-       documentation="http://contrib.spip.net/Saisies"\r
->\r
-       <nom>Saisies</nom>\r
-       <auteur lien="http://contrib.spip.net/Matthieu-Marcillaud">Matthieu Marcillaud</auteur>\r
-       <auteur lien="http://contrib.spip.net/RastaPopoulos">RastaPopoulos</auteur>\r
-       <auteur lien="http://contrib.spip.net/Joseph">Joseph</auteur>\r
-       <auteur lien="http://www.ldd.fr">Les Développements Durables</auteur>\r
-       <licence>GNU/GPL</licence>\r
-\r
-       <traduire module="saisies" reference="fr" gestionnaire="salvatore" />\r
-\r
-       <pipeline nom="header_prive" inclure="saisies_pipelines.php" />\r
-       <pipeline nom="affichage_final" inclure="saisies_pipelines.php" />\r
-       <pipeline nom="saisies_autonomes" inclure="saisies_pipelines.php" />\r
-       <pipeline nom="formulaire_saisies" inclure="saisies_pipelines.php" />\r
-       <pipeline nom="formulaire_charger" inclure="saisies_pipelines.php" />\r
-       <pipeline nom="formulaire_verifier" inclure="saisies_pipelines.php" />\r
-       <pipeline nom="styliser" inclure="saisies_pipelines.php" />\r
-\r
-       <utilise nom="verifier" />\r
-       <utilise nom="spip_bonux" />\r
-</paquet>\r
+<paquet
+       prefix="saisies"
+       categorie="outil"
+       version="2.6.1"
+       etat="stable"
+       compatibilite="[3.0.0;3.1.*]"
+       logo="images/logo_saisie_48.png"
+       documentation="http://contrib.spip.net/Saisies"
+>
+       <nom>Saisies</nom>
+       <auteur lien="http://contrib.spip.net/Matthieu-Marcillaud">Matthieu Marcillaud</auteur>
+       <auteur lien="http://contrib.spip.net/RastaPopoulos">RastaPopoulos</auteur>
+       <auteur lien="http://contrib.spip.net/Joseph">Joseph</auteur>
+       <auteur lien="http://www.ldd.fr">Les Développements Durables</auteur>
+       <licence>GNU/GPL</licence>
+
+       <traduire module="saisies" reference="fr" gestionnaire="salvatore" />
+
+       <pipeline nom="header_prive" inclure="saisies_pipelines.php" />
+       <pipeline nom="affichage_final" inclure="saisies_pipelines.php" />
+       <pipeline nom="saisies_autonomes" action="" />
+       <pipeline nom="formulaire_saisies" action="" />
+       <pipeline nom="formulaire_charger" inclure="saisies_pipelines.php" />
+       <pipeline nom="formulaire_verifier" inclure="saisies_pipelines.php" />
+       <pipeline nom="styliser" inclure="saisies_pipelines.php" />
+
+       <utilise nom="verifier" compatibilite="[0.1.10;]" />
+       <utilise nom="spip_bonux" compatibilite="[3.0.0;]" />
+</paquet>
diff --git a/www/plugins/saisies/plugin.xml b/www/plugins/saisies/plugin.xml
deleted file mode 100644 (file)
index def5278..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-<plugin>\r
-    <nom><multi>[fr]Saisies pour formulaires[en]Entries for forms</multi></nom>\r
-       <slogan>Ecrire facilement des champs de formulaires</slogan>\r
-    <auteur>Matthieu Marcillaud - RastaPopoulos - Joseph</auteur>\r
-    <licence>&#169; 2009-2013 GNU/GPL</licence>\r
-    <version>1.41.2</version>\r
-    <etat>stable</etat>\r
-    <categorie>outil</categorie>\r
-\r
-       <description>\r
-               <multi>\r
-               [fr]\r
-               Ce plugin permet de faciliter l'&#233;criture de champs de formulaires en proposant une\r
-               balise #SAISIE. Le HTML g&#233;n&#233;r&#233; est compatible avec la nomenclature des formulaires\r
-               propos&#233;e par SPIP > 2.0 et avec le plugin de configuration CFG.\r
-               [en]\r
-               This plugin makes it easier to write form fields by providing a #SAISIE tag.\r
-               The generated HTML is compatible with the classification of forms\r
-               proposed by SPIP > 2.0 and with the configuration plugin CFG.\r
-               </multi>\r
-       </description>\r
-\r
-    <lien>http://contrib.spip.net/Saisies</lien>\r
-    <prefix>saisies</prefix>\r
-    <icon>images/logo_saisie_48.png</icon>\r
-\r
-    <fonctions>balise/saisie.php</fonctions>\r
-    <fonctions>inc/saisies.php</fonctions>\r
-    <fonctions>saisies_fonctions.php</fonctions>\r
-    <options>saisies_options.php</options>\r
-\r
-    <pipeline>\r
-       <nom>header_prive</nom>\r
-       <inclure>saisies_pipelines.php</inclure>\r
-    </pipeline>\r
-    <pipeline>\r
-       <nom>affichage_final</nom>\r
-       <inclure>saisies_pipelines.php</inclure>\r
-    </pipeline>\r
-       <pipeline>\r
-               <nom>saisies_autonomes</nom>\r
-               <inclure>saisies_pipelines.php</inclure>\r
-       </pipeline>\r
-       <pipeline>\r
-               <nom>formulaire_saisies</nom>\r
-               <inclure>saisies_pipelines.php</inclure>\r
-       </pipeline>\r
-       <pipeline>\r
-               <nom>formulaire_charger</nom>\r
-               <inclure>saisies_pipelines.php</inclure>\r
-       </pipeline>\r
-       <pipeline>\r
-               <nom>formulaire_verifier</nom>\r
-               <inclure>saisies_pipelines.php</inclure>\r
-       </pipeline>\r
-       <pipeline>\r
-               <nom>styliser</nom>\r
-               <inclure>saisies_pipelines.php</inclure>\r
-       </pipeline>\r
-\r
-       <necessite id="SPIP" version="[2.0.0;3.0.99]" />\r
-       <utilise id="verifier" />\r
-       <utilise id="spip_bonux" />\r
-       <traduire gestionnaire="salvatore" module="saisies" reference="fr" />\r
-</plugin>\r
index 85c612c..254542d 100644 (file)
@@ -16,7 +16,7 @@
 \r
 [(#REM) Cas normal avec présentation ]\r
 [(#GET{valeur_uniquement}|non)\r
-<div class="afficher[ afficher_(#ENV{nom})][ saisie_(#ENV{type_saisie})][ (#ENV{li_class})][ (#GET{reponse}|non)sans_reponse vide]">\r
+<div class="afficher[ afficher_(#ENV{nom})][ saisie_(#ENV{type_saisie})][ (#ENV{conteneur_class,#ENV{li_class}})][ (#GET{reponse}|non)sans_reponse vide]">\r
        [(#REM) S'il y a des enfants on n'inclut que la vue ]\r
        [(#GET{enfants}|oui)\r
                #GET{reponse}\r
index 77b12cf..4818e29 100644 (file)
@@ -4,9 +4,9 @@
 <B_choix>\r
 <ul>\r
        <BOUCLE_choix(POUR){tableau #GET{valeur}}>\r
-        [<li class="choix">(#GET{datas}|table_valeur{#VALEUR})</li>]\r
+               [<li class="choix">(#GET{datas/#VALEUR})</li>]\r
        </BOUCLE_choix>\r
 \r
-    [<li class="choix"><em>#ENV{choix_alternatif_label}</em> : (#GET{valeur}|table_valeur{choix_alternatif})</li>]\r
-    </ul>\r
+       [<li class="choix"><em>#ENV{choix_alternatif_label}</em> : (#GET{valeur/choix_alternatif})</li>]\r
+</ul>\r
 </B_choix>\r
diff --git a/www/plugins/saisies/saisies-vues/couleur.html b/www/plugins/saisies/saisies-vues/couleur.html
new file mode 100644 (file)
index 0000000..6dc5361
--- /dev/null
@@ -0,0 +1,4 @@
+<p>
+       <span class='couleur'[ style="background-color:#(#ENV{valeur}|rtrim{#}); width:4em; border:1px solid #888; display:inline-block;"]>&nbsp;</span>
+       [(#ENV{valeur})]
+</p>
index 705e459..12ac840 100644 (file)
@@ -1,6 +1,7 @@
+[(#SET{valeur,[(#ENV*{valeur}|is_array|?{[(#ENV*{valeur})],[(#ENV*{valeur}|explode{','})]})]})]\r
 <B_destinataires>\r
 <ul>\r
-       <BOUCLE_destinataires(AUTEURS){tous}{id_auteur IN #ENV*{valeur}}>\r
+       <BOUCLE_destinataires(AUTEURS){tout}{id_auteur IN #GET*{valeur}}>\r
        <li class="choix">#NOM</li>\r
        </BOUCLE_destinataires>\r
 </ul>\r
index b3c9c55..debb78b 100644 (file)
@@ -1,4 +1,4 @@
 [(#REM) datas peut être une chaine qu'on sait décomposer ]\r
 #SET{datas, #ENV{datas}|saisies_chaine2tableau}\r
 \r
-[<p>(#GET{datas}|table_valeur{#ENV{valeur}})</p>]\r
+[<p>(#GET{datas/#ENV{valeur}})</p>]\r
index ba76acb..5556cf9 100644 (file)
@@ -1,5 +1,3 @@
 <?php
 
-include_spip('prive/formulaires/selecteur/selecteur_fonctions');
-
-?>
+include_spip('prive/formulaires/selecteur/selecteur_fonctions');
\ No newline at end of file
index 1e4ba7d..23f77bf 100644 (file)
@@ -1,3 +1,3 @@
-<BOUCLE_doc(DOCUMENTS){id_document=#ENV{valeur}}>\r
+<BOUCLE_doc(DOCUMENTS){id_document=#ENV{valeur}}{statut==.*}>\r
 <p>#ID_DOCUMENT - #TITRE (#TYPE_DOCUMENT [(#TAILLE|taille_en_octets)])</p>\r
 </BOUCLE_doc>\r
index 45dddb1..4d5f5a6 100644 (file)
@@ -1,4 +1,4 @@
 [(#REM) datas peut être une chaine qu'on sait décomposer ]\r
 #SET{datas, #ENV{datas}|saisies_chaine2tableau|saisies_aplatir_tableau}\r
 \r
-[<p>(#GET{datas}|table_valeur{#ENV{valeur}})</p>]\r
+[<p>(#GET{datas/#ENV{valeur}})</p>]\r
index 2925716..a13d173 100644 (file)
@@ -1,10 +1,10 @@
 [(#REM) datas peut être une chaine qu'on sait décomposer ]\r
-#SET{datas, #ENV{datas}|saisies_chaine2tableau}\r
+#SET{datas, #ENV{datas}|saisies_chaine2tableau|saisies_aplatir_tableau}\r
 #SET{valeur, #ENV*{valeur}|saisies_valeur2tableau}\r
 <B_choix>\r
 <ul>\r
        <BOUCLE_choix(POUR){tableau #GET{valeur}}>\r
-       <li class="choix">[(#GET{datas}|table_valeur{#VALEUR})]</li>\r
+       <li class="choix">#GET{datas/#VALEUR}</li>\r
        </BOUCLE_choix>\r
 </ul>\r
 </B_choix>\r
index 1686610..86024e2 100644 (file)
 /* Dans l'espace privé, afficher les labels des vues de Saisies */\r
 #wysiwyg .afficher .label{ display:block; }\r
 \r
-li.fieldset.pliable > fieldset > .legend{\r
+.fieldset.pliable > fieldset > .legend{\r
        cursor:pointer;\r
 }\r
 \r
-li.fieldset.pliable > fieldset > .legend span{\r
+.fieldset.pliable > fieldset > .legend span{\r
        padding-#GET{left}:15px;\r
        background:transparent url(#CHEMIN{images/deplierbas.gif}) [(#GET{left}) ]center no-repeat;\r
 }\r
 \r
-li.fieldset.plie > fieldset > .legend span{\r
+.fieldset.plie > fieldset > .legend span{\r
        background-image:url(#GET{fleche});\r
 }\r
 /*[(#REM) date triple champs...\r
        jour    mois    annee\r
        /12/    /10/    /2010/\r
 ]*/\r
-.saisie_date_jour_mois_annee .choix {float:left;}\r
+.editer.saisie_date_jour_mois_annee .choix {\r
+       background-color:transparent;\r
+       float:left;\r
+       padding:0;\r
+       border:0;\r
+}\r
 .saisie_date_jour_mois_annee .choix+.choix {margin-left:1em;}\r
 .saisie_date_jour_mois_annee .choix label{display:block; width:auto;}\r
 .saisie_date_jour_mois_annee .choix .text{width:auto;}\r
@@ -44,5 +49,5 @@ li.fieldset.plie > fieldset > .legend span{
 \r
 .formulaire_spip li.selecteur_item div.choix label {\r
        float:none;\r
-   display:inline;\r
+       display:inline;\r
 }\r
index e5befe6..342fc40 100644 (file)
   - disable_avec_post : idem disable, mais en envoyant en hidden le champ tout de meme.\r
   - readonly : est-ce que le champ est non modifiable ? (pas de saisie possible, selection possible, contenus postes)\r
               (defaut: non, valeurs : null/"non"/autre=oui ) n'est peut etre pas valable pour toutes les saisies.\r
+  - saisies_base_conteneur : définit la balise englobante de la saisie (balise div par défaut en SPIP 3.1+, et li avant)\r
+  - conteneur_class : Classe CSS à ajouter au conteneur\r
+  - li_class : pour compatibilité. Voir conteneur_class\r
 \r
   \r
   Exemples d'appels :\r
-    [(#SAISIE{input, couleur_foncee,\r
+       [(#SAISIE{input, couleur_foncee,\r
                label=<:spa:couleur_foncee:>,\r
                obligatoire=oui})]\r
 \r
        #SET{disable,#ENV{disable,#ENV{disable_avec_post}}|et{#ENV{disable,#ENV{disable_avec_post}}|!={non}}|?{#ENV{disable}|is_array|?{#ENV{disable,#ARRAY},disabled},''}}\r
        #SET{readonly,#ENV{readonly}|et{#ENV{readonly}!={non}}|?{readonly,''}}\r
        #SET{saisies_autonomes,#VAL|saisies_autonomes}\r
-       \r
+\r
        [(#ENV{type_saisie}|in_array{#GET{saisies_autonomes}}|oui)\r
                [(#INCLURE{fond=saisies/#ENV{type_saisie},env,obligatoire=#GET{obligatoire},disable=#GET{disable},readonly=#GET{readonly}})]\r
        ]\r
        [(#ENV{type_saisie}|in_array{#GET{saisies_autonomes}}|non)\r
-               #SET{erreurs,#ENV**{erreurs}|table_valeur{#ENV{nom}}}\r
-               #SET{li_class,#ENV{type_saisie}|substr{0,9}|=={selecteur}|?{selecteur_item,''}}\r
+               #SET{erreurs,#ENV**{erreurs/#ENV{nom}}}\r
+               #SET{conteneur_class,#ENV{type_saisie}|substr{0,9}|=={selecteur}|?{selecteur_item,''}}\r
                <!--!inserer_saisie_editer-->\r
-               <li class="editer editer_[(#ENV{nom}|saisie_nom2classe)][ (#GET{obligatoire})][ (#GET{erreurs}|oui)erreur][ (#GET{li_class})][ (#ENV{li_class})][ saisie_(#ENV{type_saisie})]"[ data-id="(#ENV{id_saisie})"]>\r
+               <[(#ENV{saisies_base_conteneur,[(#VAL{li}|saisie_balise_structure_formulaire)]})] class="editer editer_[(#ENV{nom}|saisie_nom2classe)][ (#GET{obligatoire})][ (#GET{erreurs}|oui)erreur][ (#GET{conteneur_class})][ (#ENV{conteneur_class,#ENV{li_class}})][ saisie_(#ENV{type_saisie})]"[ data-id="(#ENV{id_saisie})"]>\r
                        #ENV*{inserer_debut}\r
                        [<label[(#ENV{type_saisie}|match{oui_non|radio|checkbox}|non) for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"]>(#ENV*{label})[<span class='obligatoire'>(#GET{obligatoire}|oui)[(#ENV*{info_obligatoire}|is_null|?{<:info_obligatoire_02:>,#ENV*{info_obligatoire}})]</span>]</label>]\r
                        [<span class='erreur_message'>(#GET{erreurs})</span>]\r
-                       [<p class='explication'>(#ENV*{explication})</p>]\r
+                       [<p class="explication" id="[explication_(#ENV{nom}|saisie_nom2name)]">(#ENV*{explication})</p>]\r
                        [<em class='attention'>(#ENV*{attention})</em>]\r
-                       [(#INCLURE{fond=saisies/#ENV{type_saisie},env,nom=[(#ENV{nom}|saisie_nom2name)], disable=#GET{disable},readonly=#GET{readonly}})]\r
+                       [(#INCLURE{fond=saisies/#ENV{type_saisie},env,nom=[(#ENV{nom}|saisie_nom2name)], disable=#GET{disable},readonly=#GET{readonly},describedby=[(#ENV*{explication}|?{[explication_(#ENV{nom}|saisie_nom2name)]})]})]\r
                        [(#ENV{disable_avec_post}|et{#ENV{disable_avec_post}|!={non}}|oui)<input type='hidden' name='[(#ENV{nom}|saisie_nom2name)]' value="#ENV{valeur,#ENV{defaut}}" />]\r
                        #ENV**{inserer_fin}\r
-               </li>\r
+               </[(#ENV{saisies_base_conteneur,[(#VAL{li}|saisie_balise_structure_formulaire)]})]>\r
        ]\r
 ]\r
index cf44b7b..d0e8d66 100644 (file)
@@ -19,7 +19,7 @@ Par défaut ne liste que ceux des rubriques à la racine (secteurs)
                label=<:plugin:label_articles:>,\r
                multiple=oui})]\r
 ]\r
-<select name="#ENV{nom}[(#ENV{multiple}|?{[(#VAL{&#91;&#93;}|html_entity_decode)]})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][(#ENV{multiple}|oui) multiple="multiple" size="#ENV{size,10}"][ (#ENV*{attributs})]>\r
+<select name="#ENV{nom}[(#ENV{multiple}|?{\[\]})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][(#ENV{multiple}|oui) multiple="multiple" size="#ENV{size,10}"][ (#ENV*{attributs})]>\r
        [(#ENV{cacher_option_intro}|ou{#ENV{multiple}}|non)\r
                <option value="0">[(#ENV{option_intro})]</option>]\r
                <BOUCLE_articles_originaux_racine(ARTICLES){id_rubrique}{origine_traduction}>\r
index a394ed6..39d4ed8 100644 (file)
 [(#ENV{multiple}|oui)\r
        [(#SET{valeur,[(#ENV*{valeur}|is_array|?{[(#ENV*{valeur})],[(#ENV*{valeur}|explode{','})]})]})]\r
 ]\r
-<select name="#ENV{nom}[(#ENV{multiple}|?{[(#VAL{&#91;&#93;}|html_entity_decode)]})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][(#ENV{multiple}|oui) multiple="multiple" size="#ENV{size,10}"][ disabled="(#ENV{disable})"][ (#ENV*{attributs})]>\r
+<select name="#ENV{nom}[(#ENV{multiple}|?{\[\]})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][(#ENV{multiple}|oui) multiple="multiple" size="#ENV{size,10}"][ disabled="(#ENV{disable})"][ (#ENV*{attributs})]>\r
        [(#ENV{cacher_option_intro}|ou{#ENV{multiple}}|non)\r
                <option value="">[(#ENV{option_intro})]</option>]\r
        <BOUCLE_auteurs(AUTEURS){statut ?}{statut != 5poubelle}{webmestre ?}{par #ENV{tri,#ARRAY{num nom, nom}}|is_array|?{#ENV{tri,#ARRAY{num nom, nom}}|implode{','},#ENV{tri,#ARRAY{num nom, nom}}}}{tout}>\r
-               #SET{image_statut, #GET{bonhomme_statut}|table_valeur{#STATUT}}\r
+               #SET{image_statut, #GET{bonhomme_statut/#STATUT}}\r
                [(#ENV{multiple}|oui)\r
                <option value="#ID_AUTEUR"[(#ID_AUTEUR|in_array{#ENV{valeur_forcee,#GET{valeur,#ENV{defaut,#ARRAY}}}}|oui) selected="selected"][(#GET{image_statut}|oui)[style="background:url((#CHEMIN{prive/images/#GET{image_statut}})) no-repeat left; padding-left: 20px;"]]>#NOM</option>]\r
                [(#ENV{multiple}|non)\r
index 3a570c2..16b486e 100644 (file)
@@ -14,8 +14,8 @@
                label_case=<:plugin:activer:>,
                explication=<:plugin:explication_afficher_liste:>})]
 ]#SET{valeur,#ENV{valeur_forcee,#ENV{valeur}}|is_null|?{#ENV{defaut},#ENV{valeur_forcee,#ENV{valeur}}}}
-<div class="choix">
+<div class="choix[ (#ENV{class})]">
        [(#ENV{disable}|non)<input type="hidden" name="#ENV{nom}" value="#ENV{valeur_non,''}" />]
-       <input type="checkbox" name="#ENV{nom}" class="checkbox" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ (#GET{valeur}|=={#ENV{valeur_oui,on}}|oui)checked="checked"] value="#ENV{valeur_oui,on}" [ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][ (#ENV*{attributs})]/>
+       <input type="checkbox" name="#ENV{nom}" class="checkbox" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ (#GET{valeur}|=={#ENV{valeur_oui,on}}|oui)checked="checked"] value="#ENV{valeur_oui,on}" [ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][ aria-describedby="(#ENV{describedby})"][ (#ENV*{attributs})]/>
        [<label for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[(#GET{valeur}|=={#ENV{valeur_oui,on}}|oui)class="on"]>(#ENV*{label_case})</label>]
 </div>
index 98523a7..6be83dc 100644 (file)
@@ -34,6 +34,21 @@ options:
         options:
           nom: 'defaut'
           label: '<:saisies:option_defaut_label:>'
+      -
+        saisie: 'input'
+        options:
+          nom: 'valeur_oui'
+          label: '<:saisies:option_valeur_oui_label:>'
+          explication: '<:saisies:option_valeur_oui_explication:>'
+          size: 50
+          defaut: 'on'
+      -
+        saisie: 'input'
+        options:
+          nom: 'valeur_non'
+          label: '<:saisies:option_valeur_non_label:>'
+          explication: '<:saisies:option_valeur_non_explication:>'
+          size: 50
   -
     saisie: 'fieldset'
     options:
@@ -91,6 +106,12 @@ options:
           nom: 'info_obligatoire'
           label: '<:saisies:option_info_obligatoire_label:>'
           explication: '<:saisies:option_info_obligatoire_explication:>'
+      -
+        saisie: 'input'
+        options:
+          nom: 'class'
+          label: '<:saisies:option_class_label:>'
+          size: 50
   -
     saisie: 'fieldset'
     options:
index 78709a2..c14e0a9 100644 (file)
 ]
 [(#ENV{tout_selectionner}|oui)
        <div class="[(#ENV{choix,choix})][ (#ENV{choix,choix})_tout_selectionner ]none-nojs">
-               <input type="checkbox" name="#ENV{nom}_tout" class="checkbox" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_tout" value="on" onChange="if (jQuery(this).attr('checked')=='checked') jQuery(this).parent('div').parent().find('input').attr('checked','checked'); else jQuery(this).parent('div').parent().find('input').removeAttr('checked');"/>
+               <input type="checkbox" name="#ENV{nom}_tout" class="checkbox" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_tout" value="on" onChange="if (jQuery(this).prop('checked')==true) jQuery(this).parent('div').parent().find('input').prop('checked',true); else jQuery(this).parent('div').parent().find('input').prop('checked',false);"/>
                <label for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_tout"><:saisies:tout_selectionner:></label>
        </div>
 ]
 
 <BOUCLE_checkbox(POUR){tableau #GET{datas}}>
 <div class="#ENV{choix,choix}[ (#ENV{choix,choix})_#CLE]">[(#SET{id,champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_[(#COMPTEUR_BOUCLE|concat{'-',#CLE}|md5)]})]
-       <input type="checkbox" name="#ENV{nom}[]" class="checkbox" id="#GET{id}"[ (#CLE|in_array{#ENV{valeur_forcee,#GET{valeur,#GET{defaut}}}}|oui)checked="checked"] value="#CLE"[(#CLE|in_array{#GET{disabled}}|oui) disabled="disabled"][ (#ENV*{attributs})] />
+       <input type="checkbox" name="#ENV{nom}[]" class="checkbox" id="#GET{id}"[ (#CLE|in_array{#ENV{valeur_forcee,#GET{valeur,#GET{defaut}}}}|oui)checked="checked"] value="#CLE"[(#CLE|in_array{#GET{disabled}}|oui) disabled="disabled"][ aria-describedby="(#ENV{describedby})"][ (#ENV*{attributs})] />
        <label for="#GET{id}"[(#CLE|in_array{#ENV{valeur_forcee,#GET{valeur,#GET{defaut}}}}|oui)class="on"]>#VALEUR</label>
 </div>
 </BOUCLE_checkbox>
 [(#ENV{choix_alternatif}|oui)
 <div class="#ENV{choix,choix} choix_alternatif[ (#ENV{choix,choix})_alternatif]">
-    <input name="#ENV{nom}[(#VAL{91}|chr)]choix_alternatif[(#VAL{93}|chr)]" id="[champ_(#ENV{id,#ENV{nom}}|saisie_nom2classe)_choix_alternatif]" />
+    <input name="#ENV{nom}\[choix_alternatif\]" id="[champ_(#ENV{id,#ENV{nom}}|saisie_nom2classe)_choix_alternatif]"[ value="(#ENV{datas}|saisies_trouver_choix_alternatif{#GET{valeur}})"] />
+    
     <label for="[champ_(#ENV{id,#ENV{nom}}|saisie_nom2classe)_choix_alternatif]">
          [(#ENV{choix_alternatif_label, <:saisies:option_choix_alternatif_label_defaut:>})]
     </label>
+    [<p class="explication">
+      (#ENV{choix_alternatif_explication})
+    </p>]
 </div>
 ]
 </B_checkbox>
diff --git a/www/plugins/saisies/saisies/choisir_objets_edit.html b/www/plugins/saisies/saisies/choisir_objets_edit.html
new file mode 100644 (file)
index 0000000..94dc4bd
--- /dev/null
@@ -0,0 +1,21 @@
+[(#REM)
+    Saisie spécifique à SPIP 3
+    qui permet de choisir parmis des
+    objets éditoriaux éditables de SPIP,
+    les affichant avec des checkbox
+]
+
+[(#REM) defaut peut être une chaine (plusieurs valeurs ou pas) qu'on sait décomposer ]
+#SET{defaut, #ENV{defaut}|saisies_chaine2tableau}
+
+[(#REM) valeur doit être un tableau ! ]
+#SET{valeur, #ENV{valeur}|saisies_valeur2tableau}
+
+<BOUCLE_objets(POUR){tableau #REM|lister_tables_objets_edit}{cle !IN #ENV{exclus,''}}>
+#SET{id,#ENV{nom}|replace{\W,'_'}|concat{'_',#VALEUR{url_edit}}}
+<div class="choix choix_#VALEUR{url_edit}">
+    <input type="checkbox"  id="#GET{id}" name="#ENV{nom}\[\]" value="#VALEUR{url_edit}"[(#ENV{selected}|=={all}|ou{#VALEUR{url_edit}|in_any{#ENV{selected}}})checked="checked"] />
+    <label for="#GET{id}">[(#VALEUR{texte_objets}|_T)]</label>
+</div>
+</BOUCLE_objets>
+<input type="hidden" name="#ENV{nom}[]" value="" />
index 392a817..ecbe13e 100644 (file)
@@ -1,65 +1,72 @@
-[(#REM) \r
-       Zone de saisie de date utilsant le dateur de Bonux si présent.\r
-       Sur les sites en HTML5, utilise type="date"\r
-       sur le input, et type="text" par défaut pour les autres.\r
-       Pour tous on utilise class="date" et class="heure" pour activer le dateur.\r
-       \r
-       La valeur fournie peut être :\r
-       - au format spip jj/mm/aaaa (date uniquement)\r
-       - au format SQL aaaa-mm-jj (date uniquement)\r
-       - au format SQL aaaa-mm-jj hh:mm:ss (date et heure)\r
-       - un tableau avec une entrée "date" et une entrée "heure" séparée, au format SQL (date et heure obligatoire)\r
-       \r
-       Pour utiliser les heures, il faut utiliser l'option "horaire=oui".\r
-       \r
-       La date est proposée à l'affichage au format jj/mm/aaaa.\r
-]\r
-\r
-[(#REM) Initialisation de la valeur ]\r
-#SET{valeur,#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}}\r
-\r
-[(#REM) Regex de date SQL ]\r
-#SET{date_sql,"[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]( [0-9][0-9]:[0-9][0-9]:[0-9][0-9])?"}\r
-\r
-[(#REM) Si la valeur est un tableau, on considère que c'est date et heure dans un format déjà reconnu (j/m/a ou SQL) ]\r
-[(#GET{valeur}|is_array|oui)\r
-       #SET{date, #GET{valeur}|table_valeur{date}}\r
-       #SET{heure, #GET{valeur}|table_valeur{heure}}\r
-]\r
-\r
-[(#REM) Si la valeur est une chaîne, on regarde si SQL ]\r
-[(#GET{valeur}|is_string|oui)\r
-       [(#REM) Par défaut (date uniquement au format SPIP) la date c'est toute la valeur ]\r
-       #SET{date, #GET{valeur}}\r
-       #SET{heure, ''}\r
-       \r
-       [(#REM) Si c'est bien une date SQL ]\r
-       [(#GET{valeur}|match{#GET{date_sql}}|oui)\r
-               [(#REM) Si la date est complètement 0, on met des valeurs vides ]\r
-               [(#GET{valeur}|=={0000-00-00 00:00:00}|oui)\r
-                       #SET{date, ''}\r
-                       #SET{heure, ''}\r
-               ]\r
-               [(#GET{valeur}|=={0000-00-00 00:00:00}|non)\r
-                       #SET{date, #GET{valeur}|affdate{d/m/Y}}\r
-                       #SET{heure, #GET{valeur}|affdate{H:i}}\r
-               ]\r
-       ]\r
-]\r
-\r
-[(#INCLURE{fond=saisies/input,\r
-       env,\r
-       nom=#ENV{nom}[(#ENV{horaire}|?{&#91;date&#93;})],\r
-       valeur=#GET{date},\r
-       type=#HTML5|?{date,text},\r
-       class=[(#ENV{class}) ]date})]\r
-[(#ENV{horaire}|oui)\r
-[(#INCLURE{fond=saisies/input,\r
-       env,\r
-       nom=#ENV{nom}&#91;heure&#93;,\r
-       valeur=#GET{heure},\r
-       size=4,\r
-       maxlength=5,\r
-       class=[(#ENV{class}) ]heure})]\r
-]\r
-[(#ENV{disable}|non|et{#ENV{readonly}|non})[(#INCLURE{fond=formulaires/dateur/inc-dateur, heure_pas=#ENV{heure_pas,30}})]]\r
+[(#REM)
+       Zone de saisie de date utilsant le dateur de Bonux si présent.
+       Sur les sites en HTML5, utilise type="date"
+       sur le input, et type="text" par défaut pour les autres.
+       Pour tous on utilise class="date" et class="heure" pour activer le dateur.
+
+       La valeur fournie peut être :
+       - au format spip jj/mm/aaaa (date uniquement)
+       - au format SQL aaaa-mm-jj (date uniquement)
+       - au format SQL aaaa-mm-jj hh:mm:ss (date et heure)
+       - un tableau avec une entrée "date" et une entrée "heure" séparée, au format SQL (date et heure obligatoire)
+
+       Pour utiliser les heures, il faut utiliser l'option "horaire=oui".
+
+       La date est proposée à l'affichage au format jj/mm/aaaa.
+
+       La valeur retournée n'est pas nécessairement au format SQL.
+       Le cas échéant, il faut la normaliser avant enregistrement en base.
+]
+
+[(#REM) Initialisation de la valeur ]
+#SET{autodater,''}
+[(#ENV{class}|=={'autodater'}|oui)
+       #SET{autodater,#VAL{Y-m-d H:i:s}|date}
+]
+#SET{valeur,#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut,#GET{autodater}}}}}
+
+[(#REM) Regex de date SQL ]
+#SET{date_sql,"[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]( [0-9][0-9]:[0-9][0-9]:[0-9][0-9])?"}
+
+[(#REM) Si la valeur est un tableau, on considère que c'est date et heure dans un format déjà reconnu (j/m/a ou SQL) ]
+[(#GET{valeur}|is_array|oui)
+       #SET{date, #GET{valeur/date}}
+       #SET{heure, #GET{valeur/heure}}
+]
+
+[(#REM) Si la valeur est une chaîne, on regarde si SQL ]
+[(#GET{valeur}|is_string|oui)
+       [(#REM) Par défaut (date uniquement au format SPIP) la date c'est toute la valeur ]
+       #SET{date, #GET{valeur}}
+       #SET{heure, ''}
+
+       [(#REM) Si c'est bien une date SQL ]
+       [(#GET{valeur}|match{#GET{date_sql}}|oui)
+               [(#REM) Si la date est complètement 0, on met des valeurs vides ]
+               [(#GET{valeur}|=={0000-00-00 00:00:00}|oui)
+                       #SET{date, ''}
+                       #SET{heure, ''}
+               ]
+               [(#GET{valeur}|=={0000-00-00 00:00:00}|non)
+                       #SET{date, #GET{valeur}|affdate{d/m/Y}}
+                       #SET{heure, #GET{valeur}|affdate{H:i}}
+               ]
+       ]
+]
+
+[(#INCLURE{fond=saisies/input,
+       env,
+       nom=#ENV{nom}[(#ENV{horaire}|?{\[date\]})],
+       valeur=#GET{date},
+       type=text,
+       class=[(#ENV{class}) ]date})]
+[(#ENV{horaire}|oui)
+[(#INCLURE{fond=saisies/input,
+       env,
+       nom=#ENV{nom}\[heure\],
+       valeur=#GET{heure},
+       size=4,
+       maxlength=5,
+       class=[(#ENV{class}) ]heure})]
+]
+[(#ENV{disable}|non|et{#ENV{readonly}|non})[(#INCLURE{fond=formulaires/dateur/inc-dateur, heure_pas=#ENV{heure_pas,30}})]]
index b756b4a..d280e2f 100644 (file)
@@ -2,7 +2,7 @@
 
        /!\ Cette saisie nécessite du javascript pour fonctionner.
                En absence de JS, seul un champ de saisie texte est affiché.
-       
+
        Cette saisie cree 3 champs a partir d'une date au format datetime mysql.
        A chaque modification, un champ cache est modifie, contenant la date au format datetime.
 
@@ -11,7 +11,7 @@
        - size_(jour|mois|annee)
        - maxlength_(jour|mois|annee)
        - datetime, par défaut oui. Si pas oui, utilisera une date au format aaaa-mm-jj au lieu d'un datetime mysql
-       
+
        [(#SAISIE{date_jour_mois_annee, date_naissance,
                label=Date de naissance
        })]
@@ -25,21 +25,21 @@ function activer_dateur_#GET{id}() {
        jour = '\
                <div class="choix">\
                        [<label for="champ_#ENV{nom}_jour">(#ENV{label_jour,<:saisies:label_jour:>})</label>]\
-                       <input type="text" name="#ENV{nom}_jour" class="text date_jour[ (#ENV{class})]" id="champ_#ENV{nom}_jour"[ value="(#GET{valeur}|journum)"][ size="(#ENV{size_jour, 2})"][ maxlength="(#ENV{maxlength_jour, 2})"][ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][(#ENV{obligatoire}|et{#HTML5}|oui) required="required"] onChange="changer_la_date(this);" />\
+                       <input type="text" name="#ENV{nom}_jour" class="text date_jour[ (#ENV{class})]" id="champ_#ENV{nom}_jour"[ value="(#GET{valeur}|journum)"][ size="(#ENV{size_jour, 2})"][ maxlength="(#ENV{maxlength_jour, 2})"][ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][(#ENV{obligatoire}|et{#HTML5}|oui) required="required"] onChange="saisies_date_jour_mois_annee_changer_date(this, \'#ENV{datetime,oui}\');" onkeyup="if (this.value.length == 2 && jQuery.inArray(event.keyCode, [9,16]) == -1) {jQuery(this).parent().next().find(\'input\').focus();}" />\
                </div>\
        ';
-               
+
        mois = '\
                <div class="choix">\
                        [<label for="champ_#ENV{nom}_mois">(#ENV{label_mois,<:saisies:label_mois:>})</label>]\
-                       <input type="text" name="#ENV{nom}_mois" class="text date_mois[ (#ENV{class})]" id="champ_#ENV{nom}_mois"[ value="(#GET{valeur}|mois)"][ size="(#ENV{size_mois, 2})"][ maxlength="(#ENV{maxlength_mois, 2})"][ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][(#ENV{obligatoire}|et{#HTML5}|oui) required="required"] onChange="changer_la_date(this);" />\
+                       <input type="text" name="#ENV{nom}_mois" class="text date_mois[ (#ENV{class})]" id="champ_#ENV{nom}_mois"[ value="(#GET{valeur}|mois)"][ size="(#ENV{size_mois, 2})"][ maxlength="(#ENV{maxlength_mois, 2})"][ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][(#ENV{obligatoire}|et{#HTML5}|oui) required="required"] onChange="saisies_date_jour_mois_annee_changer_date(this, \'#ENV{datetime,oui}\');" onkeyup="if (this.value.length == 2 && jQuery.inArray(event.keyCode, [9,16]) == -1) {jQuery(this).parent().next().find(\'input\').focus();}" />\
                </div>\
        ';
-       
+
        annee = '\
                <div class="choix">\
                        [<label for="champ_#ENV{nom}_annee">(#ENV{label_annee,<:saisies:label_annee:>})</label>]\
-                       <input type="text" name="#ENV{nom}_annee" class="text date_annee[ (#ENV{class})]" id="champ_#ENV{nom}_annee"[ value="(#GET{valeur}|annee)"][ size="(#ENV{size_annee, 4})"][ maxlength="(#ENV{maxlength_annee, 4})"][ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][(#ENV{obligatoire}|et{#HTML5}|oui) required="required"] onChange="changer_la_date(this);" />\
+                       <input type="text" name="#ENV{nom}_annee" class="text date_annee[ (#ENV{class})]" id="champ_#ENV{nom}_annee"[ value="(#GET{valeur}|annee)"][ size="(#ENV{size_annee, 4})"][ maxlength="(#ENV{maxlength_annee, 4})"][ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][(#ENV{obligatoire}|et{#HTML5}|oui) required="required"] onChange="saisies_date_jour_mois_annee_changer_date(this, \'#ENV{datetime,oui}\');" />\
                </div>\
        ';
 
@@ -50,22 +50,6 @@ function activer_dateur_#GET{id}() {
        jQuery('##GET{id}').after(jour + mois + annee);
 }
 
-function changer_la_date(me) {
-       var li = jQuery(me).parents('li'),
-               jour = jQuery.trim(li.find('.date_jour').val()),
-               mois = jQuery.trim(li.find('.date_mois').val()),
-               annee = jQuery.trim(li.find('.date_annee').val()),
-               date = jQuery.trim(li.find('.datetime').val());
-       while(jour.length < 2) {jour = '0' + jour;}
-       while(mois.length < 2) {mois = '0' + mois;}
-       while(annee.length < 4) {annee = '0' + annee;}
-       [(#ENV{datetime,oui}|=={oui}|oui)
-       date = annee + '-' + mois + '-' + jour + date.substring(10);]
-       [(#ENV{datetime,oui}|=={oui}|non)
-       date = annee + '-' + mois + '-' + jour;]
-       li.find('.datetime').attr('value',date);
-}
-
 jQuery(document).ready(function(){
        activer_dateur_#GET{id}();
 });
index 76571c9..853026e 100644 (file)
@@ -1,8 +1,8 @@
        #SET{type_choix, #ENV{type_choix,tous}}
 #SET{choix_destinataires, #ENV*{choix_destinataires,#ARRAY}}
-#SET{erreurs,#ENV**{erreurs}|table_valeur{#ENV{nom}}}
+#SET{erreurs,#ENV**{erreurs/#ENV{nom}}}
 <BOUCLE_choix(CONDITION){si #GET{choix_destinataires}|count|<{2}|ou{#GET{type_choix}|=={tous}}}>
-<li class="editer editer_[(#ENV{nom})][ (#ENV{obligatoire})][ (#GET{erreurs}|oui)erreur][ (#ENV{li_class})][ saisie_(#ENV{type_saisie})]" [(#ENV{tout_afficher}|!={oui}|?{style="display:none;"})][ data-id="(#ENV{id_saisie})"]>
+<[(#ENV{saisies_base_conteneur,[(#VAL{li}|saisie_balise_structure_formulaire)]})] class="editer editer_[(#ENV{nom})][ (#ENV{obligatoire})][ (#GET{erreurs}|oui)erreur][ (#ENV{conteneur_class,#ENV{li_class}})][ saisie_(#ENV{type_saisie})]" [(#ENV{tout_afficher}|!={oui}|?{style="display:none;"})][ data-id="(#ENV{id_saisie})"]>
        #ENV*{inserer_debut}
        [<label for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]">(#ENV*{label})[<span class='obligatoire'>(#ENV{obligatoire}|oui)[(#ENV*{info_obligatoire}|is_null|?{<:info_obligatoire_02:>,#ENV*{info_obligatoire}})]</span>]</label>]
        [<span class='erreur_message'>(#GET{erreurs})</span>]
        [(#REM) Si 0 ou 1 destinataire possible ou si c'est tous, le destinataire est défini automatiquement ]
        <BOUCLE_tous(AUTEURS){tous}{id_auteur IN #GET*{choix_destinataires}}{par num nom,nom}>
        [(#ENV{tout_afficher}|!={oui}|oui)
-       <input type="hidden" name="#ENV{nom}&#91;&#93;" value="#ID_AUTEUR" />
+       <input type="hidden" name="#ENV{nom}\[\]" value="#ID_AUTEUR" />
        ]
        [(#ENV{tout_afficher}|!={oui}|non)
        <div class="choix">
-               <input type="checkbox" name="#ENV{nom}&#91;&#93;" class="checkbox"
+               <input type="checkbox" name="#ENV{nom}\[\]" class="checkbox"
                        readonly="readonly" checked="checked"
                        id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_destinataire#ID_AUTEUR"
                        value="#ID_AUTEUR"[ (#ID_AUTEUR|in_array{#ENV{valeur_forcee,#ENV*{valeur,#ENV*{defaut,#ARRAY}}}}|oui)checked="checked"]
        <input [(#ENV{tout_afficher}|!={oui}|?{type="hidden",type="text" readonly="readonly"})] name="#ENV{nom}[]" value="1" />
        <//B_tous>
        #ENV*{inserer_fin}
-</li>
+</[(#ENV{saisies_base_conteneur,[(#VAL{li}|saisie_balise_structure_formulaire)]})]>
 </BOUCLE_choix>
-<li class="editer editer_[(#ENV{nom})][ (#ENV{obligatoire})][ (#GET{erreurs}|oui)erreur][ (#ENV{li_class})][ saisie_(#ENV{type_saisie})]">
+
+[(#SET{valeur,[(#ENV*{valeur}|is_array|?{#ENV*{valeur},[(#ENV*{valeur}|explode{','})]})]})]
+<[(#ENV{saisies_base_conteneur,[(#VAL{li}|saisie_balise_structure_formulaire)]})] class="editer editer_[(#ENV{nom})][ (#ENV{obligatoire})][ (#GET{erreurs}|oui)erreur][ (#ENV{conteneur_class,#ENV{li_class}})][ saisie_(#ENV{type_saisie})]">
+
        #ENV*{inserer_debut}
        [<label[(#GET{type_choix}|=={un}|ou{#GET{type_choix}|=={un_radio}}|oui)for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"]>(#ENV*{label})[<span class='obligatoire'>(#ENV{obligatoire}|oui)[(#ENV*{info_obligatoire}|is_null|?{<:info_obligatoire_02:>,#ENV*{info_obligatoire}})]</span>]</label>]
        [<span class='erreur_message'>(#GET{erreurs})</span>]
        [(#REM) Sinon on propose le choix, en select ou en checkbox suivant l'option "type_choix" ]
        <B_destinataires>
                [(#GET{type_choix}|=={un}|oui)
-                       <select name="#ENV{nom}&#91;&#93;" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]">
-                [<option value="">(#ENV{option_intro})</option>]
+                       <select name="#ENV{nom}\[\]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]">
+                               [<option value="">(#ENV{option_intro})</option>]
                ]
                <BOUCLE_destinataires(AUTEURS){tous}{id_auteur IN #GET*{choix_destinataires}}{par num nom,nom}>
                        [(#GET{type_choix}|=={plusieurs}|ou{#GET{type_choix}|=={un_radio}}|oui)
                                <div class="choix">
-                                       <input type="[(#GET{type_choix}|=={plusieurs}|?{checkbox,radio})]" name="#ENV{nom}&#91;&#93;" class="checkbox"
+                                       <input type="[(#GET{type_choix}|=={plusieurs}|?{checkbox,radio})]" name="#ENV{nom}\[\]" class="checkbox"
                                                id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_destinataire#ID_AUTEUR"
-                                               value="#ID_AUTEUR"[ (#ID_AUTEUR|in_array{#ENV*{valeur_forcee,#ENV*{valeur,#ENV*{defaut,#ARRAY}}}}|oui)checked="checked"]
+                                               value="#ID_AUTEUR"[ (#ID_AUTEUR|in_array{#ENV*{valeur_forcee,#GET*{valeur,#ENV*{defaut,#ARRAY}}}}|oui)checked="checked"]
                                        />
-                                       <label for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_destinataire#ID_AUTEUR"[(#ID_AUTEUR|in_array{#ENV*{valeur_forcee,#ENV*{valeur,#ENV*{defaut,#ARRAY}}}}|oui)class="on"]>#NOM</label>
+                                       <label for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_destinataire#ID_AUTEUR"[(#ID_AUTEUR|in_array{#ENV*{valeur_forcee,#GET*{valeur,#ENV*{defaut,#ARRAY}}}}|oui)class="on"]>#NOM</label>
                                </div>
                        ]
                        [(#GET{type_choix}|=={un}|oui)
-                               <option value="#ID_AUTEUR"[ (#ID_AUTEUR|in_array{#ENV{valeur_forcee,#ENV*{valeur,#ENV*{defaut,#ARRAY}}}}|oui)selected="selected"]>#NOM</option>
+                               <option value="#ID_AUTEUR"[ (#ID_AUTEUR|in_array{#ENV{valeur_forcee,#GET*{valeur,#ENV*{defaut,#ARRAY}}}}|oui)selected="selected"]>#NOM</option>
                        ]
-            
+
                </BOUCLE_destinataires>
                [(#GET{type_choix}|=={un}|oui)
                        </select>
                ]
        </B_destinataires>
        #ENV*{inserer_fin}
-</li>
+</[(#ENV{saisies_base_conteneur,[(#VAL{li}|saisie_balise_structure_formulaire)]})]>
 <//B_choix>
index 868fa0b..5dc1ff1 100644 (file)
@@ -1,5 +1,7 @@
-<li class="explication[ explication_(#ENV{nom})][ (#ENV{li_class})][ saisie_(#ENV{type_saisie})]"[ data-id="(#ENV{id_saisie})"]>
+<[(#ENV{saisies_base_conteneur,[(#VAL{li}|saisie_balise_structure_formulaire)]})] class="editer pleine_largeur editer_explication[ explication_(#ENV{nom})][ (#ENV{conteneur_class,#ENV{li_class}})][ saisie_(#ENV{type_saisie})]"[ data-id="(#ENV{id_saisie})"]>
        #ENV*{inserer_debut}
-       [(#ENV*{texte}|propre)]
+       <div class="explication">
+               [(#ENV*{texte}|propre)]
+       </div>
        #ENV*{inserer_fin}
-</li>
+</[(#ENV{saisies_base_conteneur,[(#VAL{li}|saisie_balise_structure_formulaire)]})]>
index 45e7de9..6aeed47 100644 (file)
@@ -41,7 +41,7 @@ options:
       -
         saisie: 'input'
         options:
-          nom: 'li_class'
+          nom: 'conteneur_class'
           label: '<:saisies:option_class_label:>'
           size: 50
 defaut:
index 83c600a..6f775da 100644 (file)
@@ -8,27 +8,25 @@
 [(#GET{erreurs_fieldset}|oui)
        #SET{plie, ''}
 ]
-#SET{erreur_ici,#ENV**{erreurs}|table_valeur{#ENV{nom}}}
+#SET{erreur_ici,#ENV**{erreurs/#ENV{nom}}}
 
-<li class="fieldset[ fieldset_(#ENV{nom})][ (#ENV{li_class})][ saisie_(#ENV{type_saisie})][ (#GET{pliable})[ (#GET{plie})]]"[ data-id="(#ENV{id_saisie})"]> 
+<[(#ENV{saisies_base_conteneur,[(#VAL{li}|saisie_balise_structure_formulaire)]})] class="fieldset[ fieldset_(#ENV{nom})][ (#ENV{conteneur_class,#ENV{li_class}})][ saisie_(#ENV{type_saisie})][ (#GET{pliable})[ (#GET{plie})]]"[ data-id="(#ENV{id_saisie})"]>
        #ENV*{inserer_debut}
        <fieldset>
-               [[(#GLOBALS{debut_intertitre,<h3>}|inserer_attribut{class,legend})]
-                       [(#ENV{icone}|oui)
-                               [(#REM) en SPIP 3 on cherche avant tout dans le chemin des images
-                               ]#SET{icone,''}[(#SPIP_VERSION|version_compare{2.9, >}|oui)#SET{icone,#CHEMIN_IMAGE{#ENV{icone}}]
-                               [<img src="(#GET{icone}|sinon{#CHEMIN{#ENV{icone}}}
-                                       |image_reduire{#ENV{taille_icone}|sinon{16}}
-                                       |extraire_attribut{src})" alt="#ENV*{label}" /> ]
-                       ]
-                       <span>(#ENV*{label})</span>#GLOBALS{fin_intertitre,</h3>}]
+
+               [(#ENV{label}|oui)
+                       [(#REM) Récupérer le tag qui sera utilisé pour la légende ]
+                       [(#SET{tag, [(#ENV*{tagfield,#GLOBALS{debut_intertitre,<h3>}}|inserer_attribut{class,legend})]})]
+                       [(#INCLURE{fond=inclure/fieldset_legend, env}|wrap{#GET{tag}})]
+               ]
+
                [<span class='erreur_message'>(#GET{erreur_ici})</span>]
                [<p class='explication'>(#ENV*{explication})</p>]
                [(#ENV{saisies}|is_array|oui)
-               <ul>
+               <[(#ENV{saisies_base_conteneur,[(#VAL{ul}|saisie_balise_structure_formulaire)]})] class="editer-groupe">
                        #INCLURE{fond=#ENV{fond_generer,"inclure/generer_saisies"}, env, saisies=#ENV{saisies}, from_fieldset='on'}
-               </ul>
+               </[(#ENV{saisies_base_conteneur,[(#VAL{ul}|saisie_balise_structure_formulaire)]})]>
                ]
        </fieldset>
        #ENV*{inserer_fin}
-</li>
+</[(#ENV{saisies_base_conteneur,[(#VAL{li}|saisie_balise_structure_formulaire)]})]>
index f63ed80..2d3789c 100644 (file)
@@ -58,7 +58,7 @@ options:
       -
         saisie: 'input'
         options:
-          nom: 'li_class'
+          nom: 'conteneur_class'
           label: '<:saisies:option_class_label:>'
           size: 50
 defaut:
index 909e1b7..a363ee2 100644 (file)
@@ -20,7 +20,7 @@
                multiple=oui,
                table_liaison=articles})] 
 ]
-<select name="#ENV{nom}[(#ENV{multiple}|?{[(#VAL{&#91;&#93;}|html_entity_decode)]})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][(#ENV{multiple}|oui) multiple="multiple" size="#ENV{size,10}"][ disabled="(#ENV{disable})"][ (#ENV*{attributs})]>
+<select name="#ENV{nom}[(#ENV{multiple}|?{\[\]})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][(#ENV{multiple}|oui) multiple="multiple" size="#ENV{size,10}"][ disabled="(#ENV{disable})"][ (#ENV*{attributs})]>
        [(#ENV{cacher_option_intro}|ou{#ENV{multiple}}|non)
                <option value="0">[(#ENV{option_intro})]</option>]
        <BOUCLE_groupes(GROUPES_MOTS){id_groupe !IN #ENV{groupes_exclus,#ARRAY}}{par num titre, titre}{tables_liees==#VAL{'(^|,)'}|concat{#ENV{table_liaison,.*},'($|,)'}}{tout}>
index 866c70a..d82b209 100644 (file)
@@ -1,12 +1,12 @@
-<li class="editer editer_[(#ENV{nom})][ (#ENV{li_class})][ saisie_(#ENV{type_saisie})]" [(#ENV{tout_afficher}|!={oui}|?{style="display:none;"})][ data-id="(#ENV{id_saisie})"]>
+<[(#ENV{saisies_base_conteneur,[(#VAL{li}|saisie_balise_structure_formulaire)]})] class="editer editer_[(#ENV{nom})][ (#ENV{conteneur_class,#ENV{li_class}})][ saisie_(#ENV{type_saisie})]" [(#ENV{tout_afficher}|!={oui}|?{style="display:none;"})][ data-id="(#ENV{id_saisie})"]>
        #ENV*{inserer_debut}
        [(#ENV{tout_afficher}|!={oui}|oui)
-       <input type="hidden" name="#ENV{nom}" id="champ_[(#ENV{nom}|saisie_nom2classe)]" value="#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}"[ (#ENV*{attributs})] />
+       <input type="hidden"[ class="(#ENV{class})"] name="#ENV{nom}" id="champ_[(#ENV{nom}|saisie_nom2classe)]" value="#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}"[ (#ENV*{attributs})] />
        ]
        [(#ENV{tout_afficher}|!={oui}|non)
        [<label for="champ_[(#ENV{nom}|saisie_nom2classe)]">(#ENV*{label})[<span class='obligatoire'>(#ENV{obligatoire}|oui)[(#ENV*{info_obligatoire}|is_null|?{<:info_obligatoire_02:>,#ENV*{info_obligatoire}})]</span>]</label>]
-       <input type="text" name="#ENV{nom}" id="champ_[(#ENV{nom}|saisie_nom2classe)]" value="#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}" readonly="readonly" />
+       <input type="text"[ class="(#ENV{class})"] name="#ENV{nom}" id="champ_[(#ENV{nom}|saisie_nom2classe)]" value="#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}" readonly="readonly" />
        ]
        
        #ENV*{inserer_fin}
-</li>
+</[(#ENV{saisies_base_conteneur,[(#VAL{li}|saisie_balise_structure_formulaire)]})]>
index 2120917..679767d 100644 (file)
@@ -16,6 +16,12 @@ options:
           label: '<:saisies:option_label_label:>'
           explication: '<:saisies:option_label_explication:>'
           size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'class'
+          label: '<:saisies:option_class_label:>'
+          size: 50
       -
         saisie: 'input'
         options:
index 33ec302..4dcea59 100644 (file)
@@ -45,4 +45,4 @@
 ]</BOUCLE_selection>\r
 </datalist>\r
 </B_selection>\r
-<input type="#GET{type}" name="#ENV{nom}" class="#GET{type}[ (#ENV{class})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ list="(#GET{list_id})"][ value="(#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}})"][ size="(#ENV{size})"][ maxlength="(#ENV{maxlength})"][ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][ placeholder="(#ENV{placeholder})"][(#ENV{obligatoire}|et{#ENV{obligatoire}!={non}}|et{#HTML5}|oui) required="required"][(#ENV{autofocus}|et{#ENV{autofocus}!={non}}|et{#HTML5}|oui) autofocus="autofocus"][(#GET{val_autocomplete}|find{#ENV{autocomplete}}|oui) autocomplete="#ENV{autocomplete}"][ (#ENV*{attributs})] />\r
+<input type="#GET{type}" name="#ENV{nom}" class="#GET{type}[ (#ENV{class})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ list="(#GET{list_id})"][ value="(#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}})"][ size="(#ENV{size})"][ maxlength="(#ENV{maxlength})"][ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][ placeholder="(#ENV{placeholder})"][(#HTML5|oui)[(#ENV{obligatoire}|et{#ENV{obligatoire}!={non}}|oui) required="required"][ min="(#ENV{min})"][ max="(#ENV{max})"][ step="(#ENV{step})"][(#ENV{autofocus}|et{#ENV{autofocus}!={non}}|oui) autofocus="autofocus"]][(#GET{val_autocomplete}|find{#ENV{autocomplete}}|oui) autocomplete="#ENV{autocomplete}"][ aria-describedby="(#ENV{describedby})"][ (#ENV*{attributs})] />\r
index 62a43ab..efa8f79 100644 (file)
@@ -22,6 +22,12 @@ options:
           nom: 'defaut'\r
           label: '<:saisies:option_defaut_label:>'\r
           size: 50\r
+      -\r
+        saisie: 'input'\r
+        options:\r
+          nom: 'placeholder'\r
+          label: '<:saisies:option_placeholder_label:>'\r
+          size: 50\r
       -\r
         saisie: 'input'\r
         options:\r
index 1765f87..5e6e22e 100644 (file)
@@ -2,17 +2,25 @@
 
   Parametres :
   - class : classe(s) css ajoutes au select
+  - multiple : si quelquechose est passe, le select est multiple, sinon, c'est un select simple (seulement si non multiple)
   - option_intro : chaine de langue de la premiere ligne vide ? (defaut:"")
   - cacher_option_intro : pas de premier option vide  (defaut:"")
   - id_groupe : groupe des mots clés à afficher
+                Note : si ce groupe de mot est configuré pour avoir plusieurs
+                mots possibles, alors une liste de cases à cocher est
+                affichée au lieu du sélecteur habituel.
+                Utiliser `forcer_select=oui` pour éviter ce comportement
+                (par exemple en utilisation avec Chosen)
   - defaut : valeur par defaut si pas présente dans l'environnement
   - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement
+  - size : la taille du sélecteur (seulement si multiple)
+  - groupes_exclus : liste d'ids de groupe dont on ne veut pas les mots
   
   Exemple d'appel :
        [(#SAISIE{mot, en_region,
                label=<:plugin:en_region:>})]
 ]
-<BOUCLE_multiples(GROUPES_MOTS){id_groupe}{unseul!=oui}>
+<BOUCLE_multiples(GROUPES_MOTS){id_groupe}{unseul!=oui}{si #ENV{forcer_select}|non}>
        [(#REM) defaut peut être une chaine (plusieurs valeurs ou pas) qu'on sait décomposer ]
        #SET{defaut, #ENV{defaut}|saisies_chaine2tableau}
        [(#REM) valeur doit être un tableau ! ]
        </div>
        </BOUCLE_checkmots>
 </BOUCLE_multiples>
-<select name="#ENV{nom}" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][ value="(#ENV{valeur_forcee,#ENV{valeur}})"][ disabled="(#ENV{disable})"][ (#ENV*{attributs})]>
-[(#ENV{cacher_option_intro}|non)
+
+#SET{groupe,''}
+<select name="#ENV{nom}[(#ENV{multiple}|?{\[\]})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][(#ENV{multiple}|oui) multiple="multiple"[ size="(#ENV{size})"]][ disabled="(#ENV{disable})"][ (#ENV*{attributs})]>
+[(#ENV{cacher_option_intro}|ou{#ENV{multiple}}|non)
        <option value="">[(#ENV{option_intro})]</option>]
-<BOUCLE_mots(MOTS){par id_groupe, num titre, titre}{id_groupe ?}>[(#ENV{id_groupe,''}|non)
-       [<optgroup label="(#TYPE|unique|attribut_html)" />]
-       ]<option value="#ID_MOT" [(#ID_MOT|=={#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}}|oui) selected="selected"]>#TITRE</option>
+<BOUCLE_mots(MOTS){par id_groupe, num titre, titre}{id_groupe ?}{!id_groupe IN #ENV{groupes_exclus}}>[(#ENV{id_groupe,''}|non|et{#GET{groupe}|=={#ID_GROUPE}|non}|oui)
+       [(#GET{groupe}|intval|>{0}|oui)</optgroup>]
+       [<optgroup label="(#TYPE|attribut_html)">][(#SET{groupe,#ID_GROUPE})]]
+       [(#ENV{multiple}|non)<option value="#ID_MOT"[(#ID_MOT|=={#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}}|oui)selected="selected"]>#TITRE</option>]
+       [(#ENV{multiple}|oui)<option value="#ID_MOT"[(#ID_MOT|in_array{#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut,#ARRAY}}}}|oui)selected="selected"]>#TITRE</option>]
 </BOUCLE_mots>
+       [(#ENV{id_groupe,''}|non)
+       </optgroup>]
 </select>
 <//B_multiples>
index 03a33f9..9889094 100644 (file)
 ]
 #SET{valeur,#ENV{valeur_forcee,#ENV{valeur}}|is_null|?{#ENV{defaut},#ENV{valeur_forcee,#ENV{valeur}}}}
 <div class="choix">
-       <input type="radio" name="#ENV{nom}" class="radio" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_oui"[ (#GET{valeur}|=={#ENV{valeur_oui,on}}|oui)checked="checked"] value="#ENV{valeur_oui,on}" [ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"] />
+       <input type="radio" name="#ENV{nom}" class="radio" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_oui"[ (#GET{valeur}|=={#ENV{valeur_oui,on}}|oui)checked="checked"] value="#ENV{valeur_oui,on}" [ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][ aria-describedby="(#ENV{describedby})"] />
        <label for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_oui"[(#GET{valeur}|=={#ENV{valeur_oui,on}}|oui)class="on"]><:item_oui:></label>
 </div>
 <div class="choix">
-       <input type="radio" name="#ENV{nom}" class="radio" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_non"[ (#GET{valeur}|=={#ENV{valeur_non,''}}|ou{#GET{valeur}|non})checked="checked"] value="#ENV{valeur_non,''}" [ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"] />
+       <input type="radio" name="#ENV{nom}" class="radio" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_non"[ (#GET{valeur}|=={#ENV{valeur_non,''}}|ou{#GET{valeur}|non})checked="checked"] value="#ENV{valeur_non,''}" [ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][ aria-describedby="(#ENV{describedby})"] />
        <label for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_non"[(#GET{valeur}|=={#ENV{valeur_non,''}}|ou{#GET{valeur}|non})class="on"]><:item_non:></label>
 </div>
index 569476e..1d896d8 100644 (file)
@@ -3,17 +3,17 @@
        #SET{tab_par_nom,#ENV{formulaire}|saisies_lister_par_nom}
        #SET{padding,0}
        #SET{liste_parents,#ARRAY{0,''}}
-       <BOUCLE_parcours(POUR){tableau #GET{tab}}>
+       <BOUCLE_parcours(DATA){source tableau, #GET{tab}}>
        #SET{saisie,#VALEUR}
-       <option value="[(#GET{saisie}|table_valeur{options}|table_valeur{nom})]" style="padding-left:#GET{padding}px" [(#ENV{valeur,#ENV{saisie_a_positionner}}|=={#GET{saisie}|table_valeur{options}|table_valeur{nom}}|oui)selected="selected"]>
-               [(#GET{saisie}|table_valeur{options}|table_valeur{label}
-                       |sinon{#GET{saisie}|table_valeur{options}|table_valeur{nom}}
+       <option value="#GET{saisie/options/nom}" style="padding-left:#GET{padding}px" [(#ENV{valeur,#ENV{saisie_a_positionner}}|=={#GET{saisie/options/nom}}|oui)selected="selected"]>
+               [(#GET{saisie/options/label}
+                       |sinon{#GET{saisie/options/nom}}
                        |couper{60})]
        </option>
-               <BOUCLE_test_enfants(CONDITION){si #GET{saisie}|table_valeur{options}|table_valeur{nom}|!={#ENV{saisie_a_positionner}}|et{#GET{saisie}|table_valeur{saisies}|is_array}}>
-               #SET{tab,#GET{saisie}|table_valeur{saisies}}
+               <BOUCLE_test_enfants(CONDITION){si #GET{saisie/options/nom}|!={#ENV{saisie_a_positionner}}|et{#GET{saisie/saisies}|is_array}}>
+               #SET{tab,#GET{saisie/saisies}}
                #SET{padding,#GET{padding}|plus{20}}
-               #SET_PUSH{liste_parents,#GET{saisie}|table_valeur{options}|table_valeur{nom}}
+               #SET_PUSH{liste_parents,#GET{saisie/options/nom}}
                <BOUCLE_parcours_recursif(BOUCLE_parcours)/>
                #SET{padding,#GET{padding}|moins{20}}
                #SET{liste_parents,#GET{liste_parents}|array_slice{0,-1}}
                        <:saisies:construire_position_fin_formulaire:>
                ]
                [(#GET{parent}|oui)
-                       #SET{groupe,#GET{tab_par_nom}|table_valeur{#GET{parent}}}
-                       #SET{groupe,#GET{groupe}|table_valeur{options}|table_valeur{label}|sinon{#GET{groupe}|table_valeur{options}|table_valeur{nom}}|couper{60}}
+                       #SET{groupe,#GET{tab_par_nom/#GET{parent}}}
+                       #SET{groupe,#GET{groupe/options/label}|sinon{#GET{groupe/options/nom}}|couper{60}}
                        <:saisies:construire_position_fin_groupe{groupe=#GET{groupe}}:>
                ]
        </option>
        </B_parcours>
        #SET{parent, #GET{liste_parents}|table_valeur{#GET{liste_parents}|count|moins{1}}}
-       <option value="[#GET{parent}]" style="padding-left:#GET{padding}px; font-style:italic;">
+       <option value="\[#GET{parent}\]" style="padding-left:#GET{padding}px; font-style:italic;">
                [(#GET{parent}|non)
                        <:saisies:construire_position_fin_formulaire:>
                ]
                [(#GET{parent}|oui)
-                       #SET{groupe,#GET{tab_par_nom}|table_valeur{#GET{parent}}}
-                       #SET{groupe,#GET{groupe}|table_valeur{options}|table_valeur{label}|sinon{#GET{groupe}|table_valeur{options}|table_valeur{nom}}|couper{60}}
+                       #SET{groupe,#GET{tab_par_nom/#GET{parent}}}
+                       #SET{groupe,#GET{groupe/options/label}|sinon{#GET{groupe/options/nom}}|couper{60}}
                        <:saisies:construire_position_fin_groupe{groupe=#GET{groupe}}:>
                ]
        </option>
index 7f63b89..09293a7 100644 (file)
@@ -22,8 +22,9 @@
 #SET{datas, #GET{datas}|is_string|?{(#GET{datas}|saisies_chaine2tableau), #GET{datas}}}
 
 <BOUCLE_radio(POUR){tableau #GET{datas}}>
-<div class="#ENV{choix,choix}[ (#ENV{choix,choix})_#CLE]">
-       <input type="radio" name="#ENV{nom}" class="radio" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_#COMPTEUR_BOUCLE"[ (#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}|=={#CLE}|oui)checked="checked"] value="#CLE" [ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"] />
+#SET{disabled, #ENV{disable}|is_string|?{#ENV{disable}, #ENV{disable/#CLE}}}
+<div class="#ENV{choix,choix}[ (#ENV{choix,choix})_#CLE][ (#ENV{class})]">
+       <input type="radio" name="#ENV{nom}" class="radio" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_#COMPTEUR_BOUCLE"[ (#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}|=={#CLE}|oui)checked="checked"] value="#CLE"[(#GET{disabled}|oui) disabled="disabled"][ readonly="(#ENV{readonly})"][ aria-describedby="(#ENV{describedby})"] />
        <label for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_#COMPTEUR_BOUCLE"[(#ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}|=={#CLE}|oui)class="on"]>#VALEUR</label>
 </div>
 </BOUCLE_radio>
index 151f001..80caa80 100644 (file)
@@ -88,6 +88,12 @@ options:
           label: '<:saisies:option_attention_label:>'
           explication: '<:saisies:option_attention_explication:>'
           size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'class'
+          label: '<:saisies:option_class_label:>'
+          size: 50
       -
         saisie: 'input'
         options:
index 2083bfc..f58bd75 100644 (file)
   - cacher_option_intro : pas de premier option vide  (defaut:"")\r
   - defaut : valeur par defaut si pas présente dans l'environnement\r
   - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement\r
+  - size : la taille du sélecteur (seulement si multiple)\r
         \r
   Exemple d'appel :\r
        [(#SAISIE{secteur,secteur_region,\r
                label=<:plugin:secteur_region:>,\r
                multiple=oui})] \r
 ]\r
-<select name="#ENV{nom}[(#ENV{multiple}|?{[(#VAL{&#91;&#93;}|html_entity_decode)]})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][(#ENV{multiple}|oui) multiple="multiple"][ disabled="(#ENV{disable})"][ (#ENV*{attributs})]>\r
+<select name="#ENV{nom}[(#ENV{multiple}|?{\[\]})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][(#ENV{multiple}|oui) multiple="multiple"[ size="(#ENV{size})"]][ disabled="(#ENV{disable})"][ (#ENV*{attributs})]>\r
        [(#ENV{cacher_option_intro}|ou{#ENV{multiple}}|non)\r
                <option value="">[(#ENV{option_intro})]</option>]\r
        <BOUCLE_secteurs(RUBRIQUES){racine}{par num titre, titre}{tout}>\r
index 62c24fe..c20793d 100644 (file)
 [(#REM) Si la mediatheque est presente ]
 [(#PLUGIN{medias}|oui)
 
-[(#SET{exec,[(#PLUGIN{medias,version}|<{2}|?{'portfolio','popin-choisir_document'})]})]
-[(#SET{zajax,[(#PLUGIN{medias,version}|<{2}|?{'','var_zajax=contenu&'})]})]
-[(#SET{mod_fn,#ENV{_modalbox_retour}|?{'2','1'}})]
+[(#SET{mod_fn,#ENV{_modalbox_retour}|?{2,1}})]
 
 [(#REM) Parcourir les documents de l'article ]
 [(#ENV{id_article}|oui)
 <p class='boutons'><input class='submit' type="button" name="parcourir" value="<:saisies:bouton_parcourir_docs_article:>"
        onclick="jQuery.modalboxload(
-               '[(#URL_ECRIRE{#GET{exec},#GET{zajax}selectfunc=mediaselect#GET{mod_fn}_#ENV{nom}&id_article=#ENV{id_article}[&media=(#ENV{media})][&extension=(#ENV{extension})]})]',
+               '[(#URL_ECRIRE{popin-choisir_document,var_zajax=contenu&selectfunc=mediaselect#GET{mod_fn}_#ENV{nom}&id_article=#ENV{id_article}[&media=(#ENV{media})][&extension=(#ENV{extension})]})]',
                {autoResize: true}
        );"
 /></p>
@@ -39,7 +37,7 @@
 [(#ENV{id_rubrique}|oui)
 <p class='boutons'><input class='submit' type="button" name="parcourir" value="<:saisies:bouton_parcourir_docs_rubrique:>"
        onclick="jQuery.modalboxload(
-               '[(#URL_ECRIRE{#GET{exec},#GET{zajax}selectfunc=mediaselect#GET{mod_fn}_#ENV{nom}&id_rubrique=#ENV{id_rubrique}[&media=(#ENV{media})][&extension=(#ENV{extension})]})]',
+               '[(#URL_ECRIRE{popin-choisir_document,var_zajax=contenu&selectfunc=mediaselect#GET{mod_fn}_#ENV{nom}&id_rubrique=#ENV{id_rubrique}[&media=(#ENV{media})][&extension=(#ENV{extension})]})]',
                {autoResize: true}
        );"
 /></p>
@@ -49,7 +47,7 @@
 [(#ENV{id_breve}|oui)
 <p class='boutons'><input class='submit' type="button" name="parcourir" value="<:saisies:bouton_parcourir_docs_breve:>"
        onclick="jQuery.modalboxload(
-               '[(#URL_ECRIRE{#GET{exec},#GET{zajax}selectfunc=mediaselect#GET{mod_fn}_#ENV{nom}&id_breve=#ENV{id_breve}[&media=(#ENV{media})][&extension=(#ENV{extension})]})]',
+               '[(#URL_ECRIRE{popin-choisir_document,var_zajax=contenu&selectfunc=mediaselect#GET{mod_fn}_#ENV{nom}&id_breve=#ENV{id_breve}[&media=(#ENV{media})][&extension=(#ENV{extension})]})]',
                {autoResize: true}
        );"
 /></p>
 [(#REM) Parcourir toute la mediatheque ]
 <p class='boutons'><input class='submit' type="button" name="parcourir" value="<:saisies:bouton_parcourir_mediatheque:>"
        onclick="jQuery.modalboxload(
-               '[(#URL_ECRIRE{#GET{exec},#GET{zajax}selectfunc=mediaselect#GET{mod_fn}_#ENV{nom}[&media=(#ENV{media})][&extension=(#ENV{extension})]})]',
+               '[(#URL_ECRIRE{popin-choisir_document,var_zajax=contenu&selectfunc=mediaselect#GET{mod_fn}_#ENV{nom}[&media=(#ENV{media})][&extension=(#ENV{extension})]})]',
                {autoResize: true}
        );"
 /></p>
 
+
 [(#ENV{_modalbox_retour}|non)
 <script type="text/javascript">
        function mediaselect1_#ENV{nom}(id){
                jQuery.modalboxclose();
-               jQuery("#champ_#ENV{nom}").attr('value',id).focus();
+               jQuery("#champ_#ENV{id,#ENV{nom}}").attr('value',id).focus();
        };
 </script>
 ]
diff --git a/www/plugins/saisies/saisies/selecteur_document.yaml b/www/plugins/saisies/saisies/selecteur_document.yaml
new file mode 100644 (file)
index 0000000..0683f66
--- /dev/null
@@ -0,0 +1,59 @@
+titre: '<:saisies:saisie_selecteur_document_titre:>'\r
+description: '<:saisies:saisie_selecteur_document:>'\r
+icone: 'images/saisies_selecteur_article.png'\r
+options:\r
+ -\r
+   saisie: 'fieldset'\r
+   options:\r
+     nom: 'description'\r
+     label: '<:saisies:option_groupe_description:>'\r
+   saisies:\r
+     -\r
+       saisie: 'input'\r
+       options:\r
+         nom: 'label'\r
+         label: '<:saisies:option_label_label:>'\r
+         explication: '<:saisies:option_label_explication:>'\r
+         size: 50\r
+     -\r
+       saisie: 'input'\r
+       options:\r
+         nom: 'explication'\r
+         label: '<:saisies:option_explication_label:>'\r
+         explication: '<:saisies:option_explication_explication:>'\r
+         size: 50\r
+ -\r
+   saisie: 'fieldset'\r
+   options:\r
+     nom: 'affichage'\r
+     label: '<:saisies:option_groupe_affichage:>'\r
+   saisies:\r
+     -\r
+       saisie: 'input'\r
+       options:\r
+         nom: 'attention'\r
+         label: '<:saisies:option_attention_label:>'\r
+         explication: '<:saisies:option_attention_explication:>'\r
+         size: 50\r
+     -\r
+       saisie: 'input'\r
+       options:\r
+         nom: 'info_obligatoire'\r
+         label: '<:saisies:option_info_obligatoire_label:>'\r
+         explication: '<:saisies:option_info_obligatoire_explication:>'\r
+ -\r
+   saisie: 'fieldset'\r
+   options:\r
+     nom: 'obligatoire'\r
+     label: '<:saisies:option_groupe_validation:>'\r
+   saisies:\r
+     -\r
+       saisie: 'oui_non'\r
+       options:\r
+         nom: 'obligatoire'\r
+         label: '<:saisies:option_obligatoire_label:>'\r
+defaut:\r
+ options:\r
+   label: '<:saisies:saisie_selecteur_document_titre:>'\r
+   # champs extras (definition du champ sql)\r
+   sql: "text DEFAULT '' NOT NULL"
\ No newline at end of file
index 1d295ea..3787e45 100644 (file)
@@ -15,7 +15,7 @@ Par défaut ne liste que ceux des rubriques à la racine (secteurs)
   - valeur_forcee : valeur utilisee meme si une valeur est dans l'environnement\r
 \r
 ]\r
-<select name="#ENV{nom}[(#ENV{multiple}|?{[(#VAL{&#91;&#93;}|html_entity_decode)]})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][(#ENV{multiple}|oui) multiple="multiple" size="#ENV{size,10}"][ (#ENV*{attributs})]>\r
+<select name="#ENV{nom}[(#ENV{multiple}|?{\[\]})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][(#ENV{multiple}|oui) multiple="multiple" size="#ENV{size,10}"][ (#ENV*{attributs})]>\r
        [(#ENV{cacher_option_intro}|ou{#ENV{multiple}}|non)\r
                <option value="0">[(#ENV{option_intro})]</option>]\r
        <BOUCLE_secteurs(RUBRIQUES){racine}{par num titre, titre}{tout}>\r
index 33cf777..dfb1733 100644 (file)
 ]
 
 [(#REM) datas peut être une chaine qu'on sait décomposer ]
-#SET{datas, #ENV*{datas}}
-#SET{datas, #GET{datas}|is_string|?{(#GET{datas}|saisies_chaine2tableau), #GET{datas}}}
+#SET{datas, #ENV*{datas}|saisies_chaine2tableau}
+
 [(#REM) Attention, la valeur ou la valeur forcée peut être une chaine vide. On doit donc tester avec is_null. ]
 #SET{valeur,#ENV{valeur_forcee}|is_null|?{#ENV{valeur}|is_null|?{#ENV{defaut},#ENV{valeur}},#ENV{valeur_forcee}}}
 
-<select name="#ENV{nom}" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][ disabled="(#ENV{disable})"][ size="(#ENV{size})"][ (#ENV*{attributs})]>
+<select [(#HTML5|oui)[(#ENV{obligatoire}|et{#ENV{obligatoire}!={non}}|oui) required="required"]] name="#ENV{nom}" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][ disabled="(#ENV{disable})"][ size="(#ENV{size})"][ (#ENV*{attributs})]>
 [(#ENV{cacher_option_intro}|non)<option value="">[(#ENV{option_intro})]</option>]
 <BOUCLE_selection(POUR){tableau #GET{datas}}>
        <B_cond>
index 2b3590d..210c484 100644 (file)
@@ -93,6 +93,12 @@ options:
           label: '<:saisies:option_attention_label:>'
           explication: '<:saisies:option_attention_explication:>'
           size: 50
+      -
+        saisie: 'input'
+        options:
+          nom: 'class'
+          label: '<:saisies:option_class_label:>'
+          size: 50
       -
         saisie: 'input'
         options:
index 79be9d7..6cd36b3 100755 (executable)
 <select name="#ENV{nom}[]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]" multiple="multiple"[ class="(#ENV{class})"][ disabled="(#ENV{disable})"][ size="(#ENV{size,10})"][ (#ENV*{attributs})]>
 [(#ENV{cacher_option_intro}|non)<option value="">[(#ENV{option_intro})]</option>]
 <BOUCLE_selection(POUR){tableau #GET{datas}}>
+       <B_cond>
+       <optgroup label="#CLE">
+       <BOUCLE_cond(CONDITION){si #VALEUR|is_array}><BOUCLE_optgroup(POUR){tableau #VALEUR}>
+               <option value="#CLE" [(#CLE|in_array{#ENV{valeur_forcee,#GET{valeur,#GET{defaut}}}}|oui) selected="selected"]>#VALEUR</option>
+       </BOUCLE_optgroup></BOUCLE_cond>
+       </optgroup>
+       </B_cond>
        <option value="#CLE" [(#CLE|in_array{#ENV{valeur_forcee,#GET{valeur,#GET{defaut}}}}|oui) selected="selected"]>#VALEUR</option>
+       <//B_cond>
 </BOUCLE_selection>
 </select>
index af489cd..494caf2 100644 (file)
@@ -28,7 +28,7 @@ options:
         options:
           nom: 'datas'
           label: '<:saisies:option_datas_label:>'
-          explication: '<:saisies:option_datas_explication:>'
+          explication: '<:saisies:option_datas_sous_groupe_explication:>'
           rows: 10
           cols: 50
       -
index f64480f..5534881 100644 (file)
@@ -19,7 +19,7 @@
                multiple=oui})]
 ]
 
-<select name="#ENV{nom}[(#ENV{multiple}|?{[(#VAL{&#91;&#93;}|html_entity_decode)]})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][(#ENV{multiple}|oui) multiple="multiple" size="[(#ENV{size,#ENV{afficher_tous}|?{4,3}})]"][ disabled="(#ENV{disable})"][ (#ENV*{attributs})]>
+<select name="#ENV{nom}[(#ENV{multiple}|?{\[\]})]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]"[ class="(#ENV{class})"][(#ENV{multiple}|oui) multiple="multiple" size="[(#ENV{size,#ENV{afficher_tous}|?{4,3}})]"][ disabled="(#ENV{disable})"][ (#ENV*{attributs})]>
        [(#ENV{cacher_option_intro}|ou{#ENV{multiple}}|non)
                <option value="">[(#ENV{option_intro})]</option>]
        [(#ENV{multiple}|oui)
index 6353221..c0f07b2 100644 (file)
@@ -18,5 +18,5 @@
 [(#REM) Si la valeur est un tableau, le plugin sait le transformer en chaine, plutôt que d'afficher "Array" ]
 #SET{valeur, #ENV{valeur_forcee,#ENV{valeur,#ENV{defaut}}}}
 #SET{valeur, #GET{valeur}|is_array|?{(#GET{valeur}|saisies_tableau2chaine), #GET{valeur}}}
-<textarea name="#ENV{nom}" class="[(#ENV{class}) ][inserer_barre_(#ENV{inserer_barre}) ][(#ENV{previsualisation}|oui)inserer_previsualisation]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]" rows="[(#ENV{rows,20})]" cols="[(#ENV{cols,40})]"[ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][(#ENV{obligatoire}|et{#ENV{obligatoire}!={non}}|et{#HTML5}|oui) required="required"][ (#ENV*{attributs})]>
+<textarea name="#ENV{nom}" class="[(#ENV{class}) ][inserer_barre_(#ENV{inserer_barre}) ][(#ENV{previsualisation}|oui)inserer_previsualisation]" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]" rows="[(#ENV{rows,20})]" cols="[(#ENV{cols,40})]"[ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][ placeholder="(#ENV{placeholder})"][(#ENV{obligatoire}|et{#ENV{obligatoire}!={non}}|et{#HTML5}|oui) required="required"][ aria-describedby="(#ENV{describedby})"][ (#ENV*{attributs})]>
 #GET{valeur}</textarea>
index 35ab465..9430125 100644 (file)
@@ -22,6 +22,12 @@ options:
           nom: 'defaut'
           label: '<:saisies:option_defaut_label:>'
           rows: 4
+      -
+        saisie: 'textarea'
+        options:
+          nom: 'placeholder'
+          label: '<:saisies:option_placeholder_label:>'
+          size: 50
       -
         saisie: 'input'
         options:
index 365eb74..1d53ec8 100644 (file)
 ]
 #SET{valeur,#ENV{valeur_forcee,#ENV{valeur}}|is_null|?{#ENV{defaut},#ENV{valeur_forcee,#ENV{valeur}}}}
 <div class="choix">
-       <input type="radio" name="#ENV{nom}" class="radio" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_true"[ (#GET{valeur}|=={true}|oui)checked="checked"] value="true" [ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"] />
+       <input type="radio" name="#ENV{nom}" class="radio" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_true"[ (#GET{valeur}|=={true}|oui)checked="checked"] value="true" [ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][ aria-describedby="(#ENV{describedby})"] />
        <label for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_true"[(#GET{valeur}|=={true}|oui)class="on"]><:item_oui:></label>
 </div>
 <div class="choix">
-       <input type="radio" name="#ENV{nom}" class="radio" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_false"[ (#GET{valeur}|=={false}|oui)checked="checked"] value="false" [ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"] />
+       <input type="radio" name="#ENV{nom}" class="radio" id="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_false"[ (#GET{valeur}|=={false}|oui)checked="checked"] value="false" [ disabled="(#ENV{disable})"][ readonly="(#ENV{readonly})"][ aria-describedby="(#ENV{describedby})"] />
        <label for="champ_[(#ENV{id,#ENV{nom}}|saisie_nom2classe)]_false"[(#GET{valeur}|=={false}|oui)class="on"]><:item_non:></label>
 </div>
index 41f7590..31da91b 100644 (file)
@@ -1,5 +1,11 @@
 <?php
 
+/**
+ * Déclaration de fonctions pour les squelettes
+ *
+ * @package SPIP\Saisies\Fonctions
+**/
+
 if (!defined('_ECRIRE_INC_VERSION')) return;
 
 include_spip('inc/saisies');
@@ -7,6 +13,37 @@ include_spip('balise/saisie');
 // picker_selected (spip 3)
 include_spip('formulaires/selecteur/generique_fonctions');
 
+/**
+ * A partir de SPIP 3.1
+ * - ul.editer-groupe deviennent des div.editer-groupe
+ * - li.editer devient div.editer
+ * @param $tag
+ *   ul ou li
+ * @return string
+ *   $tag initial ou div
+ */
+function saisie_balise_structure_formulaire($tag){
+       static $is_div=null;
+       if (is_null($is_div)){
+               $version = explode(".",$GLOBALS['spip_version_branche']);
+               if ($version[0]>3 OR ($version[0]==3 AND $version[1]>0))
+                       $is_div = true;
+       }
+       if ($is_div) return "div";
+       return $tag;
+}
+// variante plus simple a ecrire dans les squelettes
+// [(#DIV|sinon{ul})]
+if (!function_exists('balise_DIV_dist')
+  and $version = explode(".",$GLOBALS['spip_version_branche'])
+  and ($version[0]>3 OR ($version[0]==3 AND $version[1]>0))){
+       function balise_DIV_dist($p){
+               $p->code = "'div'";
+               $p->interdire_scripts = false;
+               return $p;
+       }
+}
+
 /**
  * Traiter la valeur de la vue en fonction du env
  * si un traitement a ete fait en amont (champs extra) ne rien faire
@@ -22,11 +59,13 @@ function saisie_traitement_vue($valeur,$env){
        if (!function_exists('propre'))
                include_spip('inc/texte');
 
+       $valeur = trim($valeur);
+
        // si traitement est renseigne, alors le champ est deja mis en forme
        // (saisies)
        // sinon on fait une mise en forme smart
-       if (!isset($env['traitements'])){
-               if (in_array($env['type_saisie'],array('textarea'))){
+       if ($valeur and !isset($env['traitements'])) {
+               if (in_array($env['type_saisie'], array('textarea'))) {
                        $valeur = propre($valeur);
                }
                else {
@@ -34,24 +73,32 @@ function saisie_traitement_vue($valeur,$env){
                }
        }
 
-       return trim($valeur);
+       return $valeur;
 }
 
 /**
  * Passer un nom en une valeur compatible avec une classe css
- * toto => toto,
- * toto/truc => toto_truc,
- * toto[truc] => toto_truc,
+ * 
+ * - toto => toto,
+ * - toto/truc => toto_truc,
+ * - toto[truc] => toto_truc
+ *
+ * @param string $nom
+ * return string
 **/
 function saisie_nom2classe($nom) {
        return str_replace(array('/', '[', ']', '&#91;', '&#93;'), array('_', '_', '', '_', ''), $nom);
 }
 
 /**
- * Passer un nom en une valeur compatible avec un name de formulaire
- * toto => toto,
- * toto/truc => toto[truc],
- * toto[truc] => toto[truc],
+ * Passer un nom en une valeur compatible avec un `name` de formulaire
+ * 
+ * - toto => toto,
+ * - toto/truc => toto[truc],
+ * - toto[truc] => toto[truc]
+ *
+ * @param string $nom
+ * return string
 **/
 function saisie_nom2name($nom) {
        if (false === strpos($nom, '/')) {
@@ -64,13 +111,17 @@ function saisie_nom2name($nom) {
 }
 
 /**
- * Balise beurk #GLOBALS{debut_intertitre}
- * qui retourne la globale PHP du même nom si elle existe
+ * Compile la balise `#GLOBALS{xxx}` qui retourne la valeur d'une vilaine variable globale de même nom si elle existe
  *
- * @param array $p
- *             Pile au niveau de la balise
- * @return array
- *             Pile complétée du code php de la balise.
+ * @example
+ *     ```
+ *     #GLOBALS{debut_intertitre}
+ *     ```
+ * 
+ * @param Champ $p
+ *     Pile au niveau de la balise
+ * @return Champ
+ *     Pile complétée du code php de la balise.
 **/
 function balise_GLOBALS_dist($p) {
        if (function_exists('balise_ENV'))
@@ -129,4 +180,30 @@ function picker_selected_par_objet($selected) {
 
        return $liste;
 }
-?>
+
+
+/**
+ * Lister les objets qui ont une url_edit renseignée et qui sont éditables.
+ *
+ * @return array Liste des objets :
+ *               index : nom de la table (spip_articles, spip_breves, etc.)
+ *               'type' : le type de l'objet ;
+ *               'url_edit' : l'url d'édition de l'objet ;
+ *               'texte_objets' : le nom humain de l'objet éditorial.
+ */
+function lister_tables_objets_edit()
+{
+    include_spip('base/abstract_sql');
+
+    $objets = lister_tables_objets_sql();
+    $objets_edit = array();
+
+    foreach ($objets as $objet => $definition) {
+        if (isset($definition['editable']) and isset($definition['url_edit']) and $definition['url_edit'] != '') {
+            $objets_edit[$objet] = array('type' => $definition['type'], 'url_edit' => $definition['url_edit'], 'texte_objets' => $definition['texte_objets']);
+        }
+    }
+    $objets_edit = array_filter($objets_edit);
+
+    return $objets_edit;
+}
index fa2590f..a64d7dc 100644 (file)
@@ -1,22 +1,29 @@
 <?php
 
+/**
+ * Déclaration systématiquement chargées
+ *
+ * @package SPIP\Saisies
+**/
+
 // Sécurité
 if (!defined('_ECRIRE_INC_VERSION')) return;
 
-/*
- * une fonction qui regarde si $texte est une chaine de langue
- * de la forme <:qqch:>
- * si oui applique _T()
- * si non applique typo() suivant le mode choisi
- *
- * @param mixed $valeur
- *     Une valeur à tester. Si c'est un tableau, la fonction s'appliquera récursivement dessus.
- * @param string $mode_typo
- *     Le mode d'application de la fonction typo(), avec trois valeurs possibles "toujours", "jamais" ou "multi".
- * @return mixed
- *     Retourne la valeur éventuellement modifiée.
- */
+
 if (!function_exists('_T_ou_typo')){
+       /**
+        * une fonction qui regarde si $texte est une chaine de langue
+        * de la forme <:qqch:>
+        * si oui applique _T()
+        * si non applique typo() suivant le mode choisi
+        *
+        * @param mixed $valeur
+        *     Une valeur à tester. Si c'est un tableau, la fonction s'appliquera récursivement dessus.
+        * @param string $mode_typo
+        *     Le mode d'application de la fonction typo(), avec trois valeurs possibles "toujours", "jamais" ou "multi".
+        * @return mixed
+        *     Retourne la valeur éventuellement modifiée.
+        */
        function _T_ou_typo($valeur, $mode_typo='toujours') {
                // Si la valeur est bien une chaine (et pas non plus un entier déguisé)
                if (is_string($valeur) and !intval($valeur)){
@@ -45,4 +52,3 @@ if (!function_exists('_T_ou_typo')){
        }
 }
 
-?>
index 61c364a..ef94504 100644 (file)
-<?php\r
-\r
-// Sécurité\r
-if (!defined("_ECRIRE_INC_VERSION")) return;\r
-\r
-function saisies_header_prive($flux){\r
-       $js = find_in_path('javascript/saisies.js');\r
-       $flux .= "\n<script type='text/javascript' src='$js'></script>\n";\r
-       $css = generer_url_public('saisies.css');\r
-       $flux .= "\n<link rel='stylesheet' href='$css' type='text/css' media='all' />\n";\r
-       $css_constructeur = find_in_path('css/formulaires_constructeur.css');\r
-       $flux .= "\n<link rel='stylesheet' href='$css_constructeur' type='text/css' />\n";\r
-       return $flux;\r
-}\r
-\r
-function saisies_affichage_final($flux){\r
-       if (($p = strpos($flux,"<!--!inserer_saisie_editer-->"))!==false){\r
-               // On insère la CSS devant le premier <link> trouvé\r
-               if (!$pi = strpos($flux, "<link") AND !$pi=strpos($flux, '</head'))\r
-                       $pi = $p; // si pas de <link inserer comme un goret entre 2 <li> de saisies\r
-               $css = generer_url_public('saisies.css');\r
-               $ins_css = "\n<link rel='stylesheet' href='$css' type='text/css' media='all' />\n";\r
-\r
-               if (strpos($flux,"saisie_date")!==false){//si on a une saisie de type date, on va charger les css de jquery_ui\r
-                   include_spip("jqueryui_pipelines");\r
-                       if (function_exists("jqueryui_dependances")){\r
-                               $ui_plugins = jqueryui_dependances(array("jquery.ui.datepicker"));\r
-\r
-                               array_push($ui_plugins,"jquery.ui.theme");\r
-                               foreach ($ui_plugins as $ui_plug){\r
-                                       $ui_plug_css = find_in_path("css/$ui_plug.css");\r
-                                       if (strpos($flux,"css/$ui_plug.css")===false){// si pas déjà chargé\r
-                                       $ins_css .= "\n<link rel='stylesheet' href='$ui_plug_css' type='text/css' media='all' />\n";\r
-                                       }\r
-                               }\r
-                       }\r
-               }\r
-\r
-               $flux = substr_replace($flux, $ins_css, $pi, 0);\r
-               // On insère le JS à la fin du <head>\r
-               $pos_head = strpos($flux, '</head');\r
-               $js = find_in_path('javascript/saisies.js');\r
-               $ins_js = "\n<script type='text/javascript' src='$js'></script>\n";\r
-               $flux = substr_replace($flux, $ins_js, $pos_head, 0);\r
-       }\r
-       return $flux;\r
-}\r
-\r
-// Déclaration des pipelines\r
-function saisies_saisies_autonomes($flux) { return $flux; }\r
-function saisies_formulaire_saisies($flux) { return $flux; }\r
-\r
-// Déclarer automatiquement les champs d'un CVT si on les trouve dans un tableau de saisies et s'ils ne sont pas déjà déclarés\r
-function saisies_formulaire_charger($flux){\r
-       // Si le flux data est inexistant, on quitte : Le CVT d'origine a décidé de ne pas continuer\r
-       if (!is_array($flux['data'])){\r
-               return $flux;\r
-       }\r
-\r
-       // Il faut que la fonction existe et qu'elle retourne bien un tableau\r
-       if (include_spip('inc/saisies')\r
-               and $saisies = saisies_chercher_formulaire($flux['args']['form'], $flux['args']['args'])\r
-       ){\r
-               // On ajoute au contexte les champs à déclarer\r
-               $contexte = saisies_lister_valeurs_defaut($saisies);\r
-               $flux['data'] = array_merge($contexte, $flux['data']);\r
-\r
-               // On ajoute le tableau complet des saisies\r
-               $flux['data']['_saisies'] = $saisies;\r
-       }\r
-       return $flux;\r
-}\r
-\r
-// Aiguiller CVT vers un squelette propre à Saisies lorsqu'on a déclaré des saisies et qu'il n'y a pas déjà un HTML\r
-function saisies_styliser($flux){\r
-       // Si on cherche un squelette de formulaire\r
-       if (strncmp($flux['args']['fond'],'formulaires/',12)==0\r
-               // Et qu'il y a des saisies dans le contexte\r
-               and isset($flux['args']['contexte']['_saisies'])\r
-               // Et que le fichier choisi est vide ou n'existe pas\r
-               and include_spip('inc/flock')\r
-               and $ext = $flux['args']['ext']\r
-               and lire_fichier($flux['data'].'.'.$ext, $contenu_squelette)\r
-               and !trim($contenu_squelette)\r
-       ){\r
-               $flux['data'] = preg_replace("/\.$ext$/", '', find_in_path("formulaires/inc-saisies-cvt.$ext"));\r
-       }\r
-\r
-       return $flux;\r
-}\r
-\r
-// Ajouter les vérifications déclarées dans la fonction "saisies" du CVT\r
-function saisies_formulaire_verifier($flux){\r
-       // Il faut que la fonction existe et qu'elle retourne bien un tableau\r
-       if (include_spip('inc/saisies') and $saisies = saisies_chercher_formulaire($flux['args']['form'], $flux['args']['args'])){\r
-               // On ajoute au contexte les champs à déclarer\r
-               $erreurs = saisies_verifier($saisies);\r
-               if ($erreurs and !isset($erreurs['message_erreur']))\r
-                       $erreurs['message_erreur'] = _T('saisies:erreur_generique');\r
-               $flux['data'] = array_merge($erreurs, $flux['data']);\r
-       }\r
-\r
-\r
-       return $flux;\r
-}\r
-\r
-?>\r
+<?php
+
+/**
+ * Utilisation des pipelines
+ *
+ * @package SPIP\Saisies\Pipelines
+**/
+
+// Sécurité
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+/**
+ * Ajoute les scripts JS et CSS de saisies dans l'espace privé
+ *
+ * @param string $flux 
+ * @return string
+**/
+function saisies_header_prive($flux){
+       $js = find_in_path('javascript/saisies.js');
+       $flux .= "\n<script type='text/javascript' src='$js'></script>\n";
+       $css = generer_url_public('saisies.css');
+       $flux .= "\n<link rel='stylesheet' href='$css' type='text/css' media='all' />\n";
+       $css_constructeur = find_in_path('css/formulaires_constructeur.css');
+       $flux .= "\n<link rel='stylesheet' href='$css_constructeur' type='text/css' />\n";
+       return $flux;
+}
+
+/**
+ * Ajoute les scripts JS et CSS de saisies dans l'espace public
+ *
+ * Ajoute également de quoi gérer le datepicker de la saisie date si
+ * celle-ci est utilisée dans la page.
+ * 
+ * @param string $flux 
+ * @return string
+**/
+function saisies_affichage_final($flux){
+       if (
+               $GLOBALS['html'] // si c'est bien du HTML
+               and ($p = strpos($flux,"<!--!inserer_saisie_editer-->")) !== false // et qu'on a au moins une saisie
+               and strpos($flux,'<head') !== false // et qu'on a la balise <head> quelque part
+       ){
+               // On insère la CSS devant le premier <link> trouvé
+               if (!$pi = strpos($flux, "<link") AND !$pi=strpos($flux, '</head')) {
+                       $pi = $p; // si pas de <link inserer comme un goret entre 2 <li> de saisies
+               }
+               $css = generer_url_public('saisies.css');
+               $ins_css = "\n<link rel='stylesheet' href='$css' type='text/css' media='all' />\n";
+
+               if (strpos($flux,"saisie_date")!==false){//si on a une saisie de type date, on va charger les css de jquery_ui
+                   include_spip("jqueryui_pipelines");
+                       if (function_exists("jqueryui_dependances")){
+                               $ui_plugins = jqueryui_dependances(array("jquery.ui.datepicker"));
+                               $theme_css = "jquery.ui.theme";
+                               $ui_css_dir = "css";
+                               // compatibilité SPIP 3.1 et jQuery UI 1.11
+                               $version = explode(".",$GLOBALS['spip_version_branche']);
+                               if ($version[0]>3 OR ($version[0]==3 AND $version[1]>0)) {
+                                       $theme_css = "theme";
+                                       $ui_css_dir = "css/ui";
+                               }
+                               array_push($ui_plugins,$theme_css);
+                               foreach ($ui_plugins as $ui_plug){
+                                       $ui_plug_css = find_in_path("$ui_css_dir/$ui_plug.css");
+                                       if (strpos($flux,"$ui_css_dir/$ui_plug.css")===false){// si pas déjà chargé
+                                               $ins_css .= "\n<link rel='stylesheet' href='$ui_plug_css' type='text/css' media='all' />\n";
+                                       }
+                               }
+                       }
+               }
+
+               $flux = substr_replace($flux, $ins_css, $pi, 0);
+               // On insère le JS à la fin du <head>
+               $pos_head = strpos($flux, '</head');
+               $js = find_in_path('javascript/saisies.js');
+               $ins_js = "\n<script type='text/javascript' src='$js'></script>\n";
+               $flux = substr_replace($flux, $ins_js, $pos_head, 0);
+       }
+       return $flux;
+}
+
+
+/**
+ * Déclarer automatiquement les champs d'un formulaire CVT qui déclare des saisies
+ *
+ * Recherche une fonction `formulaires_XX_saisies_dist` et l'utilise si elle
+ * est présente. Cette fonction doit retourner une liste de saisies dont on se
+ * sert alors pour calculer les champs utilisés dans le formulaire.
+ * 
+ * @param array $flux 
+ * @return array
+**/
+function saisies_formulaire_charger($flux){
+       // Si le flux data est inexistant, on quitte : Le CVT d'origine a décidé de ne pas continuer
+       if (!is_array($flux['data'])){
+               return $flux;
+       }
+
+       // Il faut que la fonction existe et qu'elle retourne bien un tableau
+       include_spip('inc/saisies');
+       $saisies = saisies_chercher_formulaire($flux['args']['form'], $flux['args']['args']);
+
+       if ($saisies) {
+               // On ajoute au contexte les champs à déclarer
+               $contexte = saisies_lister_valeurs_defaut($saisies);
+               $flux['data'] = array_merge($contexte, $flux['data']);
+
+               // On ajoute le tableau complet des saisies
+               $flux['data']['_saisies'] = $saisies;
+       }
+       return $flux;
+}
+
+/**
+ * Aiguiller CVT vers un squelette propre à Saisies lorsqu'on a déclaré des saisies et qu'il n'y a pas déjà un HTML
+ *
+ * Dans le cadre d'un formulaire CVT demandé, si ce formulaire a déclaré des saisies, et
+ * qu'il n'y a pas de squelette spécifique pour afficher le HTML du formulaire,
+ * alors on utilise le formulaire générique intégré au plugin saisie, qui calculera le HTML
+ * à partir de la déclaration des saisies indiquées.
+ * 
+ * @see saisies_formulaire_charger()
+ * 
+ * @param array $flux
+ * @return array
+**/
+function saisies_styliser($flux){
+       // Si on cherche un squelette de formulaire
+       if (strncmp($flux['args']['fond'],'formulaires/',12)==0
+               // Et qu'il y a des saisies dans le contexte
+               and isset($flux['args']['contexte']['_saisies'])
+               // Et que le fichier choisi est vide ou n'existe pas
+               and include_spip('inc/flock')
+               and $ext = $flux['args']['ext']
+               and lire_fichier($flux['data'].'.'.$ext, $contenu_squelette)
+               and !trim($contenu_squelette)
+       ){
+               $flux['data'] = preg_replace("/\.$ext$/", '', find_in_path("formulaires/inc-saisies-cvt.$ext"));
+       }
+
+       return $flux;
+}
+
+/**
+ * Ajouter les vérifications déclarées dans la fonction "saisies" du CVT
+ *
+ * Si un formulaire CVT a déclaré des saisies, on utilise sa déclaration
+ * pour effectuer les vérifications du formulaire.
+ *
+ * @see saisies_formulaire_charger()
+ * @uses saisies_verifier()
+ * 
+ * @param array $flux
+ *     Liste des erreurs du formulaire
+ * @return array
+ *     iste des erreurs
+ */
+function saisies_formulaire_verifier($flux){
+       // Il faut que la fonction existe et qu'elle retourne bien un tableau
+       include_spip('inc/saisies');
+       $saisies = saisies_chercher_formulaire($flux['args']['form'], $flux['args']['args']);
+       if ($saisies) {
+               // On ajoute au contexte les champs à déclarer
+               $erreurs = saisies_verifier($saisies);
+               if ($erreurs and !isset($erreurs['message_erreur']))
+                       $erreurs['message_erreur'] = _T('saisies:erreur_generique');
+               $flux['data'] = array_merge($erreurs, $flux['data']);
+       }
+
+       return $flux;
+}
+
+
index 7c2808a..8dcb131 100644 (file)
@@ -1,10 +1,10 @@
 <svn_revision>
 <text_version>
-Origine: file:///home/svn/repository/spip-zone/_plugins_/saisies
-Revision: 85015
-Dernier commit: 2014-10-05 13:00:06 +0200 
+Origine: file:///home/svn/repository/spip-zone/_plugins_/saisies/trunk
+Revision: 95750
+Dernier commit: 2016-03-04 03:21:42 +0100 
 </text_version>
-<origine>file:///home/svn/repository/spip-zone/_plugins_/saisies</origine>
-<revision>85015</revision>
-<commit>2014-10-05 13:00:06 +0200 </commit>
+<origine>file:///home/svn/repository/spip-zone/_plugins_/saisies/trunk</origine>
+<revision>95750</revision>
+<commit>2016-03-04 03:21:42 +0100 </commit>
 </svn_revision>
\ No newline at end of file