Plugin Directory

Changeset 695801


Ignore:
Timestamp:
04/11/2013 08:31:12 AM (13 years ago)
Author:
macbrink
Message:

Lazyest Maps upgrade to GMAP3 version 5.0b

Location:
lazyest-maps
Files:
3 added
1 deleted
8 edited

Legend:

Unmodified
Added
Removed
  • lazyest-maps/trunk/css/admin.css

    r565317 r695801  
    11#icon-lazyestmaps {
    2   background: url('images/maps.png') no-repeat scroll top left transparent;
     2  background: url('images/maps.png') no-repeat top left;
    33}
  • lazyest-maps/trunk/inc/frontend.php

    r567283 r695801  
    216216               
    217217                $map .= '
    218                     {
    219                         lat:' . $latitude . ',
    220                         lng:' . $longitude .',
     218                    {latLng:[' . $latitude . ', ' . $longitude .'],
    221219                        data: ' . $data . ',
    222220                        options: {                             
  • lazyest-maps/trunk/inc/settings.php

    r566870 r695801  
    1414        <div id="lazyest-maps-settings" class="wrap">
    1515            <?php screen_icon( 'lazyestmaps' ); ?>
    16       <h2><?php echo esc_html_e( 'Show your Image Locations on a Map', 'lazyest-maps' ); ?></h2>               
    17             <div id="poststuff" class="metabox-holder">
     16      <h2><?php echo esc_html_e( 'Show your Image Locations on a Map', 'lazyest-maps' ); ?></h2>
    1817                <form id="lazyest-maps" method="post" action="admin.php">
    1918                    <?php wp_nonce_field( 'lazyest_maps' );  ?>
    20                     <input type="hidden" name="action" value="lazyest-maps" />
    21                         <div id="post-body" class="metabox-holder columns-2">                       
    22                 <?php $this->sidebar() ?>
    23                         <div id="post-body-content">
     19                    <input type="hidden" name="action" value="lazyest-maps" />
    2420                            <fieldset>
    2521                            <legend><?php esc_html__( 'Map Settings', 'lazyest-maps' ); ?></legend>
    2622                                <?php $this->main_settings(); ?>
    2723                                <?php $this->map_settings(); ?>
    28                                 <?php $this->infowindow_settings(); ?>
     24                                <?php $this->infowindow_settings(); ?>             
     25                        <?php $this->sidebar() ?>
    2926                            </fieldset>
    30                         </div>
    31                     </div>
    32                 </form>
    33             </div>                   
     27                </form>             
    3428        </div> 
    3529               
     
    4236        global $lazyest_maps;
    4337        ?>
    44         <div class="postbox">
    4538            <h3><?php esc_html_e( 'Main Options', 'lazyest-maps' ); ?></h3>
    46             <div class="inside">
    4739                <table class="form-table">
    4840                    <tbody>
     
    6759                    </tbody>
    6860                </table>
    69             </div>         
    70         </div>
    7161        <?php
    7262    }
     
    8070        global $lazyest_maps;
    8171        ?>
    82         <div class="postbox">
    8372            <h3><?php esc_html_e( 'Map Options', 'lazyest-maps' ); ?></h3>
    84             <div class="inside">
    8573                <table class="form-table">
    8674                    <tbody>
     
    9684                    </tbody>
    9785                </table>
    98             </div>         
    99         </div>
    10086        <?php       
    10187    }
     
    10995        global $lazyest_maps;
    11096        ?>
    111         <div class="postbox">
    11297            <h3><?php esc_html_e( 'Info Window Options', 'lazyest-maps' ); ?></h3>
    113             <div class="inside">
    11498                <table class="form-table">
    11599                    <tbody>
     
    134118                    </tbody>
    135119                </table>
    136             </div>         
    137         </div>
    138120        <?php
    139121    }
     
    145127     */
    146128    function sidebar() {
    147     global $wp_version;
    148129        ?>
    149     <div id="postbox-container-1" class="postbox-container">
    150       <div id="side-sortables" class="meta-box-sortables">
    151         <?php $this->settings->aboutbox(); ?>
    152         <?php $this->submitbox(); ?>
    153       </div>
    154     </div>
     130        <p class="submit">
     131            <input class="button-primary" type="submit" name="lazyest-maps[update]" value="<?php    esc_html_e( 'Save Changes', 'lazyest-maps' )    ?>" />
     132            <p><a id="back_link" href="admin.php?page=lazyest-gallery" title="<?php esc_html_e( 'Back to Lazyest Gallery Settings', 'lazyest-maps' ) ?>"><?php esc_html_e( 'Back to Lazyest Gallery Settings', 'lazyest-maps' ) ?></a></p>      </p>
    155133    <?php       
    156134    }
    157135   
    158     function submitbox() {
    159         global $lazyest_maps;
    160         ?>
    161         <div id="submitdiv" class="postbox">
    162         <h3 class="hndle"><span><?php esc_html_e( 'Lazyest Maps', 'lazyest-maps' ) ?></span></h3>
    163         <div id="version" class="misc-pub-section">               
    164       <div class="versions">
    165         <p><span id="ls-version-message"><strong><?php echo esc_html_e( 'Version', 'lazyest-maps' ); ?></strong> <?php echo $lazyest_maps->version(); ?></span></p>
    166       </div>
    167     </div>
    168     <div class="misc-pub-section misc-pub-section-last">
    169       <p><a id="back_link" href="admin.php?page=lazyest-gallery" title="<?php esc_html_e( 'Back to Lazyest Gallery Settings', 'lazyest-maps' ) ?>"><?php esc_html_e( 'Back to Lazyest Gallery Settings', 'lazyest-maps' ) ?></a></p>           
    170     </div>     
    171         <div id="major-publishing-actions">       
    172       <div id="publishing-action">
    173         <input class="button-primary" type="submit" name="lazyest-maps[update]" value="<?php    esc_html_e( 'Save Changes', 'lazyest-maps' )    ?>" />
    174       </div>
    175       <div class="clear"></div>
    176     </div>
    177         </div>
    178         <?php
    179     }
    180    
    181136} // LazyestMapSettings
  • lazyest-maps/trunk/js/gmap3.js

    r564181 r695801  
    1 /*
     1/*!
    22 *  GMAP3 Plugin for JQuery
    3  *  Version   : 4.1
    4  *  Date      : 2011-11-18
     3 *  Version   : 5.0b
     4 *  Date      : 2012-11-17
    55 *  Licence   : GPL v3 : http://www.gnu.org/licenses/gpl.html 
    66 *  Author    : DEMONTE Jean-Baptiste
     
    88 *  Web site  : http://gmap3.net
    99 *   
    10  *  Copyright (c) 2010-2011 Jean-Baptiste DEMONTE
     10 *  Copyright (c) 2010-2012 Jean-Baptiste DEMONTE
    1111 *  All rights reserved.
    1212 *   
     
    3636 * POSSIBILITY OF SUCH DAMAGE.
    3737 */
    38  
    39  (function ($) {
    40  
     38;(function ($, undef) {
     39
     40  /***************************************************************************/
     41  /*                           GMAP3 DEFAULTS                                */
     42  /***************************************************************************/
     43  // defaults are defined later in the code to pass the rails asset pipeline and
     44  //jasmine while google library is not loaded
     45  var defaults, gId = 0;
     46
     47  function initDefaults() {
     48    if (!defaults) {
     49      defaults = {
     50        verbose: false,
     51        queryLimit: {
     52          attempt: 5,
     53          delay: 250, // setTimeout(..., delay + random);
     54          random: 250
     55        },
     56        classes: {
     57          Map               : google.maps.Map,
     58          Marker            : google.maps.Marker,
     59          InfoWindow        : google.maps.InfoWindow,
     60          Circle            : google.maps.Circle,
     61          Rectangle         : google.maps.Rectangle,
     62          OverlayView       : google.maps.OverlayView,
     63          StreetViewPanorama: google.maps.StreetViewPanorama,
     64          KmlLayer          : google.maps.KmlLayer,
     65          TrafficLayer      : google.maps.TrafficLayer,
     66          BicyclingLayer    : google.maps.BicyclingLayer,
     67          GroundOverlay     : google.maps.GroundOverlay,
     68          StyledMapType     : google.maps.StyledMapType,
     69          ImageMapType      : google.maps.ImageMapType
     70        },
     71        map: {
     72          mapTypeId : google.maps.MapTypeId.ROADMAP,
     73          center: [46.578498, 2.457275],
     74          zoom: 2
     75        },
     76        overlay: {
     77          pane: "floatPane",
     78          content: "",
     79          offset: {
     80            x: 0,
     81            y: 0
     82          }
     83        },
     84        geoloc: {
     85          getCurrentPosition: {
     86            maximumAge: 60000,
     87            timeout: 5000
     88          }
     89        }
     90      }
     91    }
     92  }
     93
     94  function globalId(id, simulate){
     95    return id !== undef ? id : "gmap3_" + (simulate ? gId + 1 : ++gId);
     96  }
     97
     98  /**
     99   * attach events from a container to a sender
     100   * todo[
     101   *  events => { eventName => function, }
     102   *  onces  => { eventName => function, } 
     103   *  data   => mixed data         
     104   * ]
     105   **/
     106  function attachEvents($container, args, sender, id, senders){
     107    if (args.todo.events || args.todo.onces){
     108      var context = {
     109        id: id,
     110        data: args.todo.data,
     111        tag: args.todo.tag
     112      };
     113    }
     114    if (args.todo.events){
     115      $.each(args.todo.events, function(name, f){
     116        google.maps.event.addListener(sender, name, function(event) {
     117          f.apply($container, [senders ? senders : sender, event, context]);
     118        });
     119      });
     120    }
     121    if (args.todo.onces){
     122      $.each(args.todo.onces, function(name, f){
     123        google.maps.event.addListenerOnce(sender, name, function(event) {
     124          f.apply($container, [senders ? senders : sender, event, context]);
     125        });
     126      });
     127    }
     128  }
     129
    41130  /***************************************************************************/
    42131  /*                                STACK                                    */
    43132  /***************************************************************************/
     133 
    44134  function Stack (){
    45135    var st = [];
    46136    this.empty = function (){
    47       for(var i = 0; i < st.length; i++){
    48         if (st[i]){
    49           return false
    50         }
    51       }
    52       return true;
    53     }
     137      return !st.length;
     138    };
    54139    this.add = function(v){
    55140      st.push(v);
    56     }
    57     this.addNext = function ( v){
    58       var t=[], i, k = 0;
    59       for(i = 0; i < st.length; i++){
    60         if (!st[i]){
     141    };
     142    this.get = function (){
     143      return st.length ? st[0] : false;
     144    };
     145    this.ack = function (){
     146      st.shift();
     147    };
     148  }
     149
     150  /***************************************************************************/
     151  /*                                TASK                                     */
     152  /***************************************************************************/
     153 
     154  function Task(ctx, onEnd, todo){
     155    var session = {},
     156      that = this,
     157      current,
     158      resolve = {
     159        latLng:{ // function => bool (=> address = latLng)
     160          map:false,
     161          marker:false,
     162          infowindow:false,
     163          circle:false,
     164          overlay: false,
     165          getlatlng: false,
     166          getmaxzoom: false,
     167          getelevation: false,
     168          streetviewpanorama: false,
     169          getaddress: true
     170        },
     171        geoloc:{
     172          getgeoloc: true
     173        }
     174      };
     175     
     176    if (typeof todo === "string"){
     177      todo =  unify(todo);
     178    }
     179 
     180    function unify(todo){
     181      var result = {};
     182      result[todo] = {};
     183      return result;
     184    }
     185   
     186    function next(){
     187      var k;
     188      for(k in todo){
     189        if (k in session){ // already run
    61190          continue;
    62191        }
    63         if (k == 1) {
    64           t.push(v);
    65         }
    66         t.push(st[i]);
    67         k++;
    68       }
    69       if (k < 2) {
    70         t.push(v);
    71       }
    72       st = t;
    73     }
    74     this.get = function (){
    75       for(var i = 0; i < st.length; i++){
    76         if (st[i]) {
    77           return st[i];
    78         }
    79       }
    80       return false;
    81     }
    82     this.ack = function (){
    83       for(var i = 0; i < st.length; i++){                     
    84         if (st[i]) {
    85           delete st[i];
     192        return k;
     193      }
     194    }
     195   
     196    this.run = function (){
     197      var k, opts;
     198      while(k = next()){
     199        if (typeof ctx[k] === "function"){
     200          current = k;
     201          opts = $.extend(true, {}, defaults[k] || {}, todo[k].options || {});
     202          if (k in resolve.latLng){
     203            if (todo[k].values){
     204              resolveAllLatLng(todo[k].values, ctx, ctx[k], {todo:todo[k], opts:opts, session:session});
     205            } else {
     206              resolveLatLng(ctx, ctx[k], resolve.latLng[k], {todo:todo[k], opts:opts, session:session});
     207            }
     208          } else if (k in resolve.geoloc){
     209            geoloc(ctx, ctx[k], {todo:todo[k], opts:opts, session:session});
     210          } else {
     211            ctx[k].apply(ctx, [{todo:todo[k], opts:opts, session:session}]);
     212          }
     213          return; // wait until ack
     214        } else {
     215          session[k] = null;
     216        }
     217      }
     218      onEnd.apply(ctx, [todo, session]);
     219    };
     220   
     221    this.ack = function(result){
     222      session[current] = result;
     223      that.run.apply(that, []);
     224    };
     225  }
     226 
     227  function getKeys(obj){
     228    var k, keys = [];
     229    for(k in obj){
     230      keys.push(k);
     231    }
     232    return keys;
     233  }
     234 
     235  function tuple(args, value){
     236    var todo = {};
     237   
     238    // "copy" the common data
     239    if (args.todo){
     240      for(var k in args.todo){
     241        if ((k !== "options") && (k !== "values")){
     242          todo[k] = args.todo[k];
     243        }
     244      }
     245    }
     246    // "copy" some specific keys from value first else args.todo
     247    var i, keys = ["data", "tag", "id", "events",  "onces"];
     248    for(i=0; i<keys.length; i++){
     249      copyKey(todo, keys[i], value, args.todo);
     250    }
     251   
     252    // create an extended options
     253    todo.options = $.extend({}, args.todo.options || {}, value.options || {});
     254   
     255    return todo;
     256  }
     257 
     258  /**
     259   * copy a key content
     260   **/
     261  function copyKey(target, key){
     262    for(var i=2; i<arguments.length; i++){
     263      if (key in arguments[i]){
     264        target[key] = arguments[i][key];
     265        return;
     266      }
     267    }
     268  }
     269 
     270  /***************************************************************************/
     271  /*                             GEOCODERCACHE                               */
     272  /***************************************************************************/
     273 
     274  function GeocoderCache(){
     275    var cache = [];
     276   
     277    this.get = function(request){
     278      if (cache.length){
     279        var i, j, k, item, eq,
     280          keys = getKeys(request);
     281        for(i=0; i<cache.length; i++){
     282          item = cache[i];
     283          eq = keys.length == item.keys.length;
     284          for(j=0; (j<keys.length) && eq; j++){
     285            k = keys[j];
     286            eq = k in item.request;
     287            if (eq){
     288              if ((typeof request[k] === "object") && ("equals" in request[k]) && (typeof request[k] === "function")){
     289                eq = request[k].equals(item.request[k]);
     290              } else{
     291                eq = request[k] === item.request[k];
     292              }
     293            }
     294          }
     295          if (eq){
     296            return item.results;
     297          }
     298        }
     299      }
     300    };
     301   
     302    this.store = function(request, results){
     303      cache.push({request:request, keys:getKeys(request), results:results});
     304    };
     305  }
     306
     307  /***************************************************************************/
     308  /*                                OVERLAYVIEW                              */
     309  /***************************************************************************/
     310  function OverlayView(map, opts, latLng, $div) {
     311    var that = this, listeners = [];
     312   
     313    defaults.classes.OverlayView.call(this);
     314    this.setMap(map);
     315   
     316    this.onAdd = function() {
     317        var panes = this.getPanes();
     318        if (opts.pane in panes) {
     319            $(panes[opts.pane]).append($div);
     320        }
     321        $.each("dblclick click mouseover mousemove mouseout mouseup mousedown".split(" "), function(i, name){
     322            listeners.push(
     323                google.maps.event.addDomListener($div[0], name, function(e) {
     324                    $.Event(e).stopPropagation();
     325                    google.maps.event.trigger(that, name, [e]);
     326                })
     327            );
     328        });
     329        listeners.push(
     330            google.maps.event.addDomListener($div[0], "contextmenu", function(e) {
     331                $.Event(e).stopPropagation();
     332                google.maps.event.trigger(that, "rightclick", [e]);
     333            })
     334        );
     335        this.draw();
     336    };
     337    this.getPosition = function(){
     338      return latLng;
     339    };
     340    this.draw = function() {
     341        this.draw = function() {
     342            var ps = this.getProjection().fromLatLngToDivPixel(latLng);
     343            $div
     344                .css("left", (ps.x+opts.offset.x) + "px")
     345                .css("top" , (ps.y+opts.offset.y) + "px");
     346        }
     347    };
     348    this.onRemove = function() {
     349      for (var i = 0; i < listeners.length; i++) {
     350        google.maps.event.removeListener(listeners[i]);
     351      }
     352      $div.remove();
     353    };
     354    this.hide = function() {
     355      $div.hide();
     356    };
     357    this.show = function() {
     358      $div.show();
     359    };
     360    this.toggle = function() {
     361      if ($div) {
     362        if ($div.is(":visible")){
     363          this.show();
     364        } else {
     365          this.hide();
     366        }
     367      }
     368    };
     369    this.toggleDOM = function() {
     370      if (this.getMap()) {
     371        this.setMap(null);
     372      } else {
     373        this.setMap(map);
     374      }
     375    };
     376    this.getDOMElement = function() {
     377      return $div[0];
     378    };
     379  }
     380
     381  /***************************************************************************/
     382  /*                              CLUSTERING                                 */
     383  /***************************************************************************/
     384     
     385  /**
     386   * Usefull to get a projection
     387   * => done in a function, to let dead-code analyser works without google library loaded   
     388   **/
     389  function newEmptyOverlay(map){
     390    function Overlay(){
     391      this.onAdd = function(){};
     392      this.onRemove = function(){};
     393      this.draw = function(){};
     394      return defaults.classes.OverlayView.apply(this, []);
     395    }
     396    Overlay.prototype = defaults.classes.OverlayView.prototype;
     397    var obj = new Overlay();
     398    obj.setMap(map);
     399    return obj;
     400  }
     401 
     402  /**
     403   * Class InternalClusterer
     404   * This class manage clusters thanks to "todo" objects
     405   *
     406   * Note:
     407   * Individuals marker are created on the fly thanks to the todo objects, they are
     408   * first set to null to keep the indexes synchronised with the todo list
     409   * This is the "display" function, set by the gmap3 object, which uses theses data
     410   * to create markers when clusters are not required
     411   * To remove a marker, the objects are deleted and set not null in arrays
     412   *    markers[key]
     413   *      = null : marker exist but has not been displayed yet
     414   *      = false : marker has been removed       
     415   **/
     416  function InternalClusterer($container, map, radius, maxZoom){
     417    var updating = false,
     418      updated = false,
     419      redrawing = false,
     420      ready = false,
     421      enabled = true,
     422      that = this,
     423      events =  [],
     424      store = {},   // combin of index (id1-id2-...) => object
     425      ids = {},     // unique id => index
     426      markers = [], // index => marker
     427      todos = [],   // index => todo or null if removed
     428      values = [],  // index => value
     429      overlay = newEmptyOverlay(map),
     430      timer, projection,
     431      ffilter, fdisplay, ferror; // callback function
     432     
     433    main();
     434
     435    /**
     436     * return a marker by its id, null if not yet displayed and false if no exist or removed
     437     **/
     438    this.getById = function(id){
     439      return id in ids ? markers[ids[id]] : false;
     440    };
     441   
     442    /**
     443     * remove a marker by its id
     444     **/
     445    this.clearById = function(id){
     446      if (id in ids){
     447        var index = ids[id];
     448       
     449        if (markers[index]){ // can be null
     450          markers[index].setMap(null);
     451        }
     452        delete markers[index];
     453        markers[index] = false;
     454       
     455        delete todos[index];
     456        todos[index] = false;
     457       
     458        delete values[index];
     459        values[index] = false;
     460       
     461        delete ids[id];
     462        updated = true;
     463      }
     464    };
     465   
     466    // add a "marker todo" to the cluster
     467    this.add = function(todo, value){
     468      todo.id = globalId(todo.id);
     469      this.clearById(todo.id);
     470      ids[todo.id] = markers.length;
     471      markers.push(null); // null = marker not yet created / displayed
     472      todos.push(todo);
     473      values.push(value);
     474      updated = true;
     475    };
     476   
     477    // add a real marker to the cluster
     478    this.addMarker = function(marker, todo){
     479      todo = todo || {};
     480      todo.id = globalId(todo.id);
     481      this.clearById(todo.id);
     482      if (!todo.options){
     483        todo.options = {};
     484      }
     485      todo.options.position = marker.getPosition();
     486      attachEvents($container, {todo:todo}, marker, todo.id);
     487      ids[todo.id] = markers.length;
     488      markers.push(marker);
     489      todos.push(todo);
     490      values.push(todo.data || {});
     491      updated = true;
     492    };
     493   
     494    // return a "marker todo" by its index
     495    this.todo = function(index){
     496      return todos[index];
     497    };
     498   
     499    // return a "marker value" by its index
     500    this.value = function(index){
     501      return values[index];
     502    };
     503   
     504    // return a marker by its index
     505    this.marker = function(index){
     506      return markers[index];
     507    };
     508   
     509    // store a new marker instead if the default "false"
     510    this.setMarker = function(index, marker){
     511      markers[index] = marker;
     512    };
     513   
     514    // link the visible overlay to the logical data (to hide overlays later)
     515    this.store = function(cluster, obj, shadow){
     516      store[cluster.ref] = {obj:obj, shadow:shadow};
     517    };
     518   
     519    // free all objects
     520    this.free = function(){
     521      for(var i = 0; i < events.length; i++){
     522        google.maps.event.removeListener(events[i]);
     523      }
     524      events = [];
     525     
     526      $.each(store, function(key){
     527        flush(key);
     528      });
     529      store = {};
     530     
     531      $.each(todos, function(i){
     532        todos[i] = null;
     533      });
     534      todos = [];
     535     
     536      $.each(markers, function(i){
     537        if (markers[i]){ // false = removed
     538          markers[i].setMap(null);
     539          delete markers[i];
     540        }
     541      });
     542      markers = [];
     543     
     544      $.each(values, function(i){
     545        delete values[i];
     546      });
     547      values = [];
     548     
     549      ids = {};
     550    };
     551   
     552    // link the display function
     553    this.filter = function(f){
     554      ffilter = f;
     555      redraw();
     556    };
     557   
     558    // enable/disable the clustering feature
     559    this.enable = function(value){
     560      if (enabled != value){
     561        enabled = value;
     562        redraw();
     563      }
     564    };
     565   
     566    // link the display function
     567    this.display = function(f){
     568      fdisplay = f;
     569    };
     570   
     571    // link the errorfunction
     572    this.error = function(f){
     573      ferror = f;
     574    };
     575   
     576    // lock the redraw
     577    this.beginUpdate = function () {
     578      updating = true;
     579    };
     580   
     581    // unlock the redraw
     582    this.endUpdate = function(){
     583      updating = false;
     584      if (updated){
     585        redraw();
     586      }
     587    };
     588   
     589    // bind events
     590    function main(){
     591      projection = overlay.getProjection();
     592      if (!projection){
     593        setTimeout(function(){
     594          main.apply(that, []);
     595        },
     596        25);
     597        return;
     598      }
     599      ready = true;
     600      events.push(google.maps.event.addListener(map, "zoom_changed", function(){delayRedraw();}));
     601      events.push(google.maps.event.addListener(map, "bounds_changed", function(){delayRedraw();}));
     602      redraw();
     603    }
     604   
     605    // flush overlays
     606    function flush(key){
     607      if (typeof store[key] === "object"){ // is overlay
     608        if (typeof(store[key].obj.setMap) === "function") {
     609          store[key].obj.setMap(null);
     610        }
     611        if (typeof(store[key].obj.remove) === "function") {
     612          store[key].obj.remove();
     613        }
     614        if (typeof(store[key].shadow.remove) === "function") {
     615          store[key].obj.remove();
     616        }
     617        if (typeof(store[key].shadow.setMap) === "function") {
     618          store[key].shadow.setMap(null);
     619        }
     620        delete store[key].obj;
     621        delete store[key].shadow;
     622      } else if (markers[key]){ // marker not removed
     623        markers[key].setMap(null);
     624        // don't remove the marker object, it may be displayed later
     625      }
     626      delete store[key];
     627    }
     628   
     629    /**
     630     * return the distance between 2 latLng couple into meters
     631     * Params :   
     632     *  Lat1, Lng1, Lat2, Lng2
     633     *  LatLng1, Lat2, Lng2
     634     *  Lat1, Lng1, LatLng2
     635     *  LatLng1, LatLng2
     636     **/
     637    function distanceInMeter(){
     638      var lat1, lat2, lng1, lng2, e, f, g, h;
     639      if (arguments[0] instanceof google.maps.LatLng){
     640        lat1 = arguments[0].lat();
     641        lng1 = arguments[0].lng();
     642        if (arguments[1] instanceof google.maps.LatLng){
     643          lat2 = arguments[1].lat();
     644          lng2 = arguments[1].lng();
     645        } else {
     646          lat2 = arguments[1];
     647          lng2 = arguments[2];
     648        }
     649      } else {
     650        lat1 = arguments[0];
     651        lng1 = arguments[1];
     652        if (arguments[2] instanceof google.maps.LatLng){
     653          lat2 = arguments[2].lat();
     654          lng2 = arguments[2].lng();
     655        } else {
     656          lat2 = arguments[2];
     657          lng2 = arguments[3];
     658        }
     659      }
     660      e = Math.PI*lat1/180;
     661      f = Math.PI*lng1/180;
     662      g = Math.PI*lat2/180;
     663      h = Math.PI*lng2/180;
     664      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));
     665    }
     666   
     667    // extend the visible bounds
     668    function extendsMapBounds(){
     669      var radius = distanceInMeter(map.getCenter(), map.getBounds().getNorthEast()),
     670        circle = new google.maps.Circle({
     671          center: map.getCenter(),
     672          radius: 1.25 * radius // + 25%
     673        });
     674      return circle.getBounds();
     675    }
     676   
     677    // return an object where keys are store keys
     678    function getStoreKeys(){
     679      var keys = {}, k;
     680      for(k in store){
     681        keys[k] = true;
     682      }
     683      return keys;
     684    }
     685   
     686    // async the delay function
     687    function delayRedraw(){
     688      clearTimeout(timer);
     689      timer = setTimeout(function(){
     690        redraw();
     691      },
     692      25);
     693    }
     694   
     695    // generate bounds extended by radius
     696    function extendsBounds(latLng) {
     697      var p = projection.fromLatLngToDivPixel(latLng),
     698        ne = projection.fromDivPixelToLatLng(new google.maps.Point(p.x+radius, p.y-radius)),
     699        sw = projection.fromDivPixelToLatLng(new google.maps.Point(p.x-radius, p.y+radius));
     700      return new google.maps.LatLngBounds(sw, ne);
     701    }
     702   
     703    // run the clustering process and call the display function
     704    function redraw(){
     705      if (updating || redrawing || !ready){
     706        return;
     707      }
     708     
     709      var keys = [], used = {},
     710        zoom = map.getZoom(),
     711        forceDisabled = (maxZoom !== undef) && (zoom > maxZoom),
     712        previousKeys = getStoreKeys(),
     713        i, j, k, lat, lng, indexes, previous, check = false, bounds, cluster;
     714       
     715      // reset flag
     716      updated = false;
     717     
     718      if (zoom > 3){
     719        // extend the bounds of the visible map to manage clusters near the boundaries
     720        bounds = extendsMapBounds();
     721       
     722        // check contain only if boundaries are valid
     723        check = bounds.getSouthWest().lng() < bounds.getNorthEast().lng();
     724      }
     725     
     726      // calculate positions of "visibles" markers (in extended bounds)
     727      $.each(todos, function(index, todo){
     728        if (!todo){ // marker removed
     729          return;
     730        }
     731        if (check && (!bounds.contains(todo.options.position))){
     732          return;
     733        }
     734        if (ffilter && !ffilter(values[index])){
     735          return;
     736        }
     737        keys.push(index);
     738      });
     739     
     740      // for each "visible" marker, search its neighbors to create a cluster
     741      // we can't do a classical "for" loop, because, analysis can bypass a marker while focusing on cluster
     742      while(1){
     743        i=0;
     744        while(used[i] && (i<keys.length)){ // look for the next marker not used
     745          i++;
     746        }
     747        if (i == keys.length){
    86748          break;
    87749        }
    88       }
    89       if (this.empty()){
    90         st = [];
    91       }
    92     }
    93   }
    94  
     750       
     751        indexes = [];
     752       
     753        if (enabled && !forceDisabled){
     754          do{
     755            previous = indexes;
     756            indexes = [];
     757           
     758            if (previous.length){
     759            // re-evaluates center
     760              lat = lng = 0;
     761              for(k=0; k<previous.length; k++){
     762                lat += todos[ keys[previous[k]] ].options.position.lat();
     763                lng += todos[ keys[previous[k]] ].options.position.lng();
     764              }
     765              lat /= previous.length;
     766              lng /= previous.length;
     767              bounds = extendsBounds(new google.maps.LatLng(lat, lng));
     768            } else {
     769              bounds = extendsBounds(todos[ keys[i] ].options.position);
     770            }
     771           
     772            for(j=i; j<keys.length; j++){
     773              if (used[j]){
     774                continue;
     775              }
     776              if (bounds.contains(todos[ keys[j] ].options.position)){
     777                indexes.push(j);
     778              }
     779            }
     780          } while( (previous.length < indexes.length) && (indexes.length > 1) );
     781        } else {
     782          for(j=i; j<keys.length; j++){
     783            if (used[j]){
     784              continue;
     785            }
     786            indexes.push(j);
     787            break;
     788          }
     789        }
     790       
     791        cluster = {latLng:bounds.getCenter(), indexes:[], ref:[]};
     792        for(k=0; k<indexes.length; k++){
     793          used[ indexes[k] ] = true;
     794          cluster.indexes.push(keys[indexes[k]]);
     795          cluster.ref.push(keys[indexes[k]]);
     796        }
     797        cluster.ref = cluster.ref.join("-");
     798       
     799        if (cluster.ref in previousKeys){ // cluster doesn't change
     800          delete previousKeys[cluster.ref]; // remove this entry, these still in this array will be removed
     801        } else { // cluster is new
     802          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)
     803            store[cluster.ref] = true;
     804          }
     805          // use a closure to async the display call make faster the process of cle clustering
     806          (function(cl){
     807            setTimeout(function(){
     808              fdisplay(cl); // while displaying, will use store to link object
     809            }, 1);
     810          })(cluster);
     811        }
     812      }
     813     
     814      // flush the previous overlays which are not still used
     815      $.each(previousKeys, function(key){
     816        flush(key);
     817      });
     818      redrawing = false;
     819    }
     820  }
     821 
     822  /**
     823   * Class Clusterer
     824   * a facade with limited method for external use
     825   **/
     826  function Clusterer(id, internalClusterer){
     827    this.id = function(){
     828      return id;
     829    };
     830    this.filter = function(f){
     831      internalClusterer.filter(f);
     832    };
     833    this.enable = function(){
     834      internalClusterer.enable(true);
     835    };
     836    this.disable = function(){
     837      internalClusterer.enable(false);
     838    };
     839    this.add = function(marker, todo, lock){
     840      if (!lock) {
     841        internalClusterer.beginUpdate();
     842      }
     843      internalClusterer.addMarker(marker, todo);
     844      if (!lock) {
     845        internalClusterer.endUpdate();
     846      }
     847    };
     848    this.getById = function(id){
     849      return internalClusterer.getById(id);
     850    };
     851    this.clearById = function(id, lock){
     852      if (!lock) {
     853        internalClusterer.beginUpdate();
     854      }
     855      internalClusterer.clearById(id);
     856      if (!lock) {
     857        internalClusterer.endUpdate();
     858      }
     859    };
     860  }
    95861  /***************************************************************************/
    96862  /*                                STORE                                    */
    97863  /***************************************************************************/
     864 
    98865  function Store(){
    99     var store = {};
     866    var store = {}, // name => [id, ...]
     867      objects = {}; // id => object
     868   
     869    function ftag(tag){
     870      if (tag){
     871        if (typeof tag === "function"){
     872          return tag;
     873        }
     874        tag = array(tag);
     875        return function(val){
     876          if (val === undef){
     877            return false;
     878          }
     879          if (typeof val === "object"){
     880            for(var i=0; i<val.length; i++){
     881              if($.inArray(val[i], tag) >= 0){
     882                return true;
     883              }
     884            }
     885            return false;
     886          }
     887          return $.inArray(val, tag) >= 0;
     888        }
     889      }
     890    }
     891
     892    function normalize(res) {
     893      return {
     894        id: res.id,
     895        name: res.name,
     896        object:res.obj,
     897        tag:res.tag,
     898        data:res.data
     899      };
     900    }
    100901   
    101902    /**
    102903     * add a mixed to the store
    103904     **/
    104     this.add = function(name, obj, todo){
    105       name = name.toLowerCase();
     905    this.add = function(args, name, obj, sub){
     906      var todo = args.todo || {},
     907        id = globalId(todo.id);
    106908      if (!store[name]){
    107909        store[name] = [];
    108910      }
    109       store[name].push({obj:obj, tag:ival(todo, 'tag')});
    110       return name + '-' + (store[name].length-1);
    111     }
    112    
    113     /**
    114      * return a stored mixed
    115      **/
    116     this.get = function(name, last, tag){
    117       var i, idx, add;
    118       name = name.toLowerCase();
     911      if (id in objects){ // object already exists: remove it
     912        this.clearById(id);
     913      }
     914      objects[id] = {obj:obj, sub:sub, name:name, id:id, tag:todo.tag, data:todo.data};
     915      store[name].push(id);
     916      return id;
     917    };
     918   
     919    /**
     920     * return a stored object by its id
     921     **/
     922    this.getById = function(id, sub, full){
     923      if (id in objects){
     924          if (sub) {
     925            return objects[id].sub
     926          } else if (full) {
     927            return normalize(objects[id]);
     928          }
     929          return objects[id].obj;
     930
     931      }
     932      return false;
     933    };
     934   
     935    /**
     936     * return a stored value
     937     **/
     938    this.get = function(name, last, tag, full){
     939      var n, id, check = ftag(tag);
    119940      if (!store[name] || !store[name].length){
    120941        return null;
    121942      }
    122       idx = last ? store[name].length : -1;
    123       add = last ? -1 : 1;
    124       for(i=0; i<store[name].length; i++){
    125         idx += add;
    126         if (store[name][idx]){
    127           if (tag !== undefined) {
    128             if ( (store[name][idx].tag === undefined) || ($.inArray(store[name][idx].tag, tag) < 0) ){
    129               continue;
     943      n = store[name].length;
     944      while(n){
     945        n--;
     946        id = store[name][last ? n : store[name].length - n - 1];
     947        if (id && objects[id]){
     948          if (check && !check(objects[id].tag)){
     949            continue;
     950          }
     951          return full ? normalize(objects[id]) : objects[id].obj;
     952        }
     953      }
     954      return null;
     955    };
     956   
     957    /**
     958     * return all stored values
     959     **/
     960    this.all = function(name, tag, full){
     961      var result = [],
     962          check = ftag(tag),
     963          find = function(n){
     964            var i, id;
     965            for(i=0; i<store[n].length; i++){
     966              id = store[n][i];
     967              if (id && objects[id]){
     968                if (check && !check(objects[id].tag)){
     969                  continue;
     970                }
     971                result.push(full ? normalize(objects[id]) : objects[id].obj);
     972              }
    130973            }
    131           }
    132           return store[name][idx].obj;
    133         }
    134       }
    135       return null;
    136     }
    137    
    138     /**
    139      * return all stored mixed
    140      **/
    141     this.all = function(name, tag){
    142       var i, result = [];
    143       name = name.toLowerCase();
    144       if (!store[name] || !store[name].length){
    145         return result;
    146       }
    147       for(i=0; i<store[name].length; i++){
    148         if (!store[name][i]){
    149           continue;
    150         }
    151         if ( (tag !== undefined) && ( (store[name][i].tag === undefined) || ($.inArray(store[name][i].tag, tag) < 0) ) ){
    152           continue;
    153         }
    154         result.push(store[name][i].obj);
     974          };
     975      if (name in store){
     976        find(name);
     977      } else if (name === undef){ // internal use only
     978        for(name in store){
     979          find(name);
     980        }
    155981      }
    156982      return result;
    157     }
    158    
    159     /**
    160      * return all storation groups
    161      **/
    162     this.names = function(){
    163       var name, result = [];
    164       for(name in store){
    165         result.push(name);
    166       }
    167       return result;
    168     }
    169    
    170     /**
    171      * return an object from its reference
    172      **/
    173     this.refToObj = function(ref){
    174       ref = ref.split('-'); // name - idx
    175       if ((ref.length == 2) && store[ref[0]] && store[ref[0]][ref[1]]){
    176         return store[ref[0]][ref[1]].obj;
    177       }
    178       return null;
    179     }
    180    
     983    };
     984   
     985    /**
     986     * hide and remove an object
     987     **/
     988    function rm(obj){
     989      // Google maps element
     990      if (typeof(obj.setMap) === "function") {
     991        obj.setMap(null);
     992      }
     993      // jQuery
     994      if (typeof(obj.remove) === "function") {
     995        obj.remove();
     996      }
     997      // internal (cluster)
     998      if (typeof(obj.free) === "function") {
     999        obj.free();
     1000      }
     1001      obj = null;
     1002    }
     1003
    1811004    /**
    1821005     * remove one object from the store
    1831006     **/
    184     this.rm = function(name, tag, pop){
    185       var idx, i, tmp;
    186       name = name.toLowerCase();
     1007    this.rm = function(name, check, pop){
     1008      var idx, id;
    1871009      if (!store[name]) {
    1881010        return false;
    1891011      }
    190       if (tag !== undefined){
     1012      if (check){
    1911013        if (pop){
    1921014          for(idx = store[name].length - 1; idx >= 0; idx--){
    193             if ( (store[name][idx] !== undefined) && (store[name][idx].tag !== undefined) && ($.inArray(store[name][idx].tag, tag) >= 0) ){
     1015            id = store[name][idx];
     1016            if ( check(objects[id].tag) ){
    1941017              break;
    1951018            }
     
    1971020        } else {
    1981021          for(idx = 0; idx < store[name].length; idx++){
    199             if ( (store[name][idx] !== undefined) && (store[name][idx].tag !== undefined) && ($.inArray(store[name][idx].tag, tag) >= 0) ){
     1022            id = store[name][idx];
     1023            if (check(objects[id].tag)){
    2001024              break;
    2011025            }
     
    2081032        return false;
    2091033      }
    210       // Google maps element
    211       if (typeof(store[name][idx].obj.setMap) === 'function') {
    212         store[name][idx].obj.setMap(null);
    213       }
    214       // jQuery
    215       if (typeof(store[name][idx].obj.remove) === 'function') {
    216         store[name][idx].obj.remove();
    217       }
    218       // internal (cluster)
    219       if (typeof(store[name][idx].obj.free) === 'function') {
    220         store[name][idx].obj.free();
    221       }
    222       delete store[name][idx].obj;
    223       if (tag !== undefined){
    224         tmp = [];
    225         for(i=0; i<store[name].length; i++){
    226           if (i !== idx){
    227             tmp.push(store[name][i]);
    228           }
    229         }
    230         store[name] = tmp;
    231       } else {
    232         if (pop) {
    233           store[name].pop();
    234         } else {
    235           store[name].shift();
    236         }
    237       }
    238       return true;
    239     }
     1034      return this.clearById(store[name][idx], idx);
     1035    };
     1036   
     1037    /**
     1038     * remove object from the store by its id
     1039     **/
     1040    this.clearById = function(id, idx){
     1041      if (id in objects){
     1042        var i, name = objects[id].name;
     1043        for(i=0; idx === undef && i<store[name].length; i++){
     1044          if (id === store[name][i]){
     1045            idx = i;
     1046          }
     1047        }
     1048        rm(objects[id].obj);
     1049        if(objects[id].sub){
     1050          rm(objects[id].sub);
     1051        }
     1052        delete objects[id];
     1053        store[name].splice(idx, 1);
     1054        return true;
     1055      }
     1056      return false;
     1057    };
     1058   
     1059    /**
     1060     * return an object from a container object in the store by its id
     1061     * ! for now, only cluster manage this feature
     1062     **/
     1063    this.objGetById = function(id){
     1064      var result;
     1065      if (store["clusterer"]) {
     1066        for(var idx in store["clusterer"]){
     1067          if ((result = objects[store["clusterer"][idx]].obj.getById(id)) !== false){
     1068            return result;
     1069          }
     1070        }
     1071      }
     1072      return false;
     1073    };
     1074   
     1075    /**
     1076     * remove object from a container object in the store by its id
     1077     * ! for now, only cluster manage this feature
     1078     **/
     1079    this.objClearById = function(id){
     1080      if (store["clusterer"]) {
     1081        for(var idx in store["clusterer"]){
     1082          if (objects[store["clusterer"][idx]].obj.clearById(id)){
     1083            return true;
     1084          }
     1085        }
     1086      }
     1087      return null;
     1088    };
    2401089   
    2411090    /**
     
    2431092     **/
    2441093    this.clear = function(list, last, first, tag){
    245       var k, i, name;
     1094      var k, i, name, check = ftag(tag);
    2461095      if (!list || !list.length){
    2471096        list = [];
     
    2541103      for(i=0; i<list.length; i++){
    2551104        if (list[i]){
    256           name = list[i].toLowerCase();
     1105          name = list[i];
    2571106          if (!store[name]){
    2581107            continue;
    2591108          }
    2601109          if (last){
    261             this.rm(name, tag, true);
     1110            this.rm(name, check, true);
    2621111          } else if (first){
    263             this.rm(name, tag, false);
    264           } else {
    265             // all
    266             while (this.rm(name, tag, false));
    267           }
    268         }
    269       }
    270     }
    271   }
    272  
    273   /***************************************************************************/
    274   /*                              CLUSTERER                                  */
    275   /***************************************************************************/
    276 
    277   function Clusterer(){
    278     var markers = [], events=[], stored=[], latest=[], redrawing = false, redraw;
    279    
    280     this.events = function(){
    281       for(var i=0; i<arguments.length; i++){
    282         events.push(arguments[i]);
    283       }
    284     }
    285    
    286     this.startRedraw = function(){
    287       if (!redrawing){
    288         redrawing = true;
    289         return true;
    290       }
    291       return false;
    292     }
    293    
    294     this.endRedraw = function(){
    295       redrawing = false;
    296     }
    297    
    298     this.redraw = function(){
    299       var i, args = [], that = this;
    300       for(i=0; i<arguments.length; i++){
    301         args.push(arguments[i]);
    302       }
    303       if (this.startRedraw){
    304         redraw.apply(that, args);
    305         this.endRedraw();
    306       } else {
    307         setTimeout(function(){
    308             that.redraw.apply(that, args);
    309           },
    310           50
    311         );
    312       }
    313     };
    314    
    315     this.setRedraw = function(fnc){
    316       redraw  = fnc;
    317     }
    318    
    319     this.store = function(data, obj, shadow){
    320       stored.push({data:data, obj:obj, shadow:shadow});
    321     }
    322    
    323     this.free = function(){
    324       for(var i = 0; i < events.length; i++){
    325         google.maps.event.removeListener(events[i]);
    326       }
    327       events=[];
    328       this.freeAll();
    329     }
    330    
    331     this.freeIndex = function(i){
    332       if (typeof(stored[i].obj.setMap) === 'function') {
    333         stored[i].obj.setMap(null);
    334       }
    335       if (typeof(stored[i].obj.remove) === 'function') {
    336         stored[i].obj.remove();
    337       }
    338       if (stored[i].shadow){ // only overlays has shadow
    339         if (typeof(stored[i].shadow.remove) === 'function') {
    340           stored[i].obj.remove();
    341         }
    342         if (typeof(stored[i].shadow.setMap) === 'function') {
    343           stored[i].shadow.setMap(null);
    344         }
    345         delete stored[i].shadow;
    346       }
    347       delete stored[i].obj;
    348       delete stored[i].data;
    349       delete stored[i];
    350     }
    351    
    352     this.freeAll = function(){
    353       var i;
    354       for(i = 0; i < stored.length; i++){
    355         if (stored[i]) {
    356           this.freeIndex(i);
    357         }
    358       }
    359       stored = [];
    360     }
    361    
    362     this.freeDiff = function(clusters){
    363       var i, j, same = {}, idx = [];
    364       for(i=0; i<clusters.length; i++){
    365         idx.push( clusters[i].idx.join('-') );
    366       }
    367       for(i = 0; i < stored.length; i++){
    368         if (!stored[i]) {
    369           continue;
    370         }
    371         j = $.inArray(stored[i].data.idx.join('-'), idx);
    372         if (j >= 0){
    373           same[j] = true;
    374         } else {
    375           this.freeIndex(i);
    376         }
    377       }
    378       return same;
    379     }
    380    
    381     this.add = function(latLng, marker){
    382       markers.push({latLng:latLng, marker:marker});
    383     }
    384    
    385     this.get = function(i){
    386       return markers[i];
    387     }
    388    
    389     this.clusters = function(map, radius, maxZoom, force){
    390       var proj = map.getProjection(),
    391           nwP = proj.fromLatLngToPoint(
    392             new google.maps.LatLng(
    393                 map.getBounds().getNorthEast().lat(),
    394                 map.getBounds().getSouthWest().lng()
    395             )
    396           ),
    397           i, j, j2, p, x, y, k, k2,
    398           z = map.getZoom(),
    399           pos = {},
    400           saved = {},
    401           unik = {},
    402           clusters = [],
    403           cluster,
    404           chk,
    405           lat, lng, keys, cnt,
    406           bounds = map.getBounds(),
    407           noClusters = maxZoom && (maxZoom <= map.getZoom()),
    408           chkContain = map.getZoom() > 2;
    409      
    410       cnt = 0;
    411       keys = {};
    412       for(i = 0; i < markers.length; i++){
    413         if (chkContain && !bounds.contains(markers[i].latLng)){
    414           continue;
    415         }
    416         p = proj.fromLatLngToPoint(markers[i].latLng);
    417         pos[i] = [
    418           Math.floor((p.x - nwP.x) * Math.pow(2, z)),
    419           Math.floor((p.y - nwP.y) * Math.pow(2, z))
    420         ];
    421         keys[i] = true;
    422         cnt++;
    423       }
    424       // check if visible markers have changed
    425       if (!force && !noClusters){
    426         for(k = 0; k < latest.length; k++){
    427           if( k in keys ){
    428             cnt--;
    429           } else {
    430             break;
    431           }
    432         }
    433         if (!cnt){
    434           return false; // no change
    435         }
    436       }
    437      
    438       // save current keys to check later if an update has been done
    439       latest = keys;
    440      
    441       keys = [];
    442       for(i in pos){
    443         x = pos[i][0];
    444         y = pos[i][1];
    445         if ( !(x in saved) ){
    446           saved[x] = {};
    447         }
    448         if (!( y in saved[x]) ) {
    449           saved[x][y] = i;
    450           unik[i] = {};
    451           keys.push(i);
    452         }
    453         unik[ saved[x][y] ][i] = true;
    454       }
    455       radius = Math.pow(radius, 2);
    456       delete(saved);
    457      
    458       k = 0;
    459       while(1){
    460         while((k <keys.length) && !(keys[k] in unik)){
    461           k++;
    462         }
    463         if (k == keys.length){
    464           break;
    465         }
    466         i = keys[k];
    467         lat = pos[i][0];
    468         lng = pos[i][1];
    469         saved = null;
    470        
    471        
    472         if (noClusters){
    473           saved = {lat:lat, lng:lng, idx:[i]};
    474         } else {
    475           do{
    476             cluster = {lat:0, lng:0, idx:[]};
    477             for(k2 = k; k2<keys.length; k2++){
    478               if (!(keys[k2] in unik)){
    479                 continue;
    480               }
    481               j = keys[k2];
    482               if ( Math.pow(lat - pos[j][0], 2) + Math.pow(lng-pos[j][1], 2) <= radius ){
    483                 for(j2 in unik[j]){
    484                   cluster.lat += markers[j2].latLng.lat();
    485                   cluster.lng += markers[j2].latLng.lng();
    486                   cluster.idx.push(j2);
    487                 }
    488               }
    489             }
    490             cluster.lat /= cluster.idx.length;
    491             cluster.lng /= cluster.idx.length;
    492             if (!saved){
    493               chk = cluster.idx.length > 1;
    494               saved = cluster;
    495             } else {
    496               chk = cluster.idx.length > saved.idx.length;
    497               if (chk){
    498                 saved = cluster;
    499               }
    500             }
    501             if (chk){
    502               p = proj.fromLatLngToPoint( new google.maps.LatLng(saved.lat, saved.lng) );
    503               lat = Math.floor((p.x - nwP.x) * Math.pow(2, z));
    504               lng = Math.floor((p.y - nwP.y) * Math.pow(2, z));
    505             }
    506           } while(chk);
    507         }
    508          
    509         for(k2 = 0; k2 < saved.idx.length; k2++){
    510           if (saved.idx[k2] in unik){
    511             delete(unik[saved.idx[k2]]);
    512           }
    513         }
    514         clusters.push(saved);
    515       }
    516       return clusters;
    517     }
    518    
    519     this.getBounds = function(){
    520       var i, bounds = new google.maps.LatLngBounds();
    521       for(i=0; i<markers.length; i++){
    522         bounds.extend(markers[i].latLng);
    523       }
    524       return bounds;
    525     }
    526   }
    527 
     1112            this.rm(name, check, false);
     1113          } else { // all
     1114            while(this.rm(name, check, false));
     1115          }
     1116        }
     1117      }
     1118    };
     1119  }
     1120 
    5281121  /***************************************************************************/
    5291122  /*                           GMAP3 GLOBALS                                 */
    5301123  /***************************************************************************/
    5311124 
    532   var _default = {
    533       verbose:false,
    534       queryLimit:{
    535         attempt:5,
    536         delay:250, // setTimeout(..., delay + random);
    537         random:250
    538       },
    539       init:{
    540         mapTypeId : google.maps.MapTypeId.ROADMAP,
    541         center:[46.578498,2.457275],
    542         zoom: 2
    543       },
    544       classes:{
    545         Map               : google.maps.Map,
    546         Marker            : google.maps.Marker,
    547         InfoWindow        : google.maps.InfoWindow,
    548         Circle            : google.maps.Circle,
    549         Rectangle         : google.maps.Rectangle,
    550         OverlayView       : google.maps.OverlayView,
    551         StreetViewPanorama: google.maps.StreetViewPanorama,
    552         KmlLayer          : google.maps.KmlLayer,
    553         TrafficLayer      : google.maps.TrafficLayer,
    554         BicyclingLayer    : google.maps.BicyclingLayer,
    555         GroundOverlay     : google.maps.GroundOverlay,
    556         StyledMapType     : google.maps.StyledMapType
    557       }
    558     },
    559     _properties = ['events','onces','options','apply', 'callback', 'data', 'tag'],
    560     _noInit = ['init', 'geolatlng', 'getlatlng', 'getroute', 'getelevation', 'getdistance', 'addstyledmap', 'setdefault', 'destroy'],
    561     _directs = ['get'],
    562     geocoder = directionsService = elevationService = maxZoomService = distanceMatrixService = null;
    563    
    564   function setDefault(values){
    565     for(var k in values){
    566       if (typeof(_default[k]) === 'object'){
    567         _default[k] = $.extend({}, _default[k], values[k]);
    568       } else {
    569         _default[k] = values[k];
    570       }
    571     }
    572   }
    573  
    574   function autoInit(iname){
    575     if (!iname){
    576       return true;
    577     }
    578     for(var i = 0; i < _noInit.length; i++){
    579       if (_noInit[i] === iname) {
    580         return false;
    581       }
    582     }
    583     return true;
    584   }
    585  
    586    
    587   /**
    588    * return true if action has to be executed directly
    589    **/
    590   function isDirect (todo){
    591     var action = ival(todo, 'action');
    592     for(var i = 0; i < _directs.length; i++){
    593       if (_directs[i] === action) {
    594         return true;
    595       }
    596     }
    597     return false;
    598   }
    599        
    600   //-----------------------------------------------------------------------//
    601   // Objects tools
    602   //-----------------------------------------------------------------------//
    603  
    604   /**
    605    * return the real key by an insensitive seach
    606    **/
    607   function ikey (object, key){
    608     if (key.toLowerCase){
    609       key = key.toLowerCase();
    610       for(var k in object){
    611         if (k.toLowerCase && (k.toLowerCase() == key)) {
    612           return k;
    613         }
    614       }
    615     }
    616     return false;
    617   }
    618  
    619   /**
    620    * return the value of real key by an insensitive seach
    621    **/
    622   function ival (object, key, def){
    623     var k = ikey(object, key);
    624     return k ? object[k] : def;
    625   }
    626  
    627   /**
    628    * return true if at least one key is set in object
    629    * nb: keys in lowercase
    630    **/
    631   function hasKey (object, keys){
    632     var n, k;
    633     if (!object || !keys) {
    634       return false;
    635     }
    636     keys = array(keys);
    637     for(n in object){
    638       if (n.toLowerCase){
    639         n = n.toLowerCase();
    640         for(k in keys){
    641           if (n == keys[k]) {
    642             return true;
    643           }
    644         }
    645       }
    646     }
    647     return false;
    648   }
    649  
    650   /**
    651    * return a standard object
    652    * nb: include in lowercase
    653    **/
    654   function extractObject (todo, include, result/* = {} */){
    655     if (hasKey(todo, _properties) || hasKey(todo, include)){ // #1 classical object definition
    656       var i, k;
    657       // get defined properties values from todo
    658       for(i=0; i<_properties.length; i++){
    659         k = ikey(todo, _properties[i]);
    660         result[ _properties[i] ] = k ? todo[k] : {};
    661       }
    662       if (include && include.length){
    663         for(i=0; i<include.length; i++){
    664           if(k = ikey(todo, include[i])){
    665             result[ include[i] ] = todo[k];
    666           }
    667         }
    668       }
    669       return result;
    670     } else { // #2 simplified object (all excepted "action" are options properties)
    671       result.options= {};
    672       for(k in todo){
    673         if (k !== 'action'){
    674           result.options[k] = todo[k];
    675         }
    676       }
    677       return result;
    678     }
    679   }
    680  
    681   /**
    682    * identify object from object list or parameters list : [ objectName:{data} ] or [ otherObject:{}, ] or [ object properties ]
    683    * nb: include, exclude in lowercase
    684    **/
    685   function getObject(name, todo, include, exclude){
    686     var iname = ikey(todo, name),
    687         i, result = {}, keys=['map'];
    688     // include callback from high level
    689     result['callback'] = ival(todo, 'callback');
    690     include = array(include);
    691     exclude = array(exclude);
    692     if (iname) {
    693       return extractObject(todo[iname], include, result);
    694     }
    695     if (exclude && exclude.length){
    696       for(i=0; i<exclude.length; i++) {
    697         keys.push(exclude[i]);
    698       }
    699     }
    700     if (!hasKey(todo, keys)){
    701       result = extractObject(todo, include, result);
    702     }
    703     // initialize missing properties
    704     for(i=0; i<_properties.length; i++){
    705       if (_properties[i] in result){
    706         continue;
    707       }
    708       result[ _properties[i] ] = {};
    709     }
    710     return result;
    711   }
    712  
     1125  var services = {},
     1126    geocoderCache = new GeocoderCache();
     1127   
    7131128  //-----------------------------------------------------------------------//
    7141129  // Service tools
    7151130  //-----------------------------------------------------------------------//
    716    
    717   function getGeocoder(){
    718     if (!geocoder) {
    719       geocoder = new google.maps.Geocoder();
    720     }
    721     return geocoder;
    722   }
    723  
    724   function getDirectionsService(){
    725     if (!directionsService) {
    726       directionsService = new google.maps.DirectionsService();
    727     }
    728     return directionsService;
    729   }
    730  
    731   function getElevationService(){
    732     if (!elevationService) {
    733       elevationService = new google.maps.ElevationService();
    734     }
    735     return elevationService;
    736   }
    737  
    738   function getMaxZoomService(){
    739     if (!maxZoomService) {
    740       maxZoomService = new google.maps.MaxZoomService();
    741     }
    742     return maxZoomService;
    743   }
    744  
    745   function getDistanceMatrixService(){
    746     if (!distanceMatrixService) {
    747       distanceMatrixService = new google.maps.DistanceMatrixService();
    748     }
    749     return distanceMatrixService;
    750   }
    751    
     1131 
     1132  function geocoder(){
     1133    if (!services.geocoder) {
     1134      services.geocoder = new google.maps.Geocoder();
     1135    }
     1136    return services.geocoder;
     1137  }
     1138 
     1139  function directionsService(){
     1140    if (!services.directionsService) {
     1141      services.directionsService = new google.maps.DirectionsService();
     1142    }
     1143    return services.directionsService;
     1144  }
     1145 
     1146  function elevationService(){
     1147    if (!services.elevationService) {
     1148      services.elevationService = new google.maps.ElevationService();
     1149    }
     1150    return services.elevationService;
     1151  }
     1152 
     1153  function maxZoomService(){
     1154    if (!services.maxZoomService) {
     1155      services.maxZoomService = new google.maps.MaxZoomService();
     1156    }
     1157    return services.maxZoomService;
     1158  }
     1159 
     1160  function distanceMatrixService(){
     1161    if (!services.distanceMatrixService) {
     1162      services.distanceMatrixService = new google.maps.DistanceMatrixService();
     1163    }
     1164    return services.distanceMatrixService;
     1165  }
     1166 
    7521167  //-----------------------------------------------------------------------//
    7531168  // Unit tools
    7541169  //-----------------------------------------------------------------------//
    7551170 
     1171  function error(){
     1172    if (defaults.verbose){
     1173      var i, err = [];
     1174      if (window.console && (typeof console.error === "function") ){
     1175        for(i=0; i<arguments.length; i++){
     1176          err.push(arguments[i]);
     1177        }
     1178        console.error.apply(console, err);
     1179      } else {
     1180        err = "";
     1181        for(i=0; i<arguments.length; i++){
     1182          err += arguments[i].toString() + " " ;
     1183        }
     1184        alert(err);
     1185      }
     1186    }
     1187  }
     1188
    7561189  /**
    7571190   * return true if mixed is usable as number
    7581191   **/
    7591192  function numeric(mixed){
    760     return (typeof(mixed) === 'number' || typeof(mixed) === 'string') && mixed !== '' && !isNaN(mixed);
    761   }
    762  
    763     /**
     1193    return (typeof(mixed) === "number" || typeof(mixed) === "string") && mixed !== "" && !isNaN(mixed);
     1194  }
     1195 
     1196  /**
    7641197   * convert data to array
    7651198   **/
    7661199  function array(mixed){
    7671200    var k, a = [];
    768     if (mixed !== undefined){
    769       if (typeof(mixed) === 'object'){
    770         if (typeof(mixed.length) === 'number') {
     1201    if (mixed !== undef){
     1202      if (typeof(mixed) === "object"){
     1203        if (typeof(mixed.length) === "number") {
    7711204          a = mixed;
    7721205        } else {
     
    7871220  function toLatLng (mixed, emptyReturnMixed, noFlat){
    7881221    var empty = emptyReturnMixed ? mixed : null;
    789     if (!mixed || (typeof(mixed) === 'string')){
     1222    if (!mixed || (typeof mixed === "string")){
    7901223      return empty;
    7911224    }
     
    7951228    }
    7961229    // google.maps.LatLng object
    797     if (typeof(mixed.lat) === 'function') {
     1230    if (mixed instanceof google.maps.LatLng) {
    7981231      return mixed;
    7991232    }
     
    8031236    }
    8041237    // [X, Y] object
    805     else if ( !noFlat && mixed.length){ // and "no flat" object allowed
     1238    else if ( !noFlat && $.isArray(mixed)){
    8061239      if ( !numeric(mixed[0]) || !numeric(mixed[1]) ) {
    8071240        return empty;
     
    8151248   * convert mixed [ sw, ne ] object by google.maps.LatLngBounds
    8161249   **/
    817   function toLatLngBounds(mixed, flatAllowed, emptyReturnMixed){
    818     var ne, sw, empty;
    819     if (!mixed) {
    820       return null;
    821     }
    822     empty = emptyReturnMixed ? mixed : null;
    823     if (typeof(mixed.getCenter) === 'function') {
    824       return mixed;
    825     }
    826     if (mixed.length){
     1250  function toLatLngBounds(mixed){
     1251    var ne, sw;
     1252    if (!mixed || mixed instanceof google.maps.LatLngBounds) {
     1253      return mixed || null;
     1254    }
     1255    if ($.isArray(mixed)){
    8271256      if (mixed.length == 2){
    8281257        ne = toLatLng(mixed[0]);
     
    8331262      }
    8341263    } else {
    835       if ( ('ne' in mixed) && ('sw' in mixed) ){
     1264      if ( ("ne" in mixed) && ("sw" in mixed) ){
    8361265        ne = toLatLng(mixed.ne);
    8371266        sw = toLatLng(mixed.sw);
    838       } else if ( ('n' in mixed) && ('e' in mixed) && ('s' in mixed) && ('w' in mixed) ){
     1267      } else if ( ("n" in mixed) && ("e" in mixed) && ("s" in mixed) && ("w" in mixed) ){
    8391268        ne = toLatLng([mixed.n, mixed.e]);
    8401269        sw = toLatLng([mixed.s, mixed.w]);
     
    8441273      return new google.maps.LatLngBounds(sw, ne);
    8451274    }
    846     return empty;
     1275    return null;
     1276  }
     1277 
     1278  /**
     1279   * resolveLatLng     
     1280   **/
     1281  function resolveLatLng(ctx, method, runLatLng, args, attempt){
     1282    var latLng = runLatLng ? toLatLng(args.todo, false, true) : false,
     1283      conf = latLng ?  {latLng:latLng} : (args.todo.address ? (typeof(args.todo.address) === "string" ? {address:args.todo.address} : args.todo.address) : false),
     1284      cache = conf ? geocoderCache.get(conf) : false,
     1285      that = this;
     1286    if (conf){
     1287      attempt = attempt || 0; // convert undefined to int
     1288      if (cache){
     1289        args.latLng = cache.results[0].geometry.location;
     1290        args.results = cache.results;
     1291        args.status = cache.status;
     1292        method.apply(ctx, [args]);
     1293      } else {
     1294        if (conf.location){
     1295          conf.location = toLatLng(conf.location);
     1296        }
     1297        if (conf.bounds){
     1298          conf.bounds = toLatLngBounds(conf.bounds);
     1299        }
     1300        geocoder().geocode(
     1301          conf,
     1302          function(results, status) {
     1303            if (status === google.maps.GeocoderStatus.OK){
     1304              geocoderCache.store(conf, {results:results, status:status});
     1305              args.latLng = results[0].geometry.location;
     1306              args.results = results;
     1307              args.status = status;
     1308              method.apply(ctx, [args]);
     1309            } else if ( (status === google.maps.GeocoderStatus.OVER_QUERY_LIMIT) && (attempt < defaults.queryLimit.attempt) ){
     1310              setTimeout(
     1311                function(){
     1312                  resolveLatLng.apply(that, [ctx, method, runLatLng, args, attempt+1]);
     1313                },
     1314                defaults.queryLimit.delay + Math.floor(Math.random() * defaults.queryLimit.random)
     1315              );
     1316            } else {
     1317              error("geocode failed", status, conf);
     1318              args.latLng = args.results = false;
     1319              args.status = status;
     1320              method.apply(ctx, [args]);
     1321            }
     1322          }
     1323        );
     1324      }
     1325    } else {
     1326      args.latLng = toLatLng(args.todo, false, true);
     1327      method.apply(ctx, [args]);
     1328    }
     1329  }
     1330 
     1331  function resolveAllLatLng(list, ctx, method, args){
     1332    var that = this, i = -1;
     1333   
     1334    function resolve(){
     1335      // look for next address to resolve
     1336      do{
     1337        i++;
     1338      }while( (i < list.length) && !("address" in list[i]) );
     1339     
     1340      // no address found, so run method
     1341      if (i >= list.length){
     1342        method.apply(ctx, [args]);
     1343        return;
     1344      }
     1345     
     1346      resolveLatLng(
     1347        that,
     1348        function(args){
     1349          delete args.todo;
     1350          $.extend(list[i], args);
     1351          resolve.apply(that, []); // resolve next (using apply avoid too much recursion)
     1352        },
     1353        true,
     1354        {todo:list[i]}
     1355      );
     1356    }
     1357    resolve();
     1358  }
     1359   
     1360  /**
     1361   * geolocalise the user and return a LatLng
     1362   **/
     1363  function geoloc(ctx, method, args){
     1364    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
     1365    if (navigator && navigator.geolocation){
     1366       navigator.geolocation.getCurrentPosition(
     1367        function(pos) {
     1368          if (is_echo){
     1369            return;
     1370          }
     1371          is_echo = true;
     1372          args.latLng = new google.maps.LatLng(pos.coords.latitude,pos.coords.longitude);
     1373          method.apply(ctx, [args]);
     1374        },
     1375        function() {
     1376          if (is_echo){
     1377            return;
     1378          }
     1379          is_echo = true;
     1380          args.latLng = false;
     1381          method.apply(ctx, [args]);
     1382        },
     1383        args.opts.getCurrentPosition
     1384      );
     1385    } else {
     1386      args.latLng = false;
     1387      method.apply(ctx, [args]);
     1388    }
    8471389  }
    8481390
     
    8521394 
    8531395  function Gmap3($this){
    854  
    855     var stack = new Stack(),
    856         store = new Store(),
    857         map = null,
    858         styles = {},
    859         running = false;
     1396    var that = this,
     1397      stack = new Stack(),
     1398      store = new Store(),
     1399      map = null,
     1400      task;
    8601401   
    8611402    //-----------------------------------------------------------------------//
    8621403    // Stack tools
    8631404    //-----------------------------------------------------------------------//
    864        
     1405
    8651406    /**
    8661407     * store actions to execute in a stack manager
     
    8681409    this._plan = function(list){
    8691410      for(var k = 0; k < list.length; k++) {
    870         stack.add(list[k]);
    871       }
    872       this._run();
     1411        stack.add(new Task(that, end, list[k]));
     1412      }
     1413      run();
     1414    };
     1415   
     1416    /**
     1417     * if not running, start next action in stack
     1418     **/
     1419    function run(){
     1420      if (!task && (task = stack.get())){
     1421        task.run();
     1422      }
     1423    }
     1424   
     1425    /**
     1426     * called when action in finished, to acknoledge the current in stack and start next one
     1427     **/
     1428     function end(){
     1429      task = null;
     1430      stack.ack();
     1431      run.call(that); // restart to high level scope
     1432    }
     1433   
     1434    //-----------------------------------------------------------------------//
     1435    // Tools
     1436    //-----------------------------------------------------------------------//
     1437   
     1438    /**
     1439     * execute callback functions
     1440     **/
     1441    function callback(args){
     1442      var i, params = [];
     1443      for(i=1; i<arguments.length; i++){
     1444        params.push(arguments[i]);
     1445      }
     1446      if (typeof args.todo.callback === "function") {
     1447        args.todo.callback.apply($this, params);
     1448      } else if (typeof args.todo.callback === "object") {
     1449        $.each(args.todo.callback, function(i, cb){
     1450          if (typeof cb === "function") {
     1451            cb.apply($this, params);
     1452          }
     1453        });
     1454      }
     1455    }
     1456   
     1457    /**
     1458     * execute ending functions
     1459     **/
     1460    function manageEnd(args, obj, id){
     1461      if (id){
     1462        attachEvents($this, args, obj, id);
     1463      }
     1464      callback(args, obj);
     1465      task.ack(obj);
     1466    }
     1467   
     1468    /**
     1469     * initialize the map if not yet initialized
     1470     **/
     1471    function newMap(latLng, args){
     1472      args = args || {};
     1473      if (map) {
     1474        if (args.todo && args.todo.options){
     1475          map.setOptions(args.todo.options);
     1476        }
     1477      } else {
     1478        var opts = args.opts || $.extend(true, {}, defaults.map, args.todo && args.todo.options ? args.todo.options : {});
     1479        opts.center = latLng || toLatLng(opts.center);
     1480        map = new defaults.classes.Map($this.get(0), opts);
     1481      }
    8731482    }
    8741483     
    875     /**
    876      * store one action to execute in a stack manager after the current
    877      **/
    878     this._planNext = function(todo){
    879       stack.addNext(todo);
    880     }
    881    
    882     /**
    883      * execute action directly
    884      **/
    885     this._direct = function(todo){
    886       var action = ival(todo, 'action');
    887       return this[action]($.extend({}, action in _default ? _default[action] : {}, todo.args ? todo.args : todo));
    888     }
    889    
    890     /**
    891      * called when action in finished, to acknoledge the current in stack and start next one
    892      **/
    893     this._end = function(){
    894       running = false;
    895       stack.ack();
    896       this._run();
    897     },
    898     /**
    899      * if not running, start next action in stack
    900      **/
    901     this._run = function(){
    902       if (running) {
    903         return;
    904       }
    905       var todo = stack.get();
    906       if (!todo) {
    907         return;
    908       }
    909       running = true;
    910       this._proceed(todo);
    911     }
    912    
    913     //-----------------------------------------------------------------------//
    914     // Call tools
    915     //-----------------------------------------------------------------------//
    916    
    917     /**
    918      * run the appropriated function
    919      **/
    920     this._proceed = function(todo){
    921       todo = todo || {};
    922       var action = ival(todo, 'action') || 'init',
    923           iaction = action.toLowerCase(),
    924           ok = true,
    925           target = ival(todo, 'target'),
    926           args = ival(todo, 'args'),
    927           out;
    928       // check if init should be run automatically
    929       if ( !map && autoInit(iaction) ){
    930         this.init($.extend({}, _default.init, todo.args && todo.args.map ? todo.args.map : todo.map ? todo.map : {}), true);
    931       }
    932      
    933       // gmap3 function
    934       if (!target && !args && (iaction in this) && (typeof(this[iaction]) === 'function')){
    935         this[iaction]($.extend({}, iaction in _default ? _default[iaction] : {}, todo.args ? todo.args : todo)); // call fnc and extends defaults data
    936       } else {
    937         // "target" object function
    938         if (target && (typeof(target) === 'object')){
    939           if (ok = (typeof(target[action]) === 'function')){
    940             out = target[action].apply(target, todo.args ? todo.args : []);
    941           }
    942         // google.maps.Map direct function :  no result so not rewrited, directly wrapped using array "args" as parameters (ie. setOptions, addMapType, ...)
    943         } else if (map){
    944           if (ok = (typeof(map[action]) === 'function')){
    945             out = map[action].apply(map, todo.args ? todo.args : [] );
    946           }
    947         }
    948         if (!ok && _default.verbose) {
    949           alert("unknown action : " + action);
    950         }
    951         this._callback(out, todo);
    952         this._end();
    953       }
    954     }
    955    
    956     /**
    957      * returns the geographical coordinates from an address and call internal or given method
    958      **/
    959      this._resolveLatLng = function(todo, method, all, attempt){
    960       var address = ival(todo, 'address'),
    961           params,
    962           that = this,
    963           fnc = typeof(method) === 'function' ? method : that[method];
    964       if ( address ){
    965         if (!attempt){ // convert undefined to int
    966           attempt = 0;
    967         }
    968         if (typeof(address) === 'object'){
    969           params = address;
    970         } else {
    971           params = {'address': address};
    972         }
    973         getGeocoder().geocode(
    974           params,
    975           function(results, status) {
    976           if (status === google.maps.GeocoderStatus.OK){
    977             fnc.apply(that, [todo, all ? results : results[0].geometry.location]);
    978           } else if ( (status === google.maps.GeocoderStatus.OVER_QUERY_LIMIT) && (attempt < _default.queryLimit.attempt) ){
    979             setTimeout(function(){
    980                 that._resolveLatLng(todo, method, all, attempt+1);
    981               },
    982               _default.queryLimit.delay + Math.floor(Math.random() * _default.queryLimit.random)
    983             );
    984           } else {
    985             if (_default.verbose){
    986               alert('Geocode error : ' + status);
    987             }
    988             fnc.apply(that, [todo, false]);;
    989           }
    990         }
    991       );
    992       } else {
    993         fnc.apply(that, [todo, toLatLng(todo, false, true)]);
    994       }
    995     }
    996    
    997     /**
    998      * returns the geographical coordinates from an array of object using "address" and call internal method
    999      **/
    1000     this._resolveAllLatLng = function(todo, property, method){
    1001       var that = this,
    1002           i = -1,
    1003           solveNext = function(){
    1004             do{
    1005               i++;
    1006             }while( (i < todo[property].length) && !('address' in todo[property][i]) );
    1007             if (i < todo[property].length){
    1008               (function(todo){
    1009                 that._resolveLatLng(
    1010                   todo,
    1011                   function(todo, latLng){
    1012                     todo.latLng = latLng;
    1013                     solveNext.apply(that, []); // solve next or execute exit method
    1014                   }
    1015                 );
    1016               })(todo[property][i]);
    1017             } else {
    1018               that[method](todo);
    1019             }
    1020           };
    1021       solveNext();
    1022     }
    1023    
    1024     /**
    1025      * call a function of framework or google map object of the instance
    1026      **/
    1027     this._call = function(/* fncName [, ...] */){
    1028       var i, fname = arguments[0], args = [];
    1029       if ( !arguments.length || !map || (typeof(map[fname]) !== 'function') ){
    1030         return;
    1031       }
    1032       for(i=1; i<arguments.length; i++){
    1033         args.push(arguments[i]);
    1034       }
    1035       return map[fname].apply(map, args);
    1036     }
    1037    
    1038     /**
    1039      * init if not and manage map subcall (zoom, center)
    1040      **/
    1041     this._subcall = function(todo, latLng){
    1042       var opts = {};
    1043       if (!todo.map) return;
    1044       if (!latLng) {
    1045         latLng = ival(todo.map, 'latlng');
    1046       }
    1047       if (!map){
    1048         if (latLng) {
    1049           opts = {center:latLng};
    1050         }
    1051         this.init($.extend({}, todo.map, opts), true);
    1052       } else {
    1053         if (todo.map.center && latLng){
    1054           this._call("setCenter", latLng);
    1055         }
    1056         if (todo.map.zoom !== undefined){
    1057           this._call("setZoom", todo.map.zoom);
    1058         }
    1059         if (todo.map.mapTypeId !== undefined){
    1060           this._call("setMapTypeId", todo.map.mapTypeId);
    1061         }
    1062       }
    1063     }
    1064    
    1065     /**
    1066      * attach an event to a sender
    1067      **/
    1068     this._attachEvent = function(sender, name, fnc, data, once){
    1069       google.maps.event['addListener'+(once?'Once':'')](sender, name, function(event) {
    1070         fnc.apply($this, [sender, event, data]);
    1071       });
    1072     }
    1073    
    1074     /**
    1075      * attach events from a container to a sender
    1076      * todo[
    1077      *  events => { eventName => function, }
    1078      *  onces  => { eventName => function, } 
    1079      *  data   => mixed data         
    1080      * ]
    1081      **/
    1082     this._attachEvents = function(sender, todo){
    1083       var name;
    1084       if (!todo) {
    1085         return
    1086       }
    1087       if (todo.events){
    1088         for(name in todo.events){
    1089           if (typeof(todo.events[name]) === 'function'){
    1090             this._attachEvent(sender, name, todo.events[name], todo.data, false);
    1091           }
    1092         }
    1093       }
    1094       if (todo.onces){
    1095         for(name in todo.onces){
    1096           if (typeof(todo.onces[name]) === 'function'){
    1097             this._attachEvent(sender, name, todo.onces[name], todo.data, true);
    1098           }
    1099         }
    1100       }
    1101     }
    1102    
    1103     /**
    1104      * execute callback functions
    1105      **/
    1106     this._callback = function(result, todo){
    1107       if (typeof(todo.callback) === 'function') {
    1108         todo.callback.apply($this, [result]);
    1109       } else if (typeof(todo.callback) === 'object') {
    1110         for(var i=0; i<todo.callback.length; i++){
    1111           if (typeof(todo.callback[i]) === 'function') {
    1112             todo.callback[k].apply($this, [result]);
    1113           }
    1114         }
    1115       }
    1116     }
    1117    
    1118     /**
    1119      * execute ending functions
    1120      **/
    1121     this._manageEnd = function(result, todo, internal){
    1122       var i, apply;
    1123       if (result && (typeof(result) === 'object')){
    1124         // attach events
    1125         this._attachEvents(result, todo);
    1126         // execute "apply"
    1127         if (todo.apply && todo.apply.length){
    1128           for(i=0; i<todo.apply.length; i++){
    1129             apply = todo.apply[i];
    1130             // need an existing "action" function in the result object
    1131             if(!apply.action || (typeof(result[apply.action]) !== 'function') ) {
    1132               continue;
    1133             }
    1134             if (apply.args) {
    1135               result[apply.action].apply(result, apply.args);
    1136             } else {
    1137               result[apply.action]();
    1138             }
    1139           }
    1140         }
    1141       }
    1142       if (!internal) {
    1143         this._callback(result, todo);
    1144         this._end();
    1145       }
    1146     }
    1147    
    1148     //-----------------------------------------------------------------------//
    1149     // gmap3 functions
    1150     //-----------------------------------------------------------------------//
     1484    /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
     1485    => function with latLng resolution
     1486    = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
     1487   
     1488    /**
     1489     * Initialize google.maps.Map object
     1490     **/
     1491    this.map = function(args){
     1492      newMap(args.latLng, args);
     1493      attachEvents($this, args, map);
     1494      manageEnd(args, map);
     1495    };
    11511496   
    11521497    /**
    11531498     * destroy an existing instance
    11541499     **/
    1155     this.destroy = function(todo){
    1156       var k;
     1500    this.destroy = function(args){
    11571501      store.clear();
    11581502      $this.empty();
    1159       for(k in styles){
    1160         delete styles[ k ];
    1161       }
    1162       styles = {};
    11631503      if (map){
    1164         delete map;
    1165       }
    1166       this._callback(null, todo);
    1167       this._end();
    1168     }
    1169    
    1170     /**
    1171      * Initialize google.maps.Map object
    1172      **/
    1173     this.init = function(todo, internal){
    1174       var o, k, opts;
    1175       if (map) { // already initialized
    1176         return this._end();
    1177       }
    1178      
    1179       o = getObject('map', todo);
    1180       if ( (typeof(o.options.center) === 'boolean') && o.options.center) {
    1181         return false; // wait for an address resolution
    1182       }
    1183       opts = $.extend({}, _default.init, o.options);
    1184       if (!opts.center) {
    1185         opts.center = [_default.init.center.lat, _default.init.center.lng];
    1186       }
    1187       opts.center = toLatLng(opts.center);
    1188       map = new _default.classes.Map($this.get(0), opts);
    1189      
    1190       // add previous added styles
    1191       for(k in styles) {
    1192         map.mapTypes.set(k, styles[k]);
    1193       }
    1194      
    1195       this._manageEnd(map, o, internal);
    1196       return true;
    1197     }
    1198    
    1199     /**
    1200      * returns the geographical coordinates from an address
    1201      **/
    1202     this.getlatlng = function(todo){
    1203       this._resolveLatLng(todo, '_getLatLng', true);
    1204     },
    1205    
    1206     this._getLatLng = function(todo, results){
    1207       this._manageEnd(results, todo);
    1208     },
    1209    
    1210    
    1211     /**
    1212      * returns address from latlng       
    1213      **/
    1214     this.getaddress = function(todo, attempt){
    1215       var latLng = toLatLng(todo, false, true),
    1216           address = ival(todo, 'address'),
    1217           params = latLng ?  {latLng:latLng} : ( address ? (typeof(address) === 'string' ? {address:address} : address) : null),
    1218           callback = ival(todo, 'callback'),
    1219           that = this;
    1220       if (!attempt){ // convert undefined to int
    1221         attempt = 0;
    1222       }
    1223       if (params && typeof(callback) === 'function') {
    1224         getGeocoder().geocode(
    1225           params,
    1226           function(results, status) {
    1227             if ( (status === google.maps.GeocoderStatus.OVER_QUERY_LIMIT) && (attempt < _default.queryLimit.attempt) ){
    1228               setTimeout(function(){
    1229                   that.getaddress(todo, attempt+1);
    1230                 },
    1231                 _default.queryLimit.delay + Math.floor(Math.random() * _default.queryLimit.random)
    1232               );
     1504        map = null;
     1505      }
     1506      manageEnd(args, true);
     1507    };
     1508   
     1509    /**
     1510     * add an infowindow
     1511     **/
     1512    this.infowindow = function(args){
     1513      var objs = [], multiple = "values" in args.todo;
     1514      if (!multiple){
     1515        if (args.latLng) {
     1516          args.opts.position = args.latLng;
     1517        } else if (args.opts.position){
     1518          args.opts.position = toLatLng(args.opts.position);
     1519        }
     1520        args.todo.values = [{options:args.opts}];
     1521      }
     1522      $.each(args.todo.values, function(i, value){
     1523        var id, obj, todo = tuple(args, value);
     1524        if (!map){
     1525          newMap(todo.options.position);
     1526        }
     1527        obj = new defaults.classes.InfoWindow(todo.options);
     1528        if (obj && ((todo.open === undef) || todo.open)){
     1529          if (multiple){
     1530            obj.open(map, todo.anchor ? todo.anchor : undef);
     1531          } else {
     1532            obj.open(map, todo.anchor ? todo.anchor : (args.latLng ? undef : (args.session.marker ? args.session.marker : undef)));
     1533          }
     1534        }
     1535        objs.push(obj);
     1536        id = store.add({todo:todo}, "infowindow", obj);
     1537        attachEvents($this, {todo:todo}, obj, id);
     1538      });
     1539      manageEnd(args, multiple ? objs : objs[0]);
     1540    };
     1541   
     1542    /**
     1543     * add a circle
     1544     **/
     1545    this.circle = function(args){
     1546      var objs = [], multiple = "values" in args.todo;
     1547      if (!multiple){
     1548        args.opts.center = args.latLng || toLatLng(args.opts.center);
     1549        args.todo.values = [{options:args.opts}];
     1550      }
     1551      if (!args.todo.values.length){
     1552        manageEnd(args, false);
     1553        return;
     1554      }
     1555      $.each(args.todo.values, function(i, value){
     1556        var id, obj, todo = tuple(args, value);
     1557        todo.options.center = todo.options.center ? toLatLng(todo.options.center) : toLatLng(value);
     1558        if (!map){
     1559          newMap(todo.options.center);
     1560        }
     1561        todo.options.map = map;
     1562        obj = new defaults.classes.Circle(todo.options);
     1563        objs.push(obj);
     1564        id = store.add({todo:todo}, "circle", obj);
     1565        attachEvents($this, {todo:todo}, obj, id);
     1566      });
     1567      manageEnd(args, multiple ? objs : objs[0]);
     1568    };
     1569   
     1570    /**
     1571     * add an overlay
     1572     **/
     1573    this.overlay = function(args, internal){
     1574      var id, obj,
     1575        $div = $(document.createElement("div"))
     1576          .css("border", "none")
     1577          .css("borderWidth", "0px")
     1578          .css("position", "absolute");
     1579      $div.append(args.opts.content);
     1580      OverlayView.prototype = new defaults.classes.OverlayView();
     1581      obj = new OverlayView(map, args.opts, args.latLng, $div);
     1582      $div = null; // memory leak
     1583      if (internal){
     1584        return obj;
     1585      }
     1586      id = store.add(args, "overlay", obj);
     1587      manageEnd(args, obj, id);
     1588    };
     1589   
     1590    /**
     1591     * returns address structure from latlng       
     1592     **/
     1593    this.getaddress = function(args){
     1594      callback(args, args.results, args.status);
     1595      task.ack();
     1596    };
     1597   
     1598    /**
     1599     * returns latlng from an address
     1600     **/
     1601    this.getlatlng = function(args){
     1602      callback(args, args.results, args.status);
     1603      task.ack();
     1604    };
     1605   
     1606    /**
     1607     * return the max zoom of a location
     1608     **/
     1609    this.getmaxzoom = function(args){
     1610      maxZoomService().getMaxZoomAtLatLng(
     1611        args.latLng,
     1612        function(result) {
     1613          callback(args, result.status === google.maps.MaxZoomStatus.OK ? result.zoom : false, status);
     1614          task.ack();
     1615        }
     1616      );
     1617    };
     1618   
     1619    /**
     1620     * return the elevation of a location
     1621     **/
     1622    this.getelevation = function(args){
     1623      var i, locations = [],
     1624        f = function(results, status){
     1625          callback(args, status === google.maps.ElevationStatus.OK ? results : false, status);
     1626          task.ack();
     1627        };
     1628     
     1629      if (args.latLng){
     1630        locations.push(args.latLng);
     1631      } else {
     1632        locations = array(args.todo.locations || []);
     1633        for(i=0; i<locations.length; i++){
     1634          locations[i] = toLatLng(locations[i]);
     1635        }
     1636      }
     1637      if (locations.length){
     1638        elevationService().getElevationForLocations({locations:locations}, f);
     1639      } else {
     1640        if (args.todo.path && args.todo.path.length){
     1641          for(i=0; i<args.todo.path.length; i++){
     1642            locations.push(toLatLng(args.todo.path[i]));
     1643          }
     1644        }
     1645        if (locations.length){
     1646          elevationService().getElevationAlongPath({path:locations, samples:args.todo.samples}, f);
     1647        } else {
     1648          task.ack();
     1649        }
     1650      }
     1651    };
     1652   
     1653    /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
     1654    => function without latLng resolution
     1655    = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
     1656   
     1657    /**
     1658     * define defaults values
     1659     **/
     1660    this.defaults = function(args){
     1661      $.each(args.todo, function(name, value){
     1662        if (typeof defaults[name] === "object"){
     1663          defaults[name] = $.extend({}, defaults[name], value);
     1664        } else {
     1665          defaults[name] = value;
     1666        }
     1667      });
     1668      task.ack(true);
     1669    };
     1670   
     1671    /**
     1672     * add a rectangle
     1673     **/
     1674    this.rectangle = function(args){
     1675      var objs = [], multiple = "values" in args.todo;
     1676      if (!multiple){
     1677        args.todo.values = [{options:args.opts}];
     1678      }
     1679      if (!args.todo.values.length){
     1680        manageEnd(args, false);
     1681        return;
     1682      }
     1683      $.each(args.todo.values, function(i, value){
     1684        var id, obj, todo = tuple(args, value);
     1685        todo.options.bounds = todo.options.bounds ? toLatLngBounds(todo.options.bounds) : toLatLngBounds(value);
     1686        if (!map){
     1687          newMap(todo.options.bounds.getCenter());
     1688        }
     1689        todo.options.map = map;
     1690       
     1691        obj = new defaults.classes.Rectangle(todo.options);
     1692        objs.push(obj);
     1693        id = store.add({todo:todo}, "rectangle", obj);
     1694        attachEvents($this, {todo:todo}, obj, id);
     1695      });
     1696      manageEnd(args, multiple ? objs : objs[0]);
     1697    };
     1698   
     1699    /**
     1700     * add a polygone / polyline
     1701     **/
     1702    function poly(args, poly, path){
     1703      var objs = [], multiple = "values" in args.todo;
     1704      if (!multiple){
     1705        args.todo.values = [{options:args.opts}];
     1706      }
     1707      if (!args.todo.values.length){
     1708        manageEnd(args, false);
     1709        return;
     1710      }
     1711      newMap();
     1712      $.each(args.todo.values, function(_, value){
     1713        var id, i, j, obj, todo = tuple(args, value);
     1714        if (todo.options[path]){
     1715          if (todo.options[path][0][0] && $.isArray(todo.options[path][0][0])){
     1716            for(i=0; i<todo.options[path].length; i++){
     1717              for(j=0; j<todo.options[path][i].length; j++){
     1718                todo.options[path][i][j] = toLatLng(todo.options[path][i][j]);
     1719              }
     1720            }
     1721          } else {
     1722            for(i=0; i<todo.options[path].length; i++){
     1723              todo.options[path][i] = toLatLng(todo.options[path][i]);
     1724            }
     1725          }
     1726        }
     1727        todo.options.map = map;
     1728        obj = new google.maps[poly](todo.options);
     1729        objs.push(obj);
     1730        id = store.add({todo:todo}, poly.toLowerCase(), obj);
     1731        attachEvents($this, {todo:todo}, obj, id);
     1732      });
     1733      manageEnd(args, multiple ? objs : objs[0]);
     1734    }
     1735   
     1736    this.polyline = function(args){
     1737      poly(args, "Polyline", "path");
     1738    };
     1739   
     1740    this.polygon = function(args){
     1741      poly(args, "Polygon", "paths");
     1742    };
     1743   
     1744    /**
     1745     * add a traffic layer
     1746     **/
     1747    this.trafficlayer = function(args){
     1748      newMap();
     1749      var obj = store.get("trafficlayer");
     1750      if (!obj){
     1751        obj = new defaults.classes.TrafficLayer();
     1752        obj.setMap(map);
     1753        store.add(args, "trafficlayer", obj);
     1754      }
     1755      manageEnd(args, obj);
     1756    };
     1757   
     1758    /**
     1759     * add a bicycling layer
     1760     **/
     1761    this.bicyclinglayer = function(args){
     1762      newMap();
     1763      var obj = store.get("bicyclinglayer");
     1764      if (!obj){
     1765        obj = new defaults.classes.BicyclingLayer();
     1766        obj.setMap(map);
     1767        store.add(args, "bicyclinglayer", obj);
     1768      }
     1769      manageEnd(args, obj);
     1770    };
     1771   
     1772    /**
     1773     * add a ground overlay
     1774     **/
     1775    this.groundoverlay = function(args){
     1776      args.opts.bounds = toLatLngBounds(args.opts.bounds);
     1777      if (args.opts.bounds){
     1778        newMap(args.opts.bounds.getCenter());
     1779      }
     1780      var id, obj = new defaults.classes.GroundOverlay(args.opts.url, args.opts.bounds, args.opts.opts);
     1781      obj.setMap(map);
     1782      id = store.add(args, "groundoverlay", obj);
     1783      manageEnd(args, obj, id);
     1784    };
     1785   
     1786    /**
     1787     * set a streetview
     1788     **/
     1789    this.streetviewpanorama = function(args){
     1790      if (!args.opts.opts){
     1791        args.opts.opts = {};
     1792      }
     1793      if (args.latLng){
     1794        args.opts.opts.position = args.latLng;
     1795      } else if (args.opts.opts.position){
     1796        args.opts.opts.position = toLatLng(args.opts.opts.position);
     1797      }
     1798      if (args.todo.divId){
     1799        args.opts.container = document.getElementById(args.todo.divId)
     1800      } else if (args.opts.container){
     1801        args.opts.container = $(args.opts.container).get(0);
     1802      }
     1803      var id, obj = new defaults.classes.StreetViewPanorama(args.opts.container, args.opts.opts);
     1804      if (obj){
     1805        map.setStreetView(obj);
     1806      }
     1807      id = store.add(args, "streetviewpanorama", obj);
     1808      manageEnd(args, obj, id);
     1809    };
     1810   
     1811    this.kmllayer = function(args){
     1812      var objs = [], multiple = "values" in args.todo;
     1813      if (!multiple){
     1814        args.todo.values = [{options:args.opts}];
     1815      }
     1816      if (!args.todo.values.length){
     1817        manageEnd(args, false);
     1818        return;
     1819      }
     1820      $.each(args.todo.values, function(i, value){
     1821        var id, obj, todo = tuple(args, value);
     1822        if (!map){
     1823          newMap();
     1824        }
     1825        todo.options.opts.map = map;
     1826        obj = new defaults.classes.KmlLayer(todo.options.url, todo.options.opts);
     1827        objs.push(obj);
     1828        id = store.add({todo:todo}, "kmllayer", obj);
     1829        attachEvents($this, {todo:todo}, obj, id);
     1830      });
     1831      manageEnd(args, multiple ? objs : objs[0]);
     1832    };
     1833   
     1834    /**
     1835     * add a fix panel
     1836     **/
     1837     this.panel = function(args){
     1838      newMap();
     1839      var id, x= 0, y=0, $content,
     1840        $div = $(document.createElement("div"));
     1841     
     1842      $div
     1843        .css("position", "absolute")
     1844        .css("z-index", "1000");
     1845       
     1846      if (args.opts.content){
     1847        $content = $(args.opts.content);
     1848       
     1849        if (args.opts.left !== undef){
     1850          x = args.opts.left;
     1851        } else if (args.opts.right !== undef){
     1852          x = $this.width() - $content.width() - args.opts.right;
     1853        } else if (args.opts.center){
     1854          x = ($this.width() - $content.width()) / 2;
     1855        }
     1856       
     1857        if (args.opts.top !== undef){
     1858          y = args.opts.top;
     1859        } else if (args.opts.bottom !== undef){
     1860          y = $this.height() - $content.height() - args.opts.bottom;
     1861        } else if (args.opts.middle){
     1862          y = ($this.height() - $content.height()) / 2
     1863        }
     1864     
     1865        $div
     1866          .css("top", y+"px")
     1867          .css("left", x+"px")
     1868          .append($content);
     1869      }
     1870     
     1871      $this.first().prepend($div);
     1872      id = store.add(args, "panel", $div);
     1873      manageEnd(args, $div, id);
     1874      $div = null; // memory leak
     1875    };
     1876   
     1877    /**
     1878     * Create an InternalClusterer object
     1879     **/
     1880    function createClusterer(raw){
     1881      var internalClusterer = new InternalClusterer($this, map, raw.radius, raw.maxZoom),
     1882        todo = {},
     1883        styles = {},
     1884        isInt = /^[0-9]+$/,
     1885        calculator,
     1886        k;
     1887
     1888      for(k in raw){
     1889        if (isInt.test(k)){
     1890          styles[k] = raw[k];
     1891          styles[k].width = styles[k].width || 0;
     1892          styles[k].height = styles[k].height || 0;
     1893        } else {
     1894          todo[k] = raw[k];
     1895        }
     1896      }
     1897     
     1898      // external calculator
     1899      if (todo.calculator){
     1900        calculator = function(indexes){
     1901          var data = [];
     1902          $.each(indexes, function(i, index){
     1903            data.push(internalClusterer.value(index));
     1904          });
     1905          return todo.calculator.apply($this, [data]);
     1906        };
     1907      } else {
     1908        calculator = function(indexes){
     1909          return indexes.length;
     1910        };
     1911      }
     1912     
     1913      // set error function
     1914      internalClusterer.error(function(){
     1915        error.apply(that, arguments);
     1916      });
     1917     
     1918      // set display function
     1919      internalClusterer.display(function(cluster){
     1920        var k, style, n = 0, atodo, obj, offset,
     1921          cnt = calculator(cluster.indexes);
     1922       
     1923        // look for the style to use
     1924        if (cnt > 1){
     1925          for(k in styles){
     1926            k = 1 * k; // cast to int
     1927            if (k > n && k <= cnt){
     1928              n = k;
     1929            }
     1930          }
     1931          style = styles[n];
     1932        }
     1933       
     1934        if (style){
     1935          offset = style.offset || [-style.width/2, -style.height/2];
     1936          // create a custom overlay command
     1937          // nb: 2 extends are faster that a deeper extend
     1938          atodo = $.extend({}, todo);
     1939          atodo.options = $.extend({
     1940            pane: "overlayLayer",
     1941            content:style.content ? style.content.replace("CLUSTER_COUNT", cnt) : "",
     1942            offset:{
     1943              x: ("x" in offset ? offset.x : offset[0]) || 0,
     1944              y: ("y" in offset ? offset.y : offset[1]) || 0
     1945            }
     1946          },
     1947          todo.options || {});
     1948         
     1949          obj = that.overlay({todo:atodo, opts:atodo.options, latLng:toLatLng(cluster)}, true);
     1950         
     1951          atodo.options.pane = "floatShadow";
     1952          atodo.options.content = $(document.createElement("div")).width(style.width+"px").height(style.height+"px");
     1953          shadow = that.overlay({todo:atodo, opts:atodo.options, latLng:toLatLng(cluster)}, true);
     1954         
     1955          // store data to the clusterer
     1956          todo.data = {
     1957            latLng: toLatLng(cluster),
     1958            markers:[]
     1959          };
     1960          $.each(cluster.indexes, function(i, index){
     1961            todo.data.markers.push(internalClusterer.value(index));
     1962            if (internalClusterer.marker(index)){
     1963              internalClusterer.marker(index).setMap(null);
     1964            }
     1965          });
     1966          attachEvents($this, {todo:todo}, shadow, undef, {main:obj, shadow:shadow});
     1967          internalClusterer.store(cluster, obj, shadow);
     1968        } else {
     1969          $.each(cluster.indexes, function(i, index){
     1970            if (internalClusterer.marker(index)){
     1971              internalClusterer.marker(index).setMap(map);
    12331972            } else {
    1234               var out = status === google.maps.GeocoderStatus.OK ? results : false;
    1235               callback.apply($this, [out, status]);
    1236               if (!out && _default.verbose){
    1237                 alert('Geocode error : ' + status);
    1238               }
    1239               that._end();
     1973              var todo = internalClusterer.todo(index),
     1974                marker = new defaults.classes.Marker(todo.options);
     1975              internalClusterer.setMarker(index, marker);
     1976              attachEvents($this, {todo:todo}, marker, todo.id);
    12401977            }
    1241           }
    1242         );
     1978          });
     1979        }
     1980      });
     1981     
     1982      return internalClusterer;
     1983    }
     1984    /**
     1985     *  add a marker
     1986     **/
     1987    this.marker = function(args){
     1988      var multiple = "values" in args.todo,
     1989        init = !map;
     1990      if (!multiple){
     1991        args.opts.position = args.latLng || toLatLng(args.opts.position);
     1992        args.todo.values = [{options:args.opts}];
     1993      }
     1994      if (!args.todo.values.length){
     1995        manageEnd(args, false);
     1996        return;
     1997      }
     1998      if (init){
     1999        newMap();
     2000      }
     2001     
     2002      if (args.todo.cluster && !map.getBounds()){ // map not initialised => bounds not available : wait for map if clustering feature is required
     2003        google.maps.event.addListenerOnce(map, "bounds_changed", function() { that.marker.apply(that, [args]); });
     2004        return;
     2005      }
     2006      if (args.todo.cluster){
     2007        var clusterer, internalClusterer;
     2008        if (args.todo.cluster instanceof Clusterer){
     2009          clusterer = args.todo.cluster;
     2010          internalClusterer = store.getById(clusterer.id(), true);
     2011        } else {
     2012          internalClusterer = createClusterer(args.todo.cluster);
     2013          clusterer = new Clusterer(globalId(args.todo.id, true), internalClusterer);
     2014          store.add(args, "clusterer", clusterer, internalClusterer);
     2015        }
     2016        internalClusterer.beginUpdate();
     2017       
     2018        $.each(args.todo.values, function(i, value){
     2019          var todo = tuple(args, value);
     2020          todo.options.position = todo.options.position ? toLatLng(todo.options.position) : toLatLng(value);
     2021          todo.options.map = map;
     2022          if (init){
     2023            map.setCenter(todo.options.position);
     2024            init = false;
     2025          }
     2026          internalClusterer.add(todo, value);
     2027        });
     2028       
     2029        internalClusterer.endUpdate();
     2030        manageEnd(args, clusterer);
     2031       
    12432032      } else {
    1244         this._end();
    1245       }
    1246     }
     2033        var objs = [];
     2034        $.each(args.todo.values, function(i, value){
     2035          var id, obj, todo = tuple(args, value);
     2036          todo.options.position = todo.options.position ? toLatLng(todo.options.position) : toLatLng(value);
     2037          todo.options.map = map;
     2038          if (init){
     2039            map.setCenter(todo.options.position);
     2040            init = false;
     2041          }
     2042          obj = new defaults.classes.Marker(todo.options);
     2043          objs.push(obj);
     2044          id = store.add({todo:todo}, "marker", obj);
     2045          attachEvents($this, {todo:todo}, obj, id);
     2046        });
     2047        manageEnd(args, multiple ? objs : objs[0]);
     2048      }
     2049    };
    12472050   
    12482051    /**
    12492052     * return a route
    12502053     **/
    1251     this.getroute = function(todo){
    1252       var callback = ival(todo, 'callback'),
    1253           that = this;
    1254       if ( (typeof(callback) === 'function') && todo.options ) {
    1255         todo.options.origin = toLatLng(todo.options.origin, true);
    1256         todo.options.destination = toLatLng(todo.options.destination, true);
    1257         getDirectionsService().route(
    1258           todo.options,
    1259           function(results, status) {
    1260             var out = status == google.maps.DirectionsStatus.OK ? results : false;
    1261             callback.apply($this, [out, status]);
    1262             that._end();
    1263           }
    1264         );
    1265       } else {
    1266         this._end();
    1267       }
    1268     }
    1269    
    1270     /**
    1271      * return the elevation of a location
    1272      **/
    1273     this.getelevation = function(todo){
    1274       var fnc, path, samples, i,
    1275           locations = [],
    1276           callback = ival(todo, 'callback'),
    1277           latLng = ival(todo, 'latlng'),
    1278           that = this;
    1279          
    1280       if (typeof(callback) === 'function'){
    1281         fnc = function(results, status){
    1282           var out = status === google.maps.ElevationStatus.OK ? results : false;
    1283           callback.apply($this, [out, status]);
    1284           that._end();
    1285         };
    1286         if (latLng){
    1287           locations.push(toLatLng(latLng));
    1288         } else {
    1289           locations = ival(todo, 'locations') || [];
    1290           if (locations){
    1291             locations = array(locations);
    1292             for(i=0; i<locations.length; i++){
    1293               locations[i] = toLatLng(locations[i]);
    1294             }
    1295           }
    1296         }
    1297         if (locations.length){
    1298           getElevationService().getElevationForLocations({locations:locations}, fnc);
    1299         } else {
    1300           path = ival(todo, 'path');
    1301           samples = ival(todo, 'samples');
    1302           if (path && samples){
    1303             for(i=0; i<path.length; i++){
    1304               locations.push(toLatLng(path[i]));
    1305             }
    1306             if (locations.length){
    1307               getElevationService().getElevationAlongPath({path:locations, samples:samples}, fnc);
    1308             }
    1309           }
    1310         }
    1311       } else {
    1312         this._end();
    1313       }
    1314     }
    1315    
    1316     /**
    1317      * return the distance between an origin and a destination
    1318      *     
    1319      **/
    1320     this.getdistance = function(todo){
    1321       var i,
    1322           callback = ival(todo, 'callback'),
    1323           that = this;
    1324       if ( (typeof(callback) === 'function') && todo.options && todo.options.origins && todo.options.destinations ) {
    1325         // origins and destinations are array containing one or more address strings and/or google.maps.LatLng objects
    1326         todo.options.origins = array(todo.options.origins);
    1327         for(i=0; i<todo.options.origins.length; i++){
    1328           todo.options.origins[i] = toLatLng(todo.options.origins[i], true);
    1329         }
    1330         todo.options.destinations = array(todo.options.destinations);
    1331         for(i=0; i<todo.options.destinations.length; i++){
    1332           todo.options.destinations[i] = toLatLng(todo.options.destinations[i], true);
    1333         }
    1334         getDistanceMatrixService().getDistanceMatrix(
    1335           todo.options,
    1336           function(results, status) {
    1337             var out = status == google.maps.DistanceMatrixStatus.OK ? results : false;
    1338             callback.apply($this, [out, status]);
    1339             that._end();
    1340           }
    1341         );
    1342       } else {
    1343         this._end();
    1344       }
    1345     }
    1346    
    1347     /**
    1348      * Add a marker to a map after address resolution
    1349      * if [infowindow] add an infowindow attached to the marker   
    1350      **/
    1351     this.addmarker = function(todo){
    1352       this._resolveLatLng(todo, '_addMarker');
    1353     }
    1354    
    1355     this._addMarker = function(todo, latLng, internal){
    1356       var result, oi, to,
    1357           o = getObject('marker', todo, 'to');
    1358       if (!internal){
    1359         if (!latLng) {
    1360           this._manageEnd(false, o);
    1361           return;
    1362         }
    1363         this._subcall(todo, latLng);
    1364       } else if (!latLng){
    1365         return;
    1366       }
    1367       if (o.to){
    1368         to = store.refToObj(o.to);
    1369         result = to && (typeof(to.add) === 'function');
    1370         if (result){
    1371           to.add(latLng, todo);
    1372           if (typeof(to.redraw) === 'function'){
    1373             to.redraw();
    1374           }
    1375         }
    1376         if (!internal){
    1377           this._manageEnd(result, o);
    1378         }
    1379       } else {
    1380         o.options.position = latLng;
    1381         o.options.map = map;
    1382         result = new _default.classes.Marker(o.options);
    1383         if (hasKey(todo, 'infowindow')){
    1384           oi = getObject('infowindow', todo['infowindow'], 'open');
    1385           // if "open" is not defined, add it in first position
    1386           if ( (oi.open === undefined) || oi.open ){
    1387             oi.apply = array(oi.apply);
    1388             oi.apply.unshift({action:'open', args:[map, result]});
    1389           }
    1390           oi.action = 'addinfowindow';
    1391           this._planNext(oi);
    1392         }
    1393         if (!internal){
    1394           store.add('marker', result, o);
    1395           this._manageEnd(result, o);
    1396         }
    1397       }
    1398       return result;
    1399     }
    1400    
    1401     /**
    1402      * add markers (without address resolution)
    1403      **/
    1404     this.addmarkers = function(todo){
    1405       if (ival(todo, 'clusters')){
    1406         this._resolveAllLatLng(todo, 'markers', '_addclusteredmarkers');
    1407       } else {
    1408         this._resolveAllLatLng(todo, 'markers', '_addmarkers');
    1409       }
    1410     }
    1411    
    1412     this._addmarkers = function(todo){
    1413       var result, o, i, latLng, marker, options = {}, tmp, to,
    1414           markers = ival(todo, 'markers');
    1415       this._subcall(todo);
    1416       if (typeof(markers) !== 'object') {
    1417         return this._end();
    1418       }
    1419       o = getObject('marker', todo, ['to', 'markers']);
    1420      
    1421       if (o.to){
    1422         to = store.refToObj(o.to);
    1423         result = to && (typeof(to.add) === 'function');
    1424         if (result){
    1425           for(i=0; i<markers.length; i++){
    1426             if (latLng = toLatLng(markers[i])) {
    1427               to.add(latLng, markers[i]);
    1428             }
    1429           }
    1430           if (typeof(to.redraw) === 'function'){
    1431             to.redraw();
    1432           }
    1433         }
    1434         this._manageEnd(result, o);
    1435       } else {
    1436         $.extend(true, options, o.options);
    1437         options.map = map;
    1438         result = [];
    1439         for(i=0; i<markers.length; i++){
    1440           if (latLng = toLatLng(markers[i])){
    1441             if (markers[i].options){
    1442               tmp = {};
    1443               $.extend(true, tmp, options, markers[i].options);
    1444               o.options = tmp;
    1445             } else {
    1446               o.options = options;
    1447             }
    1448             o.options.position = latLng;
    1449             marker = new _default.classes.Marker(o.options);
    1450             result.push(marker);
    1451             o.data = markers[i].data;
    1452             o.tag = markers[i].tag;
    1453             store.add('marker', marker, o);
    1454             this._manageEnd(marker, o, true);
    1455           }
    1456         }
    1457         o.options = options; // restore previous for futur use
    1458         this._callback(result, todo);
    1459         this._end();
    1460       }
    1461     }
    1462    
    1463     this._addclusteredmarkers = function(todo){
    1464       var clusterer, i, latLng, storeId,
    1465           that = this,
    1466           radius = ival(todo, 'radius'),
    1467           maxZoom = ival(todo, 'maxZoom'),
    1468           markers = ival(todo, 'markers'),
    1469           styles = ival(todo, 'clusters');
    1470      
    1471       if (!map.getBounds()){ // map not initialised => bounds not available
    1472         // wait for map
    1473         google.maps.event.addListenerOnce(
    1474           map,
    1475           'bounds_changed',
    1476           function() {
    1477             that._addclusteredmarkers(todo);
    1478           }
    1479         );
    1480         return;
    1481       }
    1482      
    1483       if (typeof(radius) === 'number'){
    1484         clusterer = new Clusterer();
    1485         for(i=0 ; i<markers.length; i++){
    1486           latLng = toLatLng(markers[i]);
    1487           clusterer.add(latLng, markers[i]);
    1488         }
    1489         storeId = this._initClusters(todo, clusterer, radius, maxZoom, styles);
    1490       }
    1491      
    1492       this._callback(storeId, todo);
    1493       this._end();
    1494     }
    1495    
    1496    
    1497     this._initClusters = function(todo, clusterer, radius, maxZoom, styles){
    1498       var that = this;
    1499      
    1500       clusterer.setRedraw(function(force){
    1501         var same, clusters = clusterer.clusters(map, radius, maxZoom, force);
    1502         if (clusters){
    1503           same = clusterer.freeDiff(clusters);
    1504           that._displayClusters(todo, clusterer, clusters, same, styles);
     2054    this.getroute = function(args){
     2055      args.opts.origin = toLatLng(args.opts.origin, true);
     2056      args.opts.destination = toLatLng(args.opts.destination, true);
     2057      directionsService().route(
     2058        args.opts,
     2059        function(results, status) {
     2060          callback(args, status == google.maps.DirectionsStatus.OK ? results : false, status);
     2061          task.ack();
     2062        }
     2063      );
     2064    };
     2065   
     2066    /**
     2067     * add a direction renderer
     2068     **/
     2069    this.directionsrenderer = function(args){
     2070      args.opts.map = map;
     2071      var id, obj = new google.maps.DirectionsRenderer(args.opts);
     2072      if (args.todo.divId){
     2073        obj.setPanel(document.getElementById(args.todo.divId));
     2074      } else if (args.todo.container){
     2075        obj.setPanel($(args.todo.container).get(0));
     2076      }
     2077      id = store.add(args, "directionrenderer", obj);
     2078      manageEnd(args, obj, id);
     2079    };
     2080   
     2081    /**
     2082     * returns latLng of the user       
     2083     **/
     2084    this.getgeoloc = function(args){
     2085      manageEnd(args, args.latLng);
     2086    };
     2087   
     2088    /**
     2089     * add a style
     2090     **/
     2091    this.styledmaptype = function(args){
     2092      newMap();
     2093      var obj = new defaults.classes.StyledMapType(args.todo.styles, args.opts);
     2094      map.mapTypes.set(args.todo.id, obj);
     2095      manageEnd(args, obj);
     2096    };
     2097   
     2098    /**
     2099     * add an imageMapType
     2100     **/
     2101    this.imagemaptype = function(args){
     2102      newMap();
     2103      var obj = new defaults.classes.ImageMapType(args.opts);
     2104      map.mapTypes.set(args.todo.id, obj);
     2105      manageEnd(args, obj);
     2106    };
     2107   
     2108    /**
     2109     * autofit a map using its overlays (markers, rectangles ...)
     2110     **/
     2111    this.autofit = function(args){
     2112      var bounds = new google.maps.LatLngBounds();
     2113     
     2114      $.each(store.all(), function(i, obj){
     2115        if (obj.getPosition){
     2116          bounds.extend(obj.getPosition());
     2117        } else if (obj.getBounds){
     2118          bounds.extend(obj.getBounds().getNorthEast());
     2119          bounds.extend(obj.getBounds().getSouthWest());
     2120        } else if (obj.getPaths){
     2121          obj.getPaths().forEach(function(path){
     2122            path.forEach(function(latLng){
     2123              bounds.extend(latLng);
     2124            });
     2125          });
     2126        } else if (obj.getPath){
     2127          obj.getPath().forEach(function(latLng){
     2128            bounds.extend(latLng);""
     2129          });
     2130        } else if (obj.getCenter){
     2131          bounds.extend(obj.getCenter());
    15052132        }
    15062133      });
    1507      
    1508       clusterer.events(
    1509         google.maps.event.addListener(
    1510           map,
    1511           'zoom_changed',
    1512           function() {
    1513             clusterer.redraw(true);
    1514           }
    1515         ),
    1516         google.maps.event.addListener(
    1517           map,
    1518           'bounds_changed',
    1519           function() {
    1520             clusterer.redraw();
    1521           }
    1522         )
    1523       );
    1524      
    1525       clusterer.redraw();
    1526       return store.add('cluster', clusterer, todo);
    1527     }
    1528    
    1529     this._displayClusters = function(todo, clusterer, clusters, same, styles){
    1530       var k, i, ii, m, done, obj, shadow, cluster, options, tmp, w, h,
    1531           atodo,
    1532           ctodo = hasKey(todo, 'cluster') ? getObject('', ival(todo, 'cluster')) : {},
    1533           mtodo = hasKey(todo, 'marker') ? getObject('', ival(todo, 'marker')) : {};
    1534       for(i=0; i<clusters.length; i++){
    1535         if (i in same){
    1536           continue;
    1537         }
    1538         cluster = clusters[i];
    1539         done = false;
    1540         if (cluster.idx.length > 1){
    1541           // look for the cluster design to use
    1542           m = 0;
    1543           for(k in styles){
    1544             if ( (k > m) && (k <= cluster.idx.length) ){
    1545               m = k;
    1546             }
    1547           }
    1548           if (styles[m]){ // cluster defined for the current markers count
    1549             w = ival(styles[m], 'width');
    1550             h = ival(styles[m], 'height');
    1551            
    1552             // create a custom _addOverlay command
    1553             atodo = {};
    1554             $.extend(
    1555               true,
    1556               atodo,
    1557               ctodo,
    1558               { options:{
    1559                   pane: 'overlayLayer',
    1560                   content:styles[m].content.replace('CLUSTER_COUNT', cluster.idx.length),
    1561                   offset:{
    1562                     x: -w/2,
    1563                     y: -h/2
    1564                   }
    1565                 }
    1566               }
    1567             );
    1568             obj = this._addOverlay(atodo, toLatLng(cluster), true);
    1569             atodo.options.pane = 'floatShadow';
    1570             atodo.options.content = $('<div></div>');
    1571             atodo.options.content.width(w);
    1572             atodo.options.content.height(h);
    1573             shadow = this._addOverlay(atodo, toLatLng(cluster), true);
    1574            
    1575             // store data to the clusterer
    1576             ctodo.data = {
    1577               latLng: toLatLng(cluster),
    1578               markers:[]
    1579             };
    1580             for(ii=0; ii<cluster.idx.length; ii++){
    1581               ctodo.data.markers.push(
    1582                 clusterer.get(cluster.idx[ii]).marker
    1583               );
    1584             }
    1585             this._attachEvents(shadow, ctodo);
    1586             clusterer.store(cluster, obj, shadow);
    1587             done = true;
    1588           }
    1589         }
    1590         if (!done){ // cluster not defined (< min count) or = 1 so display all markers of the current cluster
    1591           // save the defaults options for the markers
    1592           options = {};
    1593           $.extend(true, options, mtodo.options);
    1594           for(ii = 0; ii <cluster.idx.length; ii++){
    1595             m = clusterer.get(cluster.idx[ii]);
    1596             mtodo.latLng = m.latLng;
    1597             mtodo.data = m.marker.data;
    1598             mtodo.tag = m.marker.tag;
    1599             if (m.marker.options){
    1600               tmp = {};
    1601               $.extend(true, tmp, options, m.marker.options);
    1602               mtodo.options = tmp;
    1603             } else {
    1604               mtodo.options = options;
    1605             }
    1606             obj = this._addMarker(mtodo, mtodo.latLng, true);
    1607             this._attachEvents(obj, mtodo);
    1608             clusterer.store(cluster, obj);
    1609           }
    1610           mtodo.options = options; // restore previous for futur use
    1611         }
    1612       }
    1613     }
    1614    
    1615     /**
    1616      * add an infowindow after address resolution
    1617      **/
    1618     this.addinfowindow = function(todo){
    1619       this._resolveLatLng(todo, '_addInfoWindow');
    1620     }
    1621    
    1622     this._addInfoWindow = function(todo, latLng){
    1623       var o, infowindow, args = [];
    1624       this._subcall(todo, latLng);
    1625       o = getObject('infowindow', todo, ['open', 'anchor']);
    1626       if (latLng) {
    1627         o.options.position = latLng;
    1628       }
    1629       infowindow = new _default.classes.InfoWindow(o.options);
    1630       if ( (o.open === undefined) || o.open ){
    1631         o.apply = array(o.apply);
    1632         args.push(map);
    1633         if (o.anchor){
    1634           args.push(o.anchor);
    1635         }
    1636         o.apply.unshift({action:'open', args:args});
    1637       }
    1638       store.add('infowindow', infowindow, o);
    1639       this._manageEnd(infowindow, o);
    1640     }
    1641    
    1642    
    1643     /**
    1644      * add a polygone / polylin on a map
    1645      **/
    1646     this.addpolyline = function(todo){
    1647       this._addPoly(todo, 'Polyline', 'path');
    1648     }
    1649    
    1650     this.addpolygon = function(todo){
    1651       this._addPoly(todo, 'Polygon', 'paths');
    1652     }
    1653    
    1654     this._addPoly = function(todo, poly, path){
    1655       var i,
    1656           obj, latLng,
    1657           o = getObject(poly.toLowerCase(), todo, path);
    1658       if (o[path]){
    1659         o.options[path] = [];
    1660         for(i=0; i<o[path].length; i++){
    1661           if (latLng = toLatLng(o[path][i])){
    1662             o.options[path].push(latLng);
    1663           }
    1664         }
    1665       }
    1666       obj = new google.maps[poly](o.options);
    1667       obj.setMap(map);
    1668       store.add(poly.toLowerCase(), obj, o);
    1669       this._manageEnd(obj, o);
    1670     }
    1671    
    1672     /**
    1673      * add a circle   
    1674      **/
    1675     this.addcircle = function(todo){
    1676       this._resolveLatLng(todo, '_addCircle');
    1677     }
    1678    
    1679     this._addCircle = function(todo, latLng){
    1680       var c, o = getObject('circle', todo);
    1681       if (!latLng) {
    1682         latLng = toLatLng(o.options.center);
    1683       }
    1684       if (!latLng) {
    1685         return this._manageEnd(false, o);
    1686       }
    1687       this._subcall(todo, latLng);
    1688       o.options.center = latLng;
    1689       o.options.map = map;
    1690       c = new _default.classes.Circle(o.options);
    1691       store.add('circle', c, o);
    1692       this._manageEnd(c, o);
    1693     }
    1694    
    1695     /**
    1696      * add a rectangle   
    1697      **/
    1698     this.addrectangle = function(todo){
    1699       this._resolveLatLng(todo, '_addRectangle');
    1700     }
    1701    
    1702     this._addRectangle = function(todo, latLng ){
    1703       var r, o = getObject('rectangle', todo);
    1704       o.options.bounds = toLatLngBounds(o.options.bounds, true);
    1705       if (!o.options.bounds) {
    1706         return this._manageEnd(false, o);
    1707       }
    1708       this._subcall(todo, o.options.bounds.getCenter());
    1709       o.options.map = map;
    1710       r = new _default.classes.Rectangle(o.options);
    1711       store.add('rectangle', r, o);
    1712       this._manageEnd(r, o);
    1713     }   
    1714    
    1715     /**
    1716      * add an overlay to a map after address resolution
    1717      **/
    1718     this.addoverlay = function(todo){
    1719       this._resolveLatLng(todo, '_addOverlay');
    1720     }
    1721    
    1722     this._addOverlay = function(todo, latLng, internal){
    1723       var ov, 
    1724           o = getObject('overlay', todo),
    1725           opts =  $.extend({
    1726                     pane: 'floatPane',
    1727                     content: '',
    1728                     offset:{
    1729                       x:0,y:0
    1730                     }
    1731                   },
    1732                   o.options),
    1733           $div = $('<div></div>'),
    1734           listeners = [];
    1735        
    1736        $div
    1737           .css('border', 'none')
    1738           .css('borderWidth', '0px')
    1739           .css('position', 'absolute');
    1740         $div.append(opts.content);
    1741      
    1742       function f() {
    1743        _default.classes.OverlayView.call(this);
    1744         this.setMap(map);
    1745       }           
    1746      
    1747       f.prototype = new _default.classes.OverlayView();
    1748      
    1749       f.prototype.onAdd = function() {
    1750         var panes = this.getPanes();
    1751         if (opts.pane in panes) {
    1752           $(panes[opts.pane]).append($div);
    1753         }
    1754       }
    1755       f.prototype.draw = function() {
    1756         var overlayProjection = this.getProjection(),
    1757             ps = overlayProjection.fromLatLngToDivPixel(latLng),
    1758             that = this;
    1759            
    1760         $div
    1761           .css('left', (ps.x+opts.offset.x) + 'px')
    1762           .css('top' , (ps.y+opts.offset.y) + 'px');
    1763        
    1764         $.each( ("dblclick click mouseover mousemove mouseout mouseup mousedown").split(" "), function( i, name ) {
    1765           listeners.push(
    1766             google.maps.event.addDomListener($div[0], name, function(e) {
    1767               google.maps.event.trigger(that, name);
    1768             })
    1769           );
    1770         });
    1771         listeners.push(
    1772           google.maps.event.addDomListener($div[0], "contextmenu", function(e) {
    1773             google.maps.event.trigger(that, "rightclick");
    1774           })
    1775         );
    1776       }
    1777       f.prototype.onRemove = function() {
    1778         for (var i = 0; i < listeners.length; i++) {
    1779           google.maps.event.removeListener(listeners[i]);
    1780         }
    1781         $div.remove();
    1782       }
    1783       f.prototype.hide = function() {
    1784         $div.hide();
    1785       }
    1786       f.prototype.show = function() {
    1787         $div.show();
    1788       }
    1789       f.prototype.toggle = function() {
    1790         if ($div) {
    1791           if ($div.is(':visible')){
    1792             this.show();
    1793           } else {
    1794             this.hide();
    1795           }
    1796         }
    1797       }
    1798       f.prototype.toggleDOM = function() {
    1799         if (this.getMap()) {
    1800           this.setMap(null);
    1801         } else {
    1802           this.setMap(map);
    1803         }
    1804       }
    1805       f.prototype.getDOMElement = function() {
    1806         return $div[0];
    1807       }
    1808       ov = new f();
    1809       if (!internal){
    1810         store.add('overlay', ov, o);
    1811         this._manageEnd(ov, o);
    1812       }
    1813       return ov;
    1814     }
    1815    
    1816     /**
    1817      * add a fix panel to a map
    1818      **/
    1819     this.addfixpanel = function(todo){
    1820       var o = getObject('fixpanel', todo),
    1821           x=y=0, $c, $div;
    1822       if (o.options.content){
    1823         $c = $(o.options.content);
    1824        
    1825         if (o.options.left !== undefined){
    1826           x = o.options.left;
    1827         } else if (o.options.right !== undefined){
    1828           x = $this.width() - $c.width() - o.options.right;
    1829         } else if (o.options.center){
    1830           x = ($this.width() - $c.width()) / 2;
    1831         }
    1832        
    1833         if (o.options.top !== undefined){
    1834           y = o.options.top;
    1835         } else if (o.options.bottom !== undefined){
    1836           y = $this.height() - $c.height() - o.options.bottom;
    1837         } else if (o.options.middle){
    1838           y = ($this.height() - $c.height()) / 2
    1839         }
    1840      
    1841         $div = $('<div></div>')
    1842                 .css('position', 'absolute')
    1843                 .css('top', y+'px')
    1844                 .css('left', x+'px')
    1845                 .css('z-index', '1000')
    1846                 .append($c);
    1847        
    1848         $this.first().prepend($div);
    1849         this._attachEvents(map, o);
    1850         store.add('fixpanel', $div, o);
    1851         this._callback($div, o);
    1852       }
    1853       this._end();
    1854     }
    1855    
    1856     /**
    1857      * add a direction renderer to a map
    1858      **/
    1859     this.adddirectionsrenderer = function(todo, internal){
    1860       var dr, o = getObject('directionrenderer', todo, 'panelId');
    1861       o.options.map = map;
    1862       dr = new google.maps.DirectionsRenderer(o.options);
    1863       if (o.panelId) {
    1864         dr.setPanel(document.getElementById(o.panelId));
    1865       }
    1866       store.add('directionrenderer', dr, o);
    1867       this._manageEnd(dr, o, internal);
    1868       return dr;
    1869     }
    1870    
    1871     /**
    1872      * set a direction panel to a dom element from its ID
    1873      **/
    1874     this.setdirectionspanel = function(todo){
    1875       var dr = store.get('directionrenderer'),
    1876           o = getObject('directionpanel', todo, 'id');
    1877       if (dr && o.id) {
    1878         dr.setPanel(document.getElementById(o.id));
    1879       }
    1880       this._manageEnd(dr, o);
    1881     }
    1882    
    1883     /**
    1884      * set directions on a map (create Direction Renderer if needed)
    1885      **/
    1886     this.setdirections = function(todo){
    1887       var dr = store.get('directionrenderer'),
    1888           o = getObject('directions', todo);
    1889       if (todo) {
    1890         o.options.directions = todo.directions ? todo.directions : (todo.options && todo.options.directions ? todo.options.directions : null);
    1891       }
    1892       if (o.options.directions) {
    1893         if (!dr) {
    1894           dr = this.adddirectionsrenderer(o, true);
    1895         } else {
    1896           dr.setDirections(o.options.directions);
    1897         }
    1898       }
    1899       this._manageEnd(dr, o);
    1900     }
    1901    
    1902     /**
    1903      * set a streetview to a map
    1904      **/
    1905     this.setstreetview = function(todo){
    1906       var panorama,
    1907           o = getObject('streetview', todo, 'id');
    1908       if (o.options.position){
    1909         o.options.position = toLatLng(o.options.position);
    1910       }
    1911       panorama = new _default.classes.StreetViewPanorama(document.getElementById(o.id),o.options);
    1912       if (panorama){
    1913         map.setStreetView(panorama);
    1914       }
    1915       this._manageEnd(panorama, o);
    1916     }
    1917    
    1918     /**
    1919      * add a kml layer to a map
    1920      **/
    1921     this.addkmllayer = function(todo){
    1922       var kml,
    1923           o = getObject('kmllayer', todo, 'url');
    1924       o.options.map = map;
    1925       if (typeof(o.url) === 'string'){
    1926         kml = new _default.classes.KmlLayer(o.url, o.options);
    1927       }
    1928       store.add('kmllayer', kml, o);
    1929       this._manageEnd(kml, o);
    1930     }
    1931    
    1932     /**
    1933      * add a traffic layer to a map
    1934      **/
    1935     this.addtrafficlayer = function(todo){
    1936       var o = getObject('trafficlayer', todo),
    1937           tl = store.get('trafficlayer');
    1938       if (!tl){
    1939         tl = new _default.classes.TrafficLayer();
    1940         tl.setMap(map);
    1941         store.add('trafficlayer', tl, o);
    1942       }
    1943       this._manageEnd(tl, o);
    1944     }
    1945    
    1946     /**
    1947      * add a bicycling layer to a map
    1948      **/
    1949     this.addbicyclinglayer = function(todo){
    1950       var o = getObject('bicyclinglayer', todo),
    1951           bl = store.get('bicyclinglayer');
    1952       if (!bl){
    1953         bl = new _default.classes.BicyclingLayer();
    1954         bl.setMap(map);
    1955         store.add('bicyclinglayer', bl, o);
    1956       }
    1957       this._manageEnd(bl, o);
    1958     }
    1959    
    1960     /**
    1961      * add a ground overlay to a map
    1962      **/
    1963     this.addgroundoverlay = function(todo){
    1964       var ov,
    1965           o = getObject('groundoverlay', todo, ['bounds', 'url']);
    1966       o.bounds = toLatLngBounds(o.bounds);
    1967       if (o.bounds && (typeof(o.url) === 'string')){
    1968         ov = new _default.classes.GroundOverlay(o.url, o.bounds);
    1969         ov.setMap(map);
    1970         store.add('groundoverlay', ov, o);
    1971       }
    1972       this._manageEnd(ov, o);
    1973     }
    1974    
    1975     /**
    1976      * geolocalise the user and return a LatLng
    1977      **/
    1978     this.geolatlng = function(todo){
    1979       var callback = ival(todo, 'callback');
    1980       if (typeof(callback) === 'function') {
    1981         if(navigator.geolocation) {
    1982           navigator.geolocation.getCurrentPosition(
    1983             function(position) {
    1984               var out = new google.maps.LatLng(position.coords.latitude,position.coords.longitude);
    1985               callback.apply($this, [out]);
    1986             },
    1987             function() {
    1988               var out = false;
    1989               callback.apply($this, [out]);
    1990             }
    1991           );
    1992         } else if (google.gears) {
    1993           google.gears.factory.create('beta.geolocation').getCurrentPosition(
    1994             function(position) {
    1995               var out = new google.maps.LatLng(position.latitude,position.longitude);
    1996               callback.apply($this, [out]);
    1997             },
    1998             function() {
    1999               out = false;
    2000               callback.apply($this, [out]);
    2001             }
    2002           );
    2003         } else {
    2004           callback.apply($this, [false]);
    2005         }
    2006       }
    2007       this._end();
    2008     }
    2009    
    2010     /**
    2011      * add a style to a map
    2012      **/
    2013     this.addstyledmap = function(todo, internal){
    2014       var o = getObject('styledmap', todo, ['id', 'style']);
    2015       if  (o.style && o.id && !styles[o.id]) {
    2016         styles[o.id] = new _default.classes.StyledMapType(o.style, o.options);
    2017         if (map) {
    2018           map.mapTypes.set(o.id, styles[o.id]);
    2019         }
    2020       }
    2021       this._manageEnd(styles[o.id], o, internal);
    2022     }
    2023    
    2024     /**
    2025      * set a style to a map (add it if needed)
    2026      **/
    2027     this.setstyledmap = function(todo){
    2028       var o = getObject('styledmap', todo, ['id', 'style']);
    2029       if (o.id) {
    2030         this.addstyledmap(o, true);
    2031         if (styles[o.id]) {
    2032           map.setMapTypeId(o.id);
    2033           this._callback(styles[o.id], todo);
    2034         }
    2035       }
    2036       this._manageEnd(styles[o.id], o);
    2037     }
    2038    
    2039     /**
    2040      * remove objects from a map
    2041      **/
    2042     this.clear = function(todo){
    2043       var list = array(ival(todo, 'list') || ival(todo, 'name')),
    2044           last = ival(todo, 'last', false),
    2045           first = ival(todo, 'first', false),
    2046           tag = ival(todo, 'tag');
    2047       if (tag !== undefined){
    2048         tag = array(tag);
    2049       }
    2050       store.clear(list, last, first, tag);
    2051       this._end();
    2052     }
    2053    
    2054     /**
    2055      * return objects previously created
    2056      **/
    2057     this.get = function(todo){
    2058       var name = ival(todo, 'name') || 'map',
    2059           first= ival(todo, 'first'),
    2060           all  = ival(todo, 'all'),
    2061           tag = ival(todo, 'tag');
    2062       name = name.toLowerCase();
    2063       if (name === 'map'){
    2064         return map;
    2065       }
    2066       if (tag !== undefined){
    2067         tag = array(tag);
    2068       }
    2069       if (first){
    2070         return store.get(name, false, tag);
    2071       } else if (all){
    2072         return store.all(name, tag);
    2073       } else {
    2074         return store.get(name, true, tag);
    2075       }
    2076     }
    2077    
    2078     /**
    2079      * return the max zoom of a location
    2080      **/
    2081     this.getmaxzoom = function(todo){
    2082       this._resolveLatLng(todo, '_getMaxZoom');
    2083     }
    2084    
    2085     this._getMaxZoom = function(todo, latLng){
    2086       var callback = ival(todo, 'callback'),
    2087           that = this;
    2088       if (callback && typeof(callback) === 'function') {
    2089         getMaxZoomService().getMaxZoomAtLatLng(
    2090           latLng,
    2091           function(result) {
    2092             var zoom = result.status === google.maps.MaxZoomStatus.OK ? result.zoom : false;
    2093             callback.apply($this, [zoom, result.status]);
    2094             that._end();
    2095           }
    2096         );
    2097       } else {
    2098         this._end();
    2099       }
    2100     }
    2101    
    2102     /**
    2103      * modify default values
    2104      **/
    2105     this.setdefault = function(todo){
    2106       setDefault(todo);
    2107       this._end();
    2108     }
    2109    
    2110     /**
    2111      * autofit a map using its overlays (markers, rectangles ...)
    2112      **/
    2113     this.autofit = function(todo, internal){
    2114       var names, list, obj, i, j,
    2115           empty = true,
    2116           bounds = new google.maps.LatLngBounds(),
    2117           maxZoom = ival(todo, 'maxZoom', null);
    21182134
    2119       names = store.names();
    2120       for(i=0; i<names.length; i++){
    2121         list = store.all(names[i]);
    2122         for(j=0; j<list.length; j++){
    2123           obj = list[j];
    2124           if (obj.getPosition){
    2125             bounds.extend(obj.getPosition());
    2126             empty = false;
    2127           } else if (obj.getBounds){
    2128             bounds.extend(obj.getBounds().getNorthEast());
    2129             bounds.extend(obj.getBounds().getSouthWest());
    2130             empty = false;
    2131           } else if (obj.getPaths){
    2132             obj.getPaths().forEach(function(path){
    2133               path.forEach(function(latLng){
    2134                 bounds.extend(latLng);
    2135                 empty = false;
    2136               });
    2137             });
    2138           } else if (obj.getPath){
    2139             obj.getPath().forEach(function(latLng){
    2140               bounds.extend(latLng);
    2141               empty = false;
    2142             });
    2143           } else if (obj.getCenter){
    2144             bounds.extend(obj.getCenter());
    2145             empty = false;
    2146           }
    2147         }
    2148       }
    2149 
    2150       if (!empty && (!map.getBounds() || !map.getBounds().equals(bounds))){
    2151         if (maxZoom !== null){ 
     2135      if (!bounds.isEmpty() && (!map.getBounds() || !map.getBounds().equals(bounds))){
     2136        if ("maxZoom" in args.todo){
    21522137          // fitBouds Callback event => detect zoom level and check maxZoom
    21532138          google.maps.event.addListenerOnce(
    21542139            map,
    2155             'bounds_changed',
     2140            "bounds_changed",
    21562141            function() {
    2157               if (this.getZoom() > maxZoom){
    2158                 this.setZoom(maxZoom);
     2142              if (this.getZoom() > args.todo.maxZoom){
     2143                this.setZoom(args.todo.maxZoom);
    21592144              }
    21602145            }
     
    21632148        map.fitBounds(bounds);
    21642149      }
    2165       if (!internal){
    2166         this._manageEnd(empty ? false : bounds, todo, internal);
    2167       }
    2168     }
    2169    
    2170   };
     2150      manageEnd(args, true);
     2151    };
     2152   
     2153    /**
     2154     * remove objects from a map
     2155     **/
     2156    this.clear = function(args){
     2157      if (typeof args.todo === "string"){
     2158        if (store.clearById(args.todo) || store.objClearById(args.todo)){
     2159          manageEnd(args, true);
     2160          return;
     2161        }
     2162        args.todo = {name:args.todo};
     2163      }
     2164      if (args.todo.id){
     2165        $.each(array(args.todo.id), function(i, id){
     2166          store.clearById(id);
     2167        });
     2168      } else {
     2169        store.clear(array(args.todo.name), args.todo.last, args.todo.first, args.todo.tag);
     2170      }
     2171      manageEnd(args, true);
     2172    };
     2173   
     2174    /**
     2175     * run a function on each items selected
     2176     **/
     2177    this.exec = function(args){
     2178      var that = this;
     2179      $.each(array(args.todo.func), function(i, func){
     2180        $.each(that.get(args.todo, true, args.todo.hasOwnProperty("full") ? args.todo.full : true), function(j, res){
     2181          func.call($this, res);
     2182        });
     2183      });
     2184      manageEnd(args, true);
     2185    };
     2186   
     2187    /**
     2188     * return objects previously created
     2189     **/
     2190    this.get = function(args, direct, full){
     2191      var name, res,
     2192          todo = direct ? args : args.todo;
     2193      if (!direct) {
     2194        full = todo.full;
     2195      }
     2196      if (typeof todo === "string"){
     2197        res = store.getById(todo, false, full) || store.objGetById(todo);
     2198        if (res === false){
     2199          name = todo;
     2200          todo = {};
     2201        }
     2202      } else {
     2203        name = todo.name;
     2204      }
     2205      if (name === "map"){
     2206        res = map;
     2207      }
     2208      if (!res){
     2209        res = [];
     2210        if (todo.id){
     2211            $.each(array(todo.id), function(i, id) {
     2212                res.push(store.getById(id, false, full) || store.objGetById(id));
     2213            });
     2214            if (!$.isArray(todo.id)) {
     2215              res = res[0];
     2216            }
     2217        } else {
     2218          $.each(name ? array(name) : [undef], function(i, aName) {
     2219            var result;
     2220            if (todo.first){
     2221                result = store.get(aName, false, todo.tag, full);
     2222                if (result) res.push(result);
     2223            } else if (todo.all){
     2224                $.each(store.all(aName, todo.tag, full), function(i, result){
     2225                  res.push(result);
     2226                });
     2227            } else {
     2228                result = store.get(aName, true, todo.tag, full);
     2229                if (result) res.push(result);
     2230            }
     2231          });
     2232          if (!todo.all && !$.isArray(name)) {
     2233            res = res[0];
     2234          }
     2235        }
     2236      }
     2237      res = $.isArray(res) || !todo.all ? res : [res];
     2238      if (direct){
     2239        return res;
     2240      } else {
     2241        manageEnd(args, res);
     2242      }
     2243    };
     2244
     2245    /**
     2246     * return the distance between an origin and a destination
     2247     *     
     2248     **/
     2249    this.getdistance = function(args){
     2250      var i;
     2251      args.opts.origins = array(args.opts.origins);
     2252      for(i=0; i<args.opts.origins.length; i++){
     2253        args.opts.origins[i] = toLatLng(args.opts.origins[i], true);
     2254      }
     2255      args.opts.destinations = array(args.opts.destinations);
     2256      for(i=0; i<args.opts.destinations.length; i++){
     2257        args.opts.destinations[i] = toLatLng(args.opts.destinations[i], true);
     2258      }
     2259      distanceMatrixService().getDistanceMatrix(
     2260        args.opts,
     2261        function(results, status) {
     2262          callback(args, status === google.maps.DistanceMatrixStatus.OK ? results : false, status);
     2263          task.ack();
     2264        }
     2265      );
     2266    };
     2267   
     2268    /**
     2269     * trigger events on the map
     2270     **/
     2271    this.trigger = function(args){
     2272      if (typeof args.todo === "string"){
     2273        google.maps.event.trigger(map, args.todo);
     2274      } else {
     2275        var options = [map, args.todo.eventName];
     2276        if (args.todo.var_args) {
     2277            $.each(args.todo.var_args, function(i, v){
     2278              options.push(v);
     2279            });
     2280        }
     2281        google.maps.event.trigger.apply(google.maps.event, options);
     2282      }
     2283      callback(args);
     2284      task.ack();
     2285    };
     2286  }
     2287
     2288  /**
     2289   * Return true if get is a direct call
     2290   * it means :
     2291   *   - get is the only key
     2292   *   - get has no callback
     2293   * @param obj {Object} The request to check
     2294   * @return {Boolean}
     2295   */
     2296  function isDirectGet(obj) {
     2297    var k;
     2298    if (!typeof obj === "object" || !obj.hasOwnProperty("get")){
     2299      return false;
     2300    }
     2301    for(k in obj) {
     2302      if (k !== "get") {
     2303        return false;
     2304      }
     2305    }
     2306    return !obj.get.hasOwnProperty("callback");
     2307  }
    21712308 
    21722309  //-----------------------------------------------------------------------//
     
    21752312   
    21762313  $.fn.gmap3 = function(){
    2177     var i, args, list = [], empty = true, results = [];
     2314    var i, list = [], empty = true, results = [];
     2315   
     2316    // init library
     2317    initDefaults();
     2318   
    21782319    // store all arguments in a todo list
    21792320    for(i=0; i<arguments.length; i++){
    2180       args = arguments[i] || {};
    2181       // resolve string todo - action without parameters can be simplified as string
    2182       if (typeof(args) === 'string'){
    2183         args = {action:args};
    2184       }
    2185       list.push(args);
    2186     }
     2321      if (arguments[i]){
     2322        list.push(arguments[i]);
     2323      }
     2324    }
     2325
    21872326    // resolve empty call - run init
    21882327    if (!list.length) {
    2189       list.push({});
    2190     }
     2328      list.push("map");
     2329    }
     2330
    21912331    // loop on each jQuery object
    21922332    $.each(this, function() {
    2193       var $this = $(this),
    2194           gmap3 = $this.data('gmap3');
     2333      var $this = $(this), gmap3 = $this.data("gmap3");
    21952334      empty = false;
    21962335      if (!gmap3){
    21972336        gmap3 = new Gmap3($this);
    2198         $this.data('gmap3', gmap3);
    2199       }
    2200       // direct call : bypass jQuery method (not stackable, return mixed)
    2201       if ( (list.length == 1) && (isDirect(list[0])) ){
    2202         results.push(gmap3._direct(list[0]));
     2337        $this.data("gmap3", gmap3);
     2338      }
     2339      if (list.length === 1 && (list[0] === "get" || isDirectGet(list[0]))){
     2340        results.push(gmap3.get(list[0] === "get" ? "map" : list[0].get, true));
    22032341      } else {
    22042342        gmap3._plan(list);
    22052343      }
    22062344    });
    2207     // return for direct call (only)
     2345   
     2346    // return for direct call only
    22082347    if (results.length){
    22092348      if (results.length === 1){ // 1 css selector
     
    22132352      }
    22142353    }
    2215     // manage setDefault call
    2216     if (empty && (arguments.length == 2) && (typeof(arguments[0]) === 'string') && (arguments[0].toLowerCase() === 'setdefault')){
    2217       setDefault(arguments[1]);
    2218     }
     2354   
    22192355    return this;
    22202356  }
    22212357
    2222 }(jQuery));
     2358})(jQuery);
  • lazyest-maps/trunk/js/gmap3.min.js

    r564181 r695801  
    1 /*
     1/*!
    22 *  GMAP3 Plugin for JQuery
    3  *  Version   : 4.1
    4  *  Date      : 2011-11-18
     3 *  Version   : 5.0b
     4 *  Date      : 2012-11-17
    55 *  Licence   : GPL v3 : http://www.gnu.org/licenses/gpl.html 
    66 *  Author    : DEMONTE Jean-Baptiste
    77 *  Contact   : jbdemonte@gmail.com
    88 *  Web site  : http://gmap3.net
    9  *   
    10  *  Copyright (c) 2010-2011 Jean-Baptiste DEMONTE
    11  *  All rights reserved.
    12  *   
    13  * Redistribution and use in source and binary forms, with or without
    14  * modification, are permitted provided that the following conditions are met:
    15  *
    16  *   - Redistributions of source code must retain the above copyright
    17  *     notice, this list of conditions and the following disclaimer.
    18  *   - Redistributions in binary form must reproduce the above
    19  *     copyright notice, this list of conditions and the following
    20  *     disclaimer in the documentation and/or other materials provided
    21  *     with the distribution.
    22  *   - Neither the name of the author nor the names of its contributors
    23  *     may be used to endorse or promote products derived from this
    24  *     software without specific prior written permission.
    25  *
    26  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    27  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    28  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    29  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    30  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    36  * POSSIBILITY OF SUCH DAMAGE.
    379 */
    38  
    39 (function(c){function q(){var D=[];this.empty=function(){for(var E=0;E<D.length;E++){if(D[E]){return false}}return true};this.add=function(E){D.push(E)};this.addNext=function(F){var H=[],G,E=0;for(G=0;G<D.length;G++){if(!D[G]){continue}if(E==1){H.push(F)}H.push(D[G]);E++}if(E<2){H.push(F)}D=H};this.get=function(){for(var E=0;E<D.length;E++){if(D[E]){return D[E]}}return false};this.ack=function(){for(var E=0;E<D.length;E++){if(D[E]){delete D[E];break}}if(this.empty()){D=[]}}}function o(){var D={};this.add=function(F,G,E){F=F.toLowerCase();if(!D[F]){D[F]=[]}D[F].push({obj:G,tag:s(E,"tag")});return F+"-"+(D[F].length-1)};this.get=function(G,I,F){var H,E,J;G=G.toLowerCase();if(!D[G]||!D[G].length){return null}E=I?D[G].length:-1;J=I?-1:1;for(H=0;H<D[G].length;H++){E+=J;if(D[G][E]){if(F!==undefined){if((D[G][E].tag===undefined)||(c.inArray(D[G][E].tag,F)<0)){continue}}return D[G][E].obj}}return null};this.all=function(G,F){var H,E=[];G=G.toLowerCase();if(!D[G]||!D[G].length){return E}for(H=0;H<D[G].length;H++){if(!D[G][H]){continue}if((F!==undefined)&&((D[G][H].tag===undefined)||(c.inArray(D[G][H].tag,F)<0))){continue}E.push(D[G][H].obj)}return E};this.names=function(){var F,E=[];for(F in D){E.push(F)}return E};this.refToObj=function(E){E=E.split("-");if((E.length==2)&&D[E[0]]&&D[E[0]][E[1]]){return D[E[0]][E[1]].obj}return null};this.rm=function(H,F,G){var E,J,I;H=H.toLowerCase();if(!D[H]){return false}if(F!==undefined){if(G){for(E=D[H].length-1;E>=0;E--){if((D[H][E]!==undefined)&&(D[H][E].tag!==undefined)&&(c.inArray(D[H][E].tag,F)>=0)){break}}}else{for(E=0;E<D[H].length;E++){if((D[H][E]!==undefined)&&(D[H][E].tag!==undefined)&&(c.inArray(D[H][E].tag,F)>=0)){break}}}}else{E=G?D[H].length-1:0}if(!(E in D[H])){return false}if(typeof(D[H][E].obj.setMap)==="function"){D[H][E].obj.setMap(null)}if(typeof(D[H][E].obj.remove)==="function"){D[H][E].obj.remove()}if(typeof(D[H][E].obj.free)==="function"){D[H][E].obj.free()}delete D[H][E].obj;if(F!==undefined){I=[];for(J=0;J<D[H].length;J++){if(J!==E){I.push(D[H][J])}}D[H]=I}else{if(G){D[H].pop()}else{D[H].shift()}}return true};this.clear=function(J,I,K,E){var F,H,G;if(!J||!J.length){J=[];for(F in D){J.push(F)}}else{J=g(J)}for(H=0;H<J.length;H++){if(J[H]){G=J[H].toLowerCase();if(!D[G]){continue}if(I){this.rm(G,E,true)}else{if(K){this.rm(G,E,false)}else{while(this.rm(G,E,false)){}}}}}}}function z(){var H=[],E=[],F=[],D=[],G=false,I;this.events=function(){for(var J=0;J<arguments.length;J++){E.push(arguments[J])}};this.startRedraw=function(){if(!G){G=true;return true}return false};this.endRedraw=function(){G=false};this.redraw=function(){var K,J=[],L=this;for(K=0;K<arguments.length;K++){J.push(arguments[K])}if(this.startRedraw){I.apply(L,J);this.endRedraw()}else{setTimeout(function(){L.redraw.apply(L,J)},50)}};this.setRedraw=function(J){I=J};this.store=function(J,K,L){F.push({data:J,obj:K,shadow:L})};this.free=function(){for(var J=0;J<E.length;J++){google.maps.event.removeListener(E[J])}E=[];this.freeAll()};this.freeIndex=function(J){if(typeof(F[J].obj.setMap)==="function"){F[J].obj.setMap(null)}if(typeof(F[J].obj.remove)==="function"){F[J].obj.remove()}if(F[J].shadow){if(typeof(F[J].shadow.remove)==="function"){F[J].obj.remove()}if(typeof(F[J].shadow.setMap)==="function"){F[J].shadow.setMap(null)}delete F[J].shadow}delete F[J].obj;delete F[J].data;delete F[J]};this.freeAll=function(){var J;for(J=0;J<F.length;J++){if(F[J]){this.freeIndex(J)}}F=[]};this.freeDiff=function(M){var L,K,N={},J=[];for(L=0;L<M.length;L++){J.push(M[L].idx.join("-"))}for(L=0;L<F.length;L++){if(!F[L]){continue}K=c.inArray(F[L].data.idx.join("-"),J);if(K>=0){N[K]=true}else{this.freeIndex(L)}}return N};this.add=function(K,J){H.push({latLng:K,marker:J})};this.get=function(J){return H[J]};this.clusters=function(ai,L,W,K){var M=ai.getProjection(),Y=M.fromLatLngToPoint(new google.maps.LatLng(ai.getBounds().getNorthEast().lat(),ai.getBounds().getSouthWest().lng())),ae,ad,J,X,U,T,aa,R,S=ai.getZoom(),O={},ah={},ac={},Q=[],af,ag,N,ak,V,ab,P=ai.getBounds(),Z=W&&(W<=ai.getZoom()),aj=ai.getZoom()>2;ab=0;V={};for(ae=0;ae<H.length;ae++){if(aj&&!P.contains(H[ae].latLng)){continue}X=M.fromLatLngToPoint(H[ae].latLng);O[ae]=[Math.floor((X.x-Y.x)*Math.pow(2,S)),Math.floor((X.y-Y.y)*Math.pow(2,S))];V[ae]=true;ab++}if(!K&&!Z){for(aa=0;aa<D.length;aa++){if(aa in V){ab--}else{break}}if(!ab){return false}}D=V;V=[];for(ae in O){U=O[ae][0];T=O[ae][1];if(!(U in ah)){ah[U]={}}if(!(T in ah[U])){ah[U][T]=ae;ac[ae]={};V.push(ae)}ac[ah[U][T]][ae]=true}L=Math.pow(L,2);delete (ah);aa=0;while(1){while((aa<V.length)&&!(V[aa] in ac)){aa++}if(aa==V.length){break}ae=V[aa];N=O[ae][0];ak=O[ae][1];ah=null;if(Z){ah={lat:N,lng:ak,idx:[ae]}}else{do{af={lat:0,lng:0,idx:[]};for(R=aa;R<V.length;R++){if(!(V[R] in ac)){continue}ad=V[R];if(Math.pow(N-O[ad][0],2)+Math.pow(ak-O[ad][1],2)<=L){for(J in ac[ad]){af.lat+=H[J].latLng.lat();af.lng+=H[J].latLng.lng();af.idx.push(J)}}}af.lat/=af.idx.length;af.lng/=af.idx.length;if(!ah){ag=af.idx.length>1;ah=af}else{ag=af.idx.length>ah.idx.length;if(ag){ah=af}}if(ag){X=M.fromLatLngToPoint(new google.maps.LatLng(ah.lat,ah.lng));N=Math.floor((X.x-Y.x)*Math.pow(2,S));ak=Math.floor((X.y-Y.y)*Math.pow(2,S))}}while(ag)}for(R=0;R<ah.idx.length;R++){if(ah.idx[R] in ac){delete (ac[ah.idx[R]])}}Q.push(ah)}return Q};this.getBounds=function(){var J,K=new google.maps.LatLngBounds();for(J=0;J<H.length;J++){K.extend(H[J].latLng)}return K}}var e={verbose:false,queryLimit:{attempt:5,delay:250,random:250},init:{mapTypeId:google.maps.MapTypeId.ROADMAP,center:[46.578498,2.457275],zoom:2},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,StreetViewPanorama:google.maps.StreetViewPanorama,KmlLayer:google.maps.KmlLayer,TrafficLayer:google.maps.TrafficLayer,BicyclingLayer:google.maps.BicyclingLayer,GroundOverlay:google.maps.GroundOverlay,StyledMapType:google.maps.StyledMapType}},v=["events","onces","options","apply","callback","data","tag"],i=["init","geolatlng","getlatlng","getroute","getelevation","getdistance","addstyledmap","setdefault","destroy"],p=["get"],m=directionsService=elevationService=maxZoomService=distanceMatrixService=null;function B(E){for(var D in E){if(typeof(e[D])==="object"){e[D]=c.extend({},e[D],E[D])}else{e[D]=E[D]}}}function u(E){if(!E){return true}for(var D=0;D<i.length;D++){if(i[D]===E){return false}}return true}function n(D){var F=s(D,"action");for(var E=0;E<p.length;E++){if(p[E]===F){return true}}return false}function t(E,F){if(F.toLowerCase){F=F.toLowerCase();for(var D in E){if(D.toLowerCase&&(D.toLowerCase()==F)){return D}}}return false}function s(E,F,G){var D=t(E,F);return D?E[D]:G}function C(E,F){var G,D;if(!E||!F){return false}F=g(F);for(G in E){if(G.toLowerCase){G=G.toLowerCase();for(D in F){if(G==F[D]){return true}}}}return false}function h(F,E,D){if(C(F,v)||C(F,E)){var H,G;for(H=0;H<v.length;H++){G=t(F,v[H]);D[v[H]]=G?F[G]:{}}if(E&&E.length){for(H=0;H<E.length;H++){if(G=t(F,E[H])){D[E[H]]=F[G]}}}return D}else{D.options={};for(G in F){if(G!=="action"){D.options[G]=F[G]}}return D}}function A(H,F,E,G){var K=t(F,H),I,D={},J=["map"];D.callback=s(F,"callback");E=g(E);G=g(G);if(K){return h(F[K],E,D)}if(G&&G.length){for(I=0;I<G.length;I++){J.push(G[I])}}if(!C(F,J)){D=h(F,E,D)}for(I=0;I<v.length;I++){if(v[I] in D){continue}D[v[I]]={}}return D}function l(){if(!m){m=new google.maps.Geocoder()}return m}function a(){if(!directionsService){directionsService=new google.maps.DirectionsService()}return directionsService}function r(){if(!elevationService){elevationService=new google.maps.ElevationService()}return elevationService}function x(){if(!maxZoomService){maxZoomService=new google.maps.MaxZoomService()}return maxZoomService}function b(){if(!distanceMatrixService){distanceMatrixService=new google.maps.DistanceMatrixService()}return distanceMatrixService}function d(D){return(typeof(D)==="number"||typeof(D)==="string")&&D!==""&&!isNaN(D)}function g(F){var E,D=[];if(F!==undefined){if(typeof(F)==="object"){if(typeof(F.length)==="number"){D=F}else{for(E in F){D.push(F[E])}}}else{D.push(F)}}return D}function f(E,G,D){var F=G?E:null;if(!E||(typeof(E)==="string")){return F}if(E.latLng){return f(E.latLng)}if(typeof(E.lat)==="function"){return E}else{if(d(E.lat)){return new google.maps.LatLng(E.lat,E.lng)}else{if(!D&&E.length){if(!d(E[0])||!d(E[1])){return F}return new google.maps.LatLng(E[0],E[1])}}}return F}function j(E,F,I){var H,D,G;if(!E){return null}G=I?E:null;if(typeof(E.getCenter)==="function"){return E}if(E.length){if(E.length==2){H=f(E[0]);D=f(E[1])}else{if(E.length==4){H=f([E[0],E[1]]);D=f([E[2],E[3]])}}}else{if(("ne" in E)&&("sw" in E)){H=f(E.ne);D=f(E.sw)}else{if(("n" in E)&&("e" in E)&&("s" in E)&&("w" in E)){H=f([E.n,E.e]);D=f([E.s,E.w])}}}if(H&&D){return new google.maps.LatLngBounds(D,H)}return G}function w(I){var D=new q(),F=new o(),H=null,G={},E=false;this._plan=function(K){for(var J=0;J<K.length;J++){D.add(K[J])}this._run()};this._planNext=function(J){D.addNext(J)};this._direct=function(J){var K=s(J,"action");return this[K](c.extend({},K in e?e[K]:{},J.args?J.args:J))};this._end=function(){E=false;D.ack();this._run()},this._run=function(){if(E){return}var J=D.get();if(!J){return}E=true;this._proceed(J)};this._proceed=function(J){J=J||{};var O=s(J,"action")||"init",N=O.toLowerCase(),M=true,P=s(J,"target"),L=s(J,"args"),K;if(!H&&u(N)){this.init(c.extend({},e.init,J.args&&J.args.map?J.args.map:J.map?J.map:{}),true)}if(!P&&!L&&(N in this)&&(typeof(this[N])==="function")){this[N](c.extend({},N in e?e[N]:{},J.args?J.args:J))}else{if(P&&(typeof(P)==="object")){if(M=(typeof(P[O])==="function")){K=P[O].apply(P,J.args?J.args:[])}}else{if(H){if(M=(typeof(H[O])==="function")){K=H[O].apply(H,J.args?J.args:[])}}}if(!M&&e.verbose){alert("unknown action : "+O)}this._callback(K,J);this._end()}};this._resolveLatLng=function(J,Q,M,L){var K=s(J,"address"),P,N=this,O=typeof(Q)==="function"?Q:N[Q];if(K){if(!L){L=0}if(typeof(K)==="object"){P=K}else{P={address:K}}l().geocode(P,function(S,R){if(R===google.maps.GeocoderStatus.OK){O.apply(N,[J,M?S:S[0].geometry.location])}else{if((R===google.maps.GeocoderStatus.OVER_QUERY_LIMIT)&&(L<e.queryLimit.attempt)){setTimeout(function(){N._resolveLatLng(J,Q,M,L+1)},e.queryLimit.delay+Math.floor(Math.random()*e.queryLimit.random))}else{if(e.verbose){alert("Geocode error : "+R)}O.apply(N,[J,false])}}})}else{O.apply(N,[J,f(J,false,true)])}};this._resolveAllLatLng=function(J,N,O){var M=this,K=-1,L=function(){do{K++}while((K<J[N].length)&&!("address" in J[N][K]));if(K<J[N].length){(function(P){M._resolveLatLng(P,function(Q,R){Q.latLng=R;L.apply(M,[])})})(J[N][K])}else{M[O](J)}};L()};this._call=function(){var K,L=arguments[0],J=[];if(!arguments.length||!H||(typeof(H[L])!=="function")){return}for(K=1;K<arguments.length;K++){J.push(arguments[K])}return H[L].apply(H,J)};this._subcall=function(J,L){var K={};if(!J.map){return}if(!L){L=s(J.map,"latlng")}if(!H){if(L){K={center:L}}this.init(c.extend({},J.map,K),true)}else{if(J.map.center&&L){this._call("setCenter",L)}if(J.map.zoom!==undefined){this._call("setZoom",J.map.zoom)}if(J.map.mapTypeId!==undefined){this._call("setMapTypeId",J.map.mapTypeId)}}};this._attachEvent=function(K,J,N,M,L){google.maps.event["addListener"+(L?"Once":"")](K,J,function(O){N.apply(I,[K,O,M])})};this._attachEvents=function(L,J){var K;if(!J){return}if(J.events){for(K in J.events){if(typeof(J.events[K])==="function"){this._attachEvent(L,K,J.events[K],J.data,false)}}}if(J.onces){for(K in J.onces){if(typeof(J.onces[K])==="function"){this._attachEvent(L,K,J.onces[K],J.data,true)}}}};this._callback=function(K,J){if(typeof(J.callback)==="function"){J.callback.apply(I,[K])}else{if(typeof(J.callback)==="object"){for(var L=0;L<J.callback.length;L++){if(typeof(J.callback[L])==="function"){J.callback[k].apply(I,[K])}}}}};this._manageEnd=function(K,J,L){var N,M;if(K&&(typeof(K)==="object")){this._attachEvents(K,J);if(J.apply&&J.apply.length){for(N=0;N<J.apply.length;N++){M=J.apply[N];if(!M.action||(typeof(K[M.action])!=="function")){continue}if(M.args){K[M.action].apply(K,M.args)}else{K[M.action]()}}}}if(!L){this._callback(K,J);this._end()}};this.destroy=function(J){var K;F.clear();I.empty();for(K in G){delete G[K]}G={};if(H){delete H}this._callback(null,J);this._end()};this.init=function(J,K){var N,L,M;if(H){return this._end()}N=A("map",J);if((typeof(N.options.center)==="boolean")&&N.options.center){return false}M=c.extend({},e.init,N.options);if(!M.center){M.center=[e.init.center.lat,e.init.center.lng]}M.center=f(M.center);H=new e.classes.Map(I.get(0),M);for(L in G){H.mapTypes.set(L,G[L])}this._manageEnd(H,N,K);return true};this.getlatlng=function(J){this._resolveLatLng(J,"_getLatLng",true)},this._getLatLng=function(J,K){this._manageEnd(K,J)},this.getaddress=function(J,L){var N=f(J,false,true),K=s(J,"address"),O=N?{latLng:N}:(K?(typeof(K)==="string"?{address:K}:K):null),P=s(J,"callback"),M=this;if(!L){L=0}if(O&&typeof(P)==="function"){l().geocode(O,function(S,Q){if((Q===google.maps.GeocoderStatus.OVER_QUERY_LIMIT)&&(L<e.queryLimit.attempt)){setTimeout(function(){M.getaddress(J,L+1)},e.queryLimit.delay+Math.floor(Math.random()*e.queryLimit.random))}else{var R=Q===google.maps.GeocoderStatus.OK?S:false;P.apply(I,[R,Q]);if(!R&&e.verbose){alert("Geocode error : "+Q)}M._end()}})}else{this._end()}};this.getroute=function(J){var L=s(J,"callback"),K=this;if((typeof(L)==="function")&&J.options){J.options.origin=f(J.options.origin,true);J.options.destination=f(J.options.destination,true);a().route(J.options,function(O,M){var N=M==google.maps.DirectionsStatus.OK?O:false;L.apply(I,[N,M]);K._end()})}else{this._end()}};this.getelevation=function(K){var N,R,P,L,O=[],Q=s(K,"callback"),J=s(K,"latlng"),M=this;if(typeof(Q)==="function"){N=function(U,S){var T=S===google.maps.ElevationStatus.OK?U:false;Q.apply(I,[T,S]);M._end()};if(J){O.push(f(J))}else{O=s(K,"locations")||[];if(O){O=g(O);for(L=0;L<O.length;L++){O[L]=f(O[L])}}}if(O.length){r().getElevationForLocations({locations:O},N)}else{R=s(K,"path");P=s(K,"samples");if(R&&P){for(L=0;L<R.length;L++){O.push(f(R[L]))}if(O.length){r().getElevationAlongPath({path:O,samples:P},N)}}}}else{this._end()}};this.getdistance=function(J){var K,M=s(J,"callback"),L=this;if((typeof(M)==="function")&&J.options&&J.options.origins&&J.options.destinations){J.options.origins=g(J.options.origins);for(K=0;K<J.options.origins.length;K++){J.options.origins[K]=f(J.options.origins[K],true)}J.options.destinations=g(J.options.destinations);for(K=0;K<J.options.destinations.length;K++){J.options.destinations[K]=f(J.options.destinations[K],true)}b().getDistanceMatrix(J.options,function(P,N){var O=N==google.maps.DistanceMatrixStatus.OK?P:false;M.apply(I,[O,N]);L._end()})}else{this._end()}};this.addmarker=function(J){this._resolveLatLng(J,"_addMarker")};this._addMarker=function(K,N,L){var J,M,P,O=A("marker",K,"to");if(!L){if(!N){this._manageEnd(false,O);return}this._subcall(K,N)}else{if(!N){return}}if(O.to){P=F.refToObj(O.to);J=P&&(typeof(P.add)==="function");if(J){P.add(N,K);if(typeof(P.redraw)==="function"){P.redraw()}}if(!L){this._manageEnd(J,O)}}else{O.options.position=N;O.options.map=H;J=new e.classes.Marker(O.options);if(C(K,"infowindow")){M=A("infowindow",K.infowindow,"open");if((M.open===undefined)||M.open){M.apply=g(M.apply);M.apply.unshift({action:"open",args:[H,J]})}M.action="addinfowindow";this._planNext(M)}if(!L){F.add("marker",J,O);this._manageEnd(J,O)}}return J};this.addmarkers=function(J){if(s(J,"clusters")){this._resolveAllLatLng(J,"markers","_addclusteredmarkers")}else{this._resolveAllLatLng(J,"markers","_addmarkers")}};this._addmarkers=function(L){var S,J,N,K,P,R={},O,Q,M=s(L,"markers");this._subcall(L);if(typeof(M)!=="object"){return this._end()}J=A("marker",L,["to","markers"]);if(J.to){Q=F.refToObj(J.to);S=Q&&(typeof(Q.add)==="function");if(S){for(N=0;N<M.length;N++){if(K=f(M[N])){Q.add(K,M[N])}}if(typeof(Q.redraw)==="function"){Q.redraw()}}this._manageEnd(S,J)}else{c.extend(true,R,J.options);R.map=H;S=[];for(N=0;N<M.length;N++){if(K=f(M[N])){if(M[N].options){O={};c.extend(true,O,R,M[N].options);J.options=O}else{J.options=R}J.options.position=K;P=new e.classes.Marker(J.options);S.push(P);J.data=M[N].data;J.tag=M[N].tag;F.add("marker",P,J);this._manageEnd(P,J,true)}}J.options=R;this._callback(S,L);this._end()}};this._addclusteredmarkers=function(K){var N,M,J,R,O=this,P=s(K,"radius"),Q=s(K,"maxZoom"),L=s(K,"markers"),S=s(K,"clusters");if(!H.getBounds()){google.maps.event.addListenerOnce(H,"bounds_changed",function(){O._addclusteredmarkers(K)});return}if(typeof(P)==="number"){N=new z();for(M=0;M<L.length;M++){J=f(L[M]);N.add(J,L[M])}R=this._initClusters(K,N,P,Q,S)}this._callback(R,K);this._end()};this._initClusters=function(K,M,J,L,O){var N=this;M.setRedraw(function(Q){var R,P=M.clusters(H,J,L,Q);if(P){R=M.freeDiff(P);N._displayClusters(K,M,P,R,O)}});M.events(google.maps.event.addListener(H,"zoom_changed",function(){M.redraw(true)}),google.maps.event.addListener(H,"bounds_changed",function(){M.redraw()}));M.redraw();return F.add("cluster",M,K)};this._displayClusters=function(N,Y,M,X,O){var W,Z,R,V,T,S,L,aa,J,ac,Q,ab,K,P=C(N,"cluster")?A("",s(N,"cluster")):{},U=C(N,"marker")?A("",s(N,"marker")):{};for(Z=0;Z<M.length;Z++){if(Z in X){continue}aa=M[Z];T=false;if(aa.idx.length>1){V=0;for(W in O){if((W>V)&&(W<=aa.idx.length)){V=W}}if(O[V]){Q=s(O[V],"width");ab=s(O[V],"height");K={};c.extend(true,K,P,{options:{pane:"overlayLayer",content:O[V].content.replace("CLUSTER_COUNT",aa.idx.length),offset:{x:-Q/2,y:-ab/2}}});S=this._addOverlay(K,f(aa),true);K.options.pane="floatShadow";K.options.content=c("<div></div>");K.options.content.width(Q);K.options.content.height(ab);L=this._addOverlay(K,f(aa),true);P.data={latLng:f(aa),markers:[]};for(R=0;R<aa.idx.length;R++){P.data.markers.push(Y.get(aa.idx[R]).marker)}this._attachEvents(L,P);Y.store(aa,S,L);T=true}}if(!T){J={};c.extend(true,J,U.options);for(R=0;R<aa.idx.length;R++){V=Y.get(aa.idx[R]);U.latLng=V.latLng;U.data=V.marker.data;U.tag=V.marker.tag;if(V.marker.options){ac={};c.extend(true,ac,J,V.marker.options);U.options=ac}else{U.options=J}S=this._addMarker(U,U.latLng,true);this._attachEvents(S,U);Y.store(aa,S)}U.options=J}}};this.addinfowindow=function(J){this._resolveLatLng(J,"_addInfoWindow")};this._addInfoWindow=function(J,L){var N,M,K=[];this._subcall(J,L);N=A("infowindow",J,["open","anchor"]);if(L){N.options.position=L}M=new e.classes.InfoWindow(N.options);if((N.open===undefined)||N.open){N.apply=g(N.apply);K.push(H);if(N.anchor){K.push(N.anchor)}N.apply.unshift({action:"open",args:K})}F.add("infowindow",M,N);this._manageEnd(M,N)};this.addpolyline=function(J){this._addPoly(J,"Polyline","path")};this.addpolygon=function(J){this._addPoly(J,"Polygon","paths")};this._addPoly=function(J,M,O){var K,N,L,P=A(M.toLowerCase(),J,O);if(P[O]){P.options[O]=[];for(K=0;K<P[O].length;K++){if(L=f(P[O][K])){P.options[O].push(L)}}}N=new google.maps[M](P.options);N.setMap(H);F.add(M.toLowerCase(),N,P);this._manageEnd(N,P)};this.addcircle=function(J){this._resolveLatLng(J,"_addCircle")};this._addCircle=function(J,K){var M,L=A("circle",J);if(!K){K=f(L.options.center)}if(!K){return this._manageEnd(false,L)}this._subcall(J,K);L.options.center=K;L.options.map=H;M=new e.classes.Circle(L.options);F.add("circle",M,L);this._manageEnd(M,L)};this.addrectangle=function(J){this._resolveLatLng(J,"_addRectangle")};this._addRectangle=function(J,L){var K,M=A("rectangle",J);M.options.bounds=j(M.options.bounds,true);if(!M.options.bounds){return this._manageEnd(false,M)}this._subcall(J,M.options.bounds.getCenter());M.options.map=H;K=new e.classes.Rectangle(M.options);F.add("rectangle",K,M);this._manageEnd(K,M)};this.addoverlay=function(J){this._resolveLatLng(J,"_addOverlay")};this._addOverlay=function(N,L,O){var M,K=A("overlay",N),J=c.extend({pane:"floatPane",content:"",offset:{x:0,y:0}},K.options),R=c("<div></div>"),Q=[];R.css("border","none").css("borderWidth","0px").css("position","absolute");R.append(J.content);function P(){e.classes.OverlayView.call(this);this.setMap(H)}P.prototype=new e.classes.OverlayView();P.prototype.onAdd=function(){var S=this.getPanes();if(J.pane in S){c(S[J.pane]).append(R)}};P.prototype.draw=function(){var S=this.getProjection(),U=S.fromLatLngToDivPixel(L),T=this;R.css("left",(U.x+J.offset.x)+"px").css("top",(U.y+J.offset.y)+"px");c.each(("dblclick click mouseover mousemove mouseout mouseup mousedown").split(" "),function(W,V){Q.push(google.maps.event.addDomListener(R[0],V,function(X){google.maps.event.trigger(T,V)}))});Q.push(google.maps.event.addDomListener(R[0],"contextmenu",function(V){google.maps.event.trigger(T,"rightclick")}))};P.prototype.onRemove=function(){for(var S=0;S<Q.length;S++){google.maps.event.removeListener(Q[S])}R.remove()};P.prototype.hide=function(){R.hide()};P.prototype.show=function(){R.show()};P.prototype.toggle=function(){if(R){if(R.is(":visible")){this.show()}else{this.hide()}}};P.prototype.toggleDOM=function(){if(this.getMap()){this.setMap(null)}else{this.setMap(H)}};P.prototype.getDOMElement=function(){return R[0]};M=new P();if(!O){F.add("overlay",M,K);this._manageEnd(M,K)}return M};this.addfixpanel=function(K){var N=A("fixpanel",K),J=y=0,M,L;if(N.options.content){M=c(N.options.content);if(N.options.left!==undefined){J=N.options.left}else{if(N.options.right!==undefined){J=I.width()-M.width()-N.options.right}else{if(N.options.center){J=(I.width()-M.width())/2}}}if(N.options.top!==undefined){y=N.options.top}else{if(N.options.bottom!==undefined){y=I.height()-M.height()-N.options.bottom}else{if(N.options.middle){y=(I.height()-M.height())/2}}}L=c("<div></div>").css("position","absolute").css("top",y+"px").css("left",J+"px").css("z-index","1000").append(M);I.first().prepend(L);this._attachEvents(H,N);F.add("fixpanel",L,N);this._callback(L,N)}this._end()};this.adddirectionsrenderer=function(J,K){var L,M=A("directionrenderer",J,"panelId");M.options.map=H;L=new google.maps.DirectionsRenderer(M.options);if(M.panelId){L.setPanel(document.getElementById(M.panelId))}F.add("directionrenderer",L,M);this._manageEnd(L,M,K);return L};this.setdirectionspanel=function(J){var K=F.get("directionrenderer"),L=A("directionpanel",J,"id");if(K&&L.id){K.setPanel(document.getElementById(L.id))}this._manageEnd(K,L)};this.setdirections=function(J){var K=F.get("directionrenderer"),L=A("directions",J);if(J){L.options.directions=J.directions?J.directions:(J.options&&J.options.directions?J.options.directions:null)}if(L.options.directions){if(!K){K=this.adddirectionsrenderer(L,true)}else{K.setDirections(L.options.directions)}}this._manageEnd(K,L)};this.setstreetview=function(J){var K,L=A("streetview",J,"id");if(L.options.position){L.options.position=f(L.options.position)}K=new e.classes.StreetViewPanorama(document.getElementById(L.id),L.options);if(K){H.setStreetView(K)}this._manageEnd(K,L)};this.addkmllayer=function(K){var J,L=A("kmllayer",K,"url");L.options.map=H;if(typeof(L.url)==="string"){J=new e.classes.KmlLayer(L.url,L.options)}F.add("kmllayer",J,L);this._manageEnd(J,L)};this.addtrafficlayer=function(J){var L=A("trafficlayer",J),K=F.get("trafficlayer");if(!K){K=new e.classes.TrafficLayer();K.setMap(H);F.add("trafficlayer",K,L)}this._manageEnd(K,L)};this.addbicyclinglayer=function(J){var K=A("bicyclinglayer",J),L=F.get("bicyclinglayer");if(!L){L=new e.classes.BicyclingLayer();L.setMap(H);F.add("bicyclinglayer",L,K)}this._manageEnd(L,K)};this.addgroundoverlay=function(J){var K,L=A("groundoverlay",J,["bounds","url"]);L.bounds=j(L.bounds);if(L.bounds&&(typeof(L.url)==="string")){K=new e.classes.GroundOverlay(L.url,L.bounds);K.setMap(H);F.add("groundoverlay",K,L)}this._manageEnd(K,L)};this.geolatlng=function(J){var K=s(J,"callback");if(typeof(K)==="function"){if(navigator.geolocation){navigator.geolocation.getCurrentPosition(function(L){var M=new google.maps.LatLng(L.coords.latitude,L.coords.longitude);K.apply(I,[M])},function(){var L=false;K.apply(I,[L])})}else{if(google.gears){google.gears.factory.create("beta.geolocation").getCurrentPosition(function(L){var M=new google.maps.LatLng(L.latitude,L.longitude);K.apply(I,[M])},function(){out=false;K.apply(I,[out])})}else{K.apply(I,[false])}}}this._end()};this.addstyledmap=function(J,K){var L=A("styledmap",J,["id","style"]);if(L.style&&L.id&&!G[L.id]){G[L.id]=new e.classes.StyledMapType(L.style,L.options);if(H){H.mapTypes.set(L.id,G[L.id])}}this._manageEnd(G[L.id],L,K)};this.setstyledmap=function(J){var K=A("styledmap",J,["id","style"]);if(K.id){this.addstyledmap(K,true);if(G[K.id]){H.setMapTypeId(K.id);this._callback(G[K.id],J)}}this._manageEnd(G[K.id],K)};this.clear=function(K){var M=g(s(K,"list")||s(K,"name")),L=s(K,"last",false),N=s(K,"first",false),J=s(K,"tag");if(J!==undefined){J=g(J)}F.clear(M,L,N,J);this._end()};this.get=function(K){var L=s(K,"name")||"map",N=s(K,"first"),M=s(K,"all"),J=s(K,"tag");L=L.toLowerCase();if(L==="map"){return H}if(J!==undefined){J=g(J)}if(N){return F.get(L,false,J)}else{if(M){return F.all(L,J)}else{return F.get(L,true,J)}}};this.getmaxzoom=function(J){this._resolveLatLng(J,"_getMaxZoom")};this._getMaxZoom=function(J,L){var M=s(J,"callback"),K=this;if(M&&typeof(M)==="function"){x().getMaxZoomAtLatLng(L,function(N){var O=N.status===google.maps.MaxZoomStatus.OK?N.zoom:false;M.apply(I,[O,N.status]);K._end()})}else{this._end()}};this.setdefault=function(J){B(J);this._end()};this.autofit=function(K,O){var R,Q,M,N,L,P=true,J=new google.maps.LatLngBounds(),S=s(K,"maxZoom",null);R=F.names();for(N=0;N<R.length;N++){Q=F.all(R[N]);for(L=0;L<Q.length;L++){M=Q[L];if(M.getPosition){J.extend(M.getPosition());P=false}else{if(M.getBounds){J.extend(M.getBounds().getNorthEast());J.extend(M.getBounds().getSouthWest());P=false}else{if(M.getPaths){M.getPaths().forEach(function(T){T.forEach(function(U){J.extend(U);P=false})})}else{if(M.getPath){M.getPath().forEach(function(T){J.extend(T);P=false})}else{if(M.getCenter){J.extend(M.getCenter());P=false}}}}}}}if(!P&&(!H.getBounds()||!H.getBounds().equals(J))){if(S!==null){google.maps.event.addListenerOnce(H,"bounds_changed",function(){if(this.getZoom()>S){this.setZoom(S)}})}H.fitBounds(J)}if(!O){this._manageEnd(P?false:J,K,O)}}}c.fn.gmap3=function(){var F,D,H=[],G=true,E=[];for(F=0;F<arguments.length;F++){D=arguments[F]||{};if(typeof(D)==="string"){D={action:D}}H.push(D)}if(!H.length){H.push({})}c.each(this,function(){var I=c(this),J=I.data("gmap3");G=false;if(!J){J=new w(I);I.data("gmap3",J)}if((H.length==1)&&(n(H[0]))){E.push(J._direct(H[0]))}else{J._plan(H)}});if(E.length){if(E.length===1){return E[0]}else{return E}}if(G&&(arguments.length==2)&&(typeof(arguments[0])==="string")&&(arguments[0].toLowerCase()==="setdefault")){B(arguments[1])}return this}}(jQuery));
     10(function(i,e){var q,x=0;function m(){if(!q){q={verbose:false,queryLimit:{attempt:5,delay:250,random:250},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,StreetViewPanorama:google.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.578498,2.457275],zoom:2},overlay:{pane:"floatPane",content:"",offset:{x:0,y:0}},geoloc:{getCurrentPosition:{maximumAge:60000,timeout:5000}}}}}function G(K,J){return K!==e?K:"gmap3_"+(J?x+1:++x)}function B(N,J,L,O,M){if(J.todo.events||J.todo.onces){var K={id:O,data:J.todo.data,tag:J.todo.tag}}if(J.todo.events){i.each(J.todo.events,function(P,Q){google.maps.event.addListener(L,P,function(R){Q.apply(N,[M?M:L,R,K])})})}if(J.todo.onces){i.each(J.todo.onces,function(P,Q){google.maps.event.addListenerOnce(L,P,function(R){Q.apply(N,[M?M:L,R,K])})})}}function w(){var J=[];this.empty=function(){return !J.length};this.add=function(K){J.push(K)};this.get=function(){return J.length?J[0]:false};this.ack=function(){J.shift()}}function a(R,J,L){var P={},N=this,O,Q={latLng:{map:false,marker:false,infowindow:false,circle:false,overlay:false,getlatlng:false,getmaxzoom:false,getelevation:false,streetviewpanorama:false,getaddress:true},geoloc:{getgeoloc:true}};if(typeof L==="string"){L=K(L)}function K(T){var S={};S[T]={};return S}function M(){var S;for(S in L){if(S in P){continue}return S}}this.run=function(){var S,T;while(S=M()){if(typeof R[S]==="function"){O=S;T=i.extend(true,{},q[S]||{},L[S].options||{});if(S in Q.latLng){if(L[S].values){F(L[S].values,R,R[S],{todo:L[S],opts:T,session:P})}else{H(R,R[S],Q.latLng[S],{todo:L[S],opts:T,session:P})}}else{if(S in Q.geoloc){D(R,R[S],{todo:L[S],opts:T,session:P})}else{R[S].apply(R,[{todo:L[S],opts:T,session:P}])}}return}else{P[S]=null}}J.apply(R,[L,P])};this.ack=function(S){P[O]=S;N.run.apply(N,[])}}function d(L){var J,K=[];for(J in L){K.push(J)}return K}function v(L,O){var J={};if(L.todo){for(var K in L.todo){if((K!=="options")&&(K!=="values")){J[K]=L.todo[K]}}}var M,N=["data","tag","id","events","onces"];for(M=0;M<N.length;M++){g(J,N[M],O,L.todo)}J.options=i.extend({},L.todo.options||{},O.options||{});return J}function g(L,K){for(var J=2;J<arguments.length;J++){if(K in arguments[J]){L[K]=arguments[J][K];return}}}function E(){var J=[];this.get=function(Q){if(J.length){var N,M,L,P,K,O=d(Q);for(N=0;N<J.length;N++){P=J[N];K=O.length==P.keys.length;for(M=0;(M<O.length)&&K;M++){L=O[M];K=L in P.request;if(K){if((typeof Q[L]==="object")&&("equals" in Q[L])&&(typeof Q[L]==="function")){K=Q[L].equals(P.request[L])}else{K=Q[L]===P.request[L]}}}if(K){return P.results}}}};this.store=function(L,K){J.push({request:L,keys:d(L),results:K})}}function I(O,N,M,J){var L=this,K=[];q.classes.OverlayView.call(this);this.setMap(O);this.onAdd=function(){var P=this.getPanes();if(N.pane in P){i(P[N.pane]).append(J)}i.each("dblclick click mouseover mousemove mouseout mouseup mousedown".split(" "),function(R,Q){K.push(google.maps.event.addDomListener(J[0],Q,function(S){i.Event(S).stopPropagation();google.maps.event.trigger(L,Q,[S])}))});K.push(google.maps.event.addDomListener(J[0],"contextmenu",function(Q){i.Event(Q).stopPropagation();google.maps.event.trigger(L,"rightclick",[Q])}));this.draw()};this.getPosition=function(){return M};this.draw=function(){this.draw=function(){var P=this.getProjection().fromLatLngToDivPixel(M);J.css("left",(P.x+N.offset.x)+"px").css("top",(P.y+N.offset.y)+"px")}};this.onRemove=function(){for(var P=0;P<K.length;P++){google.maps.event.removeListener(K[P])}J.remove()};this.hide=function(){J.hide()};this.show=function(){J.show()};this.toggle=function(){if(J){if(J.is(":visible")){this.show()}else{this.hide()}}};this.toggleDOM=function(){if(this.getMap()){this.setMap(null)}else{this.setMap(O)}};this.getDOMElement=function(){return J[0]}}function f(L){function J(){this.onAdd=function(){};this.onRemove=function(){};this.draw=function(){};return q.classes.OverlayView.apply(this,[])}J.prototype=q.classes.OverlayView.prototype;var K=new J();K.setMap(L);return K}function z(ab,al,O,X){var ak=false,af=false,ac=false,W=false,T=true,S=this,K=[],R={},aa={},ag=[],ae=[],L=[],ah=f(al),V,am,aj,M,N;Q();this.getById=function(an){return an in aa?ag[aa[an]]:false};this.clearById=function(ao){if(ao in aa){var an=aa[ao];if(ag[an]){ag[an].setMap(null)}delete ag[an];ag[an]=false;delete ae[an];ae[an]=false;delete L[an];L[an]=false;delete aa[ao];af=true}};this.add=function(an,ao){an.id=G(an.id);this.clearById(an.id);aa[an.id]=ag.length;ag.push(null);ae.push(an);L.push(ao);af=true};this.addMarker=function(ao,an){an=an||{};an.id=G(an.id);this.clearById(an.id);if(!an.options){an.options={}}an.options.position=ao.getPosition();B(ab,{todo:an},ao,an.id);aa[an.id]=ag.length;ag.push(ao);ae.push(an);L.push(an.data||{});af=true};this.todo=function(an){return ae[an]};this.value=function(an){return L[an]};this.marker=function(an){return ag[an]};this.setMarker=function(ao,an){ag[ao]=an};this.store=function(an,ao,ap){R[an.ref]={obj:ao,shadow:ap}};this.free=function(){for(var an=0;an<K.length;an++){google.maps.event.removeListener(K[an])}K=[];i.each(R,function(ao){Z(ao)});R={};i.each(ae,function(ao){ae[ao]=null});ae=[];i.each(ag,function(ao){if(ag[ao]){ag[ao].setMap(null);delete ag[ao]}});ag=[];i.each(L,function(ao){delete L[ao]});L=[];aa={}};this.filter=function(an){aj=an;ad()};this.enable=function(an){if(T!=an){T=an;ad()}};this.display=function(an){M=an};this.error=function(an){N=an};this.beginUpdate=function(){ak=true};this.endUpdate=function(){ak=false;if(af){ad()}};function Q(){am=ah.getProjection();if(!am){setTimeout(function(){Q.apply(S,[])},25);return}W=true;K.push(google.maps.event.addListener(al,"zoom_changed",function(){ai()}));K.push(google.maps.event.addListener(al,"bounds_changed",function(){ai()}));ad()}function Z(an){if(typeof R[an]==="object"){if(typeof(R[an].obj.setMap)==="function"){R[an].obj.setMap(null)}if(typeof(R[an].obj.remove)==="function"){R[an].obj.remove()}if(typeof(R[an].shadow.remove)==="function"){R[an].obj.remove()}if(typeof(R[an].shadow.setMap)==="function"){R[an].shadow.setMap(null)}delete R[an].obj;delete R[an].shadow}else{if(ag[an]){ag[an].setMap(null)}}delete R[an]}function J(){var av,au,at,aq,ar,ap,ao,an;if(arguments[0] instanceof google.maps.LatLng){av=arguments[0].lat();at=arguments[0].lng();if(arguments[1] instanceof google.maps.LatLng){au=arguments[1].lat();aq=arguments[1].lng()}else{au=arguments[1];aq=arguments[2]}}else{av=arguments[0];at=arguments[1];if(arguments[2] instanceof google.maps.LatLng){au=arguments[2].lat();aq=arguments[2].lng()}else{au=arguments[2];aq=arguments[3]}}ar=Math.PI*av/180;ap=Math.PI*at/180;ao=Math.PI*au/180;an=Math.PI*aq/180;return 1000*6371*Math.acos(Math.min(Math.cos(ar)*Math.cos(ao)*Math.cos(ap)*Math.cos(an)+Math.cos(ar)*Math.sin(ap)*Math.cos(ao)*Math.sin(an)+Math.sin(ar)*Math.sin(ao),1))}function P(){var an=J(al.getCenter(),al.getBounds().getNorthEast()),ao=new google.maps.Circle({center:al.getCenter(),radius:1.25*an});return ao.getBounds()}function U(){var ao={},an;for(an in R){ao[an]=true}return ao}function ai(){clearTimeout(V);V=setTimeout(function(){ad()},25)}function Y(ao){var aq=am.fromLatLngToDivPixel(ao),ap=am.fromDivPixelToLatLng(new google.maps.Point(aq.x+O,aq.y-O)),an=am.fromDivPixelToLatLng(new google.maps.Point(aq.x-O,aq.y+O));return new google.maps.LatLngBounds(an,ap)}function ad(){if(ak||ac||!W){return}var az=[],aB={},aA=al.getZoom(),aC=(X!==e)&&(aA>X),at=U(),ar,aq,ap,aw,ax,av,au,ao=false,an,ay;af=false;if(aA>3){an=P();ao=an.getSouthWest().lng()<an.getNorthEast().lng()}i.each(ae,function(aE,aD){if(!aD){return}if(ao&&(!an.contains(aD.options.position))){return}if(aj&&!aj(L[aE])){return}az.push(aE)});while(1){ar=0;while(aB[ar]&&(ar<az.length)){ar++}if(ar==az.length){break}av=[];if(T&&!aC){do{au=av;av=[];if(au.length){aw=ax=0;for(ap=0;ap<au.length;ap++){aw+=ae[az[au[ap]]].options.position.lat();ax+=ae[az[au[ap]]].options.position.lng()}aw/=au.length;ax/=au.length;an=Y(new google.maps.LatLng(aw,ax))}else{an=Y(ae[az[ar]].options.position)}for(aq=ar;aq<az.length;aq++){if(aB[aq]){continue}if(an.contains(ae[az[aq]].options.position)){av.push(aq)}}}while((au.length<av.length)&&(av.length>1))}else{for(aq=ar;aq<az.length;aq++){if(aB[aq]){continue}av.push(aq);break}}ay={latLng:an.getCenter(),indexes:[],ref:[]};for(ap=0;ap<av.length;ap++){aB[av[ap]]=true;ay.indexes.push(az[av[ap]]);ay.ref.push(az[av[ap]])}ay.ref=ay.ref.join("-");if(ay.ref in at){delete at[ay.ref]}else{if(av.length===1){R[ay.ref]=true}(function(aD){setTimeout(function(){M(aD)},1)})(ay)}}i.each(at,function(aD){Z(aD)});ac=false}}function C(K,J){this.id=function(){return K};this.filter=function(L){J.filter(L)};this.enable=function(){J.enable(true)};this.disable=function(){J.enable(false)};this.add=function(M,L,N){if(!N){J.beginUpdate()}J.addMarker(M,L);if(!N){J.endUpdate()}};this.getById=function(L){return J.getById(L)};this.clearById=function(M,L){if(!L){J.beginUpdate()}J.clearById(M);if(!L){J.endUpdate()}}}function u(){var L={},M={};function K(O){if(O){if(typeof O==="function"){return O}O=l(O);return function(Q){if(Q===e){return false}if(typeof Q==="object"){for(var P=0;P<Q.length;P++){if(i.inArray(Q[P],O)>=0){return true}}return false}return i.inArray(Q,O)>=0}}}function J(O){return{id:O.id,name:O.name,object:O.obj,tag:O.tag,data:O.data}}this.add=function(Q,P,S,R){var O=Q.todo||{},T=G(O.id);if(!L[P]){L[P]=[]}if(T in M){this.clearById(T)}M[T]={obj:S,sub:R,name:P,id:T,tag:O.tag,data:O.data};L[P].push(T);return T};this.getById=function(Q,P,O){if(Q in M){if(P){return M[Q].sub}else{if(O){return J(M[Q])}}return M[Q].obj}return false};this.get=function(Q,S,O,R){var U,T,P=K(O);if(!L[Q]||!L[Q].length){return null}U=L[Q].length;while(U){U--;T=L[Q][S?U:L[Q].length-U-1];if(T&&M[T]){if(P&&!P(M[T].tag)){continue}return R?J(M[T]):M[T].obj}}return null};this.all=function(R,P,S){var O=[],Q=K(P),T=function(W){var U,V;for(U=0;U<L[W].length;U++){V=L[W][U];if(V&&M[V]){if(Q&&!Q(M[V].tag)){continue}O.push(S?J(M[V]):M[V].obj)}}};if(R in L){T(R)}else{if(R===e){for(R in L){T(R)}}}return O};function N(O){if(typeof(O.setMap)==="function"){O.setMap(null)}if(typeof(O.remove)==="function"){O.remove()}if(typeof(O.free)==="function"){O.free()}O=null}this.rm=function(R,P,Q){var O,S;if(!L[R]){return false}if(P){if(Q){for(O=L[R].length-1;O>=0;O--){S=L[R][O];if(P(M[S].tag)){break}}}else{for(O=0;O<L[R].length;O++){S=L[R][O];if(P(M[S].tag)){break}}}}else{O=Q?L[R].length-1:0}if(!(O in L[R])){return false}return this.clearById(L[R][O],O)};this.clearById=function(R,O){if(R in M){var Q,P=M[R].name;for(Q=0;O===e&&Q<L[P].length;Q++){if(R===L[P][Q]){O=Q}}N(M[R].obj);if(M[R].sub){N(M[R].sub)}delete M[R];L[P].splice(O,1);return true}return false};this.objGetById=function(Q){var P;if(L.clusterer){for(var O in L.clusterer){if((P=M[L.clusterer[O]].obj.getById(Q))!==false){return P}}}return false};this.objClearById=function(P){if(L.clusterer){for(var O in L.clusterer){if(M[L.clusterer[O]].obj.clearById(P)){return true}}}return null};this.clear=function(U,T,V,O){var Q,S,R,P=K(O);if(!U||!U.length){U=[];for(Q in L){U.push(Q)}}else{U=l(U)}for(S=0;S<U.length;S++){if(U[S]){R=U[S];if(!L[R]){continue}if(T){this.rm(R,P,true)}else{if(V){this.rm(R,P,false)}else{while(this.rm(R,P,false)){}}}}}}}var c={},t=new E();function p(){if(!c.geocoder){c.geocoder=new google.maps.Geocoder()}return c.geocoder}function r(){if(!c.directionsService){c.directionsService=new google.maps.DirectionsService()}return c.directionsService}function s(){if(!c.elevationService){c.elevationService=new google.maps.ElevationService()}return c.elevationService}function h(){if(!c.maxZoomService){c.maxZoomService=new google.maps.MaxZoomService()}return c.maxZoomService}function b(){if(!c.distanceMatrixService){c.distanceMatrixService=new google.maps.DistanceMatrixService()}return c.distanceMatrixService}function y(){if(q.verbose){var J,K=[];if(window.console&&(typeof console.error==="function")){for(J=0;J<arguments.length;J++){K.push(arguments[J])}console.error.apply(console,K)}else{K="";for(J=0;J<arguments.length;J++){K+=arguments[J].toString()+" "}alert(K)}}}function j(J){return(typeof(J)==="number"||typeof(J)==="string")&&J!==""&&!isNaN(J)}function l(L){var K,J=[];if(L!==e){if(typeof(L)==="object"){if(typeof(L.length)==="number"){J=L}else{for(K in L){J.push(L[K])}}}else{J.push(L)}}return J}function k(K,M,J){var L=M?K:null;if(!K||(typeof K==="string")){return L}if(K.latLng){return k(K.latLng)}if(K instanceof google.maps.LatLng){return K}else{if(j(K.lat)){return new google.maps.LatLng(K.lat,K.lng)}else{if(!J&&i.isArray(K)){if(!j(K[0])||!j(K[1])){return L}return new google.maps.LatLng(K[0],K[1])}}}return L}function n(K){var L,J;if(!K||K instanceof google.maps.LatLngBounds){return K||null}if(i.isArray(K)){if(K.length==2){L=k(K[0]);J=k(K[1])}else{if(K.length==4){L=k([K[0],K[1]]);J=k([K[2],K[3]])}}}else{if(("ne" in K)&&("sw" in K)){L=k(K.ne);J=k(K.sw)}else{if(("n" in K)&&("e" in K)&&("s" in K)&&("w" in K)){L=k([K.n,K.e]);J=k([K.s,K.w])}}}if(L&&J){return new google.maps.LatLngBounds(J,L)}return null}function H(R,J,M,Q,N){var L=M?k(Q.todo,false,true):false,P=L?{latLng:L}:(Q.todo.address?(typeof(Q.todo.address)==="string"?{address:Q.todo.address}:Q.todo.address):false),K=P?t.get(P):false,O=this;if(P){N=N||0;if(K){Q.latLng=K.results[0].geometry.location;Q.results=K.results;Q.status=K.status;J.apply(R,[Q])}else{if(P.location){P.location=k(P.location)}if(P.bounds){P.bounds=n(P.bounds)}p().geocode(P,function(T,S){if(S===google.maps.GeocoderStatus.OK){t.store(P,{results:T,status:S});Q.latLng=T[0].geometry.location;Q.results=T;Q.status=S;J.apply(R,[Q])}else{if((S===google.maps.GeocoderStatus.OVER_QUERY_LIMIT)&&(N<q.queryLimit.attempt)){setTimeout(function(){H.apply(O,[R,J,M,Q,N+1])},q.queryLimit.delay+Math.floor(Math.random()*q.queryLimit.random))}else{y("geocode failed",S,P);Q.latLng=Q.results=false;Q.status=S;J.apply(R,[Q])}}})}}else{Q.latLng=k(Q.todo,false,true);J.apply(R,[Q])}}function F(O,J,P,K){var M=this,L=-1;function N(){do{L++}while((L<O.length)&&!("address" in O[L]));if(L>=O.length){P.apply(J,[K]);return}H(M,function(Q){delete Q.todo;i.extend(O[L],Q);N.apply(M,[])},true,{todo:O[L]})}N()}function D(J,M,K){var L=false;if(navigator&&navigator.geolocation){navigator.geolocation.getCurrentPosition(function(N){if(L){return}L=true;K.latLng=new google.maps.LatLng(N.coords.latitude,N.coords.longitude);M.apply(J,[K])},function(){if(L){return}L=true;K.latLng=false;M.apply(J,[K])},K.opts.getCurrentPosition)}else{K.latLng=false;M.apply(J,[K])}}function A(R){var Q=this,S=new w(),T=new u(),L=null,N;this._plan=function(X){for(var W=0;W<X.length;W++){S.add(new a(Q,P,X[W]))}O()};function O(){if(!N&&(N=S.get())){N.run()}}function P(){N=null;S.ack();O.call(Q)}function V(W){var X,Y=[];for(X=1;X<arguments.length;X++){Y.push(arguments[X])}if(typeof W.todo.callback==="function"){W.todo.callback.apply(R,Y)}else{if(typeof W.todo.callback==="object"){i.each(W.todo.callback,function(aa,Z){if(typeof Z==="function"){Z.apply(R,Y)}})}}}function M(W,X,Y){if(Y){B(R,W,X,Y)}V(W,X);N.ack(X)}function J(Y,W){W=W||{};if(L){if(W.todo&&W.todo.options){L.setOptions(W.todo.options)}}else{var X=W.opts||i.extend(true,{},q.map,W.todo&&W.todo.options?W.todo.options:{});X.center=Y||k(X.center);L=new q.classes.Map(R.get(0),X)}}this.map=function(W){J(W.latLng,W);B(R,W,L);M(W,L)};this.destroy=function(W){T.clear();R.empty();if(L){L=null}M(W,true)};this.infowindow=function(X){var Y=[],W="values" in X.todo;if(!W){if(X.latLng){X.opts.position=X.latLng}else{if(X.opts.position){X.opts.position=k(X.opts.position)}}X.todo.values=[{options:X.opts}]}i.each(X.todo.values,function(aa,ab){var ad,ac,Z=v(X,ab);if(!L){J(Z.options.position)}ac=new q.classes.InfoWindow(Z.options);if(ac&&((Z.open===e)||Z.open)){if(W){ac.open(L,Z.anchor?Z.anchor:e)}else{ac.open(L,Z.anchor?Z.anchor:(X.latLng?e:(X.session.marker?X.session.marker:e)))}}Y.push(ac);ad=T.add({todo:Z},"infowindow",ac);B(R,{todo:Z},ac,ad)});M(X,W?Y:Y[0])};this.circle=function(X){var Y=[],W="values" in X.todo;if(!W){X.opts.center=X.latLng||k(X.opts.center);X.todo.values=[{options:X.opts}]}if(!X.todo.values.length){M(X,false);return}i.each(X.todo.values,function(aa,ab){var ad,ac,Z=v(X,ab);Z.options.center=Z.options.center?k(Z.options.center):k(ab);if(!L){J(Z.options.center)}Z.options.map=L;ac=new q.classes.Circle(Z.options);Y.push(ac);ad=T.add({todo:Z},"circle",ac);B(R,{todo:Z},ac,ad)});M(X,W?Y:Y[0])};this.overlay=function(Y,X){var aa,Z,W=i(document.createElement("div")).css("border","none").css("borderWidth","0px").css("position","absolute");W.append(Y.opts.content);I.prototype=new q.classes.OverlayView();Z=new I(L,Y.opts,Y.latLng,W);W=null;if(X){return Z}aa=T.add(Y,"overlay",Z);M(Y,Z,aa)};this.getaddress=function(W){V(W,W.results,W.status);N.ack()};this.getlatlng=function(W){V(W,W.results,W.status);N.ack()};this.getmaxzoom=function(W){h().getMaxZoomAtLatLng(W.latLng,function(X){V(W,X.status===google.maps.MaxZoomStatus.OK?X.zoom:false,status);N.ack()})};this.getelevation=function(X){var Y,W=[],Z=function(ab,aa){V(X,aa===google.maps.ElevationStatus.OK?ab:false,aa);N.ack()};if(X.latLng){W.push(X.latLng)}else{W=l(X.todo.locations||[]);for(Y=0;Y<W.length;Y++){W[Y]=k(W[Y])}}if(W.length){s().getElevationForLocations({locations:W},Z)}else{if(X.todo.path&&X.todo.path.length){for(Y=0;Y<X.todo.path.length;Y++){W.push(k(X.todo.path[Y]))}}if(W.length){s().getElevationAlongPath({path:W,samples:X.todo.samples},Z)}else{N.ack()}}};this.defaults=function(W){i.each(W.todo,function(X,Y){if(typeof q[X]==="object"){q[X]=i.extend({},q[X],Y)}else{q[X]=Y}});N.ack(true)};this.rectangle=function(X){var Y=[],W="values" in X.todo;if(!W){X.todo.values=[{options:X.opts}]}if(!X.todo.values.length){M(X,false);return}i.each(X.todo.values,function(aa,ab){var ad,ac,Z=v(X,ab);Z.options.bounds=Z.options.bounds?n(Z.options.bounds):n(ab);if(!L){J(Z.options.bounds.getCenter())}Z.options.map=L;ac=new q.classes.Rectangle(Z.options);Y.push(ac);ad=T.add({todo:Z},"rectangle",ac);B(R,{todo:Z},ac,ad)});M(X,W?Y:Y[0])};function K(X,Y,Z){var aa=[],W="values" in X.todo;if(!W){X.todo.values=[{options:X.opts}]}if(!X.todo.values.length){M(X,false);return}J();i.each(X.todo.values,function(ad,af){var ah,ae,ac,ag,ab=v(X,af);if(ab.options[Z]){if(ab.options[Z][0][0]&&i.isArray(ab.options[Z][0][0])){for(ae=0;ae<ab.options[Z].length;ae++){for(ac=0;ac<ab.options[Z][ae].length;ac++){ab.options[Z][ae][ac]=k(ab.options[Z][ae][ac])}}}else{for(ae=0;ae<ab.options[Z].length;ae++){ab.options[Z][ae]=k(ab.options[Z][ae])}}}ab.options.map=L;ag=new google.maps[Y](ab.options);aa.push(ag);ah=T.add({todo:ab},Y.toLowerCase(),ag);B(R,{todo:ab},ag,ah)});M(X,W?aa:aa[0])}this.polyline=function(W){K(W,"Polyline","path")};this.polygon=function(W){K(W,"Polygon","paths")};this.trafficlayer=function(W){J();var X=T.get("trafficlayer");if(!X){X=new q.classes.TrafficLayer();X.setMap(L);T.add(W,"trafficlayer",X)}M(W,X)};this.bicyclinglayer=function(W){J();var X=T.get("bicyclinglayer");if(!X){X=new q.classes.BicyclingLayer();X.setMap(L);T.add(W,"bicyclinglayer",X)}M(W,X)};this.groundoverlay=function(W){W.opts.bounds=n(W.opts.bounds);if(W.opts.bounds){J(W.opts.bounds.getCenter())}var Y,X=new q.classes.GroundOverlay(W.opts.url,W.opts.bounds,W.opts.opts);X.setMap(L);Y=T.add(W,"groundoverlay",X);M(W,X,Y)};this.streetviewpanorama=function(W){if(!W.opts.opts){W.opts.opts={}}if(W.latLng){W.opts.opts.position=W.latLng}else{if(W.opts.opts.position){W.opts.opts.position=k(W.opts.opts.position)}}if(W.todo.divId){W.opts.container=document.getElementById(W.todo.divId)}else{if(W.opts.container){W.opts.container=i(W.opts.container).get(0)}}var Y,X=new q.classes.StreetViewPanorama(W.opts.container,W.opts.opts);if(X){L.setStreetView(X)}Y=T.add(W,"streetviewpanorama",X);M(W,X,Y)};this.kmllayer=function(X){var Y=[],W="values" in X.todo;if(!W){X.todo.values=[{options:X.opts}]}if(!X.todo.values.length){M(X,false);return}i.each(X.todo.values,function(aa,ab){var ad,ac,Z=v(X,ab);if(!L){J()}Z.options.opts.map=L;ac=new q.classes.KmlLayer(Z.options.url,Z.options.opts);Y.push(ac);ad=T.add({todo:Z},"kmllayer",ac);B(R,{todo:Z},ac,ad)});M(X,W?Y:Y[0])};this.panel=function(Z){J();var ab,W=0,aa=0,Y,X=i(document.createElement("div"));X.css("position","absolute").css("z-index","1000");if(Z.opts.content){Y=i(Z.opts.content);if(Z.opts.left!==e){W=Z.opts.left}else{if(Z.opts.right!==e){W=R.width()-Y.width()-Z.opts.right}else{if(Z.opts.center){W=(R.width()-Y.width())/2}}}if(Z.opts.top!==e){aa=Z.opts.top}else{if(Z.opts.bottom!==e){aa=R.height()-Y.height()-Z.opts.bottom}else{if(Z.opts.middle){aa=(R.height()-Y.height())/2}}}X.css("top",aa+"px").css("left",W+"px").append(Y)}R.first().prepend(X);ab=T.add(Z,"panel",X);M(Z,X,ab);X=null};function U(Y){var ac=new z(R,L,Y.radius,Y.maxZoom),W={},Z={},ab=/^[0-9]+$/,aa,X;for(X in Y){if(ab.test(X)){Z[X]=Y[X];Z[X].width=Z[X].width||0;Z[X].height=Z[X].height||0}else{W[X]=Y[X]}}if(W.calculator){aa=function(ad){var ae=[];i.each(ad,function(ag,af){ae.push(ac.value(af))});return W.calculator.apply(R,[ae])}}else{aa=function(ad){return ad.length}}ac.error(function(){y.apply(Q,arguments)});ac.display(function(ad){var ae,ag,ak=0,aj,ah,ai,af=aa(ad.indexes);if(af>1){for(ae in Z){ae=1*ae;if(ae>ak&&ae<=af){ak=ae}}ag=Z[ak]}if(ag){ai=ag.offset||[-ag.width/2,-ag.height/2];aj=i.extend({},W);aj.options=i.extend({pane:"overlayLayer",content:ag.content?ag.content.replace("CLUSTER_COUNT",af):"",offset:{x:("x" in ai?ai.x:ai[0])||0,y:("y" in ai?ai.y:ai[1])||0}},W.options||{});ah=Q.overlay({todo:aj,opts:aj.options,latLng:k(ad)},true);aj.options.pane="floatShadow";aj.options.content=i(document.createElement("div")).width(ag.width+"px").height(ag.height+"px");shadow=Q.overlay({todo:aj,opts:aj.options,latLng:k(ad)},true);W.data={latLng:k(ad),markers:[]};i.each(ad.indexes,function(am,al){W.data.markers.push(ac.value(al));if(ac.marker(al)){ac.marker(al).setMap(null)}});B(R,{todo:W},shadow,e,{main:ah,shadow:shadow});ac.store(ad,ah,shadow)}else{i.each(ad.indexes,function(ao,an){if(ac.marker(an)){ac.marker(an).setMap(L)}else{var al=ac.todo(an),am=new q.classes.Marker(al.options);ac.setMarker(an,am);B(R,{todo:al},am,al.id)}})}});return ac}this.marker=function(Y){var W="values" in Y.todo,ab=!L;if(!W){Y.opts.position=Y.latLng||k(Y.opts.position);Y.todo.values=[{options:Y.opts}]}if(!Y.todo.values.length){M(Y,false);return}if(ab){J()}if(Y.todo.cluster&&!L.getBounds()){google.maps.event.addListenerOnce(L,"bounds_changed",function(){Q.marker.apply(Q,[Y])});return}if(Y.todo.cluster){var X,Z;if(Y.todo.cluster instanceof C){X=Y.todo.cluster;Z=T.getById(X.id(),true)}else{Z=U(Y.todo.cluster);X=new C(G(Y.todo.id,true),Z);T.add(Y,"clusterer",X,Z)}Z.beginUpdate();i.each(Y.todo.values,function(ad,ae){var ac=v(Y,ae);ac.options.position=ac.options.position?k(ac.options.position):k(ae);ac.options.map=L;if(ab){L.setCenter(ac.options.position);ab=false}Z.add(ac,ae)});Z.endUpdate();M(Y,X)}else{var aa=[];i.each(Y.todo.values,function(ad,ae){var ag,af,ac=v(Y,ae);ac.options.position=ac.options.position?k(ac.options.position):k(ae);ac.options.map=L;if(ab){L.setCenter(ac.options.position);ab=false}af=new q.classes.Marker(ac.options);aa.push(af);ag=T.add({todo:ac},"marker",af);B(R,{todo:ac},af,ag)});M(Y,W?aa:aa[0])}};this.getroute=function(W){W.opts.origin=k(W.opts.origin,true);W.opts.destination=k(W.opts.destination,true);r().route(W.opts,function(Y,X){V(W,X==google.maps.DirectionsStatus.OK?Y:false,X);N.ack()})};this.directionsrenderer=function(W){W.opts.map=L;var Y,X=new google.maps.DirectionsRenderer(W.opts);if(W.todo.divId){X.setPanel(document.getElementById(W.todo.divId))}else{if(W.todo.container){X.setPanel(i(W.todo.container).get(0))}}Y=T.add(W,"directionrenderer",X);M(W,X,Y)};this.getgeoloc=function(W){M(W,W.latLng)};this.styledmaptype=function(W){J();var X=new q.classes.StyledMapType(W.todo.styles,W.opts);L.mapTypes.set(W.todo.id,X);M(W,X)};this.imagemaptype=function(W){J();var X=new q.classes.ImageMapType(W.opts);L.mapTypes.set(W.todo.id,X);M(W,X)};this.autofit=function(W){var X=new google.maps.LatLngBounds();i.each(T.all(),function(Y,Z){if(Z.getPosition){X.extend(Z.getPosition())}else{if(Z.getBounds){X.extend(Z.getBounds().getNorthEast());X.extend(Z.getBounds().getSouthWest())}else{if(Z.getPaths){Z.getPaths().forEach(function(aa){aa.forEach(function(ab){X.extend(ab)})})}else{if(Z.getPath){Z.getPath().forEach(function(aa){X.extend(aa);""})}else{if(Z.getCenter){X.extend(Z.getCenter())}}}}}});if(!X.isEmpty()&&(!L.getBounds()||!L.getBounds().equals(X))){if("maxZoom" in W.todo){google.maps.event.addListenerOnce(L,"bounds_changed",function(){if(this.getZoom()>W.todo.maxZoom){this.setZoom(W.todo.maxZoom)}})}L.fitBounds(X)}M(W,true)};this.clear=function(W){if(typeof W.todo==="string"){if(T.clearById(W.todo)||T.objClearById(W.todo)){M(W,true);return}W.todo={name:W.todo}}if(W.todo.id){i.each(l(W.todo.id),function(X,Y){T.clearById(Y)})}else{T.clear(l(W.todo.name),W.todo.last,W.todo.first,W.todo.tag)}M(W,true)};this.exec=function(W){var X=this;i.each(l(W.todo.func),function(Y,Z){i.each(X.get(W.todo,true,W.todo.hasOwnProperty("full")?W.todo.full:true),function(aa,ab){Z.call(R,ab)})});M(W,true)};this.get=function(Y,ab,aa){var X,Z,W=ab?Y:Y.todo;if(!ab){aa=W.full}if(typeof W==="string"){Z=T.getById(W,false,aa)||T.objGetById(W);if(Z===false){X=W;W={}}}else{X=W.name}if(X==="map"){Z=L}if(!Z){Z=[];if(W.id){i.each(l(W.id),function(ac,ad){Z.push(T.getById(ad,false,aa)||T.objGetById(ad))});if(!i.isArray(W.id)){Z=Z[0]}}else{i.each(X?l(X):[e],function(ad,ae){var ac;if(W.first){ac=T.get(ae,false,W.tag,aa);if(ac){Z.push(ac)}}else{if(W.all){i.each(T.all(ae,W.tag,aa),function(ag,af){Z.push(af)})}else{ac=T.get(ae,true,W.tag,aa);if(ac){Z.push(ac)}}}});if(!W.all&&!i.isArray(X)){Z=Z[0]}}}Z=i.isArray(Z)||!W.all?Z:[Z];if(ab){return Z}else{M(Y,Z)}};this.getdistance=function(W){var X;W.opts.origins=l(W.opts.origins);for(X=0;X<W.opts.origins.length;X++){W.opts.origins[X]=k(W.opts.origins[X],true)}W.opts.destinations=l(W.opts.destinations);for(X=0;X<W.opts.destinations.length;X++){W.opts.destinations[X]=k(W.opts.destinations[X],true)}b().getDistanceMatrix(W.opts,function(Z,Y){V(W,Y===google.maps.DistanceMatrixStatus.OK?Z:false,Y);N.ack()})};this.trigger=function(X){if(typeof X.todo==="string"){google.maps.event.trigger(L,X.todo)}else{var W=[L,X.todo.eventName];if(X.todo.var_args){i.each(X.todo.var_args,function(Z,Y){W.push(Y)})}google.maps.event.trigger.apply(google.maps.event,W)}V(X);N.ack()}}function o(K){var J;if(!typeof K==="object"||!K.hasOwnProperty("get")){return false}for(J in K){if(J!=="get"){return false}}return !K.get.hasOwnProperty("callback")}i.fn.gmap3=function(){var K,M=[],L=true,J=[];m();for(K=0;K<arguments.length;K++){if(arguments[K]){M.push(arguments[K])}}if(!M.length){M.push("map")}i.each(this,function(){var N=i(this),O=N.data("gmap3");L=false;if(!O){O=new A(N);N.data("gmap3",O)}if(M.length===1&&(M[0]==="get"||o(M[0]))){J.push(O.get(M[0]==="get"?"map":M[0].get,true))}else{O._plan(M)}});if(J.length){if(J.length===1){return J[0]}else{return J}}return this}})(jQuery);
  • lazyest-maps/trunk/js/lazyest-maps.js

    r566876 r695801  
    1212            }, 
    1313            { action: "addMarkers",
    14                 markers: lazyestMarkers[mapID],
    1514                marker:{
     15                    values: lazyestMarkers[mapID],
    1616                    options:{
    1717                        icon: new google.maps.MarkerImage( photoMarker ),
     
    2020                    },
    2121                    events:{
    22                         click: function(marker, event, data){
    23                             var map = $(this).gmap3("get"),infowindow = $(this).gmap3({
    24                                 action:"get",
    25                                 name:"infowindow"
    26                             });
     22                        click: function(marker, event, object){
     23                            var map = $(this).gmap3("get"),infowindow = $(this).gmap3({get:{name:"infowindow"}});
    2724                            if (infowindow){
    28                                 infowindow.setContent(data);
    2925                                infowindow.open(map, marker);
     26                                infowindow.setContent(object.data);
    3027                            } else {
    3128                                $(this).gmap3({
    32                                     action:"addinfowindow",
    33                                     anchor:marker,
    34                                     options:{
    35                                         content: data
     29                                    infowindow:{
     30                                        anchor:marker,
     31                                        options:{content: object.data}
    3632                                    }
    3733                                });
     
    4137                }
    4238            },
    43             { action: "autofit" }
     39            "autofit"
    4440        );
    4541    }
  • lazyest-maps/trunk/lazyest-maps.php

    r662948 r695801  
    77Author: Brimosoft
    88Author URI: http://brimosoft.nl
    9 Version: 0.5
     9Version: 0.6.0
    1010License: GNU GPLv3
    1111*/
     
    1717 * @subpackage Lazyest Maps
    1818 * @author Marcel Brinkkemper
    19  * @copyright 2012 Marcel Brinkkemper
     19 * @copyright 2012-2013 Marcel Brinkkemper
    2020 * @version 0.4
    2121 * @access public
     
    164164     */
    165165    function register_scripts() {   
    166         wp_register_script( 'google-maps', 'http://maps.googleapis.com/maps/api/js?sensor=false', array( 'jquery' ), 'V3', true );
    167         wp_register_script( 'gmap', plugins_url( 'js/gmap3.min.js',  __FILE__ ), array( 'google-maps'), '4.1', true );
    168         wp_register_script( 'lazyest-maps', plugins_url( 'js/lazyest-maps.js',  __FILE__ ), array( 'gmap'), $this->version(), true );       
     166        $j = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? 'js' : 'min.js';   
     167        wp_register_script( 'google-maps', 'http://maps.googleapis.com/maps/api/js?sensor=false', array( 'jquery'      ), 'V3',             true );
     168        wp_register_script( 'gmap',         plugins_url( "js/gmap3.$j",  __FILE__ ),              array( 'google-maps' ), '5.0b',           true );
     169        wp_register_script( 'lazyest-maps', plugins_url( "js/lazyest-maps.$j",  __FILE__ ),       array( 'gmap'        ), $this->version(), true );     
    169170    }
    170171   
  • lazyest-maps/trunk/readme.txt

    r663103 r695801  
    55Requires at least: 3.4
    66Tested up to: 3.5.1
    7 Stable tag: 0.5
     7Stable tag: 0.6.0
    88License: GPLv3 or later
    99This plugin shows your Lazyest Gallery images location on a Google Map.
     
    1818This plugin uses [Google Maps API v3](https://developers.google.com/maps/documentation/javascript/tutorial) and the [GMAP3](http://gmap3.net/) jQuery plugin
    1919
    20 = In version 0.4.0 =
     20= Since version 0.4.0 =
    2121* Map for images in a folder. It uses simple info windows to display thumbnail over marker
    2222* Button in Folder Manager to (re)read Geo Data from your images.
     
    4242== Upgrade Notice ==
    4343
    44 = 0.5 =
    45 * Bug fix for Longitude calculation
     44= 0.6.0 =
     45* Upgrade to GMAP3 version 5.0b
    4646
    4747== Changelog ==
     48
     49= 0.6.0 =
     50* Changed: GMAP3 library upgraded from 4.1 to 5.0b
     51* Changed: Settings screen lay out
     52
    4853= 0.5 =
    4954* Bug fix: Incorrect Exif variable used in longitude calculation
Note: See TracChangeset for help on using the changeset viewer.