/*jshint unused:false */
/**
* MIT License
* Copyright(c) 2015 essoduke.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED 『AS IS』, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* jQuery tinyMap 輕鬆建立 Google Maps 的 jQuery 擴充套件
* 拯救眾生免於 Google Maps API 的摧殘,輕鬆就能建立 Google 地圖的 jQuery Plugin。
*
* @author Essoduke Chang
* @version 3.2.2
* {@link http://app.essoduke.org/tinyMap/}
*
* [Changelog]
* 新增 autoLocationk 參數可以傳入 function callback。
* 新增 get 方法可獲取(指定的)marker, polyline, circle... 等圖層。
*
* Last Modified 2015.06.15.173114
*/
// Call while google maps api loaded
window.gMapsCallback = function () {
$(window).trigger('gMapsCallback');
};
// Plugins statement
;(function ($, window, document, undefined) {
// API Configure
var apiLoaded = false,
apiClusterLoaded = false,
tinyMapConfigure = {
'sensor' : false,
'language' : 'zh-TW',
'callback' : 'gMapsCallback',
'api' : '//maps.google.com/maps/api/js',
'clusterer': '//google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/src/markerclusterer_compiled.js'
},
// Default plugin settings
defaults = {
'autoLocation': false,
'center': [24, 121],
'infoWindowAutoClose': true,
'interval': 200,
'loading': '讀取中…',
'notFound': '找不到查詢的地點',
'zoom': 8
},
styles = {},
Label = {};
//#!#START STYLES
styles = {
// Grey Scale
'greyscale': [{
'featureType': 'all',
'stylers': [
{'saturation': -100},
{'gamma': 0.5}
]
}]
};
//#!#END
/**
* Parsing the location
* @param {(string|Array|Object)} loc Location
* @param {boolean} formatting Format to Google Maps LatLng object
* @return {Object}
*/
function parseLatLng (loc, formatting) {
var result = {
'lat': '',
'lng': ''
},
array = [],
re = /^[+-]?\d+(\.\d+)?$/;
if ('string' === typeof loc || Array.isArray(loc)) {
array = Array.isArray(loc) ? loc : loc.toString().replace(/\s+/, '').split(',');
if (2 === array.length) {
if (re.test(array[0]) && re.test(array[1])) {
result.lat = array[0];
result.lng = array[1];
}
} else {
return loc;
}
} else if ('object' === typeof loc) {
// Google LatLng Class
if ('function' === typeof loc.lat) {
return loc;
} else if (loc.hasOwnProperty('x') && loc.hasOwnProperty('y')) {
result.lat = loc.x;
result.lng = loc.y;
} else if (loc.hasOwnProperty('lat') && loc.hasOwnProperty('lng')) {
result.lat = loc.lat;
result.lng = loc.lng;
}
}
if (true === formatting) {
return new google.maps.LatLng(result.lat, result.lng);
}
return result;
}
/**
* tinyMap Constructor
* @param {Object} container HTML element
* @param {(Object|string)} options User settings
* @constructor
*/
function TinyMap (container, options) {
var self = this,
opt = $.extend({}, defaults, options);
/**
* Map instance
* @type {Object}
*/
self.map = null;
/**
* Map markers
* @type {Object}
*/
self._markers = [];
/**
* Map markers
* @type {Object}
*/
self._markersCluster = [];
/**
* Map markers clusterer
* @type {Object}
*/
self._clusters = [];
/**
* Map Labels
* @type {Object}
*/
self._labels = [];
/**
* Polylines layer
* @type {Object}
*/
self._polylines = [];
/**
* Polygons layer
* @type {Object}
*/
self._polygons = [];
/**
* Circles layer
* @type {Object}
*/
self._circles = [];
/**
* KML layer
* @type {Object}
*/
self._kmls = [];
/**
* Directions layer
* @type {Object}
*/
self._directions = [];
/**
* Directions icon layer
* @type {Object}
*/
self._directionsMarkers = [];
/**
* DOM of selector
* @type {Object}
*/
self._places = [];
/**
* DOM of selector
* @type {Object}
*/
self.container = container;
/**
* Merge the options
* @type {Object}
*/
self.options = opt;
/**
* Google Map options
* @type {Object}
*/
self.googleMapOptions = {};
/**
* Interval for geocoder's query interval
* @type {number}
*/
self.interval = parseInt(self.options.interval, 10) || 200;
/**
* Binding callback event for API async
*/
$(window).on('gMapsCallback', function () {
self.init();
});
$(this.container).html(opt.loading);
// Call initialize
self.init();
}
/**
* tinyMap prototype
*/
TinyMap.prototype = {
VERSION: '3.2.2',
// Google Maps LatLngBounds
bounds: {},
/**
* Overlay process
* @this {tinyMap}
*/
overlay: function () {
var map = this.map,
opt = this.options;
try {
//#!#START KML
// kml overlay
this.kml(map, opt);
//#!#END
//#!#START DIRECTION
// direction overlay
this.direction(map, opt);
//#!#END
//#!#START MARKER
// markers overlay
this.markers(map, opt);
//#!#END
//#!#START POLYLINE
// polyline overlay
this.drawPolyline(map, opt);
//#!#END
//#!#START POLYGON
// polygon overlay
this.drawPolygon(map, opt);
//#!#END
//#!#START CIRCLE
// circle overlay
this.drawCircle(map, opt);
//#!#END
//#!#START STREETVIEW
// StreetView service
this.streetView(map, opt);
//#!#END
//#!#START PLACES
// PlaceService
this.places(map, opt);
//#!#END
// GeoLocation
this.geoLocation(map, opt);
} catch (ignore) {
console.info(ignore);
}
},
/**
* Events binding
* @param {Object} marker Marker objects
* @param {(function|Object)} event Events
* @this {tinyMap}
*/
bindEvents: function (target, event) {
var self = this,
e = {};
switch (typeof event) {
case 'function':
google.maps.event.addListener(target, 'click', event);
break;
case 'object':
for (e in event) {
if ('function' === typeof event[e]) {
google.maps.event.addListener(target, e, event[e]);
} else {
if (event[e].hasOwnProperty('func') && 'function' === typeof event[e].func) {
if (event[e].hasOwnProperty('once') && true === event[e].once) {
google.maps.event.addListenerOnce(target, e, event[e].func);
} else {
google.maps.event.addListener(target, e, event[e].func);
}
} else if ('function' === typeof event[e]) {
google.maps.event.addListener(target, e, event[e]);
}
}
}
break;
}
if (target.hasOwnProperty('infoWindow')) {
google.maps.event.addListener(target, 'click', function () {
var i = 0,
m = {};
// Close all infoWindows if `infoWindowAutoClose` was true.
if (self.options.hasOwnProperty('infoWindowAutoClose') &&
true === self.options.infoWindowAutoClose
) {
for (i = self._markers.length - 1; i >= 0; i -= 1) {
m = self._markers[i];
if (m.hasOwnProperty('infoWindow') && 'function' === typeof m.infoWindow.close) {
m.infoWindow.close();
}
}
}
target.infoWindow.open(self.map, target);
});
}
},
//#!#START KML
/**
* KML overlay
* @param {Object} map Map instance
* @param {Object} opt KML options
*/
kml: function (map, opt) {
var self = this,
kmlOpt = {
'url': '',
'map': map,
'preserveViewport': false,
'suppressInfoWindows': false
},
kml = {},
i = 0;
if (opt.hasOwnProperty('kml')) {
if ('string' === typeof opt.kml) {
kmlOpt.url = opt.kml;
kml = new google.maps.KmlLayer(kmlOpt);
this._kmls.push(kml);
} else if (Array.isArray(opt.kml)) {
for (i = opt.kml.length - 1; i >= 0; i -= 1) {
if ('string' === typeof opt.kml[i]) {
kmlOpt.url = opt.kml[i];
kml = new google.maps.KmlLayer(kmlOpt);
} else if ('object' === typeof opt.kml[i]) {
kmlOpt = $.extend({}, kmlOpt, opt.kml[i]);
kml = new google.maps.KmlLayer(kmlOpt);
if (kmlOpt.hasOwnProperty('event')) {
self.bindEvents(kml, kmlOpt.event);
}
}
this._kmls.push(kml);
}
}
}
},
//#!#END
//#!#START POLYLINE
//begin add Multiple POLYLINE by Karry
/**
* Polyline overlay
* @param {Object} map Map instance
* @param {Object} opt Polyline options
*/
drawPolyline: function (map, opt) {
var self = this,
polylineX = {},
waypoints = [],
polyline = {},
distance = {},
service = {},
defOpt = {},
coords = [],
path = [],
c1 = 0,
c = {},
p = {},
i = 0;
// Route callback
routeCallback = function (result, status) {
if (status === google.maps.DirectionsStatus.OK) {
for (i = result.routes[0].overview_path.length - 1; i >= 0; i -= 1) {
path.push(result.routes[0].overview_path[i]);
}
polyline.setPath(path);
if ('function' === typeof polylineX.getDistance) {
distance = result.routes[0].legs[0].distance;
polylineX.getDistance.call(this, distance);
}
}
};
if (opt.hasOwnProperty('polyline') && Array.isArray(opt.polyline)) {
for (c1 = opt.polyline.length - 1; c1 >= 0; c1 -= 1) {
polylineX = opt.polyline[c1];
if (polylineX.hasOwnProperty('coords') &&
Array.isArray(polylineX.coords)
) {
coords = new google.maps.MVCArray();
for (i = polylineX.coords.length - 1; i >= 0; i -= 1) {
p = polylineX.coords[i];
c = parseLatLng(p, true);
if ('function' === typeof c.lat) {
coords.push(c);
}
}
// Options merge
defOpt = $.extend({}, {
'strokeColor' : polylineX.color || '#FF0000',
'strokeOpacity': polylineX.opacity || 1.0,
'strokeWeight' : polylineX.width || 2
}, polylineX);
polyline = new google.maps.Polyline(defOpt);
self._polylines.push(polyline);
if (2 < coords.getLength()) {
for (i = coords.length - 1; i >= 0; i -= 1) {
if (0 < i && (coords.length - 1 > i)) {
waypoints.push({
'location': coords.getAt(i),
'stopover': false
});
}
}
}
// Created event for circle is created.
if (polyline && defOpt.hasOwnProperty('event') &&
defOpt.event.hasOwnProperty('created') &&
'function' === typeof defOpt.event.created
) {
defOpt.event.created.call(polyline, self);
}
// Events binding
if (polylineX.hasOwnProperty('event')) {
self.bindEvents(polyline, polylineX.event);
}
if (polylineX.hasOwnProperty('snap') &&
true === polylineX.snap
) {
service = new google.maps.DirectionsService();
service.route({
'origin': coords.getAt(0),
'waypoints': waypoints,
'destination': coords.getAt(coords.length - 1),
'travelMode': google.maps.DirectionsTravelMode.DRIVING
}, routeCallback);
} else {
polyline.setPath(coords);
if (google.maps.hasOwnProperty('geometry') &&
google.maps.geometry.hasOwnProperty('spherical')
) {
if ('function' === typeof google.maps.geometry.spherical.computeDistanceBetween) {
distance = google.maps
.geometry
.spherical
.computeDistanceBetween(
coords.getAt(0),
coords.getAt(coords.length - 1)
);
if ('function' === typeof polylineX.getDistance) {
polylineX.getDistance.call(self, distance);
}
}
}
}
polyline.setMap(map);
}
}
}
},
//add Multiple POLYLINE by karry
//#!#END
//#!#START POLYGON
/**
* Polygon overlay
* @param {Object} map Map instance
* @param {Object} opt Polygon options
*/
drawPolygon: function (map, opt) {
var self = this,
polygon = {},
defOpt = {},
coords = [],
len = 0,
i = 0,
j = 0,
p = {},
c = {};
if (opt.hasOwnProperty('polygon') && Array.isArray(opt.polygon)) {
for (i = opt.polygon.length - 1; i >= 0; i -= 1) {
coords = [];
if (opt.polygon[i].hasOwnProperty('coords')) {
for (j = opt.polygon[i].coords.length - 1; j >= 0; j -= 1) {
p = opt.polygon[i].coords[j];
c = parseLatLng(p, true);
if ('function' === typeof c.lat) {
coords.push(c);
}
}
defOpt = $.extend({}, {
'path': coords,
'strokeColor': opt.polygon[i].color || '#FF0000',
'strokeOpacity': 1.0,
'strokeWeight': opt.polygon[i].width || 2,
'fillColor': opt.polygon[i].fillcolor || '#CC0000',
'fillOpacity': 0.35
}, opt.polygon[i]);
polygon = new google.maps.Polygon(defOpt);
// Created event for circle is created.
if (polygon && defOpt.hasOwnProperty('event') &&
defOpt.event.hasOwnProperty('created') &&
'function' === typeof defOpt.event.created
) {
defOpt.event.created.call(polygon, self);
}
if (defOpt.hasOwnProperty('event')) {
self.bindEvents(polygon, defOpt.event);
}
self._polygons.push(polygon);
polygon.setMap(map);
}
}
}
},
//#!#END
//#!#START CIRCLE
/**
* Circle overlay
* @param {Object} map Map instance
* @param {Object} opt Circle options
*/
drawCircle: function (map, opt) {
var self = this,
circles = {},
circle = {},
defOpt = {},
loc = {},
c = 0;
if (opt.hasOwnProperty('circle') && Array.isArray(opt.circle)) {
for (c = opt.circle.length - 1; c >= 0; c -= 1) {
circle = opt.circle[c];
defOpt = $.extend({}, {
'map': map,
'strokeColor': circle.color || '#FF0000',
'strokeOpacity': circle.opacity || 0.8,
'strokeWeight': circle.width || 2,
'fillColor': circle.fillcolor || '#FF0000',
'fillOpacity': circle.fillopacity || 0.35,
'radius': circle.radius || 10,
'zIndex': 100,
'id' : circle.hasOwnProperty('id') ? circle.id : ''
}, circle);
if (circle.hasOwnProperty('center')) {
loc = parseLatLng(circle.center, true);
defOpt.center = loc;
}
if ('function' === typeof loc.lat) {
circles = new google.maps.Circle(defOpt);
self._circles.push(circles);
// Created event for circle is created.
if (circles && defOpt.hasOwnProperty('event') &&
defOpt.event.hasOwnProperty('created') &&
'function' === typeof defOpt.event.created
) {
defOpt.event.created.call(circles, self);
}
if (circle.hasOwnProperty('event')) {
self.bindEvents(circles, circle.event);
}
}
}
}
},
//#!#END
//#!#START MARKER
/**
* Markers overlay
* @param {Object} map Map instance
* @param {Object} opt Markers options
*/
markers: function (map, opt, source) {
if (!opt.hasOwnProperty('marker') || !Array.isArray(opt.marker)) {
return false;
}
var self = this,
m = {},
i = 0,
j = 0,
loc = {},
markers = self._markers,
labels = self._labels,
geocoder = new google.maps.Geocoder(),
// Geocoder callback
geocodeCallback = function (results, status) {
if (status === google.maps.GeocoderStatus.OK) {
markers[j].setPosition(results[0].geometry.location);
} else {
throw 'Geocoder Status: ' + status;
}
};
// For first initialize of instance.
if ((!source || 0 === markers.length)) {
for (i = 0; i < opt.marker.length; i += 1) {
m = opt.marker[i];
if (m.hasOwnProperty('addr')) {
m.parseAddr = parseLatLng(m.addr, true);
if ('string' === typeof m.parseAddr) {
self.markerByGeocoder(map, m, opt);
} else {
self.markerDirect(map, m, opt);
}
}
}
source = undefined;
}
// Modify markers
if ('modify' === source) {
for (i = 0; i < opt.marker.length; i += 1) {
if (opt.marker[i].hasOwnProperty('id')) {
for (j = 0; j < markers.length; j += 1) {
if (opt.marker[i].id === markers[j].id &&
opt.marker[i].hasOwnProperty('addr')
) {
// Fix the marker which has `id` and `addr`
// will disappear when call the modify.
// @since v3.1.6
loc = parseLatLng(opt.marker[i].addr, true);
if ('string' === typeof loc) {
geocoder.geocode({'address': loc}, geocodeCallback);
} else {
markers[j].setPosition(loc);
}
if (opt.marker[i].hasOwnProperty('text')) {
if (markers[j].hasOwnProperty('infoWindow')) {
if ('function' === typeof markers[j].infoWindow.setContent) {
markers[j].infoWindow.setContent(opt.marker[i].text);
}
} else {
markers[j].infoWindow = new google.maps.InfoWindow({
'content': opt.marker[i].text
});
self.bindEvents(markers[j], opt.marker[i].event);
}
}
if (opt.marker[i].hasOwnProperty('icon')) {
markers[j].setIcon(opt.marker[i].icon);
}
// Insert if the forceInsert was true when
// id property was not matched.
// @since v3.1.2
} else {
if (opt.marker[i].hasOwnProperty('forceInsert') &&
opt.marker[i].forceInsert === true &&
opt.marker[i].hasOwnProperty('addr')) {
opt.marker[i].parseAddr = parseLatLng(opt.marker[i].addr, true);
if ('string' === typeof opt.marker[i].parseAddr) {
self.markerByGeocoder(map, opt.marker[i], opt);
} else {
self.markerDirect(map, opt.marker[i]);
}
break;
}
}
}
} else {
if (opt.marker[i].hasOwnProperty('addr')) {
opt.marker[i].parseAddr = parseLatLng(opt.marker[i].addr, true);
if ('string' === typeof opt.marker[i].parseAddr) {
self.markerByGeocoder(map, opt.marker[i]);
} else {
self.markerDirect(map, opt.marker[i]);
}
}
}
// Re-drawing the labels
for (j = 0; j < labels.length; j += 1) {
if (opt.marker[i].id === labels[j].id) {
if (opt.marker[i].hasOwnProperty('label')) {
labels[j].text = opt.marker[i].label;
}
if (opt.marker[i].hasOwnProperty('css')) {
$(labels[j].span).addClass(opt.marker[i].css);
}
labels[j].draw();
}
}
}
}
},
/**
* Build the icon options of marker
* @param {Object} opt Marker option
* @this {tinyMap}
*/
markerIcon: function (opt) {
var icons = $.extend({}, opt.icon);
if (opt.hasOwnProperty('icon')) {
if ('string' === typeof opt.icon) {
return opt.icon;
}
if (opt.icon.hasOwnProperty('url')) {
icons.url = opt.icon.url;
}
if (opt.icon.hasOwnProperty('size')) {
if (Array.isArray(opt.icon.size) &&
2 === opt.icon.size.length
) {
icons.size = new google.maps.Size(
opt.icon.size[0],
opt.icon.size[1]
);
}
}
if (opt.icon.hasOwnProperty('scaledSize')) {
if (Array.isArray(opt.icon.scaledSize) &&
2 === opt.icon.scaledSize.length
) {
icons.scaledSize = new google.maps.Size(
opt.icon.scaledSize[0],
opt.icon.scaledSize[1]
);
}
}
if (opt.icon.hasOwnProperty('anchor')) {
if (Array.isArray(opt.icon.anchor) &&
2 === opt.icon.anchor.length
) {
icons.anchor = new google.maps.Point(
opt.icon.anchor[0],
opt.icon.anchor[1]
);
}
}
}
return icons;
},
/**
* Set a marker directly by latitude and longitude
* @param {Object} opt Options
* @this {tinyMap}
*/
markerDirect: function (map, opt) {
var self = this,
marker = {},
label = {},
id = opt.hasOwnProperty('id') ? opt.id : '',
title = opt.hasOwnProperty('title') ?
opt.title.toString().replace(/<([^>]+)>/g, '') :
false,
content = opt.hasOwnProperty('text') ? opt.text.toString() : false,
icons = self.markerIcon(opt),
clusterOption = {
'maxZoom': null,
'gridSize': 60
},
markerOptions = $.extend({}, {
'map': map,
'position': opt.parseAddr,
'animation': null,
'id': id
}, opt);
if (title) {
markerOptions.title = title;
}
if (content) {
markerOptions.text = content;
markerOptions.infoWindow = new google.maps.InfoWindow({
'content': content
});
}
if (!$.isEmptyObject(icons)) {
markerOptions.icon = icons;
}
if (opt.hasOwnProperty('animation') && 'string' === typeof opt.animation) {
markerOptions.animation = google.maps.Animation[opt.animation.toUpperCase()];
}
marker = new google.maps.Marker(markerOptions);
self._markers.push(marker);
// Created event for marker is created.
if (marker && opt.hasOwnProperty('event') &&
opt.event.hasOwnProperty('created') &&
'function' === typeof opt.event.created
) {
opt.event.created.call(marker, self);
}
// Apply marker fitbounds
if (marker.hasOwnProperty('position')) {
if ('function' === typeof marker.getPosition) {
self.bounds.extend(marker.position);
}
if (self.options.hasOwnProperty('markerFitBounds') &&
true === self.options.markerFitBounds
) {
// Make sure fitBounds call after the last marker created.
if (self._markers.length === self.options.marker.length) {
map.fitBounds(self.bounds);
}
}
}
/**
* Apply marker cluster.
* Require markerclusterer.js
* @see {@link http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/src/}
* @since 2015-04-30 10:18:33
*/
if (!opt.hasOwnProperty('cluster') || (opt.hasOwnProperty('cluster') && true === opt.cluster)) {
self._markersCluster.push(marker);
}
if (self.options.hasOwnProperty('markerCluster')) {
if ('function' === typeof MarkerClusterer) {
clusterOption = $.extend({}, clusterOption, self.options.markerCluster);
if (self._markers.length === self.options.marker.length) {
self._clusters.push(new MarkerClusterer(map, self._markersCluster, clusterOption));
}
}
}
if (opt.hasOwnProperty('label')) {
label = new Label({
'text': opt.label,
'map' : map,
'css' : opt.hasOwnProperty('css') ? opt.css.toString() : '',
'id' : id
});
label.bindTo('position', marker, 'position');
label.bindTo('text', marker, 'position');
label.bindTo('visible', marker);
self._labels.push(label);
}
// Binding events
self.bindEvents(marker, opt.event);
},
/**
* Set a marker by Geocoder service
* @param {Object} opt Options
* @this {tinyMap}
*/
markerByGeocoder: function (map, opt, def) {
var geocoder = new google.maps.Geocoder(),
self = this;
geocoder.geocode({'address': opt.parseAddr}, function (results, status) {
// If exceeded, call it later by setTimeout;
if (status === google.maps.GeocoderStatus.OVER_QUERY_LIMIT) {
window.setTimeout(function () {
self.markerByGeocoder(map, opt);
}, self.interval);
} else if (status === google.maps.GeocoderStatus.OK) {
var marker = {},
label = {},
id = opt.hasOwnProperty('id') ? opt.id : '',
title = opt.hasOwnProperty('title') ?
opt.title.toString().replace(/<([^>]+)>/g, '') :
false,
content = opt.hasOwnProperty('text') ? opt.text.toString() : false,
clusterOption = {
'maxZoom': null,
'gridSize': 60
},
markerOptions = {
'map': map,
'position': results[0].geometry.location,
'animation': null,
'visible': true,
'id': id
},
icons = self.markerIcon(opt);
if (title) {
markerOptions.title = title;
}
if (content) {
markerOptions.text = content;
markerOptions.infoWindow = new google.maps.InfoWindow({
'content': content
});
}
if (!$.isEmptyObject(icons)) {
markerOptions.icon = icons;
}
if (opt.hasOwnProperty('animation') && 'string' === typeof opt.animation) {
markerOptions.animation = google.maps.Animation[opt.animation.toUpperCase()];
}
markerOptions = $.extend({}, markerOptions, opt);
marker = new google.maps.Marker(markerOptions);
// Created event for marker is created.
if (marker && opt.hasOwnProperty('event') &&
opt.event.hasOwnProperty('created') &&
'function' === typeof opt.event.created
) {
opt.event.created.call(marker, self);
}
self._markers.push(marker);
// Apply marker fitbounds
if (marker.hasOwnProperty('position')) {
if ('function' === typeof marker.getPosition) {
self.bounds.extend(marker.position);
}
if (self.options.hasOwnProperty('markerFitBounds') &&
true === self.options.markerFitBounds
) {
// Make sure fitBounds call after the last marker created.
// @since v3.1.7
if (self._markers.length === def.marker.length) {
map.fitBounds(self.bounds);
}
}
}
/**
* Apply marker cluster.
* Require markerclusterer.js
* @see {@link http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/src/}
* @since 2015-04-30 10:18:33
*/
if (!opt.hasOwnProperty('cluster') || (opt.hasOwnProperty('cluster') && true === opt.cluster)) {
self._markersCluster.push(marker);
}
if (self.options.hasOwnProperty('markerCluster') &&
'function' === typeof MarkerClusterer &&
self._markers.length === def.marker.length
) {
clusterOption = $.extend({}, clusterOption, self.options.markerCluster);
self._clusters.push(new MarkerClusterer(map, self._markersCluster, clusterOption));
}
if (opt.hasOwnProperty('label')) {
label = new Label({
'text': opt.label,
'map' : self.map,
'css' : opt.hasOwnProperty('css') ? opt.css.toString() : '',
'id' : id
});
label.bindTo('position', marker, 'position');
label.bindTo('text', marker, 'position');
label.bindTo('visible', marker);
self._labels.push(label);
}
// Binding events
self.bindEvents(marker, opt.event);
}
});
},
//#!#END
//#!#START DIRECTION
/**
* Direction overlay
* @param {Object} map Map instance
* @param {Object} opt Direction options
*/
direction: function (map, opt) {
var d = 0;
if (opt.hasOwnProperty('direction') && Array.isArray(opt.direction)) {
for (d = 0; d < opt.direction.length; d -= 1) {
this.directionService(opt.direction[d]);
}
}
},
/**
* Direction service
* @param {Object} opt Options
* @this {tinyMap}
*/
directionService: function (opt) {
// Make sure the `from` and `to` properties has setting.
if (!(opt.hasOwnProperty('from') && opt.hasOwnProperty('to'))) {
return;
}
var self = this,
directionsService = new google.maps.DirectionsService(),
directionsDisplay = new google.maps.DirectionsRenderer(),
waypointsOpts = {},
waypointsText = [],
waypointsIcon = [],
infoWindow = new google.maps.InfoWindow(),
waypoints = [],
renderOpts = {},
startText = '',
endText = '',
request = {
'travelMode': google.maps.DirectionsTravelMode.DRIVING,
'optimizeWaypoints': opt.hasOwnProperty('optimize') ? opt.optimize : true
};
i = 0;
request.origin = parseLatLng(opt.from, true);
request.destination = parseLatLng(opt.to, true);
renderOpts = $.extend({}, {
'infoWindow': infoWindow,
'map': self.map
}, opt);
if (opt.hasOwnProperty('travel') &&
google.maps.TravelMode[opt.travel.toString().toUpperCase()]
) {
request.travelMode = google.maps.TravelMode[opt.travel.toString().toUpperCase()];
}
if (opt.hasOwnProperty('panel') && $(opt.panel).length) {
renderOpts.panel = $(opt.panel).get(0);
}
if (opt.hasOwnProperty('waypoint') && Array.isArray(opt.waypoint)) {
for (i = 0; i < opt.waypoint.length; i += 1) {
waypointsOpts = {};
if ('string' === typeof opt.waypoint[i] || Array.isArray(opt.waypoint[i])) {
waypointsOpts = {
'location': parseLatLng(opt.waypoint[i], true),
'stopover': true
};
} else {
if (opt.waypoint[i].hasOwnProperty('location')) {
waypointsOpts.location = parseLatLng(opt.waypoint[i].location, true);
}
waypointsOpts.stopover = opt.waypoint[i].hasOwnProperty('stopover') ?
opt.waypoint[i].stopover :
true;
}
waypointsText.push(opt.waypoint[i].text || opt.waypoint[i].toString());
if (opt.waypoint[i].hasOwnProperty('icon')) {
waypointsIcon.push(opt.waypoint[i].icon.toString());
}
waypoints.push(waypointsOpts);
}
console.dir(waypoints);
request.waypoints = waypoints;
}
// direction service
directionsService.route(request, function (response, status) {
var legs = [],
wp = {},
i = 0;
if (status === google.maps.DirectionsStatus.OK) {
legs = response.routes[0].legs;
try {
if (opt.hasOwnProperty('fromText')) {
legs[0].start_address = opt.fromText;
startText = opt.fromText;
}
if (opt.hasOwnProperty('toText')) {
if (1 === legs.length) {
legs[0].end_address = opt.toText;
} else {
legs[legs.length - 1].end_address = opt.toText;
}
endText = opt.toText;
}
if (opt.hasOwnProperty('icon')) {
renderOpts.suppressMarkers = true;
if (opt.icon.hasOwnProperty('from') && 'string' === typeof opt.icon.from) {
self.directionServiceMarker(legs[0].start_location, {
'icon': opt.icon.from,
'text': startText
}, infoWindow, opt);
}
if (opt.icon.hasOwnProperty('to') && 'string' === typeof opt.icon.to) {
self.directionServiceMarker(legs[legs.length - 1].end_location, {
'icon': opt.icon.to,
'text': endText
}, infoWindow, opt);
}
}
for (i = legs.length - 1; i >= 0; i -= 1) {
if (opt.hasOwnProperty('icon')) {
if (opt.icon.hasOwnProperty('waypoint') && 'string' === typeof opt.icon.waypoint) {
wp.icon = opt.icon.waypoint;
} else if ('string' === typeof waypointsIcon[i - 1]) {
wp.icon = waypointsIcon[i - 1];
}
wp.text = waypointsText[i - 1];
self.directionServiceMarker(legs[i].start_location, wp, infoWindow, opt);
}
}
} catch (ignore) {
}
self.bindEvents(directionsDisplay, opt.event);
directionsDisplay.setOptions(renderOpts);
directionsDisplay.setDirections(response);
}
});
self._directions.push(directionsDisplay);
},
/**
* Create the marker for directions
* @param {Object} loc LatLng Location
* @param {Object} opt MarkerOptions
* @param {Object} info Global infoWindow object
*/
directionServiceMarker: function (loc, opt, info, d) {
var self = this,
evt = {},
setting = $.extend({}, {
'position': loc,
'map': self.map,
'id' : d.hasOwnProperty('id') ? d.id : ''
}, opt),
marker = new google.maps.Marker(setting);
if (setting.hasOwnProperty('text')) {
evt = function () {
info.setPosition(loc);
info.setContent(setting.text);
info.open(self.map, marker);
};
}
self._directionsMarkers.push(marker);
self.bindEvents(marker, evt);
},
/**
* Get directions info
* @return {Array} All directions info includes distance and duration.
*/
getDirectionsInfo: function () {
var self = this,
result = [],
legs = null,
dr = self._directions,
ci = 0,
cj = 0,
i = 0,
j = 0;
if (dr) {
for (i = 0, ci = dr.length; i < ci; i += 1) {
if (
dr[i].hasOwnProperty('directions') &&
dr[i].directions.hasOwnProperty('routes') &&
'undefined' !== typeof dr[i].directions.routes[0].legs
) {
legs = dr[i].directions.routes[0].legs;
result[i] = [];
for (j = 0, cj = legs.length; j < cj; j += 1) {
result[i].push({
'from': legs[j].start_address,
'to' : legs[j].end_address,
'distance': legs[j].distance,
'duration': legs[j].duration
});
}
}
}
}
return result;
},
//#!#END
//#!#START STREETVIEW
/**
* Switch StreetView
* @this {tinyMap}
*/
streetView: function (map, opt) {
var self = this,
pano = {},
opts = opt.hasOwnProperty('streetViewObj') ? opt.streetViewObj : {},
loc = {};
if ('function' === typeof map.getStreetView && opt.hasOwnProperty('streetViewObj')) {
pano = map.getStreetView();
// Default position of streetView
if (opts.hasOwnProperty('position')) {
loc = parseLatLng(opts.position, true);
opts.position = 'object' === typeof loc ? map.getCenter() : loc;
} else {
opts.position = map.getCenter();
}
// Pov configure
if (opts.hasOwnProperty('pov')) {
opts.pov = $.extend({}, {
'heading': 0,
'pitch' : 0,
'zoom' : 1
}, opts.pov);
}
if (opts.hasOwnProperty('visible')) {
pano.setVisible(opts.visible);
}
// Apply options
pano.setOptions(opts);
// Events Binding
if (opts.hasOwnProperty('event')) {
self.bindEvents(pano, opts.event);
}
}
},
//#!#END
//#!#START PLACES
/**
* Places API
* @this {tinyMap}
*/
places: function (map, opt) {
var self = this,
placesService = {},
reqOpt = opt.hasOwnProperty('places') ? opt.places : {},
request = $.extend({}, {
'location': map.getCenter(),
'radius' : 100
}, reqOpt),
i = 0;
if ('undefined' !== typeof google.maps.places && request.hasOwnProperty('query')) {
placesService = new google.maps.places.PlacesService(map),
placesService.textSearch(request, function (results, status) {
if (status === google.maps.places.PlacesServiceStatus.OK) {
self.places = results;
if (request.hasOwnProperty('createMarker') && true === request.createMarker) {
for (i = results.length - 1; i >= 0; i -= 1) {
if (results[i].hasOwnProperty('geometry')) {
self._markers.push(new google.maps.Marker({
'map': map,
'position': results[i].geometry.location
}));
}
}
}
}
});
}
},
//#!#END
/**
* Use HTML5 Geolocation API to detect the client's location.
* @param {Object} map Map intance
* @param {Object} opt Plugin options
*/
geoLocation: function (map, opt) {
try {
var self = this,
geolocation = navigator.geolocation,
geoOpt = {};
if (!geolocation) {
return;
}
if (opt.hasOwnProperty('geolocation')) {
geoOpt = $.extend({}, {
'maximumAge' : 600000,
'timeout' : 3000,
'enableHighAccuracy': false
}, opt.geolocation);
}
if (true === opt.autoLocation || 'function' === typeof opt.autoLocation) {
geolocation.getCurrentPosition(
function (loc) {
if (('undefined' !== typeof loc) &&
('coords' in loc) &&
('latitude' in loc.coords) &&
('longitude' in loc.coords)
) {
map.panTo(new google.maps.LatLng(
loc.coords.latitude,
loc.coords.longitude
));
if ('function' === typeof opt.autoLocation) {
opt.autoLocation.call(self, loc);
}
}
},
function (error) {
console.error(error);
},
geoOpt
);
}
} catch (ignore) {
}
},
//#!#START PANTO
/**
* Method: Google Maps PanTo
* @param {(string|Array|Object)} addr Location
* @public
*/
panTo: function (addr) {
var m = this.map,
loc = {},
geocoder = {};
if (null !== m && 'undefined' !== typeof m) {
loc = parseLatLng(addr, true);
if ('string' === typeof loc) {
geocoder = new google.maps.Geocoder();
geocoder.geocode({'address': loc}, function (results, status) {
if (status === google.maps.GeocoderStatus.OK &&
'function' === typeof m.panTo &&
Array.isArray(results) &&
results.length
) {
if (results[0].hasOwnProperty('geometry')) {
m.panTo(results[0].geometry.location);
}
} else {
console.error(status);
}
});
} else {
if ('function' === typeof m.panTo) {
m.panTo(loc);
}
}
}
return this;
},
//#!#END
//#!#START CLEAR
/**
* Method: Google Maps clear the specificed layer
* @param {string} type Layer type
* @public
*/
clear: function (layer) {
var self = this,
dMarkers = self._directionsMarkers,
labels = self._labels,
target = [],
item = {},
obj = {},
key = '',
i = 0,
j = 0;
if ('undefined' === typeof layer) {
layer = {
'marker' : [],
'label' : [],
'polygon' : [],
'polyline' : [],
'circle' : [],
'direction': [],
'kml' : []
};
}
try {
for (obj in layer) {
if (Array.isArray(layer[obj])) {
key = '_' + obj.toString().toLowerCase() + 's';
if ('undefined' !== typeof self[key]) {
target = [];
for (i = 0; i < self[key].length; i += 1) {
item = self[key][i];
if (0 === layer[obj].length || (-1 !== layer[obj].indexOf(i)) || (item.hasOwnProperty('id') && 0 < item.id.length && (-1 !== layer[obj].indexOf(item.id)))) {
// Clear label of Markers.
if ('_markers' === key) {
if ('undefined' !== typeof labels[i] && labels.hasOwnProperty('div')) {
self._labels[i].div.remove();
}
}
// Remove the direction icons.
if ('_directions' === key) {
for (j = dMarkers.length - 1; j >= 0; j -= 1) {
if ('function' === typeof dMarkers[j].setMap) {
self._directionsMarkers[j].setMap(null);
self._directionsMarkers.splice(j, 1);
}
}
}
// Remove from Map
if ('function' === typeof item.set) {
item.set('visible', false);
item.set('directions', null);
}
if ('function' === typeof item.setMap) {
item.setMap(null);
}
// Remove from Array
target.push(i);
}
}
for (i = 0; i < target.length; i += 1) {
self[key].splice(i, 1);
}
}
}
}
} catch (ignore) {
console.warn(ignore);
} finally {
return $(self.container);
}
},
//#!#END
//#!#START GET
/**
* Method: Google Maps get the specificed layer
* @param {string} type Layer type
* @public
*/
get: function (layer, callback) {
var self = this,
dMarkers = self._directionsMarkers,
labels = self._labels,
result = [],
target = [],
item = {},
obj = {},
key = '',
i = 0,
j = 0;
if ('undefined' === typeof layer) {
layer = {
'marker' : [],
'label' : [],
'polygon' : [],
'polyline' : [],
'circle' : [],
'direction': [],
'kml' : []
};
}
try {
for (obj in layer) {
if (Array.isArray(layer[obj])) {
key = '_' + obj.toString().toLowerCase() + 's';
if ('undefined' !== typeof self[key]) {
target[obj] = [];
for (i = 0; i < self[key].length; i += 1) {
item = self[key][i];
if (0 === layer[obj].length || (-1 !== layer[obj].indexOf(i)) || (item.hasOwnProperty('id') && 0 < item.id.length && (-1 !== layer[obj].indexOf(item.id)))) {
target[obj].push(item);
}
}
for (i = 0; i < target.length; i += 1) {
self[key].splice(i, 1);
}
}
}
}
if ('function' === typeof callback) {
callback.call(this, target);
}
} catch (ignore) {
console.warn(ignore);
} finally {
return target;
}
},
//#!#END
//#!#START MODIFY
/**
* Method: Google Maps dynamic add layers
* @param {Object} options Refernce by tinyMap options
* @public
*/
modify: function (options) {
var self = this,
func = [],
label = [
['kml', 'kml'],
['marker', 'markers'],
['direction', 'direction'],
['polyline', 'drawPolyline'],
['polygon', 'drawPolygon'],
['circle', 'drawCircle'],
['streetView', 'streetView'],
['markerFitBounds', 'markerFitBounds']
],
i = 0,
m = self.map;
if ('undefined' !== typeof options) {
for (i = label.length - 1; i >= 0; i -= 1) {
if (options.hasOwnProperty(label[i][0])) {
func.push(label[i][1]);
}
}
if (null !== m) {
if (func.length) {
for (i = func.length - 1; i >= 0; i -= 1) {
console.dir(func[i]);
if ('function' === typeof self[func[i]]) {
if ('streetView' === func[i]) {
options.streetViewObj = options.streetView;
delete options.streetView;
}
self[func[i]](m, options, 'modify');
}
}
} else {
m.setOptions(options);
}
if (options.hasOwnProperty('event')) {
self.bindEvents(m, options.event);
}
google.maps.event.trigger(m, 'resize');
}
}
return $(this.container);
},
//#!#END
//#!#START DESTROY
destroy: function () {
var obj = $(this.container);
if (obj.length) {
$.removeData(this.container, 'tinyMap');
}
return obj.empty();
},
//#!#END
//#!#START GETKML
getKML: function (opt) {
var m = $(this.container),
md = m.data('tinyMap'),
// Options
opts = $.extend({}, {
'marker' : true,
'polyline' : true,
'direction': true,
'download' : false
}, opt),
// MIME TYPE of KML
mime = 'data:application/vnd.google-earth.kml+xml;charset=utf-8;base64,',
// KML template
templates = {
'xml': [
'',
'