/branches/v1.10-negrette/widget/modules/carto/config.defaut.ini |
---|
New file |
0,0 → 1,3 |
[carto] |
urlPageFiche = "http://www.tela-botanica.org/page:herbiers_carto" |
Property changes: |
Added: svn:mime-type |
+text/plain |
\ No newline at end of property |
/branches/v1.10-negrette/widget/modules/carto/Carto.php |
---|
New file |
0,0 → 1,134 |
<?php |
class Carto extends WidgetCommun { |
const SERVICE_CARTO_NOM = 'carto'; |
const SERVICE_CARTO_ACTION_DEFAUT = 'carto'; |
private $carte = ''; |
private $departement = ''; |
private $clustering = true; |
/** |
* Methode appelee par defaut pour executer ce widget |
*/ |
public function executer() { |
$retour = null; |
// recuperer les parametres de l'URL |
$this->extraireParametres(); |
// verifier la disponibilite des services et ressources demandees |
$methode = $this->traiterNomMethodeExecuter($this->carte); |
if (method_exists($this, $methode)) { |
$retour = $this->$methode(); |
} else { |
$this->messages[] = "Ce type de service '$methode' n'est pas disponible."; |
} |
if (is_null($retour)) { |
$info = 'Un problème est survenu : '.print_r($this->messages, true); |
$this->envoyer($info); |
} else { |
$squelette = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'squelettes' . DIRECTORY_SEPARATOR |
. $retour['squelette'] . '.tpl.html'; |
$html = $this->traiterSquelettePhp($squelette, $retour['donnees']); |
$this->envoyer($html); |
} |
} |
public function extraireParametres() { |
extract($this->parametres); |
$this->carte = (isset($carte) ? $carte : self::SERVICE_CARTO_ACTION_DEFAUT); |
$this->departement = (isset($dept) ? $dept : '*'); |
$this->pays = (isset($pays) ? $pays : '*'); |
$this->clustering = (isset($clustering) ? (bool)$clustering : $this->clustering); |
} |
/** |
* Carte par défaut |
*/ |
public function executerCarto() { |
$widget = null; |
// Création des infos du widget |
$widget['donnees']['url_base'] = sprintf($this->config['chemins']['baseURLAbsoluDyn'], ''); |
$widget['donnees']['url_web_service'] = sprintf($this->config['chemins']['baseURLServicesTpl'], ''); |
$widget['donnees']['url_page_fiche'] = $this->config['carto']["urlPageFiche"]; |
$widget['donnees']['departement'] = $this->departement; |
$widget['donnees']['pays'] = $this->pays; |
$widget['donnees']['clustering'] = $this->clustering; |
$widget['squelette'] = 'carto'; |
return $widget; |
} |
// utilisée ? |
private function contruireUrlService() { |
// Création url données json |
$url = sprintf($this->config['chemins']['baseURLServicesTpl'], ''); |
if ($action) { |
$url .= "/$action"; |
$parametres_retenus = array(); |
$parametres_a_tester = array('dept'); |
foreach ($parametres_a_tester as $param) { |
if (isset($this->$param) && $this->$param != '*') { |
$parametres_retenus[$param] = $this->$param; |
} |
} |
if (count($parametres_retenus) > 0) { |
$parametres_url = array(); |
foreach ($parametres_retenus as $cle => $valeur) { |
$parametres_url[] = $cle.'='.$valeur; |
} |
$url .= '?'.implode('&', $parametres_url); |
} |
} |
return $url; |
} |
private function obtenirUrlsLimitesCommunales() { |
$urls = null; |
if (isset($this->departement)) { |
// si on veut afficher les limites départementales on va compter et chercher les noms de fichiers |
$fichiersKml = $this->chercherFichierKml(); |
if (count($fichiersKml) > 0) { |
foreach ($fichiersKml as $kml => $dossier){ |
$url_limites_communales = sprintf($this->config['carto']['limitesCommunaleUrlTpl'], $dossier, $kml); |
$urls[] = $url_limites_communales; |
} |
} |
} |
$urls = json_encode($urls); |
return $urls; |
} |
private function chercherFichierKml(){ |
$fichiers = array(); |
$chemins = explode(',', $this->config['carto']['communesKmzChemin']); |
$departements = explode(',', $this->departement);// plrs code de départements peuvent être demandés séparés par des virgules |
$departements_trouves = array(); |
foreach ($chemins as $dossier_chemin) { |
if ($dossier_ressource = opendir($dossier_chemin)) { |
while ($element = readdir($dossier_ressource)) { |
if ($element != '.' && $element != '..') { |
foreach ($departements as $departement) { |
$nom_dossier = basename($dossier_chemin); |
if (!isset($departements_trouves[$departement]) || $departements_trouves[$departement] == $nom_dossier) { |
$dept_protege = preg_quote($departement); |
if (!is_dir($dossier_chemin.'/'.$element) && preg_match("/^$dept_protege(?:_[0-9]+|)\.kml$/", $element)) { |
$fichiers[$element] = $nom_dossier; |
$departements_trouves[$departement] = $nom_dossier; |
} |
} |
} |
} |
} |
closedir($dossier_ressource); |
} |
} |
return $fichiers; |
} |
} |
?> |
/branches/v1.10-negrette/widget/modules/carto/squelettes/carto.tpl.html |
---|
New file |
0,0 → 1,91 |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> |
<html xmlns="http://www.w3.org/1999/xhtml"> |
<head> |
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/> |
<meta http-equiv="Content-style-type" content="text/css" /> |
<meta http-equiv="Content-script-type" content="text/javascript" /> |
<meta http-equiv="Content-language" content="fr" /> |
<!-- OpenGraph pour Facebook, Pinterest, Google+ --> |
<meta property="og:type" content="website" /> |
<meta property="og:title" content="CoEL - Cartographie des herbiers" /> |
<meta property="og:site_name" content="Tela Botanica" /> |
<meta property="og:description" content="Représentation cartographique des herbiers recensés dans Collections en Ligne" /> |
<meta property="og:image" content="http://resources.tela-botanica.org/tb/img/256x256/carre_englobant.png" /> |
<meta property="og:image:type" content="image/png" /> |
<meta property="og:image:width" content="256" /> |
<meta property="og:image:height" content="256" /> |
<meta property="og:locale" content="fr_FR" /> |
<title>Localisation des collections d'herbiers</title> |
<link rel="icon" type="image/png" href="http://resources.tela-botanica.org/tb/img/16x16/favicon.png" /> |
<link rel="shortcut icon" type="image/x-icon" href="http://resources.tela-botanica.org/tb/img/16x16/favicon.ico" /> |
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" /> |
<!--[if lte IE 8]> |
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.ie.css" /> |
<![endif]--> |
<link rel="stylesheet" href="<?=$url_base?>modules/carto/squelettes/css/carto.css" /> |
<link rel="stylesheet" href="<?=$url_base?>modules/carto/squelettes/css/MarkerCluster.css" /> |
<link rel="stylesheet" href="<?=$url_base?>modules/carto/squelettes/css/MarkerCluster.Default.css" /> |
<script type="text/javascript" src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script> |
<script type="text/javascript" src="http://resources.tela-botanica.org/jquery/1.6.4/jquery-1.6.4.min.js"></script> |
<script type="text/javascript" src="http://resources.tela-botanica.org/jquery/jquery-ui/1.8.18/js/jquery-ui-1.8.18.custom.min.js"></script> |
<script type="text/javascript" src="http://resources.tela-botanica.org/jquery/jquery-tmpl/jquery.tmpl.min.js"></script> |
<script src="<?=$url_base?>modules/carto/squelettes/scripts/leaflet.markercluster.js"></script> |
<script type="text/javascript"> |
//<![CDATA[ |
var departement = '<?= $departement ?>'; |
var pays = '<?= $pays ?>'; |
var urlWebService = '<?= $url_web_service ?>'; |
var clustering = '<?= $clustering ?>'; |
//]]> |
</script> |
<script type="text/javascript" src="<?=$url_base?>modules/carto/squelettes/scripts/carto.js"></script> |
<!-- Google Analytics --> |
<script> |
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ |
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), |
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) |
})(window,document,'script','//www.google-analytics.com/analytics.js','ga'); |
ga('create', 'UA-57885-3', 'auto'); |
ga('send', 'pageview'); |
</script> |
</head> |
<body> |
<div id="logo-tb"><img src="http://resources.tela-botanica.org/tb/img/128x128/logo_carre_officiel.png" /></div> |
<div id="map"></div> |
<!-- Squelette du contenu de la popup --> |
<script id="tpl-structure-entete" type="text/x-jquery-tmpl"> |
<div class="popup-simple-text">Situé dans ${ville} (${code_postal})</div> |
</script> |
<script id="tpl-structure" type="text/x-jquery-tmpl"> |
<div class="description-structure"> |
<h3 align="center"> |
<a target="_blank" href="<?=$url_page_fiche?>?module=FicheStructure&id=${id}">${nom}</a> |
</h3> |
<div class="collections"> |
{{if collections.length > 0}} |
<div class="popup-simple-text">Collections présentes :</div> |
<ul> |
{{each(index, collection) collections}} |
<li> |
<a target="_blank" href="<?=$url_page_fiche?>?module=FicheCollection&id=${collection.id}">${collection.nom}</a> |
</li> |
{{/each}} |
</ul> |
{{else}} |
<div class="popup-simple-text">Aucune Collection d'herbiers recensée à ce jour</div> |
{{/if}} |
</div> |
<hr /> |
</div> |
</script> |
<div id="structure" style="display:none"></div> |
</body> |
</html> |
Property changes: |
Added: svn:mime-type |
+text/plain |
\ No newline at end of property |
/branches/v1.10-negrette/widget/modules/carto/squelettes/images/petitCarreContour.jpg |
---|
Cannot display: file marked as a binary type. |
svn:mime-type = image/jpeg |
/branches/v1.10-negrette/widget/modules/carto/squelettes/images/petitCarreContour.jpg |
---|
New file |
Property changes: |
Added: svn:mime-type |
+image/jpeg |
\ No newline at end of property |
/branches/v1.10-negrette/widget/modules/carto/squelettes/scripts/carto.js |
---|
New file |
0,0 → 1,252 |
var map = null, |
optionsCoucheOSM = { |
attribution: 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors,' |
+ ' <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>', |
maxZoom: 18 |
}, |
optionsCoucheGoogle = { |
attribution: 'Map data ©'+new Date().getFullYear()+' <a href="http://maps.google.com">Google</a>', |
maxZoom: 18 |
}, |
coucheOSM = new L.TileLayer("http://osm.tela-botanica.org/tuiles/osmfr/{z}/{x}/{y}.png", |
optionsCoucheOSM), |
coucheSatellite = new L.TileLayer("http://mt1.google.com/vt/lyrs=y@218131653&hl=fr&src=app&x={x}&y={y}&z={z}", |
optionsCoucheGoogle), |
optionsCarte = { |
center : new L.LatLng(46, 2), |
zoom : 6, |
layers : [coucheOSM] |
}; |
var xmlHttpRequest = null; |
nombreCollections = 0; |
collections = new Array(); |
structures = new Array(); |
if(clustering) { |
coucheStructures = new L.MarkerClusterGroup({ |
disableClusteringAtZoom : 10 |
}); |
} else { |
coucheStructures = new L.FeatureGroup(); |
} |
infoBulle = null; |
chargementEnCours = false; |
$(document).ready(function() { |
dimensionnerCarte(); |
initialiserCarte(); |
initialiserPanneauControle(); |
$.ajax({ |
dataType: "json", |
url: urlWebService + "CoelRecherche/Nombre/*/*/*/*/*/*/*/" + departement + "/*/", |
data: { formatRetour: "text/plain", pays: pays}, |
async: false |
}).complete(function(msg) { |
if (! estStatutRequeteOK(msg.status)) { |
alert(msg.responseText); |
return; |
} |
nombreCollections = parseInt(msg.responseText); |
}); |
chargerStructures(); |
}); |
$(window).resize(function() { |
dimensionnerCarte(); |
}); |
function dimensionnerCarte() { |
$("#map").width($(window).width()); |
$("#map").height($(window).height()); |
} |
function initialiserCarte() { |
map = L.map('map', optionsCarte); |
coucheOSM.addTo(map); |
coucheStructures.addTo(map); |
map.on('zoomend', function() { |
// controle sur le niveau de zoom uniquement a la fin du placement des structures sur la carte |
if (chargementEnCours) { |
chargementEnCours = false; |
verifierZoom(); |
} |
}); |
} |
function initialiserPanneauControle() { |
var baseMaps = { |
"Plan" : coucheOSM, |
"Satellite" : coucheSatellite |
}; |
var overlayMaps = { |
"Structures" : coucheStructures |
}; |
L.control.layers(baseMaps, overlayMaps).addTo(map); |
} |
function recupererValeurNombreCollections() { |
} |
function chargerStructures() { |
if (requeteEnCours()) { |
window.setTimeout('chargerStructures()', 400); |
return; |
} |
chargementEnCours = true; |
$.ajax({ |
dataType: "json", |
url: urlWebService + "CoelRecherche/ParDefaut/*/*/*/*/*/*/*/" + departement + "/*/", |
data: { limit: nombreCollections, pays: pays}, |
async: true |
}).complete(function(msg) { |
if (!estStatutRequeteOK(msg.status)) { |
alert(msg.responseText); |
return; |
} |
collections = eval("(function(){return " + msg.responseText + ";})()"); |
ordonnerCollectionsParStructures(); |
chargerLocalisations(); |
}); |
} |
function requeteEnCours() { |
return (xmlHttpRequest != null && xmlHttpRequest.readyState != 4); |
} |
function estStatutRequeteOK(x_status) { |
return (x_status == 200 || x_status == 304 || x_status == 0); |
} |
function ordonnerCollectionsParStructures() { |
for (var index = 0; index < collections.length; index ++) { |
var indexStructure = 0; |
while (indexStructure < structures.length && structures[indexStructure].id != collections[index].cs_id_structure) { |
indexStructure ++; |
} |
if (indexStructure == structures.length) { |
var structure = { |
"id" : collections[index].cs_id_structure, |
"nom" : collections[index].cs_nom, |
"ville" : collections[index].cs_ville, |
"code_postal" : collections[index].cs_code_postal, |
"longitude" : collections[index].cs_longitude, |
"latitude" : collections[index].cs_latitude, |
"collections" : new Array() |
}; |
structures.push(structure); |
} |
var collection = { |
"id" : collections[index].cc_id_collection, |
"nom" : collections[index].cc_nom |
}; |
structures[indexStructure].collections.push(collection); |
} |
} |
function chargerLocalisations() { |
var nombreStructuresAffichees = 0; |
for (var index = 0; index < structures.length; index ++) { |
if ((structures[index].longitude != null && structures[index].longitude != "") |
&& (structures[index].latitude != null && structures[index].latitude != "")) { |
var existeMarqueur = rechercherExistenceMarqueur(structures[index].longitude, structures[index].latitude); |
if (existeMarqueur == null) { |
creerMarqueur(structures[index]); |
nombreStructuresAffichees ++; |
} else { |
ajouterStructureAMarqueur(existeMarqueur, structures[index]); |
} |
} |
} |
if (nombreStructuresAffichees > 0) { |
map.fitBounds(coucheStructures.getBounds()); |
} |
} |
function rechercherExistenceMarqueur(longitude, latitude) { |
var existeMarqueur = null; |
coucheStructures.eachLayer(function(layer) { |
if (layer.getLatLng().lat == latitude && layer.getLatLng().lng == longitude) { |
existeMarqueur = layer; |
} |
}); |
return existeMarqueur; |
} |
function creerMarqueur(structure) { |
var latlng = new L.LatLng(structure.latitude, structure.longitude); |
var marqueur = new L.Marker(latlng, { |
structures : [structure], |
structuresNom : structure.nom |
}); |
marqueur.on('click', surClickMarqueur); |
marqueur.on('mouseover', construireToolTipMarqueur); |
coucheStructures.addLayer(marqueur); |
} |
function construireToolTipMarqueur(event) { |
// changer la propriété title du marqueur ne fonctionne pas |
// en dehors du constructeur (mais cette méthode est-elle pérenne ?) |
// le clustering n'affichant pas tous les marqueurs, on doit remplir le tooltip |
event.target._icon.title = event.target.options.structuresNom; |
} |
function ajouterStructureAMarqueur(marqueur, structure) { |
marqueur.options.structures.push(structure); |
marqueur.options.structuresNom += "\n"+structure.nom; |
} |
function surClickMarqueur(event) { |
var latlng = event.target.getLatLng(); |
var structures = event.target.options.structures; |
afficherCollections(structures, latlng); |
} |
function afficherCollections(structures, latlng) { |
masquerInfoBulle(); |
infoBulle = new L.Popup({maxWidth : 0.25*$(window).width(), maxHeight : 0.35*$(window).height()}); |
infoBulle.setLatLng(latlng); |
infoBulle.openOn(map); |
remplirContenuPopup(structures); |
$("a").css("color", "#598000"); |
map.setView(latlng, map.getZoom()); |
} |
function masquerInfoBulle() { |
if (infoBulle != null && map.hasLayer(infoBulle)) { |
map.removeLayer(infoBulle); |
} |
infoBulle = null; |
} |
function remplirContenuPopup(structures) { |
$("#structure").empty(); |
var entetePopup = { |
"ville" : structures[0].ville, |
"code_postal" : structures[0].code_postal |
} |
$("#tpl-structure-entete").tmpl(entetePopup).appendTo($("#structure")); |
$.each(structures, function(index, structure) { |
var structureAjout = { |
"id" : structure.id, |
"nom" : structure.nom, |
"ville" : structure.ville, |
"code_postal" : structure.code_postal, |
"collections" : structure.collections |
}; |
$("#tpl-structure").tmpl(structureAjout).appendTo($("#structure")); |
}); |
infoBulle.setContent($("#structure").html()); |
} |
function verifierZoom() { |
if(map.getZoom() > 13) { |
map.setZoom(13); |
} |
} |
Property changes: |
Added: svn:mime-type |
+text/plain |
\ No newline at end of property |
/branches/v1.10-negrette/widget/modules/carto/squelettes/scripts/leaflet.markercluster.js |
---|
New file |
0,0 → 1,6 |
/* |
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(t,e){L.MarkerClusterGroup=L.FeatureGroup.extend({options:{maxClusterRadius:80,iconCreateFunction:null,spiderfyOnMaxZoom:!0,showCoverageOnHover:!0,zoomToBoundsOnClick:!0,singleMarkerMode:!1,disableClusteringAtZoom:null,removeOutsideVisibleBounds:!0,animateAddingMarkers:!1,spiderfyDistanceMultiplier:1,chunkedLoading:!1,chunkInterval:200,chunkDelay:50,chunkProgress:null,polygonOptions:{}},initialize:function(t){L.Util.setOptions(this,t),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=[],this._currentShownBounds=null,this._queue=[]},addLayer:function(t){if(t instanceof L.LayerGroup){var e=[];for(var i in t._layers)e.push(t._layers[i]);return this.addLayers(e)}if(!t.getLatLng)return this._nonPointGroup.addLayer(t),this;if(!this._map)return this._needsClustering.push(t),this;if(this.hasLayer(t))return this;this._unspiderfy&&this._unspiderfy(),this._addLayer(t,this._maxZoom);var n=t,s=this._map.getZoom();if(t.__parent)for(;n.__parent._zoom>=s;)n=n.__parent;return this._currentShownBounds.contains(n.getLatLng())&&(this.options.animateAddingMarkers?this._animationAddLayer(t,n):this._animationAddLayerNonAnimated(t,n)),this},removeLayer:function(t){if(t instanceof L.LayerGroup){var e=[];for(var i in t._layers)e.push(t._layers[i]);return this.removeLayers(e)}return t.getLatLng?this._map?t.__parent?(this._unspiderfy&&(this._unspiderfy(),this._unspiderfyLayer(t)),this._removeLayer(t,!0),this._featureGroup.hasLayer(t)&&(this._featureGroup.removeLayer(t),t.setOpacity&&t.setOpacity(1)),this):this:(!this._arraySplice(this._needsClustering,t)&&this.hasLayer(t)&&this._needsRemoving.push(t),this):(this._nonPointGroup.removeLayer(t),this)},addLayers:function(t){var e,i,n,s,r=this._featureGroup,o=this._nonPointGroup,a=this.options.chunkedLoading,h=this.options.chunkInterval,_=this.options.chunkProgress;if(this._map){var u=0,l=(new Date).getTime(),d=L.bind(function(){for(var e=(new Date).getTime();u<t.length;u++){if(a&&0===u%200){var i=(new Date).getTime()-e;if(i>h)break}if(s=t[u],s.getLatLng){if(!this.hasLayer(s)&&(this._addLayer(s,this._maxZoom),s.__parent&&2===s.__parent.getChildCount())){var n=s.__parent.getAllChildMarkers(),p=n[0]===s?n[1]:n[0];r.removeLayer(p)}}else o.addLayer(s)}_&&_(u,t.length,(new Date).getTime()-l),u===t.length?(this._featureGroup.eachLayer(function(t){t instanceof L.MarkerCluster&&t._iconNeedsUpdate&&t._updateIcon()}),this._topClusterLevel._recursivelyAddChildrenToMap(null,this._zoom,this._currentShownBounds)):setTimeout(d,this.options.chunkDelay)},this);d()}else{for(e=[],i=0,n=t.length;n>i;i++)s=t[i],s.getLatLng?this.hasLayer(s)||e.push(s):o.addLayer(s);this._needsClustering=this._needsClustering.concat(e)}return this},removeLayers:function(t){var e,i,n,s=this._featureGroup,r=this._nonPointGroup;if(!this._map){for(e=0,i=t.length;i>e;e++)n=t[e],this._arraySplice(this._needsClustering,n),r.removeLayer(n);return this}for(e=0,i=t.length;i>e;e++)n=t[e],n.__parent?(this._removeLayer(n,!0,!0),s.hasLayer(n)&&(s.removeLayer(n),n.setOpacity&&n.setOpacity(1))):r.removeLayer(n);return this._topClusterLevel._recursivelyAddChildrenToMap(null,this._zoom,this._currentShownBounds),s.eachLayer(function(t){t instanceof L.MarkerCluster&&t._updateIcon()}),this},clearLayers:function(){return this._map||(this._needsClustering=[],delete this._gridClusters,delete this._gridUnclustered),this._noanimationUnspiderfy&&this._noanimationUnspiderfy(),this._featureGroup.clearLayers(),this._nonPointGroup.clearLayers(),this.eachLayer(function(t){delete t.__parent}),this._map&&this._generateInitialClusters(),this},getBounds:function(){var t=new L.LatLngBounds;this._topClusterLevel&&t.extend(this._topClusterLevel._bounds);for(var e=this._needsClustering.length-1;e>=0;e--)t.extend(this._needsClustering[e].getLatLng());return t.extend(this._nonPointGroup.getBounds()),t},eachLayer:function(t,e){var i,n=this._needsClustering.slice();for(this._topClusterLevel&&this._topClusterLevel.getAllChildMarkers(n),i=n.length-1;i>=0;i--)t.call(e,n[i]);this._nonPointGroup.eachLayer(t,e)},getLayers:function(){var t=[];return this.eachLayer(function(e){t.push(e)}),t},getLayer:function(t){var e=null;return this.eachLayer(function(i){L.stamp(i)===t&&(e=i)}),e},hasLayer:function(t){if(!t)return!1;var e,i=this._needsClustering;for(e=i.length-1;e>=0;e--)if(i[e]===t)return!0;for(i=this._needsRemoving,e=i.length-1;e>=0;e--)if(i[e]===t)return!1;return!(!t.__parent||t.__parent._group!==this)||this._nonPointGroup.hasLayer(t)},zoomToShowLayer:function(t,e){var i=function(){if((t._icon||t.__parent._icon)&&!this._inZoomAnimation)if(this._map.off("moveend",i,this),this.off("animationend",i,this),t._icon)e();else if(t.__parent._icon){var n=function(){this.off("spiderfied",n,this),e()};this.on("spiderfied",n,this),t.__parent.spiderfy()}};if(t._icon&&this._map.getBounds().contains(t.getLatLng()))e();else if(t.__parent._zoom<this._map.getZoom())this._map.on("moveend",i,this),this._map.panTo(t.getLatLng());else{var n=function(){this._map.off("movestart",n,this),n=null};this._map.on("movestart",n,this),this._map.on("moveend",i,this),this.on("animationend",i,this),t.__parent.zoomToBounds(),n&&i.call(this)}},onAdd:function(t){this._map=t;var e,i,n;if(!isFinite(this._map.getMaxZoom()))throw"Map has no maxZoom specified";for(this._featureGroup.onAdd(t),this._nonPointGroup.onAdd(t),this._gridClusters||this._generateInitialClusters(),e=0,i=this._needsRemoving.length;i>e;e++)n=this._needsRemoving[e],this._removeLayer(n,!0);this._needsRemoving=[],this._zoom=this._map.getZoom(),this._currentShownBounds=this._getExpandedVisibleBounds(),this._map.on("zoomend",this._zoomEnd,this),this._map.on("moveend",this._moveEnd,this),this._spiderfierOnAdd&&this._spiderfierOnAdd(),this._bindEvents(),i=this._needsClustering,this._needsClustering=[],this.addLayers(i)},onRemove:function(t){t.off("zoomend",this._zoomEnd,this),t.off("moveend",this._moveEnd,this),this._unbindEvents(),this._map._mapPane.className=this._map._mapPane.className.replace(" leaflet-cluster-anim",""),this._spiderfierOnRemove&&this._spiderfierOnRemove(),this._hideCoverage(),this._featureGroup.onRemove(t),this._nonPointGroup.onRemove(t),this._featureGroup.clearLayers(),this._map=null},getVisibleParent:function(t){for(var e=t;e&&!e._icon;)e=e.__parent;return e||null},_arraySplice:function(t,e){for(var i=t.length-1;i>=0;i--)if(t[i]===e)return t.splice(i,1),!0},_removeLayer:function(t,e,i){var n=this._gridClusters,s=this._gridUnclustered,r=this._featureGroup,o=this._map;if(e)for(var a=this._maxZoom;a>=0&&s[a].removeObject(t,o.project(t.getLatLng(),a));a--);var h,_=t.__parent,u=_._markers;for(this._arraySplice(u,t);_&&(_._childCount--,!(_._zoom<0));)e&&_._childCount<=1?(h=_._markers[0]===t?_._markers[1]:_._markers[0],n[_._zoom].removeObject(_,o.project(_._cLatLng,_._zoom)),s[_._zoom].addObject(h,o.project(h.getLatLng(),_._zoom)),this._arraySplice(_.__parent._childClusters,_),_.__parent._markers.push(h),h.__parent=_.__parent,_._icon&&(r.removeLayer(_),i||r.addLayer(h))):(_._recalculateBounds(),i&&_._icon||_._updateIcon()),_=_.__parent;delete t.__parent},_isOrIsParent:function(t,e){for(;e;){if(t===e)return!0;e=e.parentNode}return!1},_propagateEvent:function(t){if(t.layer instanceof L.MarkerCluster){if(t.originalEvent&&this._isOrIsParent(t.layer._icon,t.originalEvent.relatedTarget))return;t.type="cluster"+t.type}this.fire(t.type,t)},_defaultIconCreateFunction:function(t){var e=t.getChildCount(),i=" marker-cluster-";return i+=10>e?"small":100>e?"medium":"large",new L.DivIcon({html:"<div><span>"+e+"</span></div>",className:"marker-cluster"+i,iconSize:new L.Point(40,40)})},_bindEvents:function(){var t=this._map,e=this.options.spiderfyOnMaxZoom,i=this.options.showCoverageOnHover,n=this.options.zoomToBoundsOnClick;(e||n)&&this.on("clusterclick",this._zoomOrSpiderfy,this),i&&(this.on("clustermouseover",this._showCoverage,this),this.on("clustermouseout",this._hideCoverage,this),t.on("zoomend",this._hideCoverage,this))},_zoomOrSpiderfy:function(t){var e=this._map;e.getMaxZoom()===e.getZoom()?this.options.spiderfyOnMaxZoom&&t.layer.spiderfy():this.options.zoomToBoundsOnClick&&t.layer.zoomToBounds(),t.originalEvent&&13===t.originalEvent.keyCode&&e._container.focus()},_showCoverage:function(t){var e=this._map;this._inZoomAnimation||(this._shownPolygon&&e.removeLayer(this._shownPolygon),t.layer.getChildCount()>2&&t.layer!==this._spiderfied&&(this._shownPolygon=new L.Polygon(t.layer.getConvexHull(),this.options.polygonOptions),e.addLayer(this._shownPolygon)))},_hideCoverage:function(){this._shownPolygon&&(this._map.removeLayer(this._shownPolygon),this._shownPolygon=null)},_unbindEvents:function(){var t=this.options.spiderfyOnMaxZoom,e=this.options.showCoverageOnHover,i=this.options.zoomToBoundsOnClick,n=this._map;(t||i)&&this.off("clusterclick",this._zoomOrSpiderfy,this),e&&(this.off("clustermouseover",this._showCoverage,this),this.off("clustermouseout",this._hideCoverage,this),n.off("zoomend",this._hideCoverage,this))},_zoomEnd:function(){this._map&&(this._mergeSplitClusters(),this._zoom=this._map._zoom,this._currentShownBounds=this._getExpandedVisibleBounds())},_moveEnd:function(){if(!this._inZoomAnimation){var t=this._getExpandedVisibleBounds();this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,this._zoom,t),this._topClusterLevel._recursivelyAddChildrenToMap(null,this._map._zoom,t),this._currentShownBounds=t}},_generateInitialClusters:function(){var t=this._map.getMaxZoom(),e=this.options.maxClusterRadius,i=e;"function"!=typeof e&&(i=function(){return e}),this.options.disableClusteringAtZoom&&(t=this.options.disableClusteringAtZoom-1),this._maxZoom=t,this._gridClusters={},this._gridUnclustered={};for(var n=t;n>=0;n--)this._gridClusters[n]=new L.DistanceGrid(i(n)),this._gridUnclustered[n]=new L.DistanceGrid(i(n));this._topClusterLevel=new L.MarkerCluster(this,-1)},_addLayer:function(t,e){var i,n,s=this._gridClusters,r=this._gridUnclustered;for(this.options.singleMarkerMode&&(t.options.icon=this.options.iconCreateFunction({getChildCount:function(){return 1},getAllChildMarkers:function(){return[t]}}));e>=0;e--){i=this._map.project(t.getLatLng(),e);var o=s[e].getNearObject(i);if(o)return o._addChild(t),t.__parent=o,void 0;if(o=r[e].getNearObject(i)){var a=o.__parent;a&&this._removeLayer(o,!1);var h=new L.MarkerCluster(this,e,o,t);s[e].addObject(h,this._map.project(h._cLatLng,e)),o.__parent=h,t.__parent=h;var _=h;for(n=e-1;n>a._zoom;n--)_=new L.MarkerCluster(this,n,_),s[n].addObject(_,this._map.project(o.getLatLng(),n));for(a._addChild(_),n=e;n>=0&&r[n].removeObject(o,this._map.project(o.getLatLng(),n));n--);return}r[e].addObject(t,i)}this._topClusterLevel._addChild(t),t.__parent=this._topClusterLevel},_enqueue:function(t){this._queue.push(t),this._queueTimeout||(this._queueTimeout=setTimeout(L.bind(this._processQueue,this),300))},_processQueue:function(){for(var t=0;t<this._queue.length;t++)this._queue[t].call(this);this._queue.length=0,clearTimeout(this._queueTimeout),this._queueTimeout=null},_mergeSplitClusters:function(){this._processQueue(),this._zoom<this._map._zoom&&this._currentShownBounds.intersects(this._getExpandedVisibleBounds())?(this._animationStart(),this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,this._zoom,this._getExpandedVisibleBounds()),this._animationZoomIn(this._zoom,this._map._zoom)):this._zoom>this._map._zoom?(this._animationStart(),this._animationZoomOut(this._zoom,this._map._zoom)):this._moveEnd()},_getExpandedVisibleBounds:function(){if(!this.options.removeOutsideVisibleBounds)return this.getBounds();var t=this._map,e=t.getBounds(),i=e._southWest,n=e._northEast,s=L.Browser.mobile?0:Math.abs(i.lat-n.lat),r=L.Browser.mobile?0:Math.abs(i.lng-n.lng);return new L.LatLngBounds(new L.LatLng(i.lat-s,i.lng-r,!0),new L.LatLng(n.lat+s,n.lng+r,!0))},_animationAddLayerNonAnimated:function(t,e){if(e===t)this._featureGroup.addLayer(t);else if(2===e._childCount){e._addToMap();var i=e.getAllChildMarkers();this._featureGroup.removeLayer(i[0]),this._featureGroup.removeLayer(i[1])}else e._updateIcon()}}),L.MarkerClusterGroup.include(L.DomUtil.TRANSITION?{_animationStart:function(){this._map._mapPane.className+=" leaflet-cluster-anim",this._inZoomAnimation++},_animationEnd:function(){this._map&&(this._map._mapPane.className=this._map._mapPane.className.replace(" leaflet-cluster-anim","")),this._inZoomAnimation--,this.fire("animationend")},_animationZoomIn:function(t,e){var i,n=this._getExpandedVisibleBounds(),s=this._featureGroup;this._topClusterLevel._recursively(n,t,0,function(r){var o,a=r._latlng,h=r._markers;for(n.contains(a)||(a=null),r._isSingleParent()&&t+1===e?(s.removeLayer(r),r._recursivelyAddChildrenToMap(null,e,n)):(r.setOpacity(0),r._recursivelyAddChildrenToMap(a,e,n)),i=h.length-1;i>=0;i--)o=h[i],n.contains(o._latlng)||s.removeLayer(o)}),this._forceLayout(),this._topClusterLevel._recursivelyBecomeVisible(n,e),s.eachLayer(function(t){t instanceof L.MarkerCluster||!t._icon||t.setOpacity(1)}),this._topClusterLevel._recursively(n,t,e,function(t){t._recursivelyRestoreChildPositions(e)}),this._enqueue(function(){this._topClusterLevel._recursively(n,t,0,function(t){s.removeLayer(t),t.setOpacity(1)}),this._animationEnd()})},_animationZoomOut:function(t,e){this._animationZoomOutSingle(this._topClusterLevel,t-1,e),this._topClusterLevel._recursivelyAddChildrenToMap(null,e,this._getExpandedVisibleBounds()),this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,t,this._getExpandedVisibleBounds())},_animationZoomOutSingle:function(t,e,i){var n=this._getExpandedVisibleBounds();t._recursivelyAnimateChildrenInAndAddSelfToMap(n,e+1,i);var s=this;this._forceLayout(),t._recursivelyBecomeVisible(n,i),this._enqueue(function(){if(1===t._childCount){var r=t._markers[0];r.setLatLng(r.getLatLng()),r.setOpacity&&r.setOpacity(1)}else t._recursively(n,i,0,function(t){t._recursivelyRemoveChildrenFromMap(n,e+1)});s._animationEnd()})},_animationAddLayer:function(t,e){var i=this,n=this._featureGroup;n.addLayer(t),e!==t&&(e._childCount>2?(e._updateIcon(),this._forceLayout(),this._animationStart(),t._setPos(this._map.latLngToLayerPoint(e.getLatLng())),t.setOpacity(0),this._enqueue(function(){n.removeLayer(t),t.setOpacity(1),i._animationEnd()})):(this._forceLayout(),i._animationStart(),i._animationZoomOutSingle(e,this._map.getMaxZoom(),this._map.getZoom())))},_forceLayout:function(){L.Util.falseFn(e.body.offsetWidth)}}:{_animationStart:function(){},_animationZoomIn:function(t,e){this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,t),this._topClusterLevel._recursivelyAddChildrenToMap(null,e,this._getExpandedVisibleBounds()),this.fire("animationend")},_animationZoomOut:function(t,e){this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,t),this._topClusterLevel._recursivelyAddChildrenToMap(null,e,this._getExpandedVisibleBounds()),this.fire("animationend")},_animationAddLayer:function(t,e){this._animationAddLayerNonAnimated(t,e)}}),L.markerClusterGroup=function(t){return new L.MarkerClusterGroup(t)},L.MarkerCluster=L.Marker.extend({initialize:function(t,e,i,n){L.Marker.prototype.initialize.call(this,i?i._cLatLng||i.getLatLng():new L.LatLng(0,0),{icon:this}),this._group=t,this._zoom=e,this._markers=[],this._childClusters=[],this._childCount=0,this._iconNeedsUpdate=!0,this._bounds=new L.LatLngBounds,i&&this._addChild(i),n&&this._addChild(n)},getAllChildMarkers:function(t){t=t||[];for(var e=this._childClusters.length-1;e>=0;e--)this._childClusters[e].getAllChildMarkers(t);for(var i=this._markers.length-1;i>=0;i--)t.push(this._markers[i]);return t},getChildCount:function(){return this._childCount},zoomToBounds:function(){for(var t,e=this._childClusters.slice(),i=this._group._map,n=i.getBoundsZoom(this._bounds),s=this._zoom+1,r=i.getZoom();e.length>0&&n>s;){s++;var o=[];for(t=0;t<e.length;t++)o=o.concat(e[t]._childClusters);e=o}n>s?this._group._map.setView(this._latlng,s):r>=n?this._group._map.setView(this._latlng,r+1):this._group._map.fitBounds(this._bounds)},getBounds:function(){var t=new L.LatLngBounds;return t.extend(this._bounds),t},_updateIcon:function(){this._iconNeedsUpdate=!0,this._icon&&this.setIcon(this)},createIcon:function(){return this._iconNeedsUpdate&&(this._iconObj=this._group.options.iconCreateFunction(this),this._iconNeedsUpdate=!1),this._iconObj.createIcon()},createShadow:function(){return this._iconObj.createShadow()},_addChild:function(t,e){this._iconNeedsUpdate=!0,this._expandBounds(t),t instanceof L.MarkerCluster?(e||(this._childClusters.push(t),t.__parent=this),this._childCount+=t._childCount):(e||this._markers.push(t),this._childCount++),this.__parent&&this.__parent._addChild(t,!0)},_expandBounds:function(t){var e,i=t._wLatLng||t._latlng;t instanceof L.MarkerCluster?(this._bounds.extend(t._bounds),e=t._childCount):(this._bounds.extend(i),e=1),this._cLatLng||(this._cLatLng=t._cLatLng||i);var n=this._childCount+e;this._wLatLng?(this._wLatLng.lat=(i.lat*e+this._wLatLng.lat*this._childCount)/n,this._wLatLng.lng=(i.lng*e+this._wLatLng.lng*this._childCount)/n):this._latlng=this._wLatLng=new L.LatLng(i.lat,i.lng)},_addToMap:function(t){t&&(this._backupLatlng=this._latlng,this.setLatLng(t)),this._group._featureGroup.addLayer(this)},_recursivelyAnimateChildrenIn:function(t,e,i){this._recursively(t,0,i-1,function(t){var i,n,s=t._markers;for(i=s.length-1;i>=0;i--)n=s[i],n._icon&&(n._setPos(e),n.setOpacity(0))},function(t){var i,n,s=t._childClusters;for(i=s.length-1;i>=0;i--)n=s[i],n._icon&&(n._setPos(e),n.setOpacity(0))})},_recursivelyAnimateChildrenInAndAddSelfToMap:function(t,e,i){this._recursively(t,i,0,function(n){n._recursivelyAnimateChildrenIn(t,n._group._map.latLngToLayerPoint(n.getLatLng()).round(),e),n._isSingleParent()&&e-1===i?(n.setOpacity(1),n._recursivelyRemoveChildrenFromMap(t,e)):n.setOpacity(0),n._addToMap()})},_recursivelyBecomeVisible:function(t,e){this._recursively(t,0,e,null,function(t){t.setOpacity(1)})},_recursivelyAddChildrenToMap:function(t,e,i){this._recursively(i,-1,e,function(n){if(e!==n._zoom)for(var s=n._markers.length-1;s>=0;s--){var r=n._markers[s];i.contains(r._latlng)&&(t&&(r._backupLatlng=r.getLatLng(),r.setLatLng(t),r.setOpacity&&r.setOpacity(0)),n._group._featureGroup.addLayer(r))}},function(e){e._addToMap(t)})},_recursivelyRestoreChildPositions:function(t){for(var e=this._markers.length-1;e>=0;e--){var i=this._markers[e];i._backupLatlng&&(i.setLatLng(i._backupLatlng),delete i._backupLatlng)}if(t-1===this._zoom)for(var n=this._childClusters.length-1;n>=0;n--)this._childClusters[n]._restorePosition();else for(var s=this._childClusters.length-1;s>=0;s--)this._childClusters[s]._recursivelyRestoreChildPositions(t)},_restorePosition:function(){this._backupLatlng&&(this.setLatLng(this._backupLatlng),delete this._backupLatlng)},_recursivelyRemoveChildrenFromMap:function(t,e,i){var n,s;this._recursively(t,-1,e-1,function(t){for(s=t._markers.length-1;s>=0;s--)n=t._markers[s],i&&i.contains(n._latlng)||(t._group._featureGroup.removeLayer(n),n.setOpacity&&n.setOpacity(1))},function(t){for(s=t._childClusters.length-1;s>=0;s--)n=t._childClusters[s],i&&i.contains(n._latlng)||(t._group._featureGroup.removeLayer(n),n.setOpacity&&n.setOpacity(1))})},_recursively:function(t,e,i,n,s){var r,o,a=this._childClusters,h=this._zoom;if(e>h)for(r=a.length-1;r>=0;r--)o=a[r],t.intersects(o._bounds)&&o._recursively(t,e,i,n,s);else if(n&&n(this),s&&this._zoom===i&&s(this),i>h)for(r=a.length-1;r>=0;r--)o=a[r],t.intersects(o._bounds)&&o._recursively(t,e,i,n,s)},_recalculateBounds:function(){var t,e=this._markers,i=this._childClusters;for(this._bounds=new L.LatLngBounds,delete this._wLatLng,t=e.length-1;t>=0;t--)this._expandBounds(e[t]);for(t=i.length-1;t>=0;t--)this._expandBounds(i[t])},_isSingleParent:function(){return this._childClusters.length>0&&this._childClusters[0]._childCount===this._childCount}}),L.DistanceGrid=function(t){this._cellSize=t,this._sqCellSize=t*t,this._grid={},this._objectPoint={}},L.DistanceGrid.prototype={addObject:function(t,e){var i=this._getCoord(e.x),n=this._getCoord(e.y),s=this._grid,r=s[n]=s[n]||{},o=r[i]=r[i]||[],a=L.Util.stamp(t);this._objectPoint[a]=e,o.push(t)},updateObject:function(t,e){this.removeObject(t),this.addObject(t,e)},removeObject:function(t,e){var i,n,s=this._getCoord(e.x),r=this._getCoord(e.y),o=this._grid,a=o[r]=o[r]||{},h=a[s]=a[s]||[];for(delete this._objectPoint[L.Util.stamp(t)],i=0,n=h.length;n>i;i++)if(h[i]===t)return h.splice(i,1),1===n&&delete a[s],!0},eachObject:function(t,e){var i,n,s,r,o,a,h,_=this._grid;for(i in _){o=_[i];for(n in o)for(a=o[n],s=0,r=a.length;r>s;s++)h=t.call(e,a[s]),h&&(s--,r--)}},getNearObject:function(t){var e,i,n,s,r,o,a,h,_=this._getCoord(t.x),u=this._getCoord(t.y),l=this._objectPoint,d=this._sqCellSize,p=null;for(e=u-1;u+1>=e;e++)if(s=this._grid[e])for(i=_-1;_+1>=i;i++)if(r=s[i])for(n=0,o=r.length;o>n;n++)a=r[n],h=this._sqDist(l[L.Util.stamp(a)],t),d>h&&(d=h,p=a);return p},_getCoord:function(t){return Math.floor(t/this._cellSize)},_sqDist:function(t,e){var i=e.x-t.x,n=e.y-t.y;return i*i+n*n}},function(){L.QuickHull={getDistant:function(t,e){var i=e[1].lat-e[0].lat,n=e[0].lng-e[1].lng;return n*(t.lat-e[0].lat)+i*(t.lng-e[0].lng)},findMostDistantPointFromBaseLine:function(t,e){var i,n,s,r=0,o=null,a=[];for(i=e.length-1;i>=0;i--)n=e[i],s=this.getDistant(n,t),s>0&&(a.push(n),s>r&&(r=s,o=n));return{maxPoint:o,newPoints:a}},buildConvexHull:function(t,e){var i=[],n=this.findMostDistantPointFromBaseLine(t,e);return n.maxPoint?(i=i.concat(this.buildConvexHull([t[0],n.maxPoint],n.newPoints)),i=i.concat(this.buildConvexHull([n.maxPoint,t[1]],n.newPoints))):[t[0]]},getConvexHull:function(t){var e,i=!1,n=!1,s=null,r=null;for(e=t.length-1;e>=0;e--){var o=t[e];(i===!1||o.lat>i)&&(s=o,i=o.lat),(n===!1||o.lat<n)&&(r=o,n=o.lat)}var a=[].concat(this.buildConvexHull([r,s],t),this.buildConvexHull([s,r],t));return a}}}(),L.MarkerCluster.include({getConvexHull:function(){var t,e,i=this.getAllChildMarkers(),n=[];for(e=i.length-1;e>=0;e--)t=i[e].getLatLng(),n.push(t);return L.QuickHull.getConvexHull(n)}}),L.MarkerCluster.include({_2PI:2*Math.PI,_circleFootSeparation:25,_circleStartAngle:Math.PI/6,_spiralFootSeparation:28,_spiralLengthStart:11,_spiralLengthFactor:5,_circleSpiralSwitchover:9,spiderfy:function(){if(this._group._spiderfied!==this&&!this._group._inZoomAnimation){var t,e=this.getAllChildMarkers(),i=this._group,n=i._map,s=n.latLngToLayerPoint(this._latlng);this._group._unspiderfy(),this._group._spiderfied=this,e.length>=this._circleSpiralSwitchover?t=this._generatePointsSpiral(e.length,s):(s.y+=10,t=this._generatePointsCircle(e.length,s)),this._animationSpiderfy(e,t)}},unspiderfy:function(t){this._group._inZoomAnimation||(this._animationUnspiderfy(t),this._group._spiderfied=null)},_generatePointsCircle:function(t,e){var i,n,s=this._group.options.spiderfyDistanceMultiplier*this._circleFootSeparation*(2+t),r=s/this._2PI,o=this._2PI/t,a=[];for(a.length=t,i=t-1;i>=0;i--)n=this._circleStartAngle+i*o,a[i]=new L.Point(e.x+r*Math.cos(n),e.y+r*Math.sin(n))._round();return a},_generatePointsSpiral:function(t,e){var i,n=this._group.options.spiderfyDistanceMultiplier*this._spiralLengthStart,s=this._group.options.spiderfyDistanceMultiplier*this._spiralFootSeparation,r=this._group.options.spiderfyDistanceMultiplier*this._spiralLengthFactor,o=0,a=[];for(a.length=t,i=t-1;i>=0;i--)o+=s/n+5e-4*i,a[i]=new L.Point(e.x+n*Math.cos(o),e.y+n*Math.sin(o))._round(),n+=this._2PI*r/o;return a},_noanimationUnspiderfy:function(){var t,e,i=this._group,n=i._map,s=i._featureGroup,r=this.getAllChildMarkers();for(this.setOpacity(1),e=r.length-1;e>=0;e--)t=r[e],s.removeLayer(t),t._preSpiderfyLatlng&&(t.setLatLng(t._preSpiderfyLatlng),delete t._preSpiderfyLatlng),t.setZIndexOffset&&t.setZIndexOffset(0),t._spiderLeg&&(n.removeLayer(t._spiderLeg),delete t._spiderLeg);i._spiderfied=null}}),L.MarkerCluster.include(L.DomUtil.TRANSITION?{SVG_ANIMATION:function(){return e.createElementNS("http://www.w3.org/2000/svg","animate").toString().indexOf("SVGAnimate")>-1}(),_animationSpiderfy:function(t,i){var n,s,r,o,a=this,h=this._group,_=h._map,u=h._featureGroup,l=_.latLngToLayerPoint(this._latlng);for(n=t.length-1;n>=0;n--)s=t[n],s.setOpacity?(s.setZIndexOffset(1e6),s.setOpacity(0),u.addLayer(s),s._setPos(l)):u.addLayer(s);h._forceLayout(),h._animationStart();var d=L.Path.SVG?0:.3,p=L.Path.SVG_NS;for(n=t.length-1;n>=0;n--)if(o=_.layerPointToLatLng(i[n]),s=t[n],s._preSpiderfyLatlng=s._latlng,s.setLatLng(o),s.setOpacity&&s.setOpacity(1),r=new L.Polyline([a._latlng,o],{weight:1.5,color:"#222",opacity:d}),_.addLayer(r),s._spiderLeg=r,L.Path.SVG&&this.SVG_ANIMATION){var c=r._path.getTotalLength();r._path.setAttribute("stroke-dasharray",c+","+c);var f=e.createElementNS(p,"animate");f.setAttribute("attributeName","stroke-dashoffset"),f.setAttribute("begin","indefinite"),f.setAttribute("from",c),f.setAttribute("to",0),f.setAttribute("dur",.25),r._path.appendChild(f),f.beginElement(),f=e.createElementNS(p,"animate"),f.setAttribute("attributeName","stroke-opacity"),f.setAttribute("attributeName","stroke-opacity"),f.setAttribute("begin","indefinite"),f.setAttribute("from",0),f.setAttribute("to",.5),f.setAttribute("dur",.25),r._path.appendChild(f),f.beginElement()}if(a.setOpacity(.3),L.Path.SVG)for(this._group._forceLayout(),n=t.length-1;n>=0;n--)s=t[n]._spiderLeg,s.options.opacity=.5,s._path.setAttribute("stroke-opacity",.5);setTimeout(function(){h._animationEnd(),h.fire("spiderfied")},200)},_animationUnspiderfy:function(t){var e,i,n,s=this._group,r=s._map,o=s._featureGroup,a=t?r._latLngToNewLayerPoint(this._latlng,t.zoom,t.center):r.latLngToLayerPoint(this._latlng),h=this.getAllChildMarkers(),_=L.Path.SVG&&this.SVG_ANIMATION;for(s._animationStart(),this.setOpacity(1),i=h.length-1;i>=0;i--)e=h[i],e._preSpiderfyLatlng&&(e.setLatLng(e._preSpiderfyLatlng),delete e._preSpiderfyLatlng,e.setOpacity?(e._setPos(a),e.setOpacity(0)):o.removeLayer(e),_&&(n=e._spiderLeg._path.childNodes[0],n.setAttribute("to",n.getAttribute("from")),n.setAttribute("from",0),n.beginElement(),n=e._spiderLeg._path.childNodes[1],n.setAttribute("from",.5),n.setAttribute("to",0),n.setAttribute("stroke-opacity",0),n.beginElement(),e._spiderLeg._path.setAttribute("stroke-opacity",0)));setTimeout(function(){var t=0;for(i=h.length-1;i>=0;i--)e=h[i],e._spiderLeg&&t++;for(i=h.length-1;i>=0;i--)e=h[i],e._spiderLeg&&(e.setOpacity&&(e.setOpacity(1),e.setZIndexOffset(0)),t>1&&o.removeLayer(e),r.removeLayer(e._spiderLeg),delete e._spiderLeg);s._animationEnd()},200)}}:{_animationSpiderfy:function(t,e){var i,n,s,r,o=this._group,a=o._map,h=o._featureGroup;for(i=t.length-1;i>=0;i--)r=a.layerPointToLatLng(e[i]),n=t[i],n._preSpiderfyLatlng=n._latlng,n.setLatLng(r),n.setZIndexOffset&&n.setZIndexOffset(1e6),h.addLayer(n),s=new L.Polyline([this._latlng,r],{weight:1.5,color:"#222"}),a.addLayer(s),n._spiderLeg=s;this.setOpacity(.3),o.fire("spiderfied")},_animationUnspiderfy:function(){this._noanimationUnspiderfy()}}),L.MarkerClusterGroup.include({_spiderfied:null,_spiderfierOnAdd:function(){this._map.on("click",this._unspiderfyWrapper,this),this._map.options.zoomAnimation&&this._map.on("zoomstart",this._unspiderfyZoomStart,this),this._map.on("zoomend",this._noanimationUnspiderfy,this),L.Path.SVG&&!L.Browser.touch&&this._map._initPathRoot()},_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()},_unspiderfyZoomStart:function(){this._map&&this._map.on("zoomanim",this._unspiderfyZoomAnim,this)},_unspiderfyZoomAnim:function(t){L.DomUtil.hasClass(this._map._mapPane,"leaflet-touching")||(this._map.off("zoomanim",this._unspiderfyZoomAnim,this),this._unspiderfy(t))},_unspiderfyWrapper:function(){this._unspiderfy()},_unspiderfy:function(t){this._spiderfied&&this._spiderfied.unspiderfy(t)},_noanimationUnspiderfy:function(){this._spiderfied&&this._spiderfied._noanimationUnspiderfy()},_unspiderfyLayer:function(t){t._spiderLeg&&(this._featureGroup.removeLayer(t),t.setOpacity(1),t.setZIndexOffset(0),this._map.removeLayer(t._spiderLeg),delete t._spiderLeg)}})}(window,document); |
/branches/v1.10-negrette/widget/modules/carto/squelettes/scripts/leaflet.markercluster-src.js |
---|
New file |
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.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)); |
/branches/v1.10-negrette/widget/modules/carto/squelettes/css/MarkerCluster.css |
---|
New file |
0,0 → 1,6 |
.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; |
} |
/branches/v1.10-negrette/widget/modules/carto/squelettes/css/MarkerCluster.Default.css |
---|
New file |
0,0 → 1,60 |
.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; |
} |
/branches/v1.10-negrette/widget/modules/carto/squelettes/css/carto.css |
---|
New file |
0,0 → 1,61 |
@CHARSET "UTF-8"; |
body { |
margin: 0px; |
padding: 0px; |
} |
#map { |
position: relative; |
} |
#logo-tb { |
position: fixed; |
top: 10px; |
left: 9px; |
z-index: 2000; /* ça ou autre chose... */ |
border: solid #bebebe 1px; |
padding: 5px; |
border-radius: 4px; |
background-color: rgba(255,255,255,0.9); |
} |
.leaflet-left .leaflet-control { |
margin-top: 95px; |
} |
#logo-tb img { |
height: 60px; |
} |
ul { |
padding-left: 10px; |
margin-top: 5px; |
margin-bottom: 5px; |
} |
ul li { |
background: url("../images/petitCarreContour.jpg") no-repeat scroll 10px 6px transparent; |
line-height: 18px; |
list-style: none outside none; |
padding-left: 25px; |
} |
a { |
text-decoration : none; |
border-bottom: 1px dotted #95AE5D; |
color: #598000; |
margin: 0; |
} |
.popup-simple-text { |
font-size: 1.15em; |
} |
#map .marker-cluster-small { |
background-color: rgba(0, 102, 0, 0.5); |
} |
#map .marker-cluster-medium { |
background-color: rgba(241, 211, 87, 0.8); |
} |
Property changes: |
Added: svn:mime-type |
+text/plain |
\ No newline at end of property |
/branches/v1.10-negrette/widget/modules/carto/config.ini |
---|
New file |
0,0 → 1,3 |
[carto] |
urlPageFiche = "http://www.tela-botanica.org/page:herbiers_carto" |
Property changes: |
Added: svn:mime-type |
+text/plain |
\ No newline at end of property |
/branches/v1.10-negrette/widget/Widget.php |
---|
New file |
0,0 → 1,160 |
<?php |
// In : utf8 url_encoded (get et post) |
// Out : utf8 |
/** |
* La classe Widget analyser l'url et chage le widget correspondant. |
* Format d'url : |
* /widget/nom_du_widget?parametres1=ma_valeur1¶metre2=ma_valeur2 |
* Les widget sont dans des dossiers en minuscule correspondant au nom de la classe du widget. |
* Exemple : /widget/carto avec la classe Carto.php dans le dossier carto. |
* |
* |
* @author jpm |
* |
*/ |
class Widget { |
/** Les paramètres de configuration extrait du fichier .ini */ |
private static $config; |
/** Le nom du widget demandé. */ |
private $widget = null; |
/** Les chemins où l'autoload doit chercher des classes. */ |
private static $autoload_chemins = array(); |
/** Les paramètres de l'url $_GET nettoyés. */ |
private $parametres = null; |
/** |
* Constructeur. |
* Parse le fichier de configuraion "widget.ini" et parse l'url à la recherche du widget demandé. |
* @param str iniFile Configuration file to use |
*/ |
public function __construct($fichier_ini = 'widget.ini.php') { |
// Chargement de la configuration |
self::$config = parse_ini_file($fichier_ini, TRUE); |
// Paramêtres de config dynamiques |
self::$config['chemins']['baseURLAbsoluDyn'] = 'http://'.$_SERVER['SERVER_NAME'].self::$config['chemins']['baseURL'].'%s'; |
// Gestion de la mémoire maximum allouée aux services |
ini_set('memory_limit', self::$config['parametres']['limiteMemoire']); |
// Réglages de PHP |
setlocale(LC_ALL, self::$config['parametres']['locale']); |
date_default_timezone_set(self::$config['parametres']['fuseauHoraire']); |
// Gestion des erreurs |
error_reporting(self::$config['parametres']['erreurNiveau']); |
if (isset($_SERVER['REQUEST_URI']) && isset($_SERVER['QUERY_STRING'])) { |
$url_morceaux = $this->parserUrl(); |
if (isset($url_morceaux[0])) { |
$this->widget = $url_morceaux[0]; |
self::$config['chemins']['widgetCourantDossier'] = self::$config['chemins']['widgetsDossier'].strtolower($this->widget).DIRECTORY_SEPARATOR; |
$this->chargerWidgetConfig(); |
} |
// Chargement des chemins pour l'autoload |
$this->chargerCheminAutoload(); |
// Enregistrement de la méthode gérant l'autoload des classes |
spl_autoload_register(array('Widget', 'chargerClasse')); |
// Nettoyage du $_GET (sécurité) |
$this->collecterParametres(); |
} else { |
$e = 'Les widget nécessite les variables serveurs suivantes pour fonctionner : REQUEST_URI et QUERY_STRING.'; |
trigger_error($e, E_USER_ERROR); |
} |
} |
private function parserUrl() { |
if (strlen($_SERVER['QUERY_STRING']) == 0) { |
$len = strlen($_SERVER['REQUEST_URI']); |
} else { |
$len = -(strlen($_SERVER['QUERY_STRING']) + 1); |
} |
$url = substr($_SERVER['REQUEST_URI'], strlen(self::$config['chemins']['baseURL']), $len); |
$url_morceaux = explode('/', $url); |
return $url_morceaux; |
} |
private function collecterParametres() { |
if (isset($_GET) && $_GET != '') { |
$this->nettoyerGet(); |
$this->parametres = $_GET; |
} |
} |
private function nettoyerGet() { |
foreach ($_GET as $cle => $valeur) { |
$verifier = array('NULL', "\n", "\r", "\\", '"', "\x00", "\x1a", ';'); |
$_GET[$cle] = strip_tags(str_replace($verifier, '', $valeur)); |
} |
} |
private function chargerCheminAutoload() { |
$chemins_communs = explode(';', self::$config['chemins']['autoload']); |
$chemins_communs = array_map('trim', $chemins_communs); |
array_unshift($chemins_communs, ''); |
$chemins_widget = array(); |
if (isset(self::$config[$this->widget]['autoload'])) { |
$chemins_widget = explode(';', self::$config[$this->widget]['autoload']); |
foreach ($chemins_widget as $cle => $chemin) { |
$chemins_widget[$cle] = self::$config['chemins']['widgetCourantDossier'].trim($chemin); |
} |
} |
self::$autoload_chemins = array_merge($chemins_communs, $chemins_widget); |
} |
/** |
* La méthode chargerClasse() charge dynamiquement les classes trouvées dans le code. |
* Cette fonction est appelée par php5 quand il trouve une instanciation de classe dans le code. |
* |
*@param string le nom de la classe appelée. |
*@return void le fichier contenant la classe doit être inclu par la fonction. |
*/ |
public static function chargerClasse($classe) { |
if (class_exists($classe)) { |
return null; |
} |
foreach (self::$autoload_chemins as $chemin) { |
$chemin = $chemin.$classe.'.php'; |
if (file_exists($chemin)) { |
require_once $chemin; |
} |
} |
} |
/** |
* Execute le widget. |
*/ |
function executer() { |
if (!is_null($this->widget)) { |
$classe_widget = ucfirst($this->widget); |
$fichier_widget = self::$config['chemins']['widgetCourantDossier'].$classe_widget.'.php'; |
if (file_exists($fichier_widget)) { |
include_once $fichier_widget; |
if (class_exists($classe_widget)) { |
$widget = new $classe_widget(self::$config, $this->parametres); |
$widget->executer(); |
} |
} |
} |
} |
/** |
* Charge le fichier de config spécifique du wiget et fusionne la config avec celle partagés par l'ensemble des widgets. |
*/ |
private function chargerWidgetConfig() { |
$widget_config_ini_fichier = self::$config['chemins']['widgetCourantDossier'].'config.ini'; |
if (file_exists($widget_config_ini_fichier)) { |
$widget_config = parse_ini_file($widget_config_ini_fichier, TRUE); |
self::$config = array_merge(self::$config, $widget_config); |
} |
} |
} |
?> |
/branches/v1.10-negrette/widget/.htaccess |
---|
New file |
0,0 → 1,13 |
<files *.ini> |
order deny,allow |
deny from all |
</files> |
#AddHandler x-httpd-php5 .php |
AddDefaultCharset UTF-8 |
RewriteEngine On |
# Redirections générale vers le fichier principal de Widget. |
RewriteCond %{REQUEST_FILENAME} !-d |
RewriteCond %{REQUEST_FILENAME} !-f |
RewriteRule ^.*$ index.php/ |
/branches/v1.10-negrette/widget/bibliotheque/WidgetCommun.php |
---|
New file |
0,0 → 1,431 |
<?php |
abstract class WidgetCommun { |
protected $config = null; |
protected $parametres = null; |
protected $messages = array(); |
protected $debug = array(); |
public function __construct($config, $parametres) { |
$this->config = $config; |
$this->parserFichierIni($config['chemins']['widgetCourantDossier'].'config.ini'); |
$this->parametres = $parametres; |
} |
/** |
* Parse le fichier ini donné en paramètre |
* @param string $fichier_ini nom du fichier ini à parser |
* @return boolean true si le fichier ini a été trouvé. |
*/ |
private function parserFichierIni($fichier_ini) { |
$retour = false; |
if (file_exists($fichier_ini)) { |
$ini = parse_ini_file($fichier_ini, true); |
$this->fusionner($ini); |
$retour = true; |
} |
return $retour; |
} |
/** |
* fusionne un tableau de paramètres avec le tableau de config global |
* @param array $ini le tableau à fusionner |
*/ |
private function fusionner(array $ini) { |
$this->config = array_merge($this->config, $ini); |
} |
protected function traiterNomMethodeExecuter($nom) { |
$methode = 'executer'; |
$methode .= str_replace(' ', '', ucwords(str_replace('-', ' ', strtolower($nom)))); |
return $methode; |
} |
//+----------------------------------------------------------------------------------------------------------------+ |
// GESTION des CLASSES CHARGÉES à la DEMANDE |
protected function getDao() { |
if (! isset($this->dao)) { |
$this->dao = new Dao(); |
} |
return $this->dao; |
} |
//+----------------------------------------------------------------------------------------------------------------+ |
// GESTION DE MÉTHODES COMMUNES ENTRE LES SERVICES |
protected function getUrlImage($id, $format = 'L') { |
$url_tpl = $this->config['chemins']['celImgUrlTpl']; |
$id = sprintf('%09s', $id).$format; |
$url = sprintf($url_tpl, $id); |
return $url; |
} |
protected function encoderMotCle($mot_cle) { |
return md5(mb_strtolower($mot_cle)); |
} |
private function protegerMotsCles($mots_cles, $type) { |
$separateur = ($type == self::TYPE_IMG) ? ',' : ';' ; |
$mots_cles_a_proteger = explode($separateur,rtrim(trim($mots_cles), $separateur)); |
foreach ($mots_cles_a_proteger as $mot) { |
$mots_cles_proteges[] = $this->bdd->quote($mot); |
} |
$mots_cles = implode(',', $mots_cles_proteges); |
return $mots_cles; |
} |
protected function tronquerCourriel($courriel) { |
$courriel = preg_replace('/[^@]+$/i', '...', $courriel); |
return $courriel; |
} |
protected function nettoyerTableau($tableau) { |
foreach ($tableau as $cle => $valeur) { |
if (is_array($valeur)) { |
$valeur = $this->nettoyerTableau($valeur); |
} else { |
$valeur = $this->nettoyerTexte($valeur); |
} |
$tableau[$cle] = $valeur; |
} |
return $tableau; |
} |
protected function nettoyerTexte($txt) { |
$txt = preg_replace('/&(?!([a-z]+|#[0-9]+|#x[0-9][a-f]+);)/i', '&', $txt); |
$txt = preg_replace('/^(?:000null|null)$/i', '', $txt); |
return $txt; |
} |
protected function etreVide($valeur) { |
$vide = false; |
if ($valeur == '' || $valeur == 'null'|| $valeur == '000null' || $valeur == '0') { |
$vide = true; |
} |
return $vide; |
} |
protected function formaterDate($date_heure_mysql, $format = '%A %d %B %Y à %H:%M') { |
$date_formatee = ''; |
if (!$this->etreVide($date_heure_mysql)) { |
$timestamp = $this->convertirDateHeureMysqlEnTimestamp($date_heure_mysql); |
$date_formatee = strftime($format, $timestamp); |
} |
return $date_formatee; |
} |
protected function convertirDateHeureMysqlEnTimestamp($date_heure_mysql){ |
$val = explode(' ', $date_heure_mysql); |
$date = explode('-', $val[0]); |
$heure = explode(':', $val[1]); |
return mktime((int) $heure[0], (int) $heure[1], (int) $heure[2], (int) $date[1], (int) $date[2], (int) $date[0]); |
} |
//+----------------------------------------------------------------------------------------------------------------+ |
// GESTION DE L'IDENTIFICATION et des UTILISATEURS |
protected function getAuthIdentifiant() { |
$id = (isset($_SERVER['PHP_AUTH_USER'])) ? $_SERVER['PHP_AUTH_USER'] : null; |
return $id; |
} |
protected function getAuthMotDePasse() { |
$mdp = (isset($_SERVER['PHP_AUTH_PW'])) ? $_SERVER['PHP_AUTH_PW'] : null; |
return $mdp; |
} |
protected function authentifierAdmin() { |
$message_accueil = "Veuillez vous identifier avec votre compte Tela Botanica."; |
$message_echec = "Accès limité aux administrateurs du CEL.\n". |
"Votre tentative d'identification a échoué.\n". |
"Actualiser la page pour essayer à nouveau si vous êtes bien inscrit comme administrateur."; |
return $this->authentifier($message_accueil, $message_echec, 'Admin'); |
} |
protected function authentifierUtilisateur() { |
$message_accueil = "Veuillez vous identifier avec votre compte Tela Botanica."; |
$message_echec = "Accès limité aux utilisateur du CEL.\n". |
"Inscrivez vous http://www.tela-botanica.org/page:inscription pour le devenir.\n". |
"Votre tentative d'identification a échoué.\n". |
"Actualiser la page pour essayer à nouveau si vous êtes déjà inscrit ou contacter 'accueil@tela-botanica.org'."; |
return $this->authentifier($message_accueil, $message_echec, 'Utilisateur'); |
} |
private function authentifier($message_accueil, $message_echec, $type) { |
$id = $this->getAuthIdentifiant(); |
if (!isset($id)) { |
$this->envoyerAuth($message_accueil, $message_echec); |
} else { |
if ($type == 'Utilisateur' && $this->getAuthMotDePasse() == 'debug') { |
$autorisation = true; |
} else { |
$methodeAutorisation = "etre{$type}Autorise"; |
$autorisation = $this->$methodeAutorisation(); |
} |
if ($autorisation == false) { |
$this->envoyerAuth($message_accueil, $message_echec); |
} |
} |
return true; |
} |
protected function etreUtilisateurAutorise() { |
$identifiant = $this->getAuthIdentifiant(); |
$mdp = md5($this->getAuthMotDePasse()); |
$url = sprintf($this->config['authentification']['serviceUrlTpl'], $identifiant, $mdp); |
$json = $this->getDao()->consulter($url); |
$existe = json_decode($json); |
$autorisation = (isset($existe) && $existe) ? true :false; |
return $autorisation; |
} |
protected function etreAdminAutorise($identifiant) { |
$identifiant = $this->getAuthIdentifiant(); |
$autorisation = ($this->etreUtilisateurAutorise() && $this->etreAdminCel($identifiant)) ? true : false; |
return $autorisation; |
} |
protected function etreAdminCel($courriel) { |
$admins = $this->config['authentification']['administrateurs']; |
$courriels_autorises = explode(',', $admins); |
$autorisation = (in_array($courriel, $courriels_autorises)) ? true : false ; |
return $autorisation; |
} |
/** |
* Prend en paramêtre un tableau de courriels et retourne après avoir intérogé un service we de l'annuaire |
* une tableau avec en clé le courriel et en valeur, un tableau associatif : |
* - nom : le nom de l'utilisateur |
* - prenom : le prénom de l'utilisateur. |
* @param array $courriels un tableau de courriels pour lesquels il faut recherche le prénom et nom. |
*/ |
protected function recupererUtilisateursNomPrenom(Array $courriels) { |
// Récupération des données au format Json |
$service = "utilisateur/prenom-nom-par-courriel/".implode(',', $courriels); |
$url = sprintf($this->config['chemins']['baseURLServicesAnnuaireTpl'], $service); |
$json = $this->getDao()->consulter($url); |
return (array) json_decode($json); |
} |
protected function recupererUtilisateursIdentite(Array $courriels) { |
// Récupération des données au format Json |
$service = "utilisateur/identite-par-courriel/".implode(',', $courriels); |
$url = sprintf($this->config['chemins']['baseURLServicesAnnuaireTpl'], $service); |
$json = $this->getDao()->consulter($url); |
$utilisateurs = json_decode($json); |
foreach ($courriels as $courriel) { |
$info = array('id' => null, 'intitule' => ''); |
if (isset($utilisateurs->$courriel)) { |
$info['intitule'] = $utilisateurs->$courriel->intitule; |
$info['id'] = $utilisateurs->$courriel->id; |
} else { |
$info['intitule'] = $this->tronquerCourriel($courriel); |
} |
$noms[$courriel] = $info; |
} |
return $noms; |
} |
//+----------------------------------------------------------------------------------------------------------------+ |
// GESTION de l'ENVOIE au NAVIGATEUR |
protected function envoyerJsonp($donnees = null, $encodage = 'utf-8') { |
$contenu = $_GET['callback'].'('.json_encode($donnees).');'; |
$this->envoyer($contenu, 'text/html', $encodage); |
} |
protected function envoyer($donnees = null, $mime = 'text/html', $encodage = 'utf-8') { |
// Traitements des messages d'erreurs et données |
if (count($this->messages) != 0) { |
header('HTTP/1.1 500 Internal Server Error'); |
$mime = 'text/html'; |
$encodage = 'utf-8'; |
$sortie = '<html>'. |
'<head><title>Messages</title></head>'. |
'<body><pre>'.implode("\n", $this->messages).'</pre><body>'. |
'</html>'; |
} else { |
$sortie = $donnees; |
if (is_null($donnees)) { |
$sortie = 'OK'; |
} |
} |
// Gestion de l'envoie du déboguage |
$this->envoyerDebogage(); |
// Envoie sur la sortie standard |
$this->envoyerContenu($encodage, $mime, $sortie); |
} |
private function envoyerDebogage() { |
if (!is_array($this->debug)) { |
$this->debug[] = $this->debug; |
} |
if (count($this->debug) != 0) { |
foreach ($this->debug as $cle => $val) { |
if (is_array($val)) { |
$this->debug[$cle] = print_r($val, true); |
} |
} |
header('X-DebugJrest-Data:'.json_encode($this->debug)); |
} |
} |
private function envoyerContenu($encodage, $mime, $contenu) { |
if (!is_null($mime) && !is_null($encodage)) { |
header("Content-Type: $mime; charset=$encodage"); |
} else if (!is_null($mime) && is_null($encodage)) { |
header("Content-Type: $mime"); |
} |
print_r($contenu); |
} |
private function envoyerAuth($message_accueil, $message_echec) { |
header('HTTP/1.0 401 Unauthorized'); |
header('WWW-Authenticate: Basic realm="'.mb_convert_encoding($message_accueil, 'ISO-8859-1', 'UTF-8').'"'); |
header('Content-type: text/plain; charset=UTF-8'); |
print $message_echec; |
exit(0); |
} |
//+----------------------------------------------------------------------------------------------------------------+ |
// GESTION DES SQUELETTES (PHP, TXT...) |
/** |
* Méthode prenant en paramètre un tableau associatif, les clés seront recherchées dans le texte pour être |
* remplacer par la valeur. Dans le texte, les clés devront être entre accolades : {} |
* |
* @param String $txt le texte où chercher les motifs. |
* @param Array $donnees un tableau associatif contenant les motifs à remplacer. |
* |
* @return String le texte avec les motifs remplacer par les valeurs du tableau. |
*/ |
protected static function traiterSqueletteTxt($txt, Array $donnees = array()) { |
$motifs = array(); |
$valeurs = array(); |
foreach ($donnees as $cle => $valeur) { |
if (strpos($cle, '{') === false && strpos($cle, '}') === false) { |
$motifs = '{'.$cle.'}'; |
$valeurs = $valeur; |
} |
} |
$txt = str_replace($motifs, $valeurs, $txt); |
return $txt; |
} |
/** |
* Méthode prenant en paramètre un chemin de fichier squelette et un tableau associatif de données, |
* en extrait les variables, charge le squelette et retourne le résultat des deux combinés. |
* |
* @param String $fichier le chemin du fichier du squelette |
* @param Array $donnees un tableau associatif contenant les variables a injecter dans le squelette. |
* |
* @return boolean false si le squelette n'existe pas, sinon la chaine résultat. |
*/ |
protected static function traiterSquelettePhp($fichier, Array $donnees = array()) { |
$sortie = false; |
if (file_exists($fichier)) { |
// Extraction des variables du tableau de données |
extract($donnees); |
// Démarage de la bufferisation de sortie |
ob_start(); |
// Si les tags courts sont activés |
if ((bool) @ini_get('short_open_tag') === true) { |
// Simple inclusion du squelette |
include $fichier; |
} else { |
// Sinon, remplacement des tags courts par la syntaxe classique avec echo |
$html_et_code_php = self::traiterTagsCourts($fichier); |
// Pour évaluer du php mélangé dans du html il est nécessaire de fermer la balise php ouverte par eval |
$html_et_code_php = '?>'.$html_et_code_php; |
// Interprétation du html et du php dans le buffer |
echo eval($html_et_code_php); |
} |
// Récupèration du contenu du buffer |
$sortie = ob_get_contents(); |
// Suppression du buffer |
@ob_end_clean(); |
} else { |
$msg = "Le fichier du squelette '$fichier' n'existe pas."; |
trigger_error($msg, E_USER_WARNING); |
} |
// Retourne le contenu |
return $sortie; |
} |
/** |
* Fonction chargeant le contenu du squelette et remplaçant les tags court php (<?= ...) par un tag long avec echo. |
* |
* @param String $chemin_squelette le chemin du fichier du squelette |
* |
* @return string le contenu du fichier du squelette php avec les tags courts remplacés. |
*/ |
private static function traiterTagsCourts($chemin_squelette) { |
$contenu = file_get_contents($chemin_squelette); |
// Remplacement de tags courts par un tag long avec echo |
$contenu = str_replace('<?=', '<?php echo ', $contenu); |
// Ajout systématique d'un point virgule avant la fermeture php |
$contenu = preg_replace("/;*\s*\?>/", "; ?>", $contenu); |
return $contenu; |
} |
//+----------------------------------------------------------------------------------------------------------------+ |
// UTILITAIRES |
/** |
* Permet de trier un tableau multi-dimenssionnel en gardant l'ordre des clés. |
* |
* @param Array $array le tableau à trier |
* @param Array $cols tableau indiquant en clé la colonne à trier et en valeur l'ordre avec SORT_ASC ou SORT_DESC |
* @author cagret at gmail dot com |
* @see http://fr.php.net/manual/fr/function.array-multisort.php Post du 21-Jun-2009 12:38 |
*/ |
public static function trierTableauMd($array, $cols) { |
$colarr = array(); |
foreach ($cols as $col => $order) { |
$colarr[$col] = array(); |
foreach ($array as $k => $row) { |
$colarr[$col]['_'.$k] = strtolower(self::supprimerAccents($row[$col])); |
} |
} |
$params = array(); |
foreach ($cols as $col => $order) { |
$params[] =& $colarr[$col]; |
$params = array_merge($params, (array)$order); |
} |
call_user_func_array('array_multisort', $params); |
$ret = array(); |
$keys = array(); |
$first = true; |
foreach ($colarr as $col => $arr) { |
foreach ($arr as $k => $v) { |
if ($first) { |
$keys[$k] = substr($k,1); |
} |
$k = $keys[$k]; |
if (!isset($ret[$k])) { |
$ret[$k] = $array[$k]; |
} |
$ret[$k][$col] = $array[$k][$col]; |
} |
$first = false; |
} |
return $ret; |
} |
private static function supprimerAccents($str, $charset='utf-8') |
{ |
$str = htmlentities($str, ENT_NOQUOTES, $charset); |
$str = preg_replace('#&([A-za-z])(?:acute|cedil|circ|grave|orn|ring|slash|th|tilde|uml);#', '\1', $str); |
$str = preg_replace('#&([A-za-z]{2})(?:lig);#', '\1', $str); // pour les ligatures e.g. 'œ' |
$str = preg_replace('#&[^;]+;#', '', $str); // supprime les autres caractères |
return $str; |
} |
} |
?> |
/branches/v1.10-negrette/widget/bibliotheque/Dao.php |
---|
New file |
0,0 → 1,155 |
<?php |
// declare(encoding='UTF-8'); |
/** |
* Classe modèle spécifique à l'application, donc d'accés au données, elle ne devrait pas être appelée de l'extérieur. |
* |
* @category php5 |
* @package Widget |
* @author Jean-Pascal MILCENT <jpm@tela-botanica.org> |
* @copyright 2010 Tela-Botanica |
* @license http://www.cecill.info/licences/Licence_CeCILL_V2-fr.txt Licence CECILL |
* @license http://www.gnu.org/licenses/gpl.html Licence GNU-GPL |
* @version SVN: $Id$ |
*/ |
class Dao { |
const HTTP_URL_REQUETE_SEPARATEUR = '&'; |
const HTTP_URL_REQUETE_CLE_VALEUR_SEPARATEUR = '='; |
private $http_methodes = array('GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'CONNECT', 'TRACE'); |
protected $parametres = null; |
private $url = null; |
private $reponse_entetes = null; |
//+----------------------------------------------------------------------------------------------------------------+ |
// ACCESSEURS |
public function getReponseEntetes($cle) { |
return $this->reponse_entetes; |
} |
public function getParametre($cle) { |
$valeur = (isset($this->parametres[$cle])) ? $this->parametres[$cle] : null; |
return $valeur; |
} |
public function ajouterParametre($cle, $valeur) { |
$this->parametres[$cle] = $valeur; |
} |
public function supprimerParametre($cle) { |
unset($this->parametres[$cle]); |
} |
public function nettoyerParametres() { |
$this->parametres = null; |
} |
//+----------------------------------------------------------------------------------------------------------------+ |
// MÉTHODES |
public function consulter($url) { |
$retour = $this->envoyerRequete($url, 'GET'); |
return $retour; |
} |
public function ajouter($url, Array $donnees) { |
$retour = $this->envoyerRequete($url, 'PUT', $donnees); |
return $retour; |
} |
public function modifier($url, Array $donnees) { |
$retour = $this->envoyerRequete($url, 'POST', $donnees); |
return $retour; |
} |
public function supprimer($url) { |
$retour = $this->envoyerRequete($url, 'DELETE'); |
return $retour; |
} |
public function envoyerRequete($url, $mode, Array $donnees = array()) { |
$this->url = $url; |
$contenu = false; |
if (! in_array($mode, $this->http_methodes)) { |
$e = "Le mode de requête '$mode' n'est pas accepté!"; |
trigger_error($e, E_USER_WARNING); |
} else { |
if ($mode == 'GET') { |
$this->traiterUrlParametres(); |
} |
$contexte = stream_context_create(array( |
'http' => array( |
'method' => $mode, |
'header' => "Content-type: application/x-www-form-urlencoded\r\n", |
'content' => http_build_query($donnees, null, self::HTTP_URL_REQUETE_SEPARATEUR)))); |
$flux = @fopen($this->url, 'r', false, $contexte); |
if (!$flux) { |
$this->reponse_entetes = $http_response_header; |
$e = "L'ouverture de l'url '{$this->url}' par la méthode HTTP '$mode' a échoué!"; |
trigger_error($e, E_USER_WARNING); |
} else { |
// Informations sur les en-têtes et métadonnées du flux |
$this->reponse_entetes = stream_get_meta_data($flux); |
// Contenu actuel de $url |
$contenu = stream_get_contents($flux); |
fclose($flux); |
} |
$this->traiterEntete(); |
} |
$this->reinitialiser(); |
return $contenu; |
} |
private function traiterUrlParametres() { |
$parametres = array(); |
if (count($this->parametres) > 0) { |
foreach ($this->parametres as $cle => $valeur) { |
$cle = rawurlencode($cle); |
$valeur = rawurlencode($valeur); |
$parametres[] = $cle.self::HTTP_URL_REQUETE_CLE_VALEUR_SEPARATEUR.$valeur; |
} |
$url_parametres = implode(self::HTTP_URL_REQUETE_SEPARATEUR, $parametres); |
$this->url = $this->url.'?'.$url_parametres; |
} |
} |
private function traiterEntete() { |
$infos = $this->analyserEntete(); |
$this->traiterEnteteDebogage($infos); |
} |
private function analyserEntete() { |
$entetes = $this->reponse_entetes; |
$infos = array('date' => null, 'uri' => $this->url, 'debogages' => null); |
if (isset($entetes['wrapper_data'])) { |
$entetes = $entetes['wrapper_data']; |
} |
foreach ($entetes as $entete) { |
if (preg_match('/^X_REST_DEBOGAGE_MESSAGES: (.+)$/', $entete, $match)) { |
$infos['debogages'] = json_decode($match[1]); |
} |
if (preg_match('/^Date: .+ ([012][0-9]:[012345][0-9]:[012345][0-9]) .*$/', $entete, $match)) { |
$infos['date'] = $match[1]; |
} |
} |
return $infos; |
} |
private function traiterEnteteDebogage($entetes_analyses) { |
if (isset($entetes['debogages'])) { |
$date = $entetes['date']; |
$uri = $entetes['uri']; |
$debogages = $entetes['debogages']; |
foreach ($debogages as $debogage) { |
$e = "DEBOGAGE : $date - $uri :\n$debogage"; |
trigger_error($e, E_USER_NOTICE); |
} |
} |
} |
private function reinitialiser() { |
$this->nettoyerParametres(); |
} |
} |
/branches/v1.10-negrette/widget/index.php |
---|
New file |
0,0 → 1,5 |
<?php |
require 'Widget.php'; |
$widget = new Widget(); |
$widget->executer(); |
?> |
/branches/v1.10-negrette/widget/widget.ini.defaut.php |
---|
New file |
0,0 → 1,29 |
;<?/* |
[parametres] |
;Memoire maxi pour les services : 128Mo = 134217728 ; 256Mo = 268435456 ; 512Mo = 536870912 ; 1Go = 1073741824 |
limiteMemoire = "512M" |
; Niveau d'erreur PHP |
erreurNiveau = 30719 ; E_ALL = 30719 |
; Séparateur d'url en entrée |
argSeparatorInput = "&" |
; Indication de la locale (setLocale(LC_ALL, ?)) pour les classes appelées par Widget.php |
locale = "fr_FR.UTF-8" |
; Indication du fuseau horraire par défaut date_default_timezone_set(?)pour les classes appelées par Widget.php |
fuseauHoraire = "Europe/Paris" |
[chemins] |
; Chemins | utiliser dans la méthode autoload des widgets |
autoload = "bibliotheque/" |
; Dossier contenant les widgets |
widgetsDossier = "modules/" |
; Dossier contenant le widget demandé construit dynamiquement dans le fichier Widget.php |
widgetCourantDossier = "" |
; Dossier contenant les fichiers des bibliothèques tierces |
bibliothequeDossier = "bibliotheque/" |
; Base de l'url servant | appeler les widgets |
baseURL = "/widget:coel:" |
; URL de base absolue des Widgets du CEL construit dynamiquement dans le fichier WidgetCommun.php |
baseURLAbsoluDyn = "" |
; URL des services web sous forme de template | utiliser avec sprintf |
baseURLServicesTpl = "http://api.tela-botanica.org/services/coel/" |
;*/?> |
/branches/v1.10-negrette/widget/. |
---|
New file |
Property changes: |
Added: svn:ignore |
+widget.ini.php |