gmaps.core.js

  1. var extend_object = function(obj, new_obj) {
  2. var name;
  3. if (obj === new_obj) {
  4. return obj;
  5. }
  6. for (name in new_obj) {
  7. if (new_obj[name] !== undefined) {
  8. obj[name] = new_obj[name];
  9. }
  10. }
  11. return obj;
  12. };
  13. var replace_object = function(obj, replace) {
  14. var name;
  15. if (obj === replace) {
  16. return obj;
  17. }
  18. for (name in replace) {
  19. if (obj[name] != undefined) {
  20. obj[name] = replace[name];
  21. }
  22. }
  23. return obj;
  24. };
  25. var array_map = function(array, callback) {
  26. var original_callback_params = Array.prototype.slice.call(arguments, 2),
  27. array_return = [],
  28. array_length = array.length,
  29. i;
  30. if (Array.prototype.map && array.map === Array.prototype.map) {
  31. array_return = Array.prototype.map.call(array, function(item) {
  32. var callback_params = original_callback_params.slice(0);
  33. callback_params.splice(0, 0, item);
  34. return callback.apply(this, callback_params);
  35. });
  36. }
  37. else {
  38. for (i = 0; i < array_length; i++) {
  39. callback_params = original_callback_params;
  40. callback_params.splice(0, 0, array[i]);
  41. array_return.push(callback.apply(this, callback_params));
  42. }
  43. }
  44. return array_return;
  45. };
  46. var array_flat = function(array) {
  47. var new_array = [],
  48. i;
  49. for (i = 0; i < array.length; i++) {
  50. new_array = new_array.concat(array[i]);
  51. }
  52. return new_array;
  53. };
  54. var coordsToLatLngs = function(coords, useGeoJSON) {
  55. var first_coord = coords[0],
  56. second_coord = coords[1];
  57. if (useGeoJSON) {
  58. first_coord = coords[1];
  59. second_coord = coords[0];
  60. }
  61. return new google.maps.LatLng(first_coord, second_coord);
  62. };
  63. var arrayToLatLng = function(coords, useGeoJSON) {
  64. var i;
  65. for (i = 0; i < coords.length; i++) {
  66. if (!(coords[i] instanceof google.maps.LatLng)) {
  67. if (coords[i].length > 0 && typeof(coords[i][0]) === "object") {
  68. coords[i] = arrayToLatLng(coords[i], useGeoJSON);
  69. }
  70. else {
  71. coords[i] = coordsToLatLngs(coords[i], useGeoJSON);
  72. }
  73. }
  74. }
  75. return coords;
  76. };
  77. var getElementsByClassName = function (class_name, context) {
  78. var element,
  79. _class = class_name.replace('.', '');
  80. if ('jQuery' in this && context) {
  81. element = $("." + _class, context)[0];
  82. } else {
  83. element = document.getElementsByClassName(_class)[0];
  84. }
  85. return element;
  86. };
  87. var getElementById = function(id, context) {
  88. var element,
  89. id = id.replace('#', '');
  90. if ('jQuery' in window && context) {
  91. element = $('#' + id, context)[0];
  92. } else {
  93. element = document.getElementById(id);
  94. };
  95. return element;
  96. };
  97. var findAbsolutePosition = function(obj) {
  98. var curleft = 0,
  99. curtop = 0;
  100. if (obj.offsetParent) {
  101. do {
  102. curleft += obj.offsetLeft;
  103. curtop += obj.offsetTop;
  104. } while (obj = obj.offsetParent);
  105. }
  106. return [curleft, curtop];
  107. };
  108. var GMaps = (function(global) {
  109. "use strict";
  110. var doc = document;
  111. /**
  112. * Creates a new GMaps instance, including a Google Maps map.
  113. * @class GMaps
  114. * @constructs
  115. * @param {object} options - `options` accepts all the [MapOptions](https://developers.google.com/maps/documentation/javascript/reference#MapOptions) and [events](https://developers.google.com/maps/documentation/javascript/reference#Map) listed in the Google Maps API. Also accepts:
  116. * * `lat` (number): Latitude of the map's center
  117. * * `lng` (number): Longitude of the map's center
  118. * * `el` (string or HTMLElement): container where the map will be rendered
  119. * * `markerClusterer` (function): A function to create a marker cluster. You can use MarkerClusterer or MarkerClustererPlus.
  120. */
  121. var GMaps = function(options) {
  122. if (!(typeof window.google === 'object' && window.google.maps)) {
  123. if (typeof window.console === 'object' && window.console.error) {
  124. console.error('Google Maps API is required. Please register the following JavaScript library https://maps.googleapis.com/maps/api/js.');
  125. }
  126. return function() {};
  127. }
  128. if (!this) return new GMaps(options);
  129. options.zoom = options.zoom || 15;
  130. options.mapType = options.mapType || 'roadmap';
  131. var valueOrDefault = function(value, defaultValue) {
  132. return value === undefined ? defaultValue : value;
  133. };
  134. var self = this,
  135. i,
  136. events_that_hide_context_menu = [
  137. 'bounds_changed', 'center_changed', 'click', 'dblclick', 'drag',
  138. 'dragend', 'dragstart', 'idle', 'maptypeid_changed', 'projection_changed',
  139. 'resize', 'tilesloaded', 'zoom_changed'
  140. ],
  141. events_that_doesnt_hide_context_menu = ['mousemove', 'mouseout', 'mouseover'],
  142. options_to_be_deleted = ['el', 'lat', 'lng', 'mapType', 'width', 'height', 'markerClusterer', 'enableNewStyle'],
  143. identifier = options.el || options.div,
  144. markerClustererFunction = options.markerClusterer,
  145. mapType = google.maps.MapTypeId[options.mapType.toUpperCase()],
  146. map_center = new google.maps.LatLng(options.lat, options.lng),
  147. zoomControl = valueOrDefault(options.zoomControl, true),
  148. zoomControlOpt = options.zoomControlOpt || {
  149. style: 'DEFAULT',
  150. position: 'TOP_LEFT'
  151. },
  152. zoomControlStyle = zoomControlOpt.style || 'DEFAULT',
  153. zoomControlPosition = zoomControlOpt.position || 'TOP_LEFT',
  154. panControl = valueOrDefault(options.panControl, true),
  155. mapTypeControl = valueOrDefault(options.mapTypeControl, true),
  156. scaleControl = valueOrDefault(options.scaleControl, true),
  157. streetViewControl = valueOrDefault(options.streetViewControl, true),
  158. overviewMapControl = valueOrDefault(overviewMapControl, true),
  159. map_options = {},
  160. map_base_options = {
  161. zoom: this.zoom,
  162. center: map_center,
  163. mapTypeId: mapType
  164. },
  165. map_controls_options = {
  166. panControl: panControl,
  167. zoomControl: zoomControl,
  168. zoomControlOptions: {
  169. style: google.maps.ZoomControlStyle[zoomControlStyle],
  170. position: google.maps.ControlPosition[zoomControlPosition]
  171. },
  172. mapTypeControl: mapTypeControl,
  173. scaleControl: scaleControl,
  174. streetViewControl: streetViewControl,
  175. overviewMapControl: overviewMapControl
  176. };
  177. if (typeof(options.el) === 'string' || typeof(options.div) === 'string') {
  178. if (identifier.indexOf("#") > -1) {
  179. /**
  180. * Container element
  181. *
  182. * @type {HTMLElement}
  183. */
  184. this.el = getElementById(identifier, options.context);
  185. } else {
  186. this.el = getElementsByClassName.apply(this, [identifier, options.context]);
  187. }
  188. } else {
  189. this.el = identifier;
  190. }
  191. if (typeof(this.el) === 'undefined' || this.el === null) {
  192. throw 'No element defined.';
  193. }
  194. window.context_menu = window.context_menu || {};
  195. window.context_menu[self.el.id] = {};
  196. /**
  197. * Collection of custom controls in the map UI
  198. *
  199. * @type {array}
  200. */
  201. this.controls = [];
  202. /**
  203. * Collection of map's overlays
  204. *
  205. * @type {array}
  206. */
  207. this.overlays = [];
  208. /**
  209. * Collection of KML/GeoRSS and FusionTable layers
  210. *
  211. * @type {array}
  212. */
  213. this.layers = [];
  214. /**
  215. * Collection of data layers (See {@link GMaps#addLayer})
  216. *
  217. * @type {object}
  218. */
  219. this.singleLayers = {};
  220. /**
  221. * Collection of map's markers
  222. *
  223. * @type {array}
  224. */
  225. this.markers = [];
  226. /**
  227. * Collection of map's lines
  228. *
  229. * @type {array}
  230. */
  231. this.polylines = [];
  232. /**
  233. * Collection of map's routes requested by {@link GMaps#getRoutes}, {@link GMaps#renderRoute}, {@link GMaps#drawRoute}, {@link GMaps#travelRoute} or {@link GMaps#drawSteppedRoute}
  234. *
  235. * @type {array}
  236. */
  237. this.routes = [];
  238. /**
  239. * Collection of map's polygons
  240. *
  241. * @type {array}
  242. */
  243. this.polygons = [];
  244. this.infoWindow = null;
  245. this.overlay_el = null;
  246. /**
  247. * Current map's zoom
  248. *
  249. * @type {number}
  250. */
  251. this.zoom = options.zoom;
  252. this.registered_events = {};
  253. this.el.style.width = options.width || this.el.scrollWidth || this.el.offsetWidth;
  254. this.el.style.height = options.height || this.el.scrollHeight || this.el.offsetHeight;
  255. google.maps.visualRefresh = options.enableNewStyle;
  256. for (i = 0; i < options_to_be_deleted.length; i++) {
  257. delete options[options_to_be_deleted[i]];
  258. }
  259. if(options.disableDefaultUI != true) {
  260. map_base_options = extend_object(map_base_options, map_controls_options);
  261. }
  262. map_options = extend_object(map_base_options, options);
  263. for (i = 0; i < events_that_hide_context_menu.length; i++) {
  264. delete map_options[events_that_hide_context_menu[i]];
  265. }
  266. for (i = 0; i < events_that_doesnt_hide_context_menu.length; i++) {
  267. delete map_options[events_that_doesnt_hide_context_menu[i]];
  268. }
  269. /**
  270. * Google Maps map instance
  271. *
  272. * @type {google.maps.Map}
  273. */
  274. this.map = new google.maps.Map(this.el, map_options);
  275. if (markerClustererFunction) {
  276. /**
  277. * Marker Clusterer instance
  278. *
  279. * @type {object}
  280. */
  281. this.markerClusterer = markerClustererFunction.apply(this, [this.map]);
  282. }
  283. var buildContextMenuHTML = function(control, e) {
  284. var html = '',
  285. options = window.context_menu[self.el.id][control];
  286. for (var i in options){
  287. if (options.hasOwnProperty(i)) {
  288. var option = options[i];
  289. html += '<li><a id="' + control + '_' + i + '" href="#">' + option.title + '</a></li>';
  290. }
  291. }
  292. if (!getElementById('gmaps_context_menu')) return;
  293. var context_menu_element = getElementById('gmaps_context_menu');
  294. context_menu_element.innerHTML = html;
  295. var context_menu_items = context_menu_element.getElementsByTagName('a'),
  296. context_menu_items_count = context_menu_items.length,
  297. i;
  298. for (i = 0; i < context_menu_items_count; i++) {
  299. var context_menu_item = context_menu_items[i];
  300. var assign_menu_item_action = function(ev){
  301. ev.preventDefault();
  302. options[this.id.replace(control + '_', '')].action.apply(self, [e]);
  303. self.hideContextMenu();
  304. };
  305. google.maps.event.clearListeners(context_menu_item, 'click');
  306. google.maps.event.addDomListenerOnce(context_menu_item, 'click', assign_menu_item_action, false);
  307. }
  308. var position = findAbsolutePosition.apply(this, [self.el]),
  309. left = position[0] + e.pixel.x - 15,
  310. top = position[1] + e.pixel.y- 15;
  311. context_menu_element.style.left = left + "px";
  312. context_menu_element.style.top = top + "px";
  313. // context_menu_element.style.display = 'block';
  314. };
  315. this.buildContextMenu = function(control, e) {
  316. if (control === 'marker') {
  317. e.pixel = {};
  318. var overlay = new google.maps.OverlayView();
  319. overlay.setMap(self.map);
  320. overlay.draw = function() {
  321. var projection = overlay.getProjection(),
  322. position = e.marker.getPosition();
  323. e.pixel = projection.fromLatLngToContainerPixel(position);
  324. buildContextMenuHTML(control, e);
  325. };
  326. }
  327. else {
  328. buildContextMenuHTML(control, e);
  329. }
  330. var context_menu_element = getElementById('gmaps_context_menu');
  331. setTimeout(function() {
  332. context_menu_element.style.display = 'block';
  333. }, 0);
  334. };
  335. /**
  336. * Add a context menu for a map or a marker.
  337. *
  338. * @param {object} options - The `options` object should contain:
  339. * * `control` (string): Kind of control the context menu will be attached. Can be "map" or "marker".
  340. * * `options` (array): A collection of context menu items:
  341. * * `title` (string): Item's title shown in the context menu.
  342. * * `name` (string): Item's identifier.
  343. * * `action` (function): Function triggered after selecting the context menu item.
  344. */
  345. this.setContextMenu = function(options) {
  346. window.context_menu[self.el.id][options.control] = {};
  347. var i,
  348. ul = doc.createElement('ul');
  349. for (i in options.options) {
  350. if (options.options.hasOwnProperty(i)) {
  351. var option = options.options[i];
  352. window.context_menu[self.el.id][options.control][option.name] = {
  353. title: option.title,
  354. action: option.action
  355. };
  356. }
  357. }
  358. ul.id = 'gmaps_context_menu';
  359. ul.style.display = 'none';
  360. ul.style.position = 'absolute';
  361. ul.style.minWidth = '100px';
  362. ul.style.background = 'white';
  363. ul.style.listStyle = 'none';
  364. ul.style.padding = '8px';
  365. ul.style.boxShadow = '2px 2px 6px #ccc';
  366. if (!getElementById('gmaps_context_menu')) {
  367. doc.body.appendChild(ul);
  368. }
  369. var context_menu_element = getElementById('gmaps_context_menu');
  370. google.maps.event.addDomListener(context_menu_element, 'mouseout', function(ev) {
  371. if (!ev.relatedTarget || !this.contains(ev.relatedTarget)) {
  372. window.setTimeout(function(){
  373. context_menu_element.style.display = 'none';
  374. }, 400);
  375. }
  376. }, false);
  377. };
  378. /**
  379. * Hide the current context menu
  380. */
  381. this.hideContextMenu = function() {
  382. var context_menu_element = getElementById('gmaps_context_menu');
  383. if (context_menu_element) {
  384. context_menu_element.style.display = 'none';
  385. }
  386. };
  387. var setupListener = function(object, name) {
  388. google.maps.event.addListener(object, name, function(e){
  389. if (e == undefined) {
  390. e = this;
  391. }
  392. options[name].apply(this, [e]);
  393. self.hideContextMenu();
  394. });
  395. };
  396. //google.maps.event.addListener(this.map, 'idle', this.hideContextMenu);
  397. google.maps.event.addListener(this.map, 'zoom_changed', this.hideContextMenu);
  398. for (var ev = 0; ev < events_that_hide_context_menu.length; ev++) {
  399. var name = events_that_hide_context_menu[ev];
  400. if (name in options) {
  401. setupListener(this.map, name);
  402. }
  403. }
  404. for (var ev = 0; ev < events_that_doesnt_hide_context_menu.length; ev++) {
  405. var name = events_that_doesnt_hide_context_menu[ev];
  406. if (name in options) {
  407. setupListener(this.map, name);
  408. }
  409. }
  410. google.maps.event.addListener(this.map, 'rightclick', function(e) {
  411. if (options.rightclick) {
  412. options.rightclick.apply(this, [e]);
  413. }
  414. if(window.context_menu[self.el.id]['map'] != undefined) {
  415. self.buildContextMenu('map', e);
  416. }
  417. });
  418. /**
  419. * Trigger a `resize` event, useful if you need to repaint the current map (for changes in the viewport or display / hide actions).
  420. */
  421. this.refresh = function() {
  422. google.maps.event.trigger(this.map, 'resize');
  423. };
  424. /**
  425. * Adjust the map zoom to include all the markers added in the map.
  426. */
  427. this.fitZoom = function() {
  428. var latLngs = [],
  429. markers_length = this.markers.length,
  430. i;
  431. for (i = 0; i < markers_length; i++) {
  432. if(typeof(this.markers[i].visible) === 'boolean' && this.markers[i].visible) {
  433. latLngs.push(this.markers[i].getPosition());
  434. }
  435. }
  436. this.fitLatLngBounds(latLngs);
  437. };
  438. /**
  439. * Adjust the map zoom to include all the coordinates in the `latLngs` array.
  440. *
  441. * @param {array} latLngs - Collection of `google.maps.LatLng` objects.
  442. */
  443. this.fitLatLngBounds = function(latLngs) {
  444. var total = latLngs.length,
  445. bounds = new google.maps.LatLngBounds(),
  446. i;
  447. for(i = 0; i < total; i++) {
  448. bounds.extend(latLngs[i]);
  449. }
  450. this.map.fitBounds(bounds);
  451. };
  452. /**
  453. * Center the map using the `lat` and `lng` coordinates.
  454. *
  455. * @param {number} lat - Latitude of the coordinate.
  456. * @param {number} lng - Longitude of the coordinate.
  457. * @param {function} [callback] - Callback that will be executed after the map is centered.
  458. */
  459. this.setCenter = function(lat, lng, callback) {
  460. this.map.panTo(new google.maps.LatLng(lat, lng));
  461. if (callback) {
  462. callback();
  463. }
  464. };
  465. /**
  466. * Return the HTML element container of the map.
  467. *
  468. * @returns {HTMLElement} the element container.
  469. */
  470. this.getElement = function() {
  471. return this.el;
  472. };
  473. /**
  474. * Increase the map's zoom.
  475. *
  476. * @param {number} [magnitude] - The number of times the map will be zoomed in.
  477. */
  478. this.zoomIn = function(value) {
  479. value = value || 1;
  480. this.zoom = this.map.getZoom() + value;
  481. this.map.setZoom(this.zoom);
  482. };
  483. /**
  484. * Decrease the map's zoom.
  485. *
  486. * @param {number} [magnitude] - The number of times the map will be zoomed out.
  487. */
  488. this.zoomOut = function(value) {
  489. value = value || 1;
  490. this.zoom = this.map.getZoom() - value;
  491. this.map.setZoom(this.zoom);
  492. };
  493. var native_methods = [],
  494. method;
  495. for (method in this.map) {
  496. if (typeof(this.map[method]) == 'function' && !this[method]) {
  497. native_methods.push(method);
  498. }
  499. }
  500. for (i = 0; i < native_methods.length; i++) {
  501. (function(gmaps, scope, method_name) {
  502. gmaps[method_name] = function(){
  503. return scope[method_name].apply(scope, arguments);
  504. };
  505. })(this, this.map, native_methods[i]);
  506. }
  507. };
  508. return GMaps;
  509. })(this);