Вход Регистрация
Файл: web-kgvip/js/gmap3.js
Строк: 2406
<?php
/*!
 *  GMAP3 Plugin for JQuery
 *  Version   : 5.1.1
 *  Date      : 2013-05-25
 *  Licence   : GPL v3 : http://www.gnu.org/licenses/gpl.html
 *  Author    : DEMONTE Jean-Baptiste
 *  Contact   : jbdemonte@gmail.com
 *  Web site  : http://gmap3.net
 *
 *  Copyright (c) 2010-2012 Jean-Baptiste DEMONTE
 *  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   - Neither the name of the author nor the names of its contributors
 *     may be used to endorse or promote products derived from this
 *     software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
;(function ($, undef) {

  
/***************************************************************************/
  /*                           GMAP3 DEFAULTS                                */
  /***************************************************************************/
  // defaults are defined later in the code to pass the rails asset pipeline and
  //jasmine while google library is not loaded
  
var defaultsgId 0;

  function 
initDefaults() {
    if (!
defaults) {
      
defaults = {
        
verbosefalse,
        
queryLimit: {
          
attempt5,
          
delay250// setTimeout(..., delay + random);
          
random250
        
},
        
classes: {
          
Map               google.maps.Map,
          
Marker            google.maps.Marker,
          
InfoWindow        google.maps.InfoWindow,
          
Circle            google.maps.Circle,
          
Rectangle         google.maps.Rectangle,
          
OverlayView       google.maps.OverlayView,
          
StreetViewPanoramagoogle.maps.StreetViewPanorama,
          
KmlLayer          google.maps.KmlLayer,
          
TrafficLayer      google.maps.TrafficLayer,
          
BicyclingLayer    google.maps.BicyclingLayer,
          
GroundOverlay     google.maps.GroundOverlay,
          
StyledMapType     google.maps.StyledMapType,
          
ImageMapType      google.maps.ImageMapType
        
},
        
map: {
          
mapTypeId google.maps.MapTypeId.ROADMAP,
          
center: [46.5784982.457275],
          
zoom2
        
},
        
overlay: {
          
pane"floatPane",
          
content"",
          
offset: {
            
x0,
            
y0
          
}
        },
        
geoloc: {
          
getCurrentPosition: {
            
maximumAge60000,
            
timeout5000
          
}
        }
      }
    }
  }

  function 
globalId(idsimulate){
    return 
id !== undef id "gmap3_" + (simulate gId : ++gId);
  }

  
/**
   * Return true if current version of Google Maps is equal or above to these in parameter
   * @param version {string} Minimal version required
   * @return {Boolean}
   */
  
function googleVersionMin(version) {
    var 
i,
      
gmVersion google.maps.version.split(".");
    
version version.split(".");
    for(
0gmVersion.lengthi++) {
      
gmVersion[i] = parseInt(gmVersion[i], 10);
    }
    for(
0version.lengthi++) {
      
version[i] = parseInt(version[i], 10);
      if (
gmVersion.hasOwnProperty(i)) {
        if (
gmVersion[i] < version[i]) {
          return 
false;
        }
      } else {
        return 
false;
      }
    }
    return 
true;
  }

  
/**
   * attach events from a container to a sender 
   * todo[
   *  events => { eventName => function, }
   *  onces  => { eventName => function, }  
   *  data   => mixed data         
   * ]
   **/
  
function attachEvents($containerargssenderidsenders){
    if (
args.todo.events || args.todo.onces) {
      var 
context = {
        
idid,
        
dataargs.todo.data,
        
tagargs.todo.tag
      
};
      if (
args.todo.events){
        $.
each(args.todo.events, function(namef){
          var 
that $container, fn = f;
          if ($.
isArray(f)) {
            
that f[0];
            fn = 
f[1]
          }
          
google.maps.event.addListener(sendername, function(event) {
            fn.
apply(that, [senders senders sendereventcontext]);
          });
        });
      }
      if (
args.todo.onces){
        $.
each(args.todo.onces, function(namef){
          var 
that $container, fn = f;
          if ($.
isArray(f)) {
            
that f[0];
            fn = 
f[1]
          }
          
google.maps.event.addListenerOnce(sendername, function(event) {
            fn.
apply(that, [senders senders sendereventcontext]);
          });
        });
      }
    }
  }

  
/***************************************************************************/
  /*                                STACK                                    */
  /***************************************************************************/
  
  
function Stack (){
    var 
st = [];
    
this.empty = function (){
      return !
st.length;
    };
    
this.add = function(v){
      
st.push(v);
    };
    
this.get = function (){
      return 
st.length st[0] : false;
    };
    
this.ack = function (){
      
st.shift();
    };
  }

  
/***************************************************************************/
  /*                                TASK                                     */
  /***************************************************************************/
  
  
function Task(ctxonEndtodo){
    var 
session = {},
      
that this
      
current,
      
resolve = {
        
latLng:{ // function => bool (=> address = latLng)
          
map:false
          
marker:false
          
infowindow:false
          
circle:false
          
overlayfalse,
          
getlatlngfalse,
          
getmaxzoomfalse,
          
getelevationfalse,
          
streetviewpanoramafalse,
          
getaddresstrue
        
},
        
geoloc:{
          
getgeoloctrue
        
}
      };
      
    if (
typeof todo === "string"){
      
todo =  unify(todo);
    }
  
    function 
unify(todo){
      var 
result = {};
      
result[todo] = {};
      return 
result;
    }
    
    function 
next(){
      var 
k;
      for(
k in todo){
        if (
k in session){ // already run
          
continue;
        }
        return 
k;
      }
    }
    
    
this.run = function (){
      var 
kopts;
      while(
next()){
        if (
typeof ctx[k] === "function"){
          
current k;
          
opts = $.extend(true, {}, defaults[k] || {}, todo[k].options || {});
          if (
k in resolve.latLng){
            if (
todo[k].values){
              
resolveAllLatLng(todo[k].valuesctxctx[k], {todo:todo[k], opts:optssession:session});
            } else {
              
resolveLatLng(ctxctx[k], resolve.latLng[k], {todo:todo[k], opts:optssession:session});
            }
          } else if (
k in resolve.geoloc){
            
geoloc(ctxctx[k], {todo:todo[k], opts:optssession:session});
          } else {
            
ctx[k].apply(ctx, [{todo:todo[k], opts:optssession:session}]);
          }
          return; 
// wait until ack
        
} else {
          
session[k] = null;
        }
      }
      
onEnd.apply(ctx, [todosession]);
    };
    
    
this.ack = function(result){
      
session[current] = result;
      
that.run.apply(that, []);
    };
  }
  
  function 
getKeys(obj){
    var 
kkeys = [];
    for(
k in obj){
      
keys.push(k);
    }
    return 
keys;
  }
  
  function 
tuple(argsvalue){
    var 
todo = {};
    
    
// "copy" the common data
    
if (args.todo){
      for(var 
k in args.todo){
        if ((
!== "options") && (!== "values")){
          
todo[k] = args.todo[k];
        }
      }
    }
    
// "copy" some specific keys from value first else args.todo
    
var ikeys = ["data""tag""id""events",  "onces"];
    for(
i=0i<keys.lengthi++){
      
copyKey(todokeys[i], valueargs.todo);
    }
    
    
// create an extended options
    
todo.options = $.extend({}, args.opts || {}, value.options || {});
    
    return 
todo;
  }
  
  
/**
   * copy a key content
   **/
  
function copyKey(targetkey){
    for(var 
i=2i<arguments.lengthi++){
      if (
key in arguments[i]){
        
target[key] = arguments[i][key];
        return;
      }
    }
  }
  
  
/***************************************************************************/
  /*                             GEOCODERCACHE                               */
  /***************************************************************************/
  
  
function GeocoderCache(){
    var 
cache = [];
    
    
this.get = function(request){
      if (
cache.length){
        var 
ijkitemeq,
          
keys getKeys(request);
        for(
i=0i<cache.lengthi++){
          
item cache[i];
          
eq keys.length == item.keys.length;
          for(
j=0; (j<keys.length) && eqj++){
            
keys[j];
            
eq k in item.request;
            if (
eq){
              if ((
typeof request[k] === "object") && ("equals" in request[k]) && (typeof request[k] === "function")){
                
eq request[k].equals(item.request[k]);
              } else{
                
eq request[k] === item.request[k];
              }
            }
          }
          if (
eq){
            return 
item.results;
          }
        }
      }
    };
    
    
this.store = function(requestresults){
      
cache.push({request:requestkeys:getKeys(request), results:results});
    };
  }

  
/***************************************************************************/
  /*                                OVERLAYVIEW                              */
  /***************************************************************************/
  
function OverlayView(mapoptslatLng$div) {
    var 
that thislisteners = [];
    
    
defaults.classes.OverlayView.call(this);
    
this.setMap(map);
    
    
this.onAdd = function() {
        var 
panes this.getPanes();
        if (
opts.pane in panes) {
            $(
panes[opts.pane]).append($div);
        }
        $.
each("dblclick click mouseover mousemove mouseout mouseup mousedown".split(" "), function(iname){
            
listeners.push(
                
google.maps.event.addDomListener($div[0], name, function(e) {
                    $.
Event(e).stopPropagation();
                    
google.maps.event.trigger(thatname, [e]);
                    
that.draw();
                })
            );
        });
        
listeners.push(
            
google.maps.event.addDomListener($div[0], "contextmenu", function(e) {
                $.
Event(e).stopPropagation();
                
google.maps.event.trigger(that"rightclick", [e]);
                
that.draw();
            })
        );
    };
    
this.getPosition = function(){
        return 
latLng;
    };
    
this.setPosition = function(newLatLng){
        
latLng newLatLng;
        
this.draw();
    };
    
this.draw = function() {
        var 
ps this.getProjection().fromLatLngToDivPixel(latLng);
        
$div
            
.css("left", (ps.x+opts.offset.x) + "px")
            .
css("top" , (ps.y+opts.offset.y) + "px");
    };
    
this.onRemove = function() {
      for (var 
0listeners.lengthi++) {
        
google.maps.event.removeListener(listeners[i]);
      }
      
$div.remove();
    };
    
this.hide = function() {
      
$div.hide();
    };
    
this.show = function() {
      
$div.show();
    };
    
this.toggle = function() {
      if (
$div) {
        if (
$div.is(":visible")){
          
this.show();
        } else {
          
this.hide();
        }
      }
    };
    
this.toggleDOM = function() {
      if (
this.getMap()) {
        
this.setMap(null);
      } else {
        
this.setMap(map);
      }
    };
    
this.getDOMElement = function() {
      return 
$div[0];
    };
  }

  
/***************************************************************************/
  /*                              CLUSTERING                                 */
  /***************************************************************************/
      
  /**
   * Usefull to get a projection
   * => done in a function, to let dead-code analyser works without google library loaded    
   **/
  
function newEmptyOverlay(mapradius){
    function 
Overlay(){ 
      
this.onAdd = function(){};
      
this.onRemove = function(){};
      
this.draw = function(){};
      return 
defaults.classes.OverlayView.apply(this, []); 
    }
    
Overlay.prototype defaults.classes.OverlayView.prototype;
    var 
obj = new Overlay();
    
obj.setMap(map); 
    return 
obj;
  }
  
  
/**
   * Class InternalClusterer
   * This class manage clusters thanks to "todo" objects
   * 
   * Note: 
   * Individuals marker are created on the fly thanks to the todo objects, they are 
   * first set to null to keep the indexes synchronised with the todo list
   * This is the "display" function, set by the gmap3 object, which uses theses data 
   * to create markers when clusters are not required
   * To remove a marker, the objects are deleted and set not null in arrays
   *    markers[key]
   *      = null : marker exist but has not been displayed yet
   *      = false : marker has been removed       
   **/
  
function InternalClusterer($containermapraw){
    var 
updating false,
      
updated false,
      
redrawing false,
      
ready false,
      
enabled true,
      
that this,
      
events =  [],
      
store = {},   // combin of index (id1-id2-...) => object
      
ids = {},     // unique id => index
      
idxs = {},    // index => unique id
      
markers = [], // index => marker
      
todos = [],   // index => todo or null if removed
      
values = [],  // index => value
      
overlay newEmptyOverlay(mapraw.radius),
      
timerprojection,
      
ffilterfdisplayferror// callback function
      
    
main();

    function 
prepareMarker(index) {
      if (!
markers[index]) {
        
delete todos[index].options.map;
        
markers[index] = new defaults.classes.Marker(todos[index].options);
        
attachEvents($container, {todotodos[index]}, markers[index], todos[index].id);
      }
    }

    
/**
     * return a marker by its id, null if not yet displayed and false if no exist or removed
     **/
    
this.getById = function(id){
      if (
id in ids) {
        
prepareMarker(ids[id]);
        return  
markers[ids[id]];
      }
      return 
false;
    };

    
/**
     * remove one object from the store
     **/
    
this.rm = function (id) {
      var 
index ids[id];
      if (
markers[index]){ // can be null
        
markers[index].setMap(null);
      }
      
delete markers[index];
      
markers[index] = false;

      
delete todos[index];
      
todos[index] = false;

      
delete values[index];
      
values[index] = false;

      
delete ids[id];
      
delete idxs[index];
      
updated true;
    };
    
    
/**
     * remove a marker by its id
     **/
    
this.clearById = function(id){
      if (
id in ids){
        
this.rm(id);
        return 
true;
      }
    };

    
/**
     * remove objects from the store
     **/
    
this.clear = function(lastfirsttag){
      var 
startstopstepindexi,
          list = [],
          
check ftag(tag);
      if (
last) {
        
start todos.length 1;
        
stop = -1;
        
step = -1;
      } else {
        
start 0;
        
stop =  todos.length;
        
step 1;
      }
      for (
index startindex != stopindex += step) {
        if (
todos[index]) {
          if (!
check || check(todos[index].tag)){
            list.
push(idxs[index]);
            if (
first || last) {
              break;
            }
          }
        }
      }
      for (
0< list.lengthi++) {
        
this.rm(list[i]);
      }
    };
    
    
// add a "marker todo" to the cluster
    
this.add = function(todovalue){
      
todo.id globalId(todo.id);
      
this.clearById(todo.id);
      
ids[todo.id] = markers.length;
      
idxs[markers.length] = todo.id;
      
markers.push(null); // null = marker not yet created / displayed
      
todos.push(todo);
      
values.push(value);
      
updated true;
    };
    
    
// add a real marker to the cluster
    
this.addMarker = function(markertodo){
      
todo todo || {};
      
todo.id globalId(todo.id);
      
this.clearById(todo.id);
      if (!
todo.options){
        
todo.options = {};
      }
      
todo.options.position marker.getPosition();
      
attachEvents($container, {todo:todo}, markertodo.id);
      
ids[todo.id] = markers.length;
      
idxs[markers.length] = todo.id;
      
markers.push(marker);
      
todos.push(todo);
      
values.push(todo.data || {});
      
updated true;
    };
    
    
// return a "marker todo" by its index 
    
this.todo = function(index){
      return 
todos[index];
    };
    
    
// return a "marker value" by its index 
    
this.value = function(index){
      return 
values[index];
    };

    
// return a marker by its index
    
this.marker = function(index){
      if (
index in markers) {
        
prepareMarker(index);
        return  
markers[index];
      }
      return 
false;
    };

    
// return a marker by its index
    
this.markerIsSet = function(index){
      return 
Boolean(markers[index]);
    };
    
    
// store a new marker instead if the default "false"
    
this.setMarker = function(indexmarker){
      
markers[index] = marker;
    };
    
    
// link the visible overlay to the logical data (to hide overlays later)
    
this.store = function(clusterobjshadow){
      
store[cluster.ref] = {obj:objshadow:shadow};
    };
    
    
// free all objects
    
this.free = function(){
      for(var 
0events.lengthi++){
        
google.maps.event.removeListener(events[i]);
      }
      
events = [];
      
      $.
each(store, function(key){
        
flush(key);
      });
      
store = {};
      
      $.
each(todos, function(i){
        
todos[i] = null;
      });
      
todos = [];
      
      $.
each(markers, function(i){
        if (
markers[i]){ // false = removed
          
markers[i].setMap(null);
          
delete markers[i];
        }
      });
      
markers = [];
      
      $.
each(values, function(i){
        
delete values[i];
      });
      
values = [];
      
      
ids = {};
      
idxs = {};
    };
    
    
// link the display function
    
this.filter = function(f){
      
ffilter f;
      
redraw();
    };
    
    
// enable/disable the clustering feature
    
this.enable = function(value){
      if (
enabled != value){
        
enabled value;
        
redraw();
      }
    };
    
    
// link the display function
    
this.display = function(f){
      
fdisplay f;
    };
    
    
// link the errorfunction
    
this.error = function(f){
      
ferror f;
    };
    
    
// lock the redraw
    
this.beginUpdate = function(){
      
updating true;
    };
    
    
// unlock the redraw
    
this.endUpdate = function(){
      
updating false;
      if (
updated){
        
redraw();
      }
    };

    
// extends current bounds with internal markers
    
this.autofit = function(bounds){
      for(var 
i=0i<todos.lengthi++){
        if (
todos[i]){
          
bounds.extend(todos[i].options.position);
        }
      }
    };
    
    
// bind events
    
function main(){
      
projection overlay.getProjection();
      if (!
projection){
        
setTimeout(function(){
          
main.apply(that, []);
        },
        
25);
        return;
      }
      
ready true;
      
events.push(google.maps.event.addListener(map"zoom_changed", function(){delayRedraw();}));
      
events.push(google.maps.event.addListener(map"bounds_changed", function(){delayRedraw();}));
      
redraw();
    }
    
    
// flush overlays
    
function flush(key){
      if (
typeof store[key] === "object"){ // is overlay
        
if (typeof(store[key].obj.setMap) === "function") {
          
store[key].obj.setMap(null);
        }
        if (
typeof(store[key].obj.remove) === "function") {
          
store[key].obj.remove();
        }
        if (
typeof(store[key].shadow.remove) === "function") {
          
store[key].obj.remove();
        }
        if (
typeof(store[key].shadow.setMap) === "function") {
          
store[key].shadow.setMap(null);
        }
        
delete store[key].obj;
        
delete store[key].shadow;
      } else if (
markers[key]){ // marker not removed
        
markers[key].setMap(null);
        
// don't remove the marker object, it may be displayed later
      
}
      
delete store[key];
    }
    
    
/**
     * return the distance between 2 latLng couple into meters
     * Params :   
     *  Lat1, Lng1, Lat2, Lng2
     *  LatLng1, Lat2, Lng2
     *  Lat1, Lng1, LatLng2
     *  LatLng1, LatLng2
     **/
    
function distanceInMeter(){
      var 
lat1lat2lng1lng2efgh;
      if (
arguments[0] instanceof google.maps.LatLng){
        
lat1 arguments[0].lat();
        
lng1 arguments[0].lng();
        if (
arguments[1] instanceof google.maps.LatLng){
          
lat2 arguments[1].lat();
          
lng2 arguments[1].lng();
        } else {
          
lat2 arguments[1];
          
lng2 arguments[2];
        }
      } else {
        
lat1 arguments[0];
        
lng1 arguments[1];
        if (
arguments[2] instanceof google.maps.LatLng){
          
lat2 arguments[2].lat();
          
lng2 arguments[2].lng();
        } else {
          
lat2 arguments[2];
          
lng2 arguments[3];
        }
      }
      
Math.PI*lat1/180;
      
Math.PI*lng1/180;
      
Math.PI*lat2/180;
      
Math.PI*lng2/180;
      return 
1000*6371 Math.acos(Math.min(Math.cos(e)*Math.cos(g)*Math.cos(f)*Math.cos(h)+Math.cos(e)*Math.sin(f)*Math.cos(g)*Math.sin(h)+Math.sin(e)*Math.sin(g),1));
    }
    
    
// extend the visible bounds 
    
function extendsMapBounds(){
      var 
radius distanceInMeter(map.getCenter(), map.getBounds().getNorthEast()), 
        
circle = new google.maps.Circle({
          
centermap.getCenter(),
          
radius1.25 radius // + 25%
        
});
      return 
circle.getBounds();
    }
    
    
// return an object where keys are store keys 
    
function getStoreKeys(){
      var 
keys = {}, k;
      for(
k in store){
        
keys[k] = true;
      }
      return 
keys;
    }
    
    
// async the delay function
    
function delayRedraw(){
      
clearTimeout(timer);
      
timer setTimeout(function(){
        
redraw();
      },
      
25);
    }
    
    
// generate bounds extended by radius
    
function extendsBounds(latLng) {
      var 
projection.fromLatLngToDivPixel(latLng),
        
ne projection.fromDivPixelToLatLng(new google.maps.Point(p.x+raw.radiusp.y-raw.radius)),
        
sw projection.fromDivPixelToLatLng(new google.maps.Point(p.x-raw.radiusp.y+raw.radius));
      return new 
google.maps.LatLngBounds(swne);
    }
    
    
// run the clustering process and call the display function
    
function redraw(){
      if (
updating || redrawing || !ready){
        return;
      }

      var 
keys = [], used = {},
        
zoom map.getZoom(),
        
forceDisabled = ("maxZoom" in raw) && (zoom raw.maxZoom),
        
previousKeys getStoreKeys(),
        
ijkindexescheck falseboundsclusterpositionpreviouslatlngloop;

      
// reset flag
      
updated false;

      if (
zoom 3){
        
// extend the bounds of the visible map to manage clusters near the boundaries
        
bounds extendsMapBounds();

        
// check contain only if boundaries are valid
        
check bounds.getSouthWest().lng() < bounds.getNorthEast().lng();
      }

      
// calculate positions of "visibles" markers (in extended bounds)
      
for(i=0i<todos.lengthi++){
        if (
todos[i] && (!check || bounds.contains(todos[i].options.position)) && (!ffilter || ffilter(values[i]))){
          
keys.push(i);
        }
      }

      
// for each "visible" marker, search its neighbors to create a cluster
      // we can't do a classical "for" loop, because, analysis can bypass a marker while focusing on cluster
      
while(1){
        
i=0;
        while(
used[i] && (i<keys.length)){ // look for the next marker not used
          
i++;
        }
        if (
== keys.length){
          break;
        }

        
indexes = [];

        if (
enabled && !forceDisabled){
          
loop 10;
          do{
            
previous indexes;
            
indexes = [];
            
loop--;

            if (
previous.length){
              
position bounds.getCenter()
            } else {
              
position todoskeys[i] ].options.position;
            }
            
bounds extendsBounds(position);

            for(
j=ij<keys.lengthj++){
              if (
used[j]){
                continue;
              }
              if (
bounds.contains(todoskeys[j] ].options.position)){
                
indexes.push(j);
              }
            }
          } while( (
previous.length indexes.length) && (indexes.length 1) && loop);
        } else {
          for(
j=ij<keys.lengthj++){
            if (
used[j]){
              continue;
            }
            
indexes.push(j);
            break;
          }
        }

        
cluster = {indexes:[], ref:[]};
        
lat lng 0;
        for(
k=0k<indexes.lengthk++){
          
usedindexes[k] ] = true;
          
cluster.indexes.push(keys[indexes[k]]);
          
cluster.ref.push(keys[indexes[k]]);
          
lat += todoskeys[indexes[k]] ].options.position.lat();
          
lng += todoskeys[indexes[k]] ].options.position.lng();
        }
        
lat /= indexes.length;
        
lng /= indexes.length;
        
cluster.latLng = new google.maps.LatLng(latlng);

        
cluster.ref cluster.ref.join("-");

        if (
cluster.ref in previousKeys){ // cluster doesn't change
          
delete previousKeys[cluster.ref]; // remove this entry, these still in this array will be removed
        
} else { // cluster is new
          
if (indexes.length === 1){ // alone markers are not stored, so need to keep the key (else, will be displayed every time and marker will blink)
            
store[cluster.ref] = true;
          }
          
fdisplay(cluster);
        }
      }

      
// flush the previous overlays which are not still used
      
$.each(previousKeys, function(key){
        
flush(key);
      });
      
redrawing false;
    }
  }
  
  
/**
   * Class Clusterer
   * a facade with limited method for external use
   **/
  
function Clusterer(idinternalClusterer){
    
this.id = function(){
      return 
id;
    };
    
this.filter = function(f){
      
internalClusterer.filter(f);
    };
    
this.enable = function(){
      
internalClusterer.enable(true);
    };
    
this.disable = function(){
      
internalClusterer.enable(false);
    };
    
this.add = function(markertodolock){
      if (!
lock) {
        
internalClusterer.beginUpdate();
      }
      
internalClusterer.addMarker(markertodo);
      if (!
lock) {
        
internalClusterer.endUpdate();
      }
    };
    
this.getById = function(id){
      return 
internalClusterer.getById(id);
    };
    
this.clearById = function(idlock){
      var 
result;
      if (!
lock) {
        
internalClusterer.beginUpdate();
      }
      
result internalClusterer.clearById(id);
      if (!
lock) {
        
internalClusterer.endUpdate();
      }
      return 
result;
    };
    
this.clear = function(lastfirsttaglock){
      if (!
lock) {
        
internalClusterer.beginUpdate();
      }
      
internalClusterer.clear(lastfirsttag);
      if (!
lock) {
        
internalClusterer.endUpdate();
      }
    };
  }
  
/***************************************************************************/
  /*                                STORE                                    */
  /***************************************************************************/
  
  
function Store(){
    var 
store = {}, // name => [id, ...]
      
objects = {}; // id => object

    
function normalize(res) {
      return {
        
idres.id,
        
nameres.name,
        
object:res.obj,
        
tag:res.tag,
        
data:res.data
      
};
    }
    
    
/**
     * add a mixed to the store
     **/
    
this.add = function(argsnameobjsub){
      var 
todo args.todo || {},
        
id globalId(todo.id);
      if (!
store[name]){
        
store[name] = [];
      }
      if (
id in objects){ // object already exists: remove it
        
this.clearById(id);
      }
      
objects[id] = {obj:objsub:subname:nameid:idtag:todo.tagdata:todo.data};
      
store[name].push(id);
      return 
id;
    };
    
    
/**
     * return a stored object by its id
     **/
    
this.getById = function(idsubfull){
      if (
id in objects){
          if (
sub) {
            return 
objects[id].sub
          
} else if (full) {
            return 
normalize(objects[id]);
          }
          return 
objects[id].obj;

      }
      return 
false;
    };
    
    
/**
     * return a stored value
     **/
    
this.get = function(namelasttagfull){
      var 
nidcheck ftag(tag);
      if (!
store[name] || !store[name].length){
        return 
null;
      }
      
store[name].length;
      while(
n){
        
n--;
        
id store[name][last store[name].length 1];
        if (
id && objects[id]){
          if (
check && !check(objects[id].tag)){
            continue;
          }
          return 
full normalize(objects[id]) : objects[id].obj;
        }
      }
      return 
null;
    };
    
    
/**
     * return all stored values
     **/
    
this.all = function(nametagfull){
      var 
result = [],
          
check ftag(tag),
          
find = function(n){
            var 
iid;
            for(
i=0i<store[n].lengthi++){
              
id store[n][i];
              if (
id && objects[id]){
                if (
check && !check(objects[id].tag)){
                  continue;
                }
                
result.push(full normalize(objects[id]) : objects[id].obj);
              }
            }
          };
      if (
name in store){
        
find(name);
      } else if (
name === undef){ // internal use only
        
for(name in store){
          
find(name);
        }
      }
      return 
result;
    };
    
    
/**
     * hide and remove an object
     **/
    
function rm(obj){
      
// Google maps element
      
if (typeof(obj.setMap) === "function") {
        
obj.setMap(null);
      }
      
// jQuery
      
if (typeof(obj.remove) === "function") {
        
obj.remove();
      }
      
// internal (cluster)
      
if (typeof(obj.free) === "function") {
        
obj.free();
      }
      
obj null;
    }

    
/**
     * remove one object from the store
     **/
    
this.rm = function(namecheckpop){
      var 
idxid;
      if (!
store[name]) {
        return 
false;
      }
      if (
check){
        if (
pop){
          for(
idx store[name].length 1idx >= 0idx--){
            
id store[name][idx];
            if ( 
check(objects[id].tag) ){
              break;
            }
          }
        } else {
          for(
idx 0idx store[name].lengthidx++){
            
id store[name][idx];
            if (
check(objects[id].tag)){
              break;
            }
          }
        }
      } else {
        
idx pop store[name].length 0;
      }
      if ( !(
idx in store[name]) ) {
        return 
false;
      }
      return 
this.clearById(store[name][idx], idx);
    };
    
    
/**
     * remove object from the store by its id
     **/
    
this.clearById = function(ididx){
      if (
id in objects){
        var 
iname objects[id].name;
        for(
i=0idx === undef && i<store[name].lengthi++){
          if (
id === store[name][i]){
            
idx i;
          }
        }
        
rm(objects[id].obj);
        if(
objects[id].sub){
          
rm(objects[id].sub);
        }
        
delete objects[id];
        
store[name].splice(idx1);
        return 
true;
      }
      return 
false;
    };
    
    
/**
     * return an object from a container object in the store by its id
     * ! for now, only cluster manage this feature 
     **/
    
this.objGetById = function(id){
      var 
result;
      if (
store["clusterer"]) {
        for(var 
idx in store["clusterer"]){
          if ((
result objects[store["clusterer"][idx]].obj.getById(id)) !== false){
            return 
result;
          }
        }
      }
      return 
false;
    };
    
    
/**
     * remove object from a container object in the store by its id
     * ! for now, only cluster manage this feature 
     **/
    
this.objClearById = function(id){
      if (
store["clusterer"]) {
        for(var 
idx in store["clusterer"]){
          if (
objects[store["clusterer"][idx]].obj.clearById(id)){
            return 
true;
          }
        }
      }
      return 
null;
    };
    
    
/**
     * remove objects from the store
     **/
    
this.clear = function(list, lastfirsttag){
      var 
kinamecheck ftag(tag);
      if (!list || !list.
length){
        list = [];
        for(
k in store){
          list.
push(k);
        }
      } else {
        list = array(list);
      }
      for(
i=0i<list.lengthi++){
        
name = list[i];
        if (
last){
          
this.rm(namechecktrue);
        } else if (
first){
          
this.rm(namecheckfalse);
        } else { 
// all
          
while(this.rm(namecheckfalse));
        }
      }
    };

    
/**
     * remove object from a container object in the store by its tags
     * ! for now, only cluster manage this feature
     **/
    
this.objClear = function(list, lastfirsttag){
      if (
store["clusterer"] && ($.inArray("marker", list) >= || !list.length)) {
        for(var 
idx in store["clusterer"]){
          
objects[store["clusterer"][idx]].obj.clear(lastfirsttag);
        }
      }
    };
  }
  
  
/***************************************************************************/
  /*                           GMAP3 GLOBALS                                 */
  /***************************************************************************/
  
  
var services = {},
    
geocoderCache = new GeocoderCache();
    
  
//-----------------------------------------------------------------------//
  // Service tools
  //-----------------------------------------------------------------------//
  
  
function geocoder(){
    if (!
services.geocoder) {
      
services.geocoder = new google.maps.Geocoder();
    }
    return 
services.geocoder;
  }
  
  function 
directionsService(){
    if (!
services.directionsService) {
      
services.directionsService = new google.maps.DirectionsService();
    }
    return 
services.directionsService;
  }
  
  function 
elevationService(){
    if (!
services.elevationService) {
      
services.elevationService = new google.maps.ElevationService();
    }
    return 
services.elevationService;
  }
  
  function 
maxZoomService(){
    if (!
services.maxZoomService) {
      
services.maxZoomService = new google.maps.MaxZoomService();
    }
    return 
services.maxZoomService;
  }
  
  function 
distanceMatrixService(){
    if (!
services.distanceMatrixService) {
      
services.distanceMatrixService = new google.maps.DistanceMatrixService();
    }
    return 
services.distanceMatrixService;
  }
  
  
//-----------------------------------------------------------------------//
  // Unit tools
  //-----------------------------------------------------------------------//
  
  
function error(){
    if (
defaults.verbose){
      var 
ierr = [];
      if (
window.console && (typeof console.error === "function") ){
        for(
i=0i<arguments.lengthi++){
          
err.push(arguments[i]);
        }
        
console.error.apply(consoleerr);
      } else {
        
err "";
        for(
i=0i<arguments.lengthi++){
          
err += arguments[i].toString() + " " ;
        }
        
alert(err);
      }
    }
  }

  
/**
   * return true if mixed is usable as number
   **/
  
function numeric(mixed){
    return (
typeof(mixed) === "number" || typeof(mixed) === "string") && mixed !== "" && !isNaN(mixed);
  }
  
  
/**
   * convert data to array
   **/
  
function array(mixed){
    var 
k= [];
    if (
mixed !== undef){
      if (
typeof(mixed) === "object"){
        if (
typeof(mixed.length) === "number") {
          
mixed;
        } else {
          for(
k in mixed) {
            
a.push(mixed[k]);
          }
        }
      } else{ 
        
a.push(mixed);
      }
    }
    return 
a;
  }

  
/**
   * create a function to check a tag
   */
  
function ftag(tag){
    if (
tag){
      if (
typeof tag === "function"){
        return 
tag;
      }
      
tag = array(tag);
      return function(
val){
        if (
val === undef){
          return 
false;
        }
        if (
typeof val === "object"){
          for(var 
i=0i<val.lengthi++){
            if($.
inArray(val[i], tag) >= 0){
              return 
true;
            }
          }
          return 
false;
        }
        return $.
inArray(valtag) >= 0;
      }
    }
  }
  
  
/**
   * convert mixed [ lat, lng ] objet to google.maps.LatLng
   **/
  
function toLatLng (mixedemptyReturnMixednoFlat){
    var empty = 
emptyReturnMixed mixed null;
    if (!
mixed || (typeof mixed === "string")){
      return empty;
    }
    
// defined latLng
    
if (mixed.latLng) {
      return 
toLatLng(mixed.latLng);
    }
    
// google.maps.LatLng object
    
if (mixed instanceof google.maps.LatLng) {
      return 
mixed;
    } 
    
// {lat:X, lng:Y} object
    
else if ( numeric(mixed.lat) ) {
      return new 
google.maps.LatLng(mixed.latmixed.lng);
    }
    
// [X, Y] object 
    
else if ( !noFlat && $.isArray(mixed)){
      if ( !
numeric(mixed[0]) || !numeric(mixed[1]) ) {
        return empty;
      }
      return new 
google.maps.LatLng(mixed[0], mixed[1]);
    }
    return empty;
  }
  
  
/**
   * convert mixed [ sw, ne ] object by google.maps.LatLngBounds
   **/
  
function toLatLngBounds(mixed){
    var 
nesw;
    if (!
mixed || mixed instanceof google.maps.LatLngBounds) {
      return 
mixed || null;
    }
    if ($.
isArray(mixed)){
      if (
mixed.length == 2){
        
ne toLatLng(mixed[0]);
        
sw toLatLng(mixed[1]);
      } else if (
mixed.length == 4){
        
ne toLatLng([mixed[0], mixed[1]]);
        
sw toLatLng([mixed[2], mixed[3]]);
      }
    } else {
      if ( (
"ne" in mixed) && ("sw" in mixed) ){
        
ne toLatLng(mixed.ne);
        
sw toLatLng(mixed.sw);
      } else if ( (
"n" in mixed) && ("e" in mixed) && ("s" in mixed) && ("w" in mixed) ){
        
ne toLatLng([mixed.nmixed.e]);
        
sw toLatLng([mixed.smixed.w]);
      }
    }
    if (
ne && sw){
      return new 
google.maps.LatLngBounds(swne);
    }
    return 
null;
  }
  
  
/**
   * resolveLatLng      
   **/
  
function resolveLatLng(ctxmethodrunLatLngargsattempt){
    var 
latLng runLatLng toLatLng(args.todofalsetrue) : false,
      
conf latLng ?  {latLng:latLng} : (args.todo.address ? (typeof(args.todo.address) === "string" ? {address:args.todo.address} : args.todo.address) : false),
      
cache conf geocoderCache.get(conf) : false,
      
that this;
    if (
conf){
      
attempt attempt || 0// convert undefined to int
      
if (cache){
        
args.latLng cache.results[0].geometry.location;
        
args.results cache.results;
        
args.status cache.status;
        
method.apply(ctx, [args]);
      } else {
        if (
conf.location){
          
conf.location toLatLng(conf.location);
        }
        if (
conf.bounds){
          
conf.bounds toLatLngBounds(conf.bounds);
        }
        
geocoder().geocode(
          
conf
          function(
resultsstatus) {
            if (
status === google.maps.GeocoderStatus.OK){
              
geocoderCache.store(conf, {results:resultsstatus:status});
              
args.latLng results[0].geometry.location;
              
args.results results;
              
args.status status;
              
method.apply(ctx, [args]);
            } else if ( (
status === google.maps.GeocoderStatus.OVER_QUERY_LIMIT) && (attempt defaults.queryLimit.attempt) ){
              
setTimeout(
                function(){
                  
resolveLatLng.apply(that, [ctxmethodrunLatLngargsattempt+1]);
                },
                
defaults.queryLimit.delay Math.floor(Math.random() * defaults.queryLimit.random)
              );
            } else {
              
error("geocode failed"statusconf);
              
args.latLng args.results false;
              
args.status status;
              
method.apply(ctx, [args]);
            }
          }
        );
      }
    } else {
      
args.latLng toLatLng(args.todofalsetrue);
      
method.apply(ctx, [args]);
    }
  }
  
  function 
resolveAllLatLng(list, ctxmethodargs){
    var 
that this= -1;
    
    function 
resolve(){
      
// look for next address to resolve
      
do{
        
i++;
      }while( (
< list.length) && !("address" in list[i]) );
      
      
// no address found, so run method 
      
if (>= list.length){
        
method.apply(ctx, [args]);
        return;
      }
      
      
resolveLatLng(
        
that,
        function(
args){
          
delete args.todo;
          $.
extend(list[i], args);
          
resolve.apply(that, []); // resolve next (using apply avoid too much recursion)
        
},
        
true,
        {
todo:list[i]}
      );
    }
    
resolve();
  }
    
  
/**
   * geolocalise the user and return a LatLng
   **/
  
function geoloc(ctxmethodargs){
    var 
is_echo false// sometime, a kind of echo appear, this trick will notice once the first call is run to ignore the next one
    
if (navigator && navigator.geolocation){
       
navigator.geolocation.getCurrentPosition(
        function(
pos) {
          if (
is_echo){
            return;
          }
          
is_echo true;
          
args.latLng = new google.maps.LatLng(pos.coords.latitude,pos.coords.longitude);
          
method.apply(ctx, [args]);
        }, 
        function() {
          if (
is_echo){
            return;
          }
          
is_echo true;
          
args.latLng false;
          
method.apply(ctx, [args]);
        },
        
args.opts.getCurrentPosition
      
);
    } else {
      
args.latLng false;
      
method.apply(ctx, [args]);
    }
  }

  
/***************************************************************************/
  /*                                GMAP3                                    */
  /***************************************************************************/
  
  
function Gmap3($this){
    var 
that this,
      
stack = new Stack(),
      
store = new Store(),
      
map null,
      
task;
    
    
//-----------------------------------------------------------------------//
    // Stack tools
    //-----------------------------------------------------------------------//

    /**
     * store actions to execute in a stack manager
     **/
    
this._plan = function(list){
      for(var 
0< list.lengthk++) {
        
stack.add(new Task(thatend, list[k]));
      }
      
run();
    };
    
    
/**
     * if not running, start next action in stack
     **/
    
function run(){
      if (!
task && (task stack.get())){
        
task.run();
      }
    }
    
    
/**
     * called when action in finished, to acknoledge the current in stack and start next one
     **/
     
function end(){
      
task null;
      
stack.ack();
      
run.call(that); // restart to high level scope
    
}
    
    
//-----------------------------------------------------------------------//
    // Tools
    //-----------------------------------------------------------------------//
    
    /**
     * execute callback functions 
     **/
    
function callback(args){
      if (
args.todo.callback) {
        var 
params = Array.prototype.slice.call(arguments1);
        if (
typeof args.todo.callback === "function") {
          
args.todo.callback.apply($thisparams);
        } else if ($.
isArray(args.todo.callback)) {
          if (
typeof args.todo.callback[1] === "function") {
            
args.todo.callback[1].apply(args.todo.callback[0], params);
          }
        }
      }
    }
    
    
/**
     * execute ending functions 
     **/
    
function manageEnd(argsobjid){
      if (
id){
        
attachEvents($thisargsobjid);
      }
      
callback(argsobj);
      
task.ack(obj);
    }
    
    
/**
     * initialize the map if not yet initialized
     **/
    
function newMap(latLngargs){
      
args args || {};
      if (
map) {
        if (
args.todo && args.todo.options){
          if (
args.todo.options.center) {
            
args.todo.options.center toLatLng(args.todo.options.center);
          }
          
map.setOptions(args.todo.options);
        }
      } else {
        var 
opts args.opts || $.extend(true, {}, defaults.mapargs.todo && args.todo.options args.todo.options : {});
        
opts.center latLng || toLatLng(opts.center);
        
map = new defaults.classes.Map($this.get(0), opts);
      }
    }
     
    
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 
    => function with latLng resolution
    = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
    
    /**
     * Initialize google.maps.Map object
     **/
    
this.map = function(args){
      
newMap(args.latLngargs);
      
attachEvents($thisargsmap);
      
manageEnd(argsmap);
    };
    
    
/**
     * destroy an existing instance
     **/
    
this.destroy = function(args){
      
store.clear();
      
$this.empty();
      if (
map){
        
map null;
      }
      
manageEnd(argstrue);
    };
    
    
/**
     * add an infowindow
     **/
    
this.infowindow = function(args){
      var 
objs = [], multiple "values" in args.todo;
      if (!
multiple){
        if (
args.latLng) {
          
args.opts.position args.latLng;
        }
        
args.todo.values = [{options:args.opts}];
      }
      $.
each(args.todo.values, function(ivalue){
        var 
idobjtodo tuple(argsvalue);
        
todo.options.position todo.options.position toLatLng(todo.options.position) : toLatLng(value.latLng);
        if (!
map){
          
newMap(todo.options.position);
        }
        
obj = new defaults.classes.InfoWindow(todo.options);
        if (
obj && ((todo.open === undef) || todo.open)){
          if (
multiple){
            
obj.open(maptodo.anchor todo.anchor undef);
          } else {
            
obj.open(maptodo.anchor todo.anchor : (args.latLng undef : (args.session.marker args.session.marker undef)));
          }
        }
        
objs.push(obj);
        
id store.add({todo:todo}, "infowindow"obj);
        
attachEvents($this, {todo:todo}, objid);
      });
      
manageEnd(argsmultiple objs objs[0]);
    };
    
    
/**
     * add a circle
     **/
    
this.circle = function(args){
      var 
objs = [], multiple "values" in args.todo;
      if (!
multiple){
        
args.opts.center args.latLng || toLatLng(args.opts.center);
        
args.todo.values = [{options:args.opts}];
      }
      if (!
args.todo.values.length){
        
manageEnd(argsfalse);
        return;
      }
      $.
each(args.todo.values, function(ivalue){
        var 
idobjtodo tuple(argsvalue);
        
todo.options.center todo.options.center toLatLng(todo.options.center) : toLatLng(value);
        if (!
map){
          
newMap(todo.options.center);
        }
        
todo.options.map map;
        
obj = new defaults.classes.Circle(todo.options);
        
objs.push(obj);
        
id store.add({todo:todo}, "circle"obj);
        
attachEvents($this, {todo:todo}, objid);
      });
      
manageEnd(argsmultiple objs objs[0]);
    };
    
    
/**
     * add an overlay
     **/
    
this.overlay = function(argsinternal){
      var 
objs = [], multiple "values" in args.todo;
      if (!
multiple){
        
args.todo.values = [{latLngargs.latLngoptionsargs.opts}];
      }
      if (!
args.todo.values.length){
        
manageEnd(argsfalse);
        return;
      }
      if (!
OverlayView.__initialised) {
        
OverlayView.prototype = new defaults.classes.OverlayView();
        
OverlayView.__initialised true;
      }
      $.
each(args.todo.values, function(ivalue){
        var 
idobjtodo tuple(argsvalue),
            
$div = $(document.createElement("div")).css({
              
border"none",
              
borderWidth"0px",
              
position"absolute"
            
});
        
$div.append(todo.options.content);
        
obj = new OverlayView(maptodo.optionstoLatLng(todo) || toLatLng(value), $div);
        
objs.push(obj);
        
$div null// memory leak
        
if (!internal){
          
id store.add(args"overlay"obj);
          
attachEvents($this, {todo:todo}, objid);
        }
      });
      if (
internal){
        return 
objs[0];
      }
      
manageEnd(argsmultiple objs objs[0]);
    };
    
    
/**
     * returns address structure from latlng        
     **/
    
this.getaddress = function(args){
      
callback(argsargs.resultsargs.status);
      
task.ack();
    };
    
    
/**
     * returns latlng from an address
     **/
    
this.getlatlng = function(args){
      
callback(argsargs.resultsargs.status);
      
task.ack();
    };
    
    
/**
     * return the max zoom of a location
     **/
    
this.getmaxzoom = function(args){
      
maxZoomService().getMaxZoomAtLatLng(
        
args.latLng
        function(
result) {
          
callback(argsresult.status === google.maps.MaxZoomStatus.OK result.zoom falsestatus);
          
task.ack();
        }
      );
    };
    
    
/**
     * return the elevation of a location
     **/
    
this.getelevation = function(args){
      var 
ilocations = [],
        
= function(resultsstatus){
          
callback(argsstatus === google.maps.ElevationStatus.OK results falsestatus);
          
task.ack();
        };
      
      if (
args.latLng){
        
locations.push(args.latLng);
      } else {
        
locations = array(args.todo.locations || []);
        for(
i=0i<locations.lengthi++){
          
locations[i] = toLatLng(locations[i]);
        }
      }
      if (
locations.length){
        
elevationService().getElevationForLocations({locations:locations}, f);
      } else {
        if (
args.todo.path && args.todo.path.length){
          for(
i=0i<args.todo.path.lengthi++){
            
locations.push(toLatLng(args.todo.path[i]));
          }
        }
        if (
locations.length){
          
elevationService().getElevationAlongPath({path:locationssamples:args.todo.samples}, f);
        } else {
          
task.ack();
        }
      }
    };
    
    
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 
    => function without latLng resolution
    = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
    
    /**
     * define defaults values
     **/
    
this.defaults = function(args){
      $.
each(args.todo, function(namevalue){
        if (
typeof defaults[name] === "object"){
          
defaults[name] = $.extend({}, defaults[name], value);
        } else {
          
defaults[name] = value;
        }
      });
      
task.ack(true);
    };
    
    
/**
     * add a rectangle
     **/
    
this.rectangle = function(args){
      var 
objs = [], multiple "values" in args.todo;
      if (!
multiple){
        
args.todo.values = [{options:args.opts}];
      }
      if (!
args.todo.values.length){
        
manageEnd(argsfalse);
        return;
      }
      $.
each(args.todo.values, function(ivalue){
        var 
idobjtodo tuple(argsvalue);
        
todo.options.bounds todo.options.bounds toLatLngBounds(todo.options.bounds) : toLatLngBounds(value);
        if (!
map){
          
newMap(todo.options.bounds.getCenter());
        }
        
todo.options.map map;
        
        
obj = new defaults.classes.Rectangle(todo.options);
        
objs.push(obj);
        
id store.add({todo:todo}, "rectangle"obj);
        
attachEvents($this, {todo:todo}, objid);
      });
      
manageEnd(argsmultiple objs objs[0]);
    };
    
    
/**
     * add a polygone / polyline
     **/
    
function poly(argspolypath){
      var 
objs = [], multiple "values" in args.todo;
      if (!
multiple){
        
args.todo.values = [{options:args.opts}];
      }
      if (!
args.todo.values.length){
        
manageEnd(argsfalse);
        return;
      }
      
newMap();
      $.
each(args.todo.values, function(_value){
        var 
idijobjtodo tuple(argsvalue);
        if (
todo.options[path]){
          if (
todo.options[path][0][0] && $.isArray(todo.options[path][0][0])){
            for(
i=0i<todo.options[path].lengthi++){
              for(
j=0j<todo.options[path][i].lengthj++){
                
todo.options[path][i][j] = toLatLng(todo.options[path][i][j]);
              }
            }
          } else {
            for(
i=0i<todo.options[path].lengthi++){
              
todo.options[path][i] = toLatLng(todo.options[path][i]);
            }
          }
        }
        
todo.options.map map;
        
obj = new google.maps[poly](todo.options);
        
objs.push(obj);
        
id store.add({todo:todo}, poly.toLowerCase(), obj);
        
attachEvents($this, {todo:todo}, objid);
      });
      
manageEnd(argsmultiple objs objs[0]);
    }
    
    
this.polyline = function(args){
      
poly(args"Polyline""path");
    };
    
    
this.polygon = function(args){
      
poly(args"Polygon""paths");
    };
    
    
/**
     * add a traffic layer
     **/
    
this.trafficlayer = function(args){
      
newMap();
      var 
obj store.get("trafficlayer");
      if (!
obj){
        
obj = new defaults.classes.TrafficLayer();
        
obj.setMap(map);
        
store.add(args"trafficlayer"obj);
      }
      
manageEnd(argsobj);
    };
    
    
/**
     * add a bicycling layer
     **/
    
this.bicyclinglayer = function(args){
      
newMap();
      var 
obj store.get("bicyclinglayer");
      if (!
obj){
        
obj = new defaults.classes.BicyclingLayer();
        
obj.setMap(map);
        
store.add(args"bicyclinglayer"obj);
      }
      
manageEnd(argsobj);
    };
    
    
/**
     * add a ground overlay
     **/
    
this.groundoverlay = function(args){
      
args.opts.bounds toLatLngBounds(args.opts.bounds);
      if (
args.opts.bounds){
        
newMap(args.opts.bounds.getCenter());
      }
      var 
idobj = new defaults.classes.GroundOverlay(args.opts.urlargs.opts.boundsargs.opts.opts);
      
obj.setMap(map);
      
id store.add(args"groundoverlay"obj);
      
manageEnd(argsobjid);
    };
    
    
/**
     * set a streetview
     **/
    
this.streetviewpanorama = function(args){
      if (!
args.opts.opts){
        
args.opts.opts = {};
      }
      if (
args.latLng){
        
args.opts.opts.position args.latLng;
      } else if (
args.opts.opts.position){
        
args.opts.opts.position toLatLng(args.opts.opts.position);
      }
      if (
args.todo.divId){
        
args.opts.container document.getElementById(args.todo.divId)
      } else if (
args.opts.container){
        
args.opts.container = $(args.opts.container).get(0);
      }
      var 
idobj = new defaults.classes.StreetViewPanorama(args.opts.containerargs.opts.opts);
      if (
obj){
        
map.setStreetView(obj);
      }
      
id store.add(args"streetviewpanorama"obj);
      
manageEnd(argsobjid);
    };
    
    
this.kmllayer = function(args){
      var 
objs = [], multiple "values" in args.todo;
      if (!
multiple){
        
args.todo.values = [{options:args.opts}];
      }
      if (!
args.todo.values.length){
        
manageEnd(argsfalse);
        return;
      }
      $.
each(args.todo.values, function(ivalue){
        var 
idobjoptionstodo tuple(argsvalue);
        if (!
map){
          
newMap();
        }
        
options todo.options;
        
// compatibility 5.0-
        
if (todo.options.opts) {
            
options todo.options.opts;
            if (
todo.options.url) {
                
options.url todo.options.url;
            }
        }
        
// -- end --
        
options.map map;
        if (
googleVersionMin("3.10")) {
            
obj = new defaults.classes.KmlLayer(options);
        } else {
            
obj = new defaults.classes.KmlLayer(options.urloptions);
        }
        
objs.push(obj);
        
id store.add({todo:todo}, "kmllayer"obj);
        
attachEvents($this, {todo:todo}, objid);
      });
      
manageEnd(argsmultiple objs objs[0]);
    };
    
    
/**
     * add a fix panel
     **/
     
this.panel = function(args){
      
newMap();
      var 
idx0y=0$content,
        
$div = $(document.createElement("div"));
      
      
$div.css({
        
position"absolute",
        
zIndex1000,
        
visibility"hidden"
      
});
        
      if (
args.opts.content){
        
$content = $(args.opts.content);
        
$div.append($content);
        
$this.first().prepend($div);
        
        if (
args.opts.left !== undef){
          
args.opts.left;
        } else if (
args.opts.right !== undef){
          
$this.width() - $content.width() - args.opts.right;
        } else if (
args.opts.center){
          
= ($this.width() - $content.width()) / 2;
        }
        
        if (
args.opts.top !== undef){
          
args.opts.top;
        } else if (
args.opts.bottom !== undef){
          
$this.height() - $content.height() - args.opts.bottom;
        } else if (
args.opts.middle){
          
= ($this.height() - $content.height()) / 2
        
}
      
        
$div.css({
            
topy,
            
leftx,
            
visibility"visible"
        
});
      }

      
id store.add(args"panel"$div);
      
manageEnd(args$divid);
      
$div null// memory leak
    
};
    
    
/**
     * Create an InternalClusterer object
     **/
    
function createClusterer(raw){
      var 
internalClusterer = new InternalClusterer($thismapraw),
        
todo = {},
        
styles = {},
        
thresholds = [],
        
isInt = /^[0-9]+$/,
        
calculator,
        
k;

      for(
k in raw){
        if (
isInt.test(k)){
          
thresholds.push(1*k); // cast to int
          
styles[k] = raw[k];
          
styles[k].width styles[k].width || 0;
          
styles[k].height styles[k].height || 0;
        } else {
          
todo[k] = raw[k];
        }
      }
      
thresholds.sort(function (ab) { return b});
      
      
// external calculator
      
if (todo.calculator){
        
calculator = function(indexes){
          var 
data = [];
          $.
each(indexes, function(iindex){
            
data.push(internalClusterer.value(index));
          });
          return 
todo.calculator.apply($this, [data]);
        };
      } else {
        
calculator = function(indexes){
          return 
indexes.length;
        };
      }
      
      
// set error function
      
internalClusterer.error(function(){
        
error.apply(thatarguments);
      });
      
      
// set display function
      
internalClusterer.display(function(cluster){
        var 
istyleatodoobjoffset,
          
cnt calculator(cluster.indexes);
        
        
// look for the style to use
        
if (raw.force || cnt 1) {
          for(
0thresholds.lengthi++) {
            if (
thresholds[i] <= cnt) {
              
style styles[thresholds[i]];
            }
          }
        }
        
        if (
style){
          
offset style.offset || [-style.width/2, -style.height/2];
          
// create a custom overlay command
          // nb: 2 extends are faster that a deeper extend
          
atodo = $.extend({}, todo);
          
atodo.options = $.extend({
            
pane"overlayLayer",
            
content:style.content style.content.replace("CLUSTER_COUNT"cnt) : "",
            
offset:{
              
x: ("x" in offset offset.offset[0]) || 0,
              
y: ("y" in offset offset.offset[1]) || 0
            
}
          },
          
todo.options || {});
          
          
obj that.overlay({todo:atodoopts:atodo.optionslatLng:toLatLng(cluster)}, true);
          
          
atodo.options.pane "floatShadow";
          
atodo.options.content = $(document.createElement("div")).width(style.width+"px").height(style.height+"px").css({cursor:"pointer"});
          
shadow that.overlay({todo:atodoopts:atodo.optionslatLng:toLatLng(cluster)}, true);
          
          
// store data to the clusterer
          
todo.data = {
            
latLngtoLatLng(cluster),
            
markers:[]
          };
          $.
each(cluster.indexes, function(iindex){
            
todo.data.markers.push(internalClusterer.value(index));
            if (
internalClusterer.markerIsSet(index)){
              
internalClusterer.marker(index).setMap(null);
            }
          });
          
attachEvents($this, {todo:todo}, shadowundef, {main:objshadow:shadow});
          
internalClusterer.store(clusterobjshadow);
        } else {
          $.
each(cluster.indexes, function(iindex){
            
internalClusterer.marker(index).setMap(map);
          });
        }
      });
      
      return 
internalClusterer;
    }
    
/**
     *  add a marker
     **/
    
this.marker = function(args){
      var 
multiple "values" in args.todo,
        
init = !map;
      if (!
multiple){
        
args.opts.position args.latLng || toLatLng(args.opts.position);
        
args.todo.values = [{options:args.opts}];
      }
      if (!
args.todo.values.length){
        
manageEnd(argsfalse);
        return;
      }
      if (
init){
        
newMap();
      }
      
      if (
args.todo.cluster && !map.getBounds()){ // map not initialised => bounds not available : wait for map if clustering feature is required
        
google.maps.event.addListenerOnce(map"bounds_changed", function() { that.marker.apply(that, [args]); });
        return;
      }
      if (
args.todo.cluster){
        var 
clustererinternalClusterer;
        if (
args.todo.cluster instanceof Clusterer){
          
clusterer args.todo.cluster;
          
internalClusterer store.getById(clusterer.id(), true);
        } else {
          
internalClusterer createClusterer(args.todo.cluster);
          
clusterer = new Clusterer(globalId(args.todo.idtrue), internalClusterer);
          
store.add(args"clusterer"clustererinternalClusterer);
        }
        
internalClusterer.beginUpdate();
        
        $.
each(args.todo.values, function(ivalue){
          var 
todo tuple(argsvalue);
          
todo.options.position todo.options.position toLatLng(todo.options.position) : toLatLng(value);
          if (
todo.options.position) {
            
todo.options.map map;
            if (
init){
              
map.setCenter(todo.options.position);
              
init false;
            }
            
internalClusterer.add(todovalue);
          }
        });
        
        
internalClusterer.endUpdate();
        
manageEnd(argsclusterer);
        
      } else {
        var 
objs = [];
        $.
each(args.todo.values, function(ivalue){
          var 
idobjtodo tuple(argsvalue);
          
todo.options.position todo.options.position toLatLng(todo.options.position) : toLatLng(value);
          if (
todo.options.position) {
            
todo.options.map map;
            if (
init){
              
map.setCenter(todo.options.position);
              
init false;
            }
            
obj = new defaults.classes.Marker(todo.options);
            
objs.push(obj);
            
id store.add({todo:todo}, "marker"obj);
            
attachEvents($this, {todo:todo}, objid);
          }
        });
        
manageEnd(argsmultiple objs objs[0]);
      }
    };
    
    
/**
     * return a route
     **/
    
this.getroute = function(args){
      
args.opts.origin toLatLng(args.opts.origintrue);
      
args.opts.destination toLatLng(args.opts.destinationtrue);
      
directionsService().route(
        
args.opts,
        function(
resultsstatus) {
          
callback(argsstatus == google.maps.DirectionsStatus.OK results falsestatus);
          
task.ack();
        }
      );
    };
    
    
/**
     * add a direction renderer
     **/
    
this.directionsrenderer = function(args){
      
args.opts.map map;
      var 
idobj = new google.maps.DirectionsRenderer(args.opts);
      if (
args.todo.divId){
        
obj.setPanel(document.getElementById(args.todo.divId));
      } else if (
args.todo.container){
        
obj.setPanel($(args.todo.container).get(0));
      }
      
id store.add(args"directionsrenderer"obj);
      
manageEnd(argsobjid);
    };
    
    
/**
     * returns latLng of the user        
     **/
    
this.getgeoloc = function(args){
      
manageEnd(argsargs.latLng);
    };
    
    
/**
     * add a style
     **/
    
this.styledmaptype = function(args){
      
newMap();
      var 
obj = new defaults.classes.StyledMapType(args.todo.stylesargs.opts);
      
map.mapTypes.set(args.todo.idobj);
      
manageEnd(argsobj);
    };
    
    
/**
     * add an imageMapType
     **/
    
this.imagemaptype = function(args){
      
newMap();
      var 
obj = new defaults.classes.ImageMapType(args.opts);
      
map.mapTypes.set(args.todo.idobj);
      
manageEnd(argsobj);
    };
    
    
/**
     * autofit a map using its overlays (markers, rectangles ...)
     **/
    
this.autofit = function(args){
      var 
bounds = new google.maps.LatLngBounds();
      $.
each(store.all(), function(iobj){
        if (
obj.getPosition){
          
bounds.extend(obj.getPosition());
        } else if (
obj.getBounds){
          
bounds.extend(obj.getBounds().getNorthEast());
          
bounds.extend(obj.getBounds().getSouthWest());
        } else if (
obj.getPaths){
          
obj.getPaths().forEach(function(path){
            
path.forEach(function(latLng){
              
bounds.extend(latLng);
            });
          });
        } else if (
obj.getPath){
          
obj.getPath().forEach(function(latLng){
            
bounds.extend(latLng);""
          
});
        } else if (
obj.getCenter){
          
bounds.extend(obj.getCenter());
        } else if (
obj instanceof Clusterer){
          
obj store.getById(obj.id(), true);
          if (
obj){
            
obj.autofit(bounds);
          }
        }
      });

      if (!
bounds.isEmpty() && (!map.getBounds() || !map.getBounds().equals(bounds))){
        if (
"maxZoom" in args.todo){
          
// fitBouds Callback event => detect zoom level and check maxZoom
          
google.maps.event.addListenerOnce(
            
map
            
"bounds_changed"
            function() {
              if (
this.getZoom() > args.todo.maxZoom){
                
this.setZoom(args.todo.maxZoom);
              }
            }
          );
        }
        
map.fitBounds(bounds);
      }
      
manageEnd(argstrue);
    };
    
    
/**
     * remove objects from a map
     **/
    
this.clear = function(args){
      if (
typeof args.todo === "string"){
        if (
store.clearById(args.todo) || store.objClearById(args.todo)){
          
manageEnd(argstrue);
          return;
        }
        
args.todo = {name:args.todo};
      }
      if (
args.todo.id){
        $.
each(array(args.todo.id), function(iid){
          
store.clearById(id) || store.objClearById(id);
        });
      } else {
        
store.clear(array(args.todo.name), args.todo.lastargs.todo.firstargs.todo.tag);
        
store.objClear(array(args.todo.name), args.todo.lastargs.todo.firstargs.todo.tag);
      }
      
manageEnd(argstrue);
    };
    
    
/**
     * run a function on each items selected
     **/
    
this.exec = function(args){
      var 
that this;
      $.
each(array(args.todo.func), function(ifunc){
        $.
each(that.get(args.todotrueargs.todo.hasOwnProperty("full") ? args.todo.full true), function(jres){
          
func.call($thisres);
        });
      });
      
manageEnd(argstrue);
    };
    
    
/**
     * return objects previously created
     **/
    
this.get = function(argsdirectfull){
      var 
nameres,
          
todo direct args args.todo;
      if (!
direct) {
        
full todo.full;
      }
      if (
typeof todo === "string"){
        
res store.getById(todofalsefull) || store.objGetById(todo);
        if (
res === false){
          
name todo;
          
todo = {};
        }
      } else {
        
name todo.name;
      }
      if (
name === "map"){
        
res map;
      }
      if (!
res){
        
res = [];
        if (
todo.id){
            $.
each(array(todo.id), function(iid) {
                
res.push(store.getById(idfalsefull) || store.objGetById(id));
            });
            if (!$.
isArray(todo.id)) {
              
res res[0];
            }
        } else {
          $.
each(name ? array(name) : [undef], function(iaName) {
            var 
result;
            if (
todo.first){
                
result store.get(aNamefalsetodo.tagfull);
                if (
resultres.push(result);
            } else if (
todo.all){
                $.
each(store.all(aNametodo.tagfull), function(iresult){
                  
res.push(result);
                });
            } else {
                
result store.get(aNametruetodo.tagfull);
                if (
resultres.push(result);
            }
          });
          if (!
todo.all && !$.isArray(name)) {
            
res res[0];
          }
        }
      }
      
res = $.isArray(res) || !todo.all res : [res];
      if (
direct){
        return 
res;
      } else {
        
manageEnd(argsres);
      }
    };

    
/**
     * return the distance between an origin and a destination
     *      
     **/
    
this.getdistance = function(args){
      var 
i;
      
args.opts.origins = array(args.opts.origins);
      for(
i=0i<args.opts.origins.lengthi++){
        
args.opts.origins[i] = toLatLng(args.opts.origins[i], true);
      }
      
args.opts.destinations = array(args.opts.destinations);
      for(
i=0i<args.opts.destinations.lengthi++){
        
args.opts.destinations[i] = toLatLng(args.opts.destinations[i], true);
      }
      
distanceMatrixService().getDistanceMatrix(
        
args.opts,
        function(
resultsstatus) {
          
callback(argsstatus === google.maps.DistanceMatrixStatus.OK results falsestatus);
          
task.ack();
        }
      );
    };
    
    
/**
     * trigger events on the map 
     **/
    
this.trigger = function(args){
      if (
typeof args.todo === "string"){
        
google.maps.event.trigger(mapargs.todo);
      } else {
        var 
options = [mapargs.todo.eventName];
        if (
args.todo.var_args) {
            $.
each(args.todo.var_args, function(iv){
              
options.push(v);
            });
        }
        
google.maps.event.trigger.apply(google.maps.eventoptions);
      }
      
callback(args);
      
task.ack();
    };
  }

  
/**
   * Return true if get is a direct call
   * it means :
   *   - get is the only key
   *   - get has no callback
   * @param obj {Object} The request to check
   * @return {Boolean}
   */
  
function isDirectGet(obj) {
    var 
k;
    if (!
typeof obj === "object" || !obj.hasOwnProperty("get")){
      return 
false;
    }
    for(
k in obj) {
      if (
!== "get") {
        return 
false;
      }
    }
    return !
obj.get.hasOwnProperty("callback");
  }
  
  
//-----------------------------------------------------------------------//
  // jQuery plugin
  //-----------------------------------------------------------------------//
    
  
$.fn.gmap3 = function(){
    var 
i, list = [], empty = trueresults = [];
    
    
// init library
    
initDefaults();
    
    
// store all arguments in a todo list 
    
for(i=0i<arguments.lengthi++){
      if (
arguments[i]){
        list.
push(arguments[i]);
      }
    }

    
// resolve empty call - run init
    
if (!list.length) {
      list.
push("map");
    }

    
// loop on each jQuery object
    
$.each(this, function() {
      var 
$this = $(this), gmap3 $this.data("gmap3");
      empty = 
false;
      if (!
gmap3){
        
gmap3 = new Gmap3($this);
        
$this.data("gmap3"gmap3);
      }
      if (list.
length === && (list[0] === "get" || isDirectGet(list[0]))){
        if (list[
0] === "get") {
          
results.push(gmap3.get("map"true));
        } else {
          
results.push(gmap3.get(list[0].gettrue, list[0].get.full));
        }
      } else {
        
gmap3._plan(list);
      }
    });
    
    
// return for direct call only 
    
if (results.length){
      if (
results.length === 1){ // 1 css selector
        
return results[0];
      } else {
        return 
results;
      }
    }
    
    return 
this;
  }

})(
jQuery);
?>
Онлайн: 2
Реклама