ff-streaming-platform

git clone git://archive.git.mtrnord.blog/MTRNord/ff-streaming-platform.git
Log | Files | Refs | README | LICENSE

gmaps.js (55095B)


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