github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/public/js/maps/gmaps.js (about)

     1  /*!
     2   * GMaps.js v0.4.5
     3   * http://hpneo.github.com/gmaps/
     4   *
     5   * Copyright 2013, Gustavo Leon
     6   * Released under the MIT License.
     7   */
     8  
     9  if (!(typeof window.google === 'object' && window.google.maps)) {
    10    throw 'Google Maps API is required. Please register the following JavaScript library http://maps.google.com/maps/api/js?sensor=true.'
    11  }
    12  
    13  var extend_object = function(obj, new_obj) {
    14    var name;
    15  
    16    if (obj === new_obj) {
    17      return obj;
    18    }
    19  
    20    for (name in new_obj) {
    21      obj[name] = new_obj[name];
    22    }
    23  
    24    return obj;
    25  };
    26  
    27  var replace_object = function(obj, replace) {
    28    var name;
    29  
    30    if (obj === replace) {
    31      return obj;
    32    }
    33  
    34    for (name in replace) {
    35      if (obj[name] != undefined) {
    36        obj[name] = replace[name];
    37      }
    38    }
    39  
    40    return obj;
    41  };
    42  
    43  var array_map = function(array, callback) {
    44    var original_callback_params = Array.prototype.slice.call(arguments, 2),
    45        array_return = [],
    46        array_length = array.length,
    47        i;
    48  
    49    if (Array.prototype.map && array.map === Array.prototype.map) {
    50      array_return = Array.prototype.map.call(array, function(item) {
    51        callback_params = original_callback_params;
    52        callback_params.splice(0, 0, item);
    53  
    54        return callback.apply(this, callback_params);
    55      });
    56    }
    57    else {
    58      for (i = 0; i < array_length; i++) {
    59        callback_params = original_callback_params;
    60        callback_params.splice(0, 0, array[i]);
    61        array_return.push(callback.apply(this, callback_params));
    62      }
    63    }
    64  
    65    return array_return;
    66  };
    67  
    68  var array_flat = function(array) {
    69    var new_array = [],
    70        i;
    71  
    72    for (i = 0; i < array.length; i++) {
    73      new_array = new_array.concat(array[i]);
    74    }
    75  
    76    return new_array;
    77  };
    78  
    79  var coordsToLatLngs = function(coords, useGeoJSON) {
    80    var first_coord = coords[0],
    81        second_coord = coords[1];
    82  
    83    if (useGeoJSON) {
    84      first_coord = coords[1];
    85      second_coord = coords[0];
    86    }
    87  
    88    return new google.maps.LatLng(first_coord, second_coord);
    89  };
    90  
    91  var arrayToLatLng = function(coords, useGeoJSON) {
    92    var i;
    93  
    94    for (i = 0; i < coords.length; i++) {
    95      if (coords[i].length > 0 && typeof(coords[i][0]) != "number") {
    96        coords[i] = arrayToLatLng(coords[i], useGeoJSON);
    97      }
    98      else {
    99        coords[i] = coordsToLatLngs(coords[i], useGeoJSON);
   100      }
   101    }
   102  
   103    return coords;
   104  };
   105  
   106  var getElementById = function(id, context) {
   107    var element,
   108    id = id.replace('#', '');
   109  
   110    if ('jQuery' in this && context) {
   111      element = $("#" + id, context)[0];
   112    } else {
   113      element = document.getElementById(id);
   114    };
   115  
   116    return element;
   117  };
   118  
   119  var findAbsolutePosition = function(obj)  {
   120    var curleft = 0,
   121        curtop = 0;
   122  
   123    if (obj.offsetParent) {
   124      do {
   125        curleft += obj.offsetLeft;
   126        curtop += obj.offsetTop;
   127      } while (obj = obj.offsetParent);
   128    }
   129  
   130    return [curleft, curtop];
   131  };
   132  
   133  var GMaps = (function(global) {
   134    "use strict";
   135  
   136    var doc = document;
   137  
   138    var GMaps = function(options) {
   139      options.zoom = options.zoom || 15;
   140      options.mapType = options.mapType || 'roadmap';
   141  
   142      var self = this,
   143          i,
   144          events_that_hide_context_menu = ['bounds_changed', 'center_changed', 'click', 'dblclick', 'drag', 'dragend', 'dragstart', 'idle', 'maptypeid_changed', 'projection_changed', 'resize', 'tilesloaded', 'zoom_changed'],
   145          events_that_doesnt_hide_context_menu = ['mousemove', 'mouseout', 'mouseover'],
   146          options_to_be_deleted = ['el', 'lat', 'lng', 'mapType', 'width', 'height', 'markerClusterer', 'enableNewStyle'],
   147          container_id = options.el || options.div,
   148          markerClustererFunction = options.markerClusterer,
   149          mapType = google.maps.MapTypeId[options.mapType.toUpperCase()],
   150          map_center = new google.maps.LatLng(options.lat, options.lng),
   151          zoomControl = options.zoomControl || true,
   152          zoomControlOpt = options.zoomControlOpt || {
   153            style: 'DEFAULT',
   154            position: 'TOP_LEFT'
   155          },
   156          zoomControlStyle = zoomControlOpt.style || 'DEFAULT',
   157          zoomControlPosition = zoomControlOpt.position || 'TOP_LEFT',
   158          panControl = options.panControl || true,
   159          mapTypeControl = options.mapTypeControl || true,
   160          scaleControl = options.scaleControl || true,
   161          streetViewControl = options.streetViewControl || true,
   162          overviewMapControl = overviewMapControl || true,
   163          map_options = {},
   164          map_base_options = {
   165            zoom: this.zoom,
   166            center: map_center,
   167            mapTypeId: mapType
   168          },
   169          map_controls_options = {
   170            panControl: panControl,
   171            zoomControl: zoomControl,
   172            zoomControlOptions: {
   173              style: google.maps.ZoomControlStyle[zoomControlStyle],
   174              position: google.maps.ControlPosition[zoomControlPosition]
   175            },
   176            mapTypeControl: mapTypeControl,
   177            scaleControl: scaleControl,
   178            streetViewControl: streetViewControl,
   179            overviewMapControl: overviewMapControl
   180          };
   181  
   182      if (typeof(options.el) === 'string' || typeof(options.div) === 'string') {
   183        this.el = getElementById(container_id, options.context);
   184      } else {
   185        this.el = container_id;
   186      }
   187  
   188      if (typeof(this.el) === 'undefined' || this.el === null) {
   189        throw 'No element defined.';
   190      }
   191  
   192      window.context_menu = window.context_menu || {};
   193      window.context_menu[self.el.id] = {};
   194  
   195      this.controls = [];
   196      this.overlays = [];
   197      this.layers = []; // array with kml/georss and fusiontables layers, can be as many
   198      this.singleLayers = {}; // object with the other layers, only one per layer
   199      this.markers = [];
   200      this.polylines = [];
   201      this.routes = [];
   202      this.polygons = [];
   203      this.infoWindow = null;
   204      this.overlay_el = null;
   205      this.zoom = options.zoom;
   206      this.registered_events = {};
   207  
   208      this.el.style.width = options.width || this.el.scrollWidth || this.el.offsetWidth;
   209      this.el.style.height = options.height || this.el.scrollHeight || this.el.offsetHeight;
   210  
   211      google.maps.visualRefresh = options.enableNewStyle;
   212  
   213      for (i = 0; i < options_to_be_deleted.length; i++) {
   214        delete options[options_to_be_deleted[i]];
   215      }
   216  
   217      if(options.disableDefaultUI != true) {
   218        map_base_options = extend_object(map_base_options, map_controls_options);
   219      }
   220  
   221      map_options = extend_object(map_base_options, options);
   222  
   223      for (i = 0; i < events_that_hide_context_menu.length; i++) {
   224        delete map_options[events_that_hide_context_menu[i]];
   225      }
   226  
   227      for (i = 0; i < events_that_doesnt_hide_context_menu.length; i++) {
   228        delete map_options[events_that_doesnt_hide_context_menu[i]];
   229      }
   230  
   231      this.map = new google.maps.Map(this.el, map_options);
   232  
   233      if (markerClustererFunction) {
   234        this.markerClusterer = markerClustererFunction.apply(this, [this.map]);
   235      }
   236  
   237      var buildContextMenuHTML = function(control, e) {
   238        var html = '',
   239            options = window.context_menu[self.el.id][control];
   240  
   241        for (var i in options){
   242          if (options.hasOwnProperty(i)) {
   243            var option = options[i];
   244  
   245            html += '<li><a id="' + control + '_' + i + '" href="#">' + option.title + '</a></li>';
   246          }
   247        }
   248  
   249        if (!getElementById('gmaps_context_menu')) return;
   250  
   251        var context_menu_element = getElementById('gmaps_context_menu');
   252        
   253        context_menu_element.innerHTML = html;
   254  
   255        var context_menu_items = context_menu_element.getElementsByTagName('a'),
   256            context_menu_items_count = context_menu_items.length
   257            i;
   258  
   259        for (i = 0; i < context_menu_items_count; i++) {
   260          var context_menu_item = context_menu_items[i];
   261  
   262          var assign_menu_item_action = function(ev){
   263            ev.preventDefault();
   264  
   265            options[this.id.replace(control + '_', '')].action.apply(self, [e]);
   266            self.hideContextMenu();
   267          };
   268  
   269          google.maps.event.clearListeners(context_menu_item, 'click');
   270          google.maps.event.addDomListenerOnce(context_menu_item, 'click', assign_menu_item_action, false);
   271        }
   272  
   273        var position = findAbsolutePosition.apply(this, [self.el]),
   274            left = position[0] + e.pixel.x - 15,
   275            top = position[1] + e.pixel.y- 15;
   276  
   277        context_menu_element.style.left = left + "px";
   278        context_menu_element.style.top = top + "px";
   279  
   280        context_menu_element.style.display = 'block';
   281      };
   282  
   283      this.buildContextMenu = function(control, e) {
   284        if (control === 'marker') {
   285          e.pixel = {};
   286  
   287          var overlay = new google.maps.OverlayView();
   288          overlay.setMap(self.map);
   289          
   290          overlay.draw = function() {
   291            var projection = overlay.getProjection(),
   292                position = e.marker.getPosition();
   293            
   294            e.pixel = projection.fromLatLngToContainerPixel(position);
   295  
   296            buildContextMenuHTML(control, e);
   297          };
   298        }
   299        else {
   300          buildContextMenuHTML(control, e);
   301        }
   302      };
   303  
   304      this.setContextMenu = function(options) {
   305        window.context_menu[self.el.id][options.control] = {};
   306  
   307        var i,
   308            ul = doc.createElement('ul');
   309  
   310        for (i in options.options) {
   311          if (options.options.hasOwnProperty(i)) {
   312            var option = options.options[i];
   313  
   314            window.context_menu[self.el.id][options.control][option.name] = {
   315              title: option.title,
   316              action: option.action
   317            };
   318          }
   319        }
   320  
   321        ul.id = 'gmaps_context_menu';
   322        ul.style.display = 'none';
   323        ul.style.position = 'absolute';
   324        ul.style.minWidth = '100px';
   325        ul.style.background = 'white';
   326        ul.style.listStyle = 'none';
   327        ul.style.padding = '8px';
   328        ul.style.boxShadow = '2px 2px 6px #ccc';
   329  
   330        doc.body.appendChild(ul);
   331  
   332        var context_menu_element = getElementById('gmaps_context_menu')
   333  
   334        google.maps.event.addDomListener(context_menu_element, 'mouseout', function(ev) {
   335          if (!ev.relatedTarget || !this.contains(ev.relatedTarget)) {
   336            window.setTimeout(function(){
   337              context_menu_element.style.display = 'none';
   338            }, 400);
   339          }
   340        }, false);
   341      };
   342  
   343      this.hideContextMenu = function() {
   344        var context_menu_element = getElementById('gmaps_context_menu');
   345  
   346        if (context_menu_element) {
   347          context_menu_element.style.display = 'none';
   348        }
   349      };
   350  
   351      var setupListener = function(object, name) {
   352        google.maps.event.addListener(object, name, function(e){
   353          if (e == undefined) {
   354            e = this;
   355          }
   356  
   357          options[name].apply(this, [e]);
   358  
   359          self.hideContextMenu();
   360        });
   361      };
   362  
   363      for (var ev = 0; ev < events_that_hide_context_menu.length; ev++) {
   364        var name = events_that_hide_context_menu[ev];
   365  
   366        if (name in options) {
   367          setupListener(this.map, name);
   368        }
   369      }
   370  
   371      for (var ev = 0; ev < events_that_doesnt_hide_context_menu.length; ev++) {
   372        var name = events_that_doesnt_hide_context_menu[ev];
   373  
   374        if (name in options) {
   375          setupListener(this.map, name);
   376        }
   377      }
   378  
   379      google.maps.event.addListener(this.map, 'rightclick', function(e) {
   380        if (options.rightclick) {
   381          options.rightclick.apply(this, [e]);
   382        }
   383  
   384        if(window.context_menu[self.el.id]['map'] != undefined) {
   385          self.buildContextMenu('map', e);
   386        }
   387      });
   388  
   389      this.refresh = function() {
   390        google.maps.event.trigger(this.map, 'resize');
   391      };
   392  
   393      this.fitZoom = function() {
   394        var latLngs = [],
   395            markers_length = this.markers.length,
   396            i;
   397  
   398        for (i = 0; i < markers_length; i++) {
   399          latLngs.push(this.markers[i].getPosition());
   400        }
   401  
   402        this.fitLatLngBounds(latLngs);
   403      };
   404  
   405      this.fitLatLngBounds = function(latLngs) {
   406        var total = latLngs.length;
   407        var bounds = new google.maps.LatLngBounds();
   408  
   409        for(var i=0; i < total; i++) {
   410          bounds.extend(latLngs[i]);
   411        }
   412  
   413        this.map.fitBounds(bounds);
   414      };
   415  
   416      this.setCenter = function(lat, lng, callback) {
   417        this.map.panTo(new google.maps.LatLng(lat, lng));
   418  
   419        if (callback) {
   420          callback();
   421        }
   422      };
   423  
   424      this.getElement = function() {
   425        return this.el;
   426      };
   427  
   428      this.zoomIn = function(value) {
   429        value = value || 1;
   430  
   431        this.zoom = this.map.getZoom() + value;
   432        this.map.setZoom(this.zoom);
   433      };
   434  
   435      this.zoomOut = function(value) {
   436        value = value || 1;
   437  
   438        this.zoom = this.map.getZoom() - value;
   439        this.map.setZoom(this.zoom);
   440      };
   441  
   442      var native_methods = [],
   443          method;
   444  
   445      for (method in this.map) {
   446        if (typeof(this.map[method]) == 'function' && !this[method]) {
   447          native_methods.push(method);
   448        }
   449      }
   450  
   451      for (i=0; i < native_methods.length; i++) {
   452        (function(gmaps, scope, method_name) {
   453          gmaps[method_name] = function(){
   454            return scope[method_name].apply(scope, arguments);
   455          };
   456        })(this, this.map, native_methods[i]);
   457      }
   458    };
   459  
   460    return GMaps;
   461  })(this);
   462  
   463  GMaps.prototype.createControl = function(options) {
   464    var control = document.createElement('div');
   465  
   466    control.style.cursor = 'pointer';
   467    control.style.fontFamily = 'Arial, sans-serif';
   468    control.style.fontSize = '13px';
   469    control.style.boxShadow = 'rgba(0, 0, 0, 0.398438) 0px 2px 4px';
   470  
   471    for (var option in options.style) {
   472      control.style[option] = options.style[option];
   473    }
   474  
   475    if (options.id) {
   476      control.id = options.id;
   477    }
   478  
   479    if (options.classes) {
   480      control.className = options.classes;
   481    }
   482  
   483    if (options.content) {
   484      control.innerHTML = options.content;
   485    }
   486  
   487    for (var ev in options.events) {
   488      (function(object, name) {
   489        google.maps.event.addDomListener(object, name, function(){
   490          options.events[name].apply(this, [this]);
   491        });
   492      })(control, ev);
   493    }
   494  
   495    control.index = 1;
   496  
   497    return control;
   498  };
   499  
   500  GMaps.prototype.addControl = function(options) {
   501    var position = google.maps.ControlPosition[options.position.toUpperCase()];
   502  
   503    delete options.position;
   504  
   505    var control = this.createControl(options);
   506    this.controls.push(control);
   507    
   508    this.map.controls[position].push(control);
   509  
   510    return control;
   511  };
   512  
   513  GMaps.prototype.createMarker = function(options) {
   514    if (options.lat == undefined && options.lng == undefined && options.position == undefined) {
   515      throw 'No latitude or longitude defined.';
   516    }
   517  
   518    var self = this,
   519        details = options.details,
   520        fences = options.fences,
   521        outside = options.outside,
   522        base_options = {
   523          position: new google.maps.LatLng(options.lat, options.lng),
   524          map: null
   525        };
   526  
   527    delete options.lat;
   528    delete options.lng;
   529    delete options.fences;
   530    delete options.outside;
   531  
   532    var marker_options = extend_object(base_options, options),
   533        marker = new google.maps.Marker(marker_options);
   534  
   535    marker.fences = fences;
   536  
   537    if (options.infoWindow) {
   538      marker.infoWindow = new google.maps.InfoWindow(options.infoWindow);
   539  
   540      var info_window_events = ['closeclick', 'content_changed', 'domready', 'position_changed', 'zindex_changed'];
   541  
   542      for (var ev = 0; ev < info_window_events.length; ev++) {
   543        (function(object, name) {
   544          if (options.infoWindow[name]) {
   545            google.maps.event.addListener(object, name, function(e){
   546              options.infoWindow[name].apply(this, [e]);
   547            });
   548          }
   549        })(marker.infoWindow, info_window_events[ev]);
   550      }
   551    }
   552  
   553    var marker_events = ['animation_changed', 'clickable_changed', 'cursor_changed', 'draggable_changed', 'flat_changed', 'icon_changed', 'position_changed', 'shadow_changed', 'shape_changed', 'title_changed', 'visible_changed', 'zindex_changed'];
   554  
   555    var marker_events_with_mouse = ['dblclick', 'drag', 'dragend', 'dragstart', 'mousedown', 'mouseout', 'mouseover', 'mouseup'];
   556  
   557    for (var ev = 0; ev < marker_events.length; ev++) {
   558      (function(object, name) {
   559        if (options[name]) {
   560          google.maps.event.addListener(object, name, function(){
   561            options[name].apply(this, [this]);
   562          });
   563        }
   564      })(marker, marker_events[ev]);
   565    }
   566  
   567    for (var ev = 0; ev < marker_events_with_mouse.length; ev++) {
   568      (function(map, object, name) {
   569        if (options[name]) {
   570          google.maps.event.addListener(object, name, function(me){
   571            if(!me.pixel){
   572              me.pixel = map.getProjection().fromLatLngToPoint(me.latLng)
   573            }
   574            
   575            options[name].apply(this, [me]);
   576          });
   577        }
   578      })(this.map, marker, marker_events_with_mouse[ev]);
   579    }
   580  
   581    google.maps.event.addListener(marker, 'click', function() {
   582      this.details = details;
   583  
   584      if (options.click) {
   585        options.click.apply(this, [this]);
   586      }
   587  
   588      if (marker.infoWindow) {
   589        self.hideInfoWindows();
   590        marker.infoWindow.open(self.map, marker);
   591      }
   592    });
   593  
   594    google.maps.event.addListener(marker, 'rightclick', function(e) {
   595      e.marker = this;
   596  
   597      if (options.rightclick) {
   598        options.rightclick.apply(this, [e]);
   599      }
   600  
   601      if (window.context_menu[self.el.id]['marker'] != undefined) {
   602        self.buildContextMenu('marker', e);
   603      }
   604    });
   605  
   606    if (marker.fences) {
   607      google.maps.event.addListener(marker, 'dragend', function() {
   608        self.checkMarkerGeofence(marker, function(m, f) {
   609          outside(m, f);
   610        });
   611      });
   612    }
   613  
   614    return marker;
   615  };
   616  
   617  GMaps.prototype.addMarker = function(options) {
   618    var marker;
   619    if(options.hasOwnProperty('gm_accessors_')) {
   620      // Native google.maps.Marker object
   621      marker = options;
   622    }
   623    else {
   624      if ((options.hasOwnProperty('lat') && options.hasOwnProperty('lng')) || options.position) {
   625        marker = this.createMarker(options);
   626      }
   627      else {
   628        throw 'No latitude or longitude defined.';
   629      }
   630    }
   631  
   632    marker.setMap(this.map);
   633  
   634    if(this.markerClusterer) {
   635      this.markerClusterer.addMarker(marker);
   636    }
   637  
   638    this.markers.push(marker);
   639  
   640    GMaps.fire('marker_added', marker, this);
   641  
   642    return marker;
   643  };
   644  
   645  GMaps.prototype.addMarkers = function(array) {
   646    for (var i = 0, marker; marker=array[i]; i++) {
   647      this.addMarker(marker);
   648    }
   649  
   650    return this.markers;
   651  };
   652  
   653  GMaps.prototype.hideInfoWindows = function() {
   654    for (var i = 0, marker; marker = this.markers[i]; i++){
   655      if (marker.infoWindow){
   656        marker.infoWindow.close();
   657      }
   658    }
   659  };
   660  
   661  GMaps.prototype.removeMarker = function(marker) {
   662    for (var i = 0; i < this.markers.length; i++) {
   663      if (this.markers[i] === marker) {
   664        this.markers[i].setMap(null);
   665        this.markers.splice(i, 1);
   666  
   667        GMaps.fire('marker_removed', marker, this);
   668  
   669        break;
   670      }
   671    }
   672  
   673    return marker;
   674  };
   675  
   676  GMaps.prototype.removeMarkers = function(collection) {
   677    var collection = (collection || this.markers);
   678  
   679    for (var i = 0;i < this.markers.length; i++) {
   680      if(this.markers[i] === collection[i]) {
   681        this.markers[i].setMap(null);
   682      }
   683    }
   684  
   685    var new_markers = [];
   686  
   687    for (var i = 0;i < this.markers.length; i++) {
   688      if(this.markers[i].getMap() != null) {
   689        new_markers.push(this.markers[i]);
   690      }
   691    }
   692  
   693    this.markers = new_markers;
   694  };
   695  
   696  GMaps.prototype.drawOverlay = function(options) {
   697    var overlay = new google.maps.OverlayView(),
   698        auto_show = true;
   699  
   700    overlay.setMap(this.map);
   701  
   702    if (options.auto_show != null) {
   703      auto_show = options.auto_show;
   704    }
   705  
   706    overlay.onAdd = function() {
   707      var el = document.createElement('div');
   708  
   709      el.style.borderStyle = "none";
   710      el.style.borderWidth = "0px";
   711      el.style.position = "absolute";
   712      el.style.zIndex = 100;
   713      el.innerHTML = options.content;
   714  
   715      overlay.el = el;
   716  
   717      if (!options.layer) {
   718        options.layer = 'overlayLayer';
   719      }
   720      
   721      var panes = this.getPanes(),
   722          overlayLayer = panes[options.layer],
   723          stop_overlay_events = ['contextmenu', 'DOMMouseScroll', 'dblclick', 'mousedown'];
   724  
   725      overlayLayer.appendChild(el);
   726  
   727      for (var ev = 0; ev < stop_overlay_events.length; ev++) {
   728        (function(object, name) {
   729          google.maps.event.addDomListener(object, name, function(e){
   730            if (navigator.userAgent.toLowerCase().indexOf('msie') != -1 && document.all) {
   731              e.cancelBubble = true;
   732              e.returnValue = false;
   733            }
   734            else {
   735              e.stopPropagation();
   736            }
   737          });
   738        })(el, stop_overlay_events[ev]);
   739      }
   740  
   741      google.maps.event.trigger(this, 'ready');
   742    };
   743  
   744    overlay.draw = function() {
   745      var projection = this.getProjection(),
   746          pixel = projection.fromLatLngToDivPixel(new google.maps.LatLng(options.lat, options.lng));
   747  
   748      options.horizontalOffset = options.horizontalOffset || 0;
   749      options.verticalOffset = options.verticalOffset || 0;
   750  
   751      var el = overlay.el,
   752          content = el.children[0],
   753          content_height = content.clientHeight,
   754          content_width = content.clientWidth;
   755  
   756      switch (options.verticalAlign) {
   757        case 'top':
   758          el.style.top = (pixel.y - content_height + options.verticalOffset) + 'px';
   759          break;
   760        default:
   761        case 'middle':
   762          el.style.top = (pixel.y - (content_height / 2) + options.verticalOffset) + 'px';
   763          break;
   764        case 'bottom':
   765          el.style.top = (pixel.y + options.verticalOffset) + 'px';
   766          break;
   767      }
   768  
   769      switch (options.horizontalAlign) {
   770        case 'left':
   771          el.style.left = (pixel.x - content_width + options.horizontalOffset) + 'px';
   772          break;
   773        default:
   774        case 'center':
   775          el.style.left = (pixel.x - (content_width / 2) + options.horizontalOffset) + 'px';
   776          break;
   777        case 'right':
   778          el.style.left = (pixel.x + options.horizontalOffset) + 'px';
   779          break;
   780      }
   781  
   782      el.style.display = auto_show ? 'block' : 'none';
   783  
   784      if (!auto_show) {
   785        options.show.apply(this, [el]);
   786      }
   787    };
   788  
   789    overlay.onRemove = function() {
   790      var el = overlay.el;
   791  
   792      if (options.remove) {
   793        options.remove.apply(this, [el]);
   794      }
   795      else {
   796        overlay.el.parentNode.removeChild(overlay.el);
   797        overlay.el = null;
   798      }
   799    };
   800  
   801    this.overlays.push(overlay);
   802    return overlay;
   803  };
   804  
   805  GMaps.prototype.removeOverlay = function(overlay) {
   806    for (var i = 0; i < this.overlays.length; i++) {
   807      if (this.overlays[i] === overlay) {
   808        this.overlays[i].setMap(null);
   809        this.overlays.splice(i, 1);
   810  
   811        break;
   812      }
   813    }
   814  };
   815  
   816  GMaps.prototype.removeOverlays = function() {
   817    for (var i = 0, item; item = this.overlays[i]; i++) {
   818      item.setMap(null);
   819    }
   820  
   821    this.overlays = [];
   822  };
   823  
   824  GMaps.prototype.drawPolyline = function(options) {
   825    var path = [],
   826        points = options.path;
   827  
   828    if (points.length) {
   829      if (points[0][0] === undefined) {
   830        path = points;
   831      }
   832      else {
   833        for (var i=0, latlng; latlng=points[i]; i++) {
   834          path.push(new google.maps.LatLng(latlng[0], latlng[1]));
   835        }
   836      }
   837    }
   838  
   839    var polyline_options = {
   840      map: this.map,
   841      path: path,
   842      strokeColor: options.strokeColor,
   843      strokeOpacity: options.strokeOpacity,
   844      strokeWeight: options.strokeWeight,
   845      geodesic: options.geodesic,
   846      clickable: true,
   847      editable: false,
   848      visible: true
   849    };
   850  
   851    if (options.hasOwnProperty("clickable")) {
   852      polyline_options.clickable = options.clickable;
   853    }
   854  
   855    if (options.hasOwnProperty("editable")) {
   856      polyline_options.editable = options.editable;
   857    }
   858  
   859    if (options.hasOwnProperty("icons")) {
   860      polyline_options.icons = options.icons;
   861    }
   862  
   863    if (options.hasOwnProperty("zIndex")) {
   864      polyline_options.zIndex = options.zIndex;
   865    }
   866  
   867    var polyline = new google.maps.Polyline(polyline_options);
   868  
   869    var polyline_events = ['click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'rightclick'];
   870  
   871    for (var ev = 0; ev < polyline_events.length; ev++) {
   872      (function(object, name) {
   873        if (options[name]) {
   874          google.maps.event.addListener(object, name, function(e){
   875            options[name].apply(this, [e]);
   876          });
   877        }
   878      })(polyline, polyline_events[ev]);
   879    }
   880  
   881    this.polylines.push(polyline);
   882  
   883    GMaps.fire('polyline_added', polyline, this);
   884  
   885    return polyline;
   886  };
   887  
   888  GMaps.prototype.removePolyline = function(polyline) {
   889    for (var i = 0; i < this.polylines.length; i++) {
   890      if (this.polylines[i] === polyline) {
   891        this.polylines[i].setMap(null);
   892        this.polylines.splice(i, 1);
   893  
   894        GMaps.fire('polyline_removed', polyline, this);
   895  
   896        break;
   897      }
   898    }
   899  };
   900  
   901  GMaps.prototype.removePolylines = function() {
   902    for (var i = 0, item; item = this.polylines[i]; i++) {
   903      item.setMap(null);
   904    }
   905  
   906    this.polylines = [];
   907  };
   908  
   909  GMaps.prototype.drawCircle = function(options) {
   910    options =  extend_object({
   911      map: this.map,
   912      center: new google.maps.LatLng(options.lat, options.lng)
   913    }, options);
   914  
   915    delete options.lat;
   916    delete options.lng;
   917  
   918    var polygon = new google.maps.Circle(options),
   919        polygon_events = ['click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'rightclick'];
   920  
   921    for (var ev = 0; ev < polygon_events.length; ev++) {
   922      (function(object, name) {
   923        if (options[name]) {
   924          google.maps.event.addListener(object, name, function(e){
   925            options[name].apply(this, [e]);
   926          });
   927        }
   928      })(polygon, polygon_events[ev]);
   929    }
   930  
   931    this.polygons.push(polygon);
   932  
   933    return polygon;
   934  };
   935  
   936  GMaps.prototype.drawRectangle = function(options) {
   937    options = extend_object({
   938      map: this.map
   939    }, options);
   940  
   941    var latLngBounds = new google.maps.LatLngBounds(
   942      new google.maps.LatLng(options.bounds[0][0], options.bounds[0][1]),
   943      new google.maps.LatLng(options.bounds[1][0], options.bounds[1][1])
   944    );
   945  
   946    options.bounds = latLngBounds;
   947  
   948    var polygon = new google.maps.Rectangle(options),
   949        polygon_events = ['click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'rightclick'];
   950  
   951    for (var ev = 0; ev < polygon_events.length; ev++) {
   952      (function(object, name) {
   953        if (options[name]) {
   954          google.maps.event.addListener(object, name, function(e){
   955            options[name].apply(this, [e]);
   956          });
   957        }
   958      })(polygon, polygon_events[ev]);
   959    }
   960  
   961    this.polygons.push(polygon);
   962  
   963    return polygon;
   964  };
   965  
   966  GMaps.prototype.drawPolygon = function(options) {
   967    var useGeoJSON = false;
   968  
   969    if(options.hasOwnProperty("useGeoJSON")) {
   970      useGeoJSON = options.useGeoJSON;
   971    }
   972  
   973    delete options.useGeoJSON;
   974  
   975    options = extend_object({
   976      map: this.map
   977    }, options);
   978  
   979    if (useGeoJSON == false) {
   980      options.paths = [options.paths.slice(0)];
   981    }
   982  
   983    if (options.paths.length > 0) {
   984      if (options.paths[0].length > 0) {
   985        options.paths = array_flat(array_map(options.paths, arrayToLatLng, useGeoJSON));
   986      }
   987    }
   988  
   989    var polygon = new google.maps.Polygon(options),
   990        polygon_events = ['click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'rightclick'];
   991  
   992    for (var ev = 0; ev < polygon_events.length; ev++) {
   993      (function(object, name) {
   994        if (options[name]) {
   995          google.maps.event.addListener(object, name, function(e){
   996            options[name].apply(this, [e]);
   997          });
   998        }
   999      })(polygon, polygon_events[ev]);
  1000    }
  1001  
  1002    this.polygons.push(polygon);
  1003  
  1004    GMaps.fire('polygon_added', polygon, this);
  1005  
  1006    return polygon;
  1007  };
  1008  
  1009  GMaps.prototype.removePolygon = function(polygon) {
  1010    for (var i = 0; i < this.polygons.length; i++) {
  1011      if (this.polygons[i] === polygon) {
  1012        this.polygons[i].setMap(null);
  1013        this.polygons.splice(i, 1);
  1014  
  1015        GMaps.fire('polygon_removed', polygon, this);
  1016  
  1017        break;
  1018      }
  1019    }
  1020  };
  1021  
  1022  GMaps.prototype.removePolygons = function() {
  1023    for (var i = 0, item; item = this.polygons[i]; i++) {
  1024      item.setMap(null);
  1025    }
  1026  
  1027    this.polygons = [];
  1028  };
  1029  
  1030  GMaps.prototype.getFromFusionTables = function(options) {
  1031    var events = options.events;
  1032  
  1033    delete options.events;
  1034  
  1035    var fusion_tables_options = options,
  1036        layer = new google.maps.FusionTablesLayer(fusion_tables_options);
  1037  
  1038    for (var ev in events) {
  1039      (function(object, name) {
  1040        google.maps.event.addListener(object, name, function(e) {
  1041          events[name].apply(this, [e]);
  1042        });
  1043      })(layer, ev);
  1044    }
  1045  
  1046    this.layers.push(layer);
  1047  
  1048    return layer;
  1049  };
  1050  
  1051  GMaps.prototype.loadFromFusionTables = function(options) {
  1052    var layer = this.getFromFusionTables(options);
  1053    layer.setMap(this.map);
  1054  
  1055    return layer;
  1056  };
  1057  
  1058  GMaps.prototype.getFromKML = function(options) {
  1059    var url = options.url,
  1060        events = options.events;
  1061  
  1062    delete options.url;
  1063    delete options.events;
  1064  
  1065    var kml_options = options,
  1066        layer = new google.maps.KmlLayer(url, kml_options);
  1067  
  1068    for (var ev in events) {
  1069      (function(object, name) {
  1070        google.maps.event.addListener(object, name, function(e) {
  1071          events[name].apply(this, [e]);
  1072        });
  1073      })(layer, ev);
  1074    }
  1075  
  1076    this.layers.push(layer);
  1077  
  1078    return layer;
  1079  };
  1080  
  1081  GMaps.prototype.loadFromKML = function(options) {
  1082    var layer = this.getFromKML(options);
  1083    layer.setMap(this.map);
  1084  
  1085    return layer;
  1086  };
  1087  
  1088  GMaps.prototype.addLayer = function(layerName, options) {
  1089    //var default_layers = ['weather', 'clouds', 'traffic', 'transit', 'bicycling', 'panoramio', 'places'];
  1090    options = options || {};
  1091    var layer;
  1092  
  1093    switch(layerName) {
  1094      case 'weather': this.singleLayers.weather = layer = new google.maps.weather.WeatherLayer();
  1095        break;
  1096      case 'clouds': this.singleLayers.clouds = layer = new google.maps.weather.CloudLayer();
  1097        break;
  1098      case 'traffic': this.singleLayers.traffic = layer = new google.maps.TrafficLayer();
  1099        break;
  1100      case 'transit': this.singleLayers.transit = layer = new google.maps.TransitLayer();
  1101        break;
  1102      case 'bicycling': this.singleLayers.bicycling = layer = new google.maps.BicyclingLayer();
  1103        break;
  1104      case 'panoramio':
  1105          this.singleLayers.panoramio = layer = new google.maps.panoramio.PanoramioLayer();
  1106          layer.setTag(options.filter);
  1107          delete options.filter;
  1108  
  1109          //click event
  1110          if (options.click) {
  1111            google.maps.event.addListener(layer, 'click', function(event) {
  1112              options.click(event);
  1113              delete options.click;
  1114            });
  1115          }
  1116        break;
  1117        case 'places':
  1118          this.singleLayers.places = layer = new google.maps.places.PlacesService(this.map);
  1119  
  1120          //search and  nearbySearch callback, Both are the same
  1121          if (options.search || options.nearbySearch) {
  1122            var placeSearchRequest  = {
  1123              bounds : options.bounds || null,
  1124              keyword : options.keyword || null,
  1125              location : options.location || null,
  1126              name : options.name || null,
  1127              radius : options.radius || null,
  1128              rankBy : options.rankBy || null,
  1129              types : options.types || null
  1130            };
  1131  
  1132            if (options.search) {
  1133              layer.search(placeSearchRequest, options.search);
  1134            }
  1135  
  1136            if (options.nearbySearch) {
  1137              layer.nearbySearch(placeSearchRequest, options.nearbySearch);
  1138            }
  1139          }
  1140  
  1141          //textSearch callback
  1142          if (options.textSearch) {
  1143            var textSearchRequest  = {
  1144              bounds : options.bounds || null,
  1145              location : options.location || null,
  1146              query : options.query || null,
  1147              radius : options.radius || null
  1148            };
  1149  
  1150            layer.textSearch(textSearchRequest, options.textSearch);
  1151          }
  1152        break;
  1153    }
  1154  
  1155    if (layer !== undefined) {
  1156      if (typeof layer.setOptions == 'function') {
  1157        layer.setOptions(options);
  1158      }
  1159      if (typeof layer.setMap == 'function') {
  1160        layer.setMap(this.map);
  1161      }
  1162  
  1163      return layer;
  1164    }
  1165  };
  1166  
  1167  GMaps.prototype.removeLayer = function(layer) {
  1168    if (typeof(layer) == "string" && this.singleLayers[layer] !== undefined) {
  1169       this.singleLayers[layer].setMap(null);
  1170  
  1171       delete this.singleLayers[layer];
  1172    }
  1173    else {
  1174      for (var i = 0; i < this.layers.length; i++) {
  1175        if (this.layers[i] === layer) {
  1176          this.layers[i].setMap(null);
  1177          this.layers.splice(i, 1);
  1178  
  1179          break;
  1180        }
  1181      }
  1182    }
  1183  };
  1184  
  1185  var travelMode, unitSystem;
  1186  
  1187  GMaps.prototype.getRoutes = function(options) {
  1188    switch (options.travelMode) {
  1189      case 'bicycling':
  1190        travelMode = google.maps.TravelMode.BICYCLING;
  1191        break;
  1192      case 'transit':
  1193        travelMode = google.maps.TravelMode.TRANSIT;
  1194        break;
  1195      case 'driving':
  1196        travelMode = google.maps.TravelMode.DRIVING;
  1197        break;
  1198      default:
  1199        travelMode = google.maps.TravelMode.WALKING;
  1200        break;
  1201    }
  1202  
  1203    if (options.unitSystem === 'imperial') {
  1204      unitSystem = google.maps.UnitSystem.IMPERIAL;
  1205    }
  1206    else {
  1207      unitSystem = google.maps.UnitSystem.METRIC;
  1208    }
  1209  
  1210    var base_options = {
  1211          avoidHighways: false,
  1212          avoidTolls: false,
  1213          optimizeWaypoints: false,
  1214          waypoints: []
  1215        },
  1216        request_options =  extend_object(base_options, options);
  1217  
  1218    request_options.origin = /string/.test(typeof options.origin) ? options.origin : new google.maps.LatLng(options.origin[0], options.origin[1]);
  1219    request_options.destination = /string/.test(typeof options.destination) ? options.destination : new google.maps.LatLng(options.destination[0], options.destination[1]);
  1220    request_options.travelMode = travelMode;
  1221    request_options.unitSystem = unitSystem;
  1222  
  1223    delete request_options.callback;
  1224  
  1225    var self = this,
  1226        service = new google.maps.DirectionsService();
  1227  
  1228    service.route(request_options, function(result, status) {
  1229      if (status === google.maps.DirectionsStatus.OK) {
  1230        for (var r in result.routes) {
  1231          if (result.routes.hasOwnProperty(r)) {
  1232            self.routes.push(result.routes[r]);
  1233          }
  1234        }
  1235      }
  1236  
  1237      if (options.callback) {
  1238        options.callback(self.routes);
  1239      }
  1240    });
  1241  };
  1242  
  1243  GMaps.prototype.removeRoutes = function() {
  1244    this.routes = [];
  1245  };
  1246  
  1247  GMaps.prototype.getElevations = function(options) {
  1248    options = extend_object({
  1249      locations: [],
  1250      path : false,
  1251      samples : 256
  1252    }, options);
  1253  
  1254    if (options.locations.length > 0) {
  1255      if (options.locations[0].length > 0) {
  1256        options.locations = array_flat(array_map([options.locations], arrayToLatLng,  false));
  1257      }
  1258    }
  1259  
  1260    var callback = options.callback;
  1261    delete options.callback;
  1262  
  1263    var service = new google.maps.ElevationService();
  1264  
  1265    //location request
  1266    if (!options.path) {
  1267      delete options.path;
  1268      delete options.samples;
  1269  
  1270      service.getElevationForLocations(options, function(result, status) {
  1271        if (callback && typeof(callback) === "function") {
  1272          callback(result, status);
  1273        }
  1274      });
  1275    //path request
  1276    } else {
  1277      var pathRequest = {
  1278        path : options.locations,
  1279        samples : options.samples
  1280      };
  1281  
  1282      service.getElevationAlongPath(pathRequest, function(result, status) {
  1283       if (callback && typeof(callback) === "function") {
  1284          callback(result, status);
  1285        }
  1286      });
  1287    }
  1288  };
  1289  
  1290  GMaps.prototype.cleanRoute = GMaps.prototype.removePolylines;
  1291  
  1292  GMaps.prototype.drawRoute = function(options) {
  1293    var self = this;
  1294  
  1295    this.getRoutes({
  1296      origin: options.origin,
  1297      destination: options.destination,
  1298      travelMode: options.travelMode,
  1299      waypoints: options.waypoints,
  1300      unitSystem: options.unitSystem,
  1301      callback: function(e) {
  1302        if (e.length > 0) {
  1303          self.drawPolyline({
  1304            path: e[e.length - 1].overview_path,
  1305            strokeColor: options.strokeColor,
  1306            strokeOpacity: options.strokeOpacity,
  1307            strokeWeight: options.strokeWeight
  1308          });
  1309          
  1310          if (options.callback) {
  1311            options.callback(e[e.length - 1]);
  1312          }
  1313        }
  1314      }
  1315    });
  1316  };
  1317  
  1318  GMaps.prototype.travelRoute = function(options) {
  1319    if (options.origin && options.destination) {
  1320      this.getRoutes({
  1321        origin: options.origin,
  1322        destination: options.destination,
  1323        travelMode: options.travelMode,
  1324        waypoints : options.waypoints,
  1325        callback: function(e) {
  1326          //start callback
  1327          if (e.length > 0 && options.start) {
  1328            options.start(e[e.length - 1]);
  1329          }
  1330  
  1331          //step callback
  1332          if (e.length > 0 && options.step) {
  1333            var route = e[e.length - 1];
  1334            if (route.legs.length > 0) {
  1335              var steps = route.legs[0].steps;
  1336              for (var i=0, step; step=steps[i]; i++) {
  1337                step.step_number = i;
  1338                options.step(step, (route.legs[0].steps.length - 1));
  1339              }
  1340            }
  1341          }
  1342  
  1343          //end callback
  1344          if (e.length > 0 && options.end) {
  1345             options.end(e[e.length - 1]);
  1346          }
  1347        }
  1348      });
  1349    }
  1350    else if (options.route) {
  1351      if (options.route.legs.length > 0) {
  1352        var steps = options.route.legs[0].steps;
  1353        for (var i=0, step; step=steps[i]; i++) {
  1354          step.step_number = i;
  1355          options.step(step);
  1356        }
  1357      }
  1358    }
  1359  };
  1360  
  1361  GMaps.prototype.drawSteppedRoute = function(options) {
  1362    var self = this;
  1363    
  1364    if (options.origin && options.destination) {
  1365      this.getRoutes({
  1366        origin: options.origin,
  1367        destination: options.destination,
  1368        travelMode: options.travelMode,
  1369        waypoints : options.waypoints,
  1370        callback: function(e) {
  1371          //start callback
  1372          if (e.length > 0 && options.start) {
  1373            options.start(e[e.length - 1]);
  1374          }
  1375  
  1376          //step callback
  1377          if (e.length > 0 && options.step) {
  1378            var route = e[e.length - 1];
  1379            if (route.legs.length > 0) {
  1380              var steps = route.legs[0].steps;
  1381              for (var i=0, step; step=steps[i]; i++) {
  1382                step.step_number = i;
  1383                self.drawPolyline({
  1384                  path: step.path,
  1385                  strokeColor: options.strokeColor,
  1386                  strokeOpacity: options.strokeOpacity,
  1387                  strokeWeight: options.strokeWeight
  1388                });
  1389                options.step(step, (route.legs[0].steps.length - 1));
  1390              }
  1391            }
  1392          }
  1393  
  1394          //end callback
  1395          if (e.length > 0 && options.end) {
  1396             options.end(e[e.length - 1]);
  1397          }
  1398        }
  1399      });
  1400    }
  1401    else if (options.route) {
  1402      if (options.route.legs.length > 0) {
  1403        var steps = options.route.legs[0].steps;
  1404        for (var i=0, step; step=steps[i]; i++) {
  1405          step.step_number = i;
  1406          self.drawPolyline({
  1407            path: step.path,
  1408            strokeColor: options.strokeColor,
  1409            strokeOpacity: options.strokeOpacity,
  1410            strokeWeight: options.strokeWeight
  1411          });
  1412          options.step(step);
  1413        }
  1414      }
  1415    }
  1416  };
  1417  
  1418  GMaps.Route = function(options) {
  1419    this.origin = options.origin;
  1420    this.destination = options.destination;
  1421    this.waypoints = options.waypoints;
  1422  
  1423    this.map = options.map;
  1424    this.route = options.route;
  1425    this.step_count = 0;
  1426    this.steps = this.route.legs[0].steps;
  1427    this.steps_length = this.steps.length;
  1428  
  1429    this.polyline = this.map.drawPolyline({
  1430      path: new google.maps.MVCArray(),
  1431      strokeColor: options.strokeColor,
  1432      strokeOpacity: options.strokeOpacity,
  1433      strokeWeight: options.strokeWeight
  1434    }).getPath();
  1435  };
  1436  
  1437  GMaps.Route.prototype.getRoute = function(options) {
  1438    var self = this;
  1439  
  1440    this.map.getRoutes({
  1441      origin : this.origin,
  1442      destination : this.destination,
  1443      travelMode : options.travelMode,
  1444      waypoints : this.waypoints || [],
  1445      callback : function() {
  1446        self.route = e[0];
  1447  
  1448        if (options.callback) {
  1449          options.callback.call(self);
  1450        }
  1451      }
  1452    });
  1453  };
  1454  
  1455  GMaps.Route.prototype.back = function() {
  1456    if (this.step_count > 0) {
  1457      this.step_count--;
  1458      var path = this.route.legs[0].steps[this.step_count].path;
  1459  
  1460      for (var p in path){
  1461        if (path.hasOwnProperty(p)){
  1462          this.polyline.pop();
  1463        }
  1464      }
  1465    }
  1466  };
  1467  
  1468  GMaps.Route.prototype.forward = function() {
  1469    if (this.step_count < this.steps_length) {
  1470      var path = this.route.legs[0].steps[this.step_count].path;
  1471  
  1472      for (var p in path){
  1473        if (path.hasOwnProperty(p)){
  1474          this.polyline.push(path[p]);
  1475        }
  1476      }
  1477      this.step_count++;
  1478    }
  1479  };
  1480  
  1481  GMaps.prototype.checkGeofence = function(lat, lng, fence) {
  1482    return fence.containsLatLng(new google.maps.LatLng(lat, lng));
  1483  };
  1484  
  1485  GMaps.prototype.checkMarkerGeofence = function(marker, outside_callback) {
  1486    if (marker.fences) {
  1487      for (var i = 0, fence; fence = marker.fences[i]; i++) {
  1488        var pos = marker.getPosition();
  1489        if (!this.checkGeofence(pos.lat(), pos.lng(), fence)) {
  1490          outside_callback(marker, fence);
  1491        }
  1492      }
  1493    }
  1494  };
  1495  
  1496  GMaps.prototype.toImage = function(options) {
  1497    var options = options || {},
  1498        static_map_options = {};
  1499  
  1500    static_map_options['size'] = options['size'] || [this.el.clientWidth, this.el.clientHeight];
  1501    static_map_options['lat'] = this.getCenter().lat();
  1502    static_map_options['lng'] = this.getCenter().lng();
  1503  
  1504    if (this.markers.length > 0) {
  1505      static_map_options['markers'] = [];
  1506      
  1507      for (var i = 0; i < this.markers.length; i++) {
  1508        static_map_options['markers'].push({
  1509          lat: this.markers[i].getPosition().lat(),
  1510          lng: this.markers[i].getPosition().lng()
  1511        });
  1512      }
  1513    }
  1514  
  1515    if (this.polylines.length > 0) {
  1516      var polyline = this.polylines[0];
  1517      
  1518      static_map_options['polyline'] = {};
  1519      static_map_options['polyline']['path'] = google.maps.geometry.encoding.encodePath(polyline.getPath());
  1520      static_map_options['polyline']['strokeColor'] = polyline.strokeColor
  1521      static_map_options['polyline']['strokeOpacity'] = polyline.strokeOpacity
  1522      static_map_options['polyline']['strokeWeight'] = polyline.strokeWeight
  1523    }
  1524  
  1525    return GMaps.staticMapURL(static_map_options);
  1526  };
  1527  
  1528  GMaps.staticMapURL = function(options){
  1529    var parameters = [],
  1530        data,
  1531        static_root = 'http://maps.googleapis.com/maps/api/staticmap';
  1532  
  1533    if (options.url) {
  1534      static_root = options.url;
  1535      delete options.url;
  1536    }
  1537  
  1538    static_root += '?';
  1539  
  1540    var markers = options.markers;
  1541    
  1542    delete options.markers;
  1543  
  1544    if (!markers && options.marker) {
  1545      markers = [options.marker];
  1546      delete options.marker;
  1547    }
  1548  
  1549    var polyline = options.polyline;
  1550    delete options.polyline;
  1551  
  1552    /** Map options **/
  1553    if (options.center) {
  1554      parameters.push('center=' + options.center);
  1555      delete options.center;
  1556    }
  1557    else if (options.address) {
  1558      parameters.push('center=' + options.address);
  1559      delete options.address;
  1560    }
  1561    else if (options.lat) {
  1562      parameters.push(['center=', options.lat, ',', options.lng].join(''));
  1563      delete options.lat;
  1564      delete options.lng;
  1565    }
  1566    else if (options.visible) {
  1567      var visible = encodeURI(options.visible.join('|'));
  1568      parameters.push('visible=' + visible);
  1569    }
  1570  
  1571    var size = options.size;
  1572    if (size) {
  1573      if (size.join) {
  1574        size = size.join('x');
  1575      }
  1576      delete options.size;
  1577    }
  1578    else {
  1579      size = '630x300';
  1580    }
  1581    parameters.push('size=' + size);
  1582  
  1583    if (!options.zoom) {
  1584      options.zoom = 15;
  1585    }
  1586  
  1587    var sensor = options.hasOwnProperty('sensor') ? !!options.sensor : true;
  1588    delete options.sensor;
  1589    parameters.push('sensor=' + sensor);
  1590  
  1591    for (var param in options) {
  1592      if (options.hasOwnProperty(param)) {
  1593        parameters.push(param + '=' + options[param]);
  1594      }
  1595    }
  1596  
  1597    /** Markers **/
  1598    if (markers) {
  1599      var marker, loc;
  1600  
  1601      for (var i=0; data=markers[i]; i++) {
  1602        marker = [];
  1603  
  1604        if (data.size && data.size !== 'normal') {
  1605          marker.push('size:' + data.size);
  1606        }
  1607        else if (data.icon) {
  1608          marker.push('icon:' + encodeURI(data.icon));
  1609        }
  1610  
  1611        if (data.color) {
  1612          marker.push('color:' + data.color.replace('#', '0x'));
  1613        }
  1614  
  1615        if (data.label) {
  1616          marker.push('label:' + data.label[0].toUpperCase());
  1617        }
  1618  
  1619        loc = (data.address ? data.address : data.lat + ',' + data.lng);
  1620  
  1621        if (marker.length || i === 0) {
  1622          marker.push(loc);
  1623          marker = marker.join('|');
  1624          parameters.push('markers=' + encodeURI(marker));
  1625        }
  1626        // New marker without styles
  1627        else {
  1628          marker = parameters.pop() + encodeURI('|' + loc);
  1629          parameters.push(marker);
  1630        }
  1631      }
  1632    }
  1633  
  1634    /** Polylines **/
  1635    function parseColor(color, opacity) {
  1636      if (color[0] === '#'){
  1637        color = color.replace('#', '0x');
  1638  
  1639        if (opacity) {
  1640          opacity = parseFloat(opacity);
  1641          opacity = Math.min(1, Math.max(opacity, 0));
  1642          if (opacity === 0) {
  1643            return '0x00000000';
  1644          }
  1645          opacity = (opacity * 255).toString(16);
  1646          if (opacity.length === 1) {
  1647            opacity += opacity;
  1648          }
  1649  
  1650          color = color.slice(0,8) + opacity;
  1651        }
  1652      }
  1653      return color;
  1654    }
  1655  
  1656    if (polyline) {
  1657      data = polyline;
  1658      polyline = [];
  1659  
  1660      if (data.strokeWeight) {
  1661        polyline.push('weight:' + parseInt(data.strokeWeight, 10));
  1662      }
  1663  
  1664      if (data.strokeColor) {
  1665        var color = parseColor(data.strokeColor, data.strokeOpacity);
  1666        polyline.push('color:' + color);
  1667      }
  1668  
  1669      if (data.fillColor) {
  1670        var fillcolor = parseColor(data.fillColor, data.fillOpacity);
  1671        polyline.push('fillcolor:' + fillcolor);
  1672      }
  1673  
  1674      var path = data.path;
  1675      if (path.join) {
  1676        for (var j=0, pos; pos=path[j]; j++) {
  1677          polyline.push(pos.join(','));
  1678        }
  1679      }
  1680      else {
  1681        polyline.push('enc:' + path);
  1682      }
  1683  
  1684      polyline = polyline.join('|');
  1685      parameters.push('path=' + encodeURI(polyline));
  1686    }
  1687  
  1688    parameters = parameters.join('&');
  1689    return static_root + parameters;
  1690  };
  1691  
  1692  GMaps.prototype.addMapType = function(mapTypeId, options) {
  1693    if (options.hasOwnProperty("getTileUrl") && typeof(options["getTileUrl"]) == "function") {
  1694      options.tileSize = options.tileSize || new google.maps.Size(256, 256);
  1695  
  1696      var mapType = new google.maps.ImageMapType(options);
  1697  
  1698      this.map.mapTypes.set(mapTypeId, mapType);
  1699    }
  1700    else {
  1701      throw "'getTileUrl' function required.";
  1702    }
  1703  };
  1704  
  1705  GMaps.prototype.addOverlayMapType = function(options) {
  1706    if (options.hasOwnProperty("getTile") && typeof(options["getTile"]) == "function") {
  1707      var overlayMapTypeIndex = options.index;
  1708  
  1709      delete options.index;
  1710  
  1711      this.map.overlayMapTypes.insertAt(overlayMapTypeIndex, options);
  1712    }
  1713    else {
  1714      throw "'getTile' function required.";
  1715    }
  1716  };
  1717  
  1718  GMaps.prototype.removeOverlayMapType = function(overlayMapTypeIndex) {
  1719    this.map.overlayMapTypes.removeAt(overlayMapTypeIndex);
  1720  };
  1721  
  1722  GMaps.prototype.addStyle = function(options) {
  1723    var styledMapType = new google.maps.StyledMapType(options.styles, options.styledMapName);
  1724  
  1725    this.map.mapTypes.set(options.mapTypeId, styledMapType);
  1726  };
  1727  
  1728  GMaps.prototype.setStyle = function(mapTypeId) {
  1729    this.map.setMapTypeId(mapTypeId);
  1730  };
  1731  
  1732  GMaps.prototype.createPanorama = function(streetview_options) {
  1733    if (!streetview_options.hasOwnProperty('lat') || !streetview_options.hasOwnProperty('lng')) {
  1734      streetview_options.lat = this.getCenter().lat();
  1735      streetview_options.lng = this.getCenter().lng();
  1736    }
  1737  
  1738    this.panorama = GMaps.createPanorama(streetview_options);
  1739  
  1740    this.map.setStreetView(this.panorama);
  1741  
  1742    return this.panorama;
  1743  };
  1744  
  1745  GMaps.createPanorama = function(options) {
  1746    var el = getElementById(options.el, options.context);
  1747  
  1748    options.position = new google.maps.LatLng(options.lat, options.lng);
  1749  
  1750    delete options.el;
  1751    delete options.context;
  1752    delete options.lat;
  1753    delete options.lng;
  1754  
  1755    var streetview_events = ['closeclick', 'links_changed', 'pano_changed', 'position_changed', 'pov_changed', 'resize', 'visible_changed'],
  1756        streetview_options = extend_object({visible : true}, options);
  1757  
  1758    for (var i = 0; i < streetview_events.length; i++) {
  1759      delete streetview_options[streetview_events[i]];
  1760    }
  1761  
  1762    var panorama = new google.maps.StreetViewPanorama(el, streetview_options);
  1763  
  1764    for (var i = 0; i < streetview_events.length; i++) {
  1765      (function(object, name) {
  1766        if (options[name]) {
  1767          google.maps.event.addListener(object, name, function(){
  1768            options[name].apply(this);
  1769          });
  1770        }
  1771      })(panorama, streetview_events[i]);
  1772    }
  1773  
  1774    return panorama;
  1775  };
  1776  
  1777  GMaps.prototype.on = function(event_name, handler) {
  1778    return GMaps.on(event_name, this, handler);
  1779  };
  1780  
  1781  GMaps.prototype.off = function(event_name) {
  1782    GMaps.off(event_name, this);
  1783  };
  1784  
  1785  GMaps.custom_events = ['marker_added', 'marker_removed', 'polyline_added', 'polyline_removed', 'polygon_added', 'polygon_removed', 'geolocated', 'geolocation_failed'];
  1786  
  1787  GMaps.on = function(event_name, object, handler) {
  1788    if (GMaps.custom_events.indexOf(event_name) == -1) {
  1789      return google.maps.event.addListener(object, event_name, handler);
  1790    }
  1791    else {
  1792      var registered_event = {
  1793        handler : handler,
  1794        eventName : event_name
  1795      };
  1796  
  1797      object.registered_events[event_name] = object.registered_events[event_name] || [];
  1798      object.registered_events[event_name].push(registered_event);
  1799  
  1800      return registered_event;
  1801    }
  1802  };
  1803  
  1804  GMaps.off = function(event_name, object) {
  1805    if (GMaps.custom_events.indexOf(event_name) == -1) {
  1806      google.maps.event.clearListeners(object, event_name);
  1807    }
  1808    else {
  1809      object.registered_events[event_name] = [];
  1810    }
  1811  };
  1812  
  1813  GMaps.fire = function(event_name, object, scope) {
  1814    if (GMaps.custom_events.indexOf(event_name) == -1) {
  1815      google.maps.event.trigger(object, event_name, Array.prototype.slice.apply(arguments).slice(2));
  1816    }
  1817    else {
  1818      if(event_name in scope.registered_events) {
  1819        var firing_events = scope.registered_events[event_name];
  1820  
  1821        for(var i = 0; i < firing_events.length; i++) {
  1822          (function(handler, scope, object) {
  1823            handler.apply(scope, [object]);
  1824          })(firing_events[i]['handler'], scope, object);
  1825        }
  1826      }
  1827    }
  1828  };
  1829  
  1830  GMaps.geolocate = function(options) {
  1831    var complete_callback = options.always || options.complete;
  1832  
  1833    if (navigator.geolocation) {
  1834      navigator.geolocation.getCurrentPosition(function(position) {
  1835        options.success(position);
  1836  
  1837        if (complete_callback) {
  1838          complete_callback();
  1839        }
  1840      }, function(error) {
  1841        options.error(error);
  1842  
  1843        if (complete_callback) {
  1844          complete_callback();
  1845        }
  1846      }, options.options);
  1847    }
  1848    else {
  1849      options.not_supported();
  1850  
  1851      if (complete_callback) {
  1852        complete_callback();
  1853      }
  1854    }
  1855  };
  1856  
  1857  GMaps.geocode = function(options) {
  1858    this.geocoder = new google.maps.Geocoder();
  1859    var callback = options.callback;
  1860    if (options.hasOwnProperty('lat') && options.hasOwnProperty('lng')) {
  1861      options.latLng = new google.maps.LatLng(options.lat, options.lng);
  1862    }
  1863  
  1864    delete options.lat;
  1865    delete options.lng;
  1866    delete options.callback;
  1867    
  1868    this.geocoder.geocode(options, function(results, status) {
  1869      callback(results, status);
  1870    });
  1871  };
  1872  
  1873  //==========================
  1874  // Polygon containsLatLng
  1875  // https://github.com/tparkin/Google-Maps-Point-in-Polygon
  1876  // Poygon getBounds extension - google-maps-extensions
  1877  // http://code.google.com/p/google-maps-extensions/source/browse/google.maps.Polygon.getBounds.js
  1878  if (!google.maps.Polygon.prototype.getBounds) {
  1879    google.maps.Polygon.prototype.getBounds = function(latLng) {
  1880      var bounds = new google.maps.LatLngBounds();
  1881      var paths = this.getPaths();
  1882      var path;
  1883  
  1884      for (var p = 0; p < paths.getLength(); p++) {
  1885        path = paths.getAt(p);
  1886        for (var i = 0; i < path.getLength(); i++) {
  1887          bounds.extend(path.getAt(i));
  1888        }
  1889      }
  1890  
  1891      return bounds;
  1892    };
  1893  }
  1894  
  1895  if (!google.maps.Polygon.prototype.containsLatLng) {
  1896    // Polygon containsLatLng - method to determine if a latLng is within a polygon
  1897    google.maps.Polygon.prototype.containsLatLng = function(latLng) {
  1898      // Exclude points outside of bounds as there is no way they are in the poly
  1899      var bounds = this.getBounds();
  1900  
  1901      if (bounds !== null && !bounds.contains(latLng)) {
  1902        return false;
  1903      }
  1904  
  1905      // Raycast point in polygon method
  1906      var inPoly = false;
  1907  
  1908      var numPaths = this.getPaths().getLength();
  1909      for (var p = 0; p < numPaths; p++) {
  1910        var path = this.getPaths().getAt(p);
  1911        var numPoints = path.getLength();
  1912        var j = numPoints - 1;
  1913  
  1914        for (var i = 0; i < numPoints; i++) {
  1915          var vertex1 = path.getAt(i);
  1916          var vertex2 = path.getAt(j);
  1917  
  1918          if (vertex1.lng() < latLng.lng() && vertex2.lng() >= latLng.lng() || vertex2.lng() < latLng.lng() && vertex1.lng() >= latLng.lng()) {
  1919            if (vertex1.lat() + (latLng.lng() - vertex1.lng()) / (vertex2.lng() - vertex1.lng()) * (vertex2.lat() - vertex1.lat()) < latLng.lat()) {
  1920              inPoly = !inPoly;
  1921            }
  1922          }
  1923  
  1924          j = i;
  1925        }
  1926      }
  1927  
  1928      return inPoly;
  1929    };
  1930  }
  1931  
  1932  google.maps.LatLngBounds.prototype.containsLatLng = function(latLng) {
  1933    return this.contains(latLng);
  1934  };
  1935  
  1936  google.maps.Marker.prototype.setFences = function(fences) {
  1937    this.fences = fences;
  1938  };
  1939  
  1940  google.maps.Marker.prototype.addFence = function(fence) {
  1941    this.fences.push(fence);
  1942  };
  1943  
  1944  google.maps.Marker.prototype.getId = function() {
  1945    return this['__gm_id'];
  1946  };
  1947  
  1948  //==========================
  1949  // Array indexOf
  1950  // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf
  1951  if (!Array.prototype.indexOf) {
  1952    Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
  1953        "use strict";
  1954        if (this == null) {
  1955            throw new TypeError();
  1956        }
  1957        var t = Object(this);
  1958        var len = t.length >>> 0;
  1959        if (len === 0) {
  1960            return -1;
  1961        }
  1962        var n = 0;
  1963        if (arguments.length > 1) {
  1964            n = Number(arguments[1]);
  1965            if (n != n) { // shortcut for verifying if it's NaN
  1966                n = 0;
  1967            } else if (n != 0 && n != Infinity && n != -Infinity) {
  1968                n = (n > 0 || -1) * Math.floor(Math.abs(n));
  1969            }
  1970        }
  1971        if (n >= len) {
  1972            return -1;
  1973        }
  1974        var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
  1975        for (; k < len; k++) {
  1976            if (k in t && t[k] === searchElement) {
  1977                return k;
  1978            }
  1979        }
  1980        return -1;
  1981    }
  1982  }