You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

2543 lines
62 KiB

/*!
* GMAP3 Plugin for jQuery
* Version : 6.0.0
* Date : 2014-04-25
* Author : DEMONTE Jean-Baptiste
* Contact : jbdemonte@gmail.com
* Web site : http://gmap3.net
* Licence : GPL v3 : http://www.gnu.org/licenses/gpl.html
*
* Copyright (c) 2010-2014 Jean-Baptiste DEMONTE
* All rights reserved.
*/
;(function ($, undef) {
var defaults, gm,
gId = 0,
isFunction = $.isFunction,
isArray = $.isArray;
function isObject(m) {
return typeof m === "object";
}
function isString(m) {
return typeof m === "string";
}
function isNumber(m) {
return typeof m === "number";
}
function isUndefined(m) {
return m === undef;
}
/**
* Initialize default values
* defaults are defined at first gmap3 call to pass the rails asset pipeline and jasmine while google library is not yet loaded
*/
function initDefaults() {
gm = google.maps;
if (!defaults) {
defaults = {
verbose: false,
queryLimit: {
attempt: 5,
delay: 250, // setTimeout(..., delay + random);
random: 250
},
classes: (function () {
var r = {};
$.each("Map Marker InfoWindow Circle Rectangle OverlayView StreetViewPanorama KmlLayer TrafficLayer BicyclingLayer GroundOverlay StyledMapType ImageMapType".split(" "), function (_, k) {
r[k] = gm[k];
});
return r;
}()),
map: {
mapTypeId : gm.MapTypeId.ROADMAP,
center: [46.578498, 2.457275],
zoom: 2
},
overlay: {
pane: "floatPane",
content: "",
offset: {
x: 0,
y: 0
}
},
geoloc: {
getCurrentPosition: {
maximumAge: 60000,
timeout: 5000
}
}
}
}
}
/**
* Generate a new ID if not defined
* @param id {string} (optional)
* @param simulate {boolean} (optional)
* @returns {*}
*/
function globalId(id, simulate) {
return isUndefined(id) ? "gmap3_" + (simulate ? gId + 1 : ++gId) : id;
}
/**
* Return true if current version of Google Maps is equal or above to these in parameter
* @param version {string} Minimal version required
* @return {Boolean}
*/
function googleVersionMin(version) {
var i,
gmVersion = gm.version.split(".");
version = version.split(".");
for (i = 0; i < gmVersion.length; i++) {
gmVersion[i] = parseInt(gmVersion[i], 10);
}
for (i = 0; i < version.length; i++) {
version[i] = parseInt(version[i], 10);
if (gmVersion.hasOwnProperty(i)) {
if (gmVersion[i] < version[i]) {
return false;
}
} else {
return false;
}
}
return true;
}
/**
* attach events from a container to a sender
* td[
* events => { eventName => function, }
* onces => { eventName => function, }
* data => mixed data
* ]
**/
function attachEvents($container, args, sender, id, senders) {
var td = args.td || {},
context = {
id: id,
data: td.data,
tag: td.tag
};
function bind(items, handler) {
if (items) {
$.each(items, function (name, f) {
var self = $container, fn = f;
if (isArray(f)) {
self = f[0];
fn = f[1];
}
handler(sender, name, function (event) {
fn.apply(self, [senders || sender, event, context]);
});
});
}
}
bind(td.events, gm.event.addListener);
bind(td.onces, gm.event.addListenerOnce);
}
/**
* Extract keys from object
* @param obj {object}
* @returns {Array}
*/
function getKeys(obj) {
var k, keys = [];
for (k in obj) {
if (obj.hasOwnProperty(k)) {
keys.push(k);
}
}
return keys;
}
/**
* copy a key content
**/
function copyKey(target, key) {
var i,
args = arguments;
for (i = 2; i < args.length; i++) {
if (key in args[i]) {
if (args[i].hasOwnProperty(key)) {
target[key] = args[i][key];
return;
}
}
}
}
/**
* Build a tuple
* @param args {object}
* @param value {object}
* @returns {object}
*/
function tuple(args, value) {
var k, i,
keys = ["data", "tag", "id", "events", "onces"],
td = {};
// "copy" the common data
if (args.td) {
for (k in args.td) {
if (args.td.hasOwnProperty(k)) {
if ((k !== "options") && (k !== "values")) {
td[k] = args.td[k];
}
}
}
}
// "copy" some specific keys from value first else args.td
for (i = 0; i < keys.length; i++) {
copyKey(td, keys[i], value, args.td);
}
// create an extended options
td.options = $.extend({}, args.opts || {}, value.options || {});
return td;
}
/**
* Log error
*/
function error() {
if (defaults.verbose) {
var i, err = [];
if (window.console && (isFunction(console.error))) {
for (i = 0; i < arguments.length; i++) {
err.push(arguments[i]);
}
console.error.apply(console, err);
} else {
err = "";
for (i = 0; i < arguments.length; i++) {
err += arguments[i].toString() + " ";
}
alert(err);
}
}
}
/**
* return true if mixed is usable as number
**/
function numeric(mixed) {
return (isNumber(mixed) || isString(mixed)) && mixed !== "" && !isNaN(mixed);
}
/**
* convert data to array
**/
function array(mixed) {
var k, a = [];
if (!isUndefined(mixed)) {
if (isObject(mixed)) {
if (isNumber(mixed.length)) {
a = mixed;
} else {
for (k in mixed) {
a.push(mixed[k]);
}
}
} else {
a.push(mixed);
}
}
return a;
}
/**
* create a function to check a tag
*/
function ftag(tag) {
if (tag) {
if (isFunction(tag)) {
return tag;
}
tag = array(tag);
return function (val) {
var i;
if (isUndefined(val)) {
return false;
}
if (isObject(val)) {
for (i = 0; i < val.length; i++) {
if ($.inArray(val[i], tag) >= 0) {
return true;
}
}
return false;
}
return $.inArray(val, tag) >= 0;
};
}
}
/**
* convert mixed [ lat, lng ] objet to gm.LatLng
**/
function toLatLng(mixed, emptyReturnMixed, noFlat) {
var empty = emptyReturnMixed ? mixed : null;
if (!mixed || (isString(mixed))) {
return empty;
}
// defined latLng
if (mixed.latLng) {
return toLatLng(mixed.latLng);
}
// gm.LatLng object
if (mixed instanceof gm.LatLng) {
return mixed;
}
// {lat:X, lng:Y} object
if (numeric(mixed.lat)) {
return new gm.LatLng(mixed.lat, mixed.lng);
}
// [X, Y] object
if (!noFlat && isArray(mixed)) {
if (!numeric(mixed[0]) || !numeric(mixed[1])) {
return empty;
}
return new gm.LatLng(mixed[0], mixed[1]);
}
return empty;
}
/**
* convert mixed [ sw, ne ] object by gm.LatLngBounds
**/
function toLatLngBounds(mixed) {
var ne, sw;
if (!mixed || mixed instanceof gm.LatLngBounds) {
return mixed || null;
}
if (isArray(mixed)) {
if (mixed.length === 2) {
ne = toLatLng(mixed[0]);
sw = toLatLng(mixed[1]);
} else if (mixed.length === 4) {
ne = toLatLng([mixed[0], mixed[1]]);
sw = toLatLng([mixed[2], mixed[3]]);
}
} else {
if (("ne" in mixed) && ("sw" in mixed)) {
ne = toLatLng(mixed.ne);
sw = toLatLng(mixed.sw);
} else if (("n" in mixed) && ("e" in mixed) && ("s" in mixed) && ("w" in mixed)) {
ne = toLatLng([mixed.n, mixed.e]);
sw = toLatLng([mixed.s, mixed.w]);
}
}
if (ne && sw) {
return new gm.LatLngBounds(sw, ne);
}
return null;
}
/**
* resolveLatLng
**/
function resolveLatLng(ctx, method, runLatLng, args, attempt) {
var latLng = runLatLng ? toLatLng(args.td, false, true) : false,
conf = latLng ? {latLng: latLng} : (args.td.address ? (isString(args.td.address) ? {address: args.td.address} : args.td.address) : false),
cache = conf ? geocoderCache.get(conf) : false,
self = this;
if (conf) {
attempt = attempt || 0; // convert undefined to int
if (cache) {
args.latLng = cache.results[0].geometry.location;
args.results = cache.results;
args.status = cache.status;
method.apply(ctx, [args]);
} else {
if (conf.location) {
conf.location = toLatLng(conf.location);
}
if (conf.bounds) {
conf.bounds = toLatLngBounds(conf.bounds);
}
geocoder().geocode(
conf,
function (results, status) {
if (status === gm.GeocoderStatus.OK) {
geocoderCache.store(conf, {results: results, status: status});
args.latLng = results[0].geometry.location;
args.results = results;
args.status = status;
method.apply(ctx, [args]);
} else if ((status === gm.GeocoderStatus.OVER_QUERY_LIMIT) && (attempt < defaults.queryLimit.attempt)) {
setTimeout(
function () {
resolveLatLng.apply(self, [ctx, method, runLatLng, args, attempt + 1]);
},
defaults.queryLimit.delay + Math.floor(Math.random() * defaults.queryLimit.random)
);
} else {
error("geocode failed", status, conf);
args.latLng = args.results = false;
args.status = status;
method.apply(ctx, [args]);
}
}
);
}
} else {
args.latLng = toLatLng(args.td, false, true);
method.apply(ctx, [args]);
}
}
function resolveAllLatLng(list, ctx, method, args) {
var self = this, i = -1;
function resolve() {
// look for next address to resolve
do {
i++;
} while ((i < list.length) && !("address" in list[i]));
// no address found, so run method
if (i >= list.length) {
method.apply(ctx, [args]);
return;
}
resolveLatLng(
self,
function (args) {
delete args.td;
$.extend(list[i], args);
resolve.apply(self, []); // resolve next (using apply avoid too much recursion)
},
true,
{td: list[i]}
);
}
resolve();
}
/**
* geolocalise the user and return a LatLng
**/
function geoloc(ctx, method, args) {
var is_echo = false; // sometime, a kind of echo appear, this trick will notice once the first call is run to ignore the next one
if (navigator && navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
function (pos) {
if (!is_echo) {
is_echo = true;
args.latLng = new gm.LatLng(pos.coords.latitude, pos.coords.longitude);
method.apply(ctx, [args]);
}
},
function () {
if (!is_echo) {
is_echo = true;
args.latLng = false;
method.apply(ctx, [args]);
}
},
args.opts.getCurrentPosition
);
} else {
args.latLng = false;
method.apply(ctx, [args]);
}
}
/**
* Return true if get is a direct call
* it means :
* - get is the only key
* - get has no callback
* @param obj {Object} The request to check
* @return {Boolean}
*/
function isDirectGet(obj) {
var k,
result = false;
if (isObject(obj) && obj.hasOwnProperty("get")) {
for (k in obj) {
if (k !== "get") {
return false;
}
}
result = !obj.get.hasOwnProperty("callback");
}
return result;
}
var services = {},
geocoderCache = new GeocoderCache();
function geocoder(){
if (!services.geocoder) {
services.geocoder = new gm.Geocoder();
}
return services.geocoder;
}
/**
* Class GeocoderCache
* @constructor
*/
function GeocoderCache() {
var cache = [];
this.get = function (request) {
if (cache.length) {
var i, j, k, item, eq,
keys = getKeys(request);
for (i = 0; i < cache.length; i++) {
item = cache[i];
eq = keys.length === item.keys.length;
for (j = 0; (j < keys.length) && eq; j++) {
k = keys[j];
eq = k in item.request;
if (eq) {
if (isObject(request[k]) && ("equals" in request[k]) && isFunction(request[k])) {
eq = request[k].equals(item.request[k]);
} else {
eq = request[k] === item.request[k];
}
}
}
if (eq) {
return item.results;
}
}
}
};
this.store = function (request, results) {
cache.push({request: request, keys: getKeys(request), results: results});
};
}
/**
* Class Stack
* @constructor
*/
function Stack() {
var st = [],
self = this;
self.empty = function () {
return !st.length;
};
self.add = function (v) {
st.push(v);
};
self.get = function () {
return st.length ? st[0] : false;
};
self.ack = function () {
st.shift();
};
}
/**
* Class Store
* @constructor
*/
function Store() {
var store = {}, // name => [id, ...]
objects = {}, // id => object
self = this;
function normalize(res) {
return {
id: res.id,
name: res.name,
object: res.obj,
tag: res.tag,
data: res.data
};
}
/**
* add a mixed to the store
**/
self.add = function (args, name, obj, sub) {
var td = args.td || {},
id = globalId(td.id);
if (!store[name]) {
store[name] = [];
}
if (id in objects) { // object already exists: remove it
self.clearById(id);
}
objects[id] = {obj: obj, sub: sub, name: name, id: id, tag: td.tag, data: td.data};
store[name].push(id);
return id;
};
/**
* return a stored object by its id
**/
self.getById = function (id, sub, full) {
var result = false;
if (id in objects) {
if (sub) {
result = objects[id].sub;
} else if (full) {
result = normalize(objects[id]);
} else {
result = objects[id].obj;
}
}
return result;
};
/**
* return a stored value
**/
self.get = function (name, last, tag, full) {
var n, id, check = ftag(tag);
if (!store[name] || !store[name].length) {
return null;
}
n = store[name].length;
while (n) {
n--;
id = store[name][last ? n : store[name].length - n - 1];
if (id && objects[id]) {
if (check && !check(objects[id].tag)) {
continue;
}
return full ? normalize(objects[id]) : objects[id].obj;
}
}
return null;
};
/**
* return all stored values
**/
self.all = function (name, tag, full) {
var result = [],
check = ftag(tag),
find = function (n) {
var i, id;
for (i = 0; i < store[n].length; i++) {
id = store[n][i];
if (id && objects[id]) {
if (check && !check(objects[id].tag)) {
continue;
}
result.push(full ? normalize(objects[id]) : objects[id].obj);
}
}
};
if (name in store) {
find(name);
} else if (isUndefined(name)) { // internal use only
for (name in store) {
find(name);
}
}
return result;
};
/**
* hide and remove an object
**/
function rm(obj) {
// Google maps element
if (isFunction(obj.setMap)) {
obj.setMap(null);
}
// jQuery
if (isFunction(obj.remove)) {
obj.remove();
}
// internal (cluster)
if (isFunction(obj.free)) {
obj.free();
}
obj = null;
}
/**
* remove one object from the store
**/
self.rm = function (name, check, pop) {
var idx, id;
if (!store[name]) {
return false;
}
if (check) {
if (pop) {
for (idx = store[name].length - 1; idx >= 0; idx--) {
id = store[name][idx];
if (check(objects[id].tag)) {
break;
}
}
} else {
for (idx = 0; idx < store[name].length; idx++) {
id = store[name][idx];
if (check(objects[id].tag)) {
break;
}
}
}
} else {
idx = pop ? store[name].length - 1 : 0;
}
if (!(idx in store[name])) {
return false;
}
return self.clearById(store[name][idx], idx);
};
/**
* remove object from the store by its id
**/
self.clearById = function (id, idx) {
if (id in objects) {
var i, name = objects[id].name;
for (i = 0; isUndefined(idx) && i < store[name].length; i++) {
if (id === store[name][i]) {
idx = i;
}
}
rm(objects[id].obj);
if (objects[id].sub) {
rm(objects[id].sub);
}
delete objects[id];
store[name].splice(idx, 1);
return true;
}
return false;
};
/**
* return an object from a container object in the store by its id
* ! for now, only cluster manage this feature
**/
self.objGetById = function (id) {
var result, idx;
if (store.clusterer) {
for (idx in store.clusterer) {
if ((result = objects[store.clusterer[idx]].obj.getById(id)) !== false) {
return result;
}
}
}
return false;
};
/**
* remove object from a container object in the store by its id
* ! for now, only cluster manage this feature
**/
self.objClearById = function (id) {
var idx;
if (store.clusterer) {
for (idx in store.clusterer) {
if (objects[store.clusterer[idx]].obj.clearById(id)) {
return true;
}
}
}
return null;
};
/**
* remove objects from the store
**/
self.clear = function (list, last, first, tag) {
var k, i, name,
check = ftag(tag);
if (!list || !list.length) {
list = [];
for (k in store) {
list.push(k);
}
} else {
list = array(list);
}
for (i = 0; i < list.length; i++) {
name = list[i];
if (last) {
self.rm(name, check, true);
} else if (first) {
self.rm(name, check, false);
} else { // all
while (self.rm(name, check, false)) {
}
}
}
};
/**
* remove object from a container object in the store by its tags
* ! for now, only cluster manage this feature
**/
self.objClear = function (list, last, first, tag) {
var idx;
if (store.clusterer && ($.inArray("marker", list) >= 0 || !list.length)) {
for (idx in store.clusterer) {
objects[store.clusterer[idx]].obj.clear(last, first, tag);
}
}
};
}
/**
* Class Task
* @param ctx
* @param onEnd
* @param td
* @constructor
*/
function Task(ctx, onEnd, td) {
var session = {},
self = this,
current,
resolve = {
latLng: { // function => bool (=> address = latLng)
map: false,
marker: false,
infowindow: false,
circle: false,
overlay: false,
getlatlng: false,
getmaxzoom: false,
getelevation: false,
streetviewpanorama: false,
getaddress: true
},
geoloc: {
getgeoloc: true
}
};
function unify(td) {
var result = {};
result[td] = {};
return result;
}
if (isString(td)) {
td = unify(td);
}
function next() {
var k;
for (k in td) {
if (td.hasOwnProperty(k) && !session.hasOwnProperty(k)) {
return k;
}
}
}
self.run = function () {
var k, opts;
while (k = next()) {
if (isFunction(ctx[k])) {
current = k;
opts = $.extend(true, {}, defaults[k] || {}, td[k].options || {});
if (k in resolve.latLng) {
if (td[k].values) {
resolveAllLatLng(td[k].values, ctx, ctx[k], {td: td[k], opts: opts, session: session});
} else {
resolveLatLng(ctx, ctx[k], resolve.latLng[k], {td: td[k], opts: opts, session: session});
}
} else if (k in resolve.geoloc) {
geoloc(ctx, ctx[k], {td: td[k], opts: opts, session: session});
} else {
ctx[k].apply(ctx, [{td: td[k], opts: opts, session: session}]);
}
return; // wait until ack
} else {
session[k] = null;
}
}
onEnd.apply(ctx, [td, session]);
};
self.ack = function(result){
session[current] = result;
self.run.apply(self, []);
};
}
function directionsService(){
if (!services.ds) {
services.ds = new gm.DirectionsService();
}
return services.ds;
}
function distanceMatrixService() {
if (!services.dms) {
services.dms = new gm.DistanceMatrixService();
}
return services.dms;
}
function maxZoomService() {
if (!services.mzs) {
services.mzs = new gm.MaxZoomService();
}
return services.mzs;
}
function elevationService() {
if (!services.es) {
services.es = new gm.ElevationService();
}
return services.es;
}
/**
* Usefull to get a projection
* => done in a function, to let dead-code analyser works without google library loaded
**/
function newEmptyOverlay(map, radius) {
function Overlay() {
var self = this;
self.onAdd = function () {};
self.onRemove = function () {};
self.draw = function () {};
return defaults.classes.OverlayView.apply(self, []);
}
Overlay.prototype = defaults.classes.OverlayView.prototype;
var obj = new Overlay();
obj.setMap(map);
return obj;
}
/**
* Class InternalClusterer
* This class manage clusters thanks to "td" objects
*
* Note:
* Individuals marker are created on the fly thanks to the td objects, they are
* first set to null to keep the indexes synchronised with the td list
* This is the "display" function, set by the gmap3 object, which uses theses data
* to create markers when clusters are not required
* To remove a marker, the objects are deleted and set not null in arrays
* markers[key]
* = null : marker exist but has not been displayed yet
* = false : marker has been removed
**/
function InternalClusterer($container, map, raw) {
var timer, projection,
ffilter, fdisplay, ferror, // callback function
updating = false,
updated = false,
redrawing = false,
ready = false,
enabled = true,
self = this,
events = [],
store = {}, // combin of index (id1-id2-...) => object
ids = {}, // unique id => index
idxs = {}, // index => unique id
markers = [], // index => marker
tds = [], // index => td or null if removed
values = [], // index => value
overlay = newEmptyOverlay(map, raw.radius);
main();
function prepareMarker(index) {
if (!markers[index]) {
delete tds[index].options.map;
markers[index] = new defaults.classes.Marker(tds[index].options);
attachEvents($container, {td: tds[index]}, markers[index], tds[index].id);
}
}
/**
* return a marker by its id, null if not yet displayed and false if no exist or removed
**/
self.getById = function (id) {
if (id in ids) {
prepareMarker(ids[id]);
return markers[ids[id]];
}
return false;
};
/**
* remove one object from the store
**/
self.rm = function (id) {
var index = ids[id];
if (markers[index]) { // can be null
markers[index].setMap(null);
}
delete markers[index];
markers[index] = false;
delete tds[index];
tds[index] = false;
delete values[index];
values[index] = false;
delete ids[id];
delete idxs[index];
updated = true;
};
/**
* remove a marker by its id
**/
self.clearById = function (id) {
if (id in ids){
self.rm(id);
return true;
}
};
/**
* remove objects from the store
**/
self.clear = function (last, first, tag) {
var start, stop, step, index, i,
list = [],
check = ftag(tag);
if (last) {
start = tds.length - 1;
stop = -1;
step = -1;
} else {
start = 0;
stop = tds.length;
step = 1;
}
for (index = start; index !== stop; index += step) {
if (tds[index]) {
if (!check || check(tds[index].tag)) {
list.push(idxs[index]);
if (first || last) {
break;
}
}
}
}
for (i = 0; i < list.length; i++) {
self.rm(list[i]);
}
};
// add a "marker td" to the cluster
self.add = function (td, value) {
td.id = globalId(td.id);
self.clearById(td.id);
ids[td.id] = markers.length;
idxs[markers.length] = td.id;
markers.push(null); // null = marker not yet created / displayed
tds.push(td);
values.push(value);
updated = true;
};
// add a real marker to the cluster
self.addMarker = function (marker, td) {
td = td || {};
td.id = globalId(td.id);
self.clearById(td.id);
if (!td.options) {
td.options = {};
}
td.options.position = marker.getPosition();
attachEvents($container, {td: td}, marker, td.id);
ids[td.id] = markers.length;
idxs[markers.length] = td.id;
markers.push(marker);
tds.push(td);
values.push(td.data || {});
updated = true;
};
// return a "marker td" by its index
self.td = function (index) {
return tds[index];
};
// return a "marker value" by its index
self.value = function (index) {
return values[index];
};
// return a marker by its index
self.marker = function (index) {
if (index in markers) {
prepareMarker(index);
return markers[index];
}
return false;
};
// return a marker by its index
self.markerIsSet = function (index) {
return Boolean(markers[index]);
};
// store a new marker instead if the default "false"
self.setMarker = function (index, marker) {
markers[index] = marker;
};
// link the visible overlay to the logical data (to hide overlays later)
self.store = function (cluster, obj, shadow) {
store[cluster.ref] = {obj: obj, shadow: shadow};
};
// free all objects
self.free = function () {
var i;
for(i = 0; i < events.length; i++) {
gm.event.removeListener(events[i]);
}
events = [];
$.each(store, function (key) {
flush(key);
});
store = {};
$.each(tds, function (i) {
tds[i] = null;
});
tds = [];
$.each(markers, function (i) {
if (markers[i]) { // false = removed
markers[i].setMap(null);
delete markers[i];
}
});
markers = [];
$.each(values, function (i) {
delete values[i];
});
values = [];
ids = {};
idxs = {};
};
// link the display function
self.filter = function (f) {
ffilter = f;
redraw();
};
// enable/disable the clustering feature
self.enable = function (value) {
if (enabled !== value) {
enabled = value;
redraw();
}
};
// link the display function
self.display = function (f) {
fdisplay = f;
};
// link the errorfunction
self.error = function (f) {
ferror = f;
};
// lock the redraw
self.beginUpdate = function () {
updating = true;
};
// unlock the redraw
self.endUpdate = function () {
updating = false;
if (updated) {
redraw();
}
};
// extends current bounds with internal markers
self.autofit = function (bounds) {
var i;
for (i = 0; i < tds.length; i++) {
if (tds[i]) {
bounds.extend(tds[i].options.position);
}
}
};
// bind events
function main() {
projection = overlay.getProjection();
if (!projection) {
setTimeout(function () { main.apply(self, []); }, 25);
return;
}
ready = true;
events.push(gm.event.addListener(map, "zoom_changed", delayRedraw));
events.push(gm.event.addListener(map, "bounds_changed", delayRedraw));
redraw();
}
// flush overlays
function flush(key) {
if (isObject(store[key])) { // is overlay
if (isFunction(store[key].obj.setMap)) {
store[key].obj.setMap(null);
}
if (isFunction(store[key].obj.remove)) {
store[key].obj.remove();
}
if (isFunction(store[key].shadow.remove)) {
store[key].obj.remove();
}
if (isFunction(store[key].shadow.setMap)) {
store[key].shadow.setMap(null);
}
delete store[key].obj;
delete store[key].shadow;
} else if (markers[key]) { // marker not removed
markers[key].setMap(null);
// don't remove the marker object, it may be displayed later
}
delete store[key];
}
/**
* return the distance between 2 latLng couple into meters
* Params :
* Lat1, Lng1, Lat2, Lng2
* LatLng1, Lat2, Lng2
* Lat1, Lng1, LatLng2
* LatLng1, LatLng2
**/
function distanceInMeter() {
var lat1, lat2, lng1, lng2, e, f, g, h,
cos = Math.cos,
sin = Math.sin,
args = arguments;
if (args[0] instanceof gm.LatLng) {
lat1 = args[0].lat();
lng1 = args[0].lng();
if (args[1] instanceof gm.LatLng) {
lat2 = args[1].lat();
lng2 = args[1].lng();
} else {
lat2 = args[1];
lng2 = args[2];
}
} else {
lat1 = args[0];
lng1 = args[1];
if (args[2] instanceof gm.LatLng) {
lat2 = args[2].lat();
lng2 = args[2].lng();
} else {
lat2 = args[2];
lng2 = args[3];
}
}
e = Math.PI * lat1 / 180;
f = Math.PI * lng1 / 180;
g = Math.PI * lat2 / 180;
h = Math.PI * lng2 / 180;
return 1000 * 6371 * Math.acos(Math.min(cos(e) * cos(g) * cos(f) * cos(h) + cos(e) * sin(f) * cos(g) * sin(h) + sin(e) * sin(g), 1));
}
// extend the visible bounds
function extendsMapBounds() {
var radius = distanceInMeter(map.getCenter(), map.getBounds().getNorthEast()),
circle = new gm.Circle({
center: map.getCenter(),
radius: 1.25 * radius // + 25%
});
return circle.getBounds();
}
// return an object where keys are store keys
function getStoreKeys() {
var k,
keys = {};
for (k in store) {
keys[k] = true;
}
return keys;
}
// async the delay function
function delayRedraw() {
clearTimeout(timer);
timer = setTimeout(redraw, 25);
}
// generate bounds extended by radius
function extendsBounds(latLng) {
var p = projection.fromLatLngToDivPixel(latLng),
ne = projection.fromDivPixelToLatLng(new gm.Point(p.x + raw.radius, p.y - raw.radius)),
sw = projection.fromDivPixelToLatLng(new gm.Point(p.x - raw.radius, p.y + raw.radius));
return new gm.LatLngBounds(sw, ne);
}
// run the clustering process and call the display function
function redraw() {
if (updating || redrawing || !ready) {
return;
}
var i, j, k, indexes, check = false, bounds, cluster, position, previous, lat, lng, loop,
keys = [],
used = {},
zoom = map.getZoom(),
forceDisabled = ("maxZoom" in raw) && (zoom > raw.maxZoom),
previousKeys = getStoreKeys();
// reset flag
updated = false;
if (zoom > 3) {
// extend the bounds of the visible map to manage clusters near the boundaries
bounds = extendsMapBounds();
// check contain only if boundaries are valid
check = bounds.getSouthWest().lng() < bounds.getNorthEast().lng();
}
// calculate positions of "visibles" markers (in extended bounds)
for (i = 0; i < tds.length; i++) {
if (tds[i] && (!check || bounds.contains(tds[i].options.position)) && (!ffilter || ffilter(values[i]))) {
keys.push(i);
}
}
// for each "visible" marker, search its neighbors to create a cluster
// we can't do a classical "for" loop, because, analysis can bypass a marker while focusing on cluster
while (1) {
i = 0;
while (used[i] && (i < keys.length)) { // look for the next marker not used
i++;
}
if (i === keys.length) {
break;
}
indexes = [];
if (enabled && !forceDisabled) {
loop = 10;
do {
previous = indexes;
indexes = [];
loop--;
if (previous.length) {
position = bounds.getCenter();
} else {
position = tds[keys[i]].options.position;
}
bounds = extendsBounds(position);
for (j = i; j < keys.length; j++) {
if (used[j]) {
continue;
}
if (bounds.contains(tds[keys[j]].options.position)) {
indexes.push(j);
}
}
} while ((previous.length < indexes.length) && (indexes.length > 1) && loop);
} else {
for (j = i; j < keys.length; j++) {
if (!used[j]) {
indexes.push(j);
break;
}
}
}
cluster = {indexes: [], ref: []};
lat = lng = 0;
for (k = 0; k < indexes.length; k++) {
used[indexes[k]] = true;
cluster.indexes.push(keys[indexes[k]]);
cluster.ref.push(keys[indexes[k]]);
lat += tds[keys[indexes[k]]].options.position.lat();
lng += tds[keys[indexes[k]]].options.position.lng();
}
lat /= indexes.length;
lng /= indexes.length;
cluster.latLng = new gm.LatLng(lat, lng);
cluster.ref = cluster.ref.join("-");
if (cluster.ref in previousKeys) { // cluster doesn't change
delete previousKeys[cluster.ref]; // remove this entry, these still in this array will be removed
} else { // cluster is new
if (indexes.length === 1) { // alone markers are not stored, so need to keep the key (else, will be displayed every time and marker will blink)
store[cluster.ref] = true;
}
fdisplay(cluster);
}
}
// flush the previous overlays which are not still used
$.each(previousKeys, function (key) {
flush(key);
});
redrawing = false;
}
}
/**
* Class Clusterer
* a facade with limited method for external use
**/
function Clusterer(id, internalClusterer) {
var self = this;
self.id = function () {
return id;
};
self.filter = function (f) {
internalClusterer.filter(f);
};
self.enable = function () {
internalClusterer.enable(true);
};
self.disable = function () {
internalClusterer.enable(false);
};
self.add = function (marker, td, lock) {
if (!lock) {
internalClusterer.beginUpdate();
}
internalClusterer.addMarker(marker, td);
if (!lock) {
internalClusterer.endUpdate();
}
};
self.getById = function (id) {
return internalClusterer.getById(id);
};
self.clearById = function (id, lock) {
var result;
if (!lock) {
internalClusterer.beginUpdate();
}
result = internalClusterer.clearById(id);
if (!lock) {
internalClusterer.endUpdate();
}
return result;
};
self.clear = function (last, first, tag, lock) {
if (!lock) {
internalClusterer.beginUpdate();
}
internalClusterer.clear(last, first, tag);
if (!lock) {
internalClusterer.endUpdate();
}
};
}
/**
* Class OverlayView
* @constructor
*/
function OverlayView(map, opts, latLng, $div) {
var self = this,
listeners = [];
defaults.classes.OverlayView.call(self);
self.setMap(map);
self.onAdd = function () {
var panes = self.getPanes();
if (opts.pane in panes) {
$(panes[opts.pane]).append($div);
}
$.each("dblclick click mouseover mousemove mouseout mouseup mousedown".split(" "), function (i, name) {
listeners.push(
gm.event.addDomListener($div[0], name, function (e) {
$.Event(e).stopPropagation();
gm.event.trigger(self, name, [e]);
self.draw();
})
);
});
listeners.push(
gm.event.addDomListener($div[0], "contextmenu", function (e) {
$.Event(e).stopPropagation();
gm.event.trigger(self, "rightclick", [e]);
self.draw();
})
);
};
self.getPosition = function () {
return latLng;
};
self.setPosition = function (newLatLng) {
latLng = newLatLng;
self.draw();
};
self.draw = function () {
var ps = self.getProjection().fromLatLngToDivPixel(latLng);
$div
.css("left", (ps.x + opts.offset.x) + "px")
.css("top", (ps.y + opts.offset.y) + "px");
};
self.onRemove = function () {
var i;
for (i = 0; i < listeners.length; i++) {
gm.event.removeListener(listeners[i]);
}
$div.remove();
};
self.hide = function () {
$div.hide();
};
self.show = function () {
$div.show();
};
self.toggle = function () {
if ($div) {
if ($div.is(":visible")) {
self.show();
} else {
self.hide();
}
}
};
self.toggleDOM = function () {
self.setMap(self.getMap() ? null : map);
};
self.getDOMElement = function () {
return $div[0];
};
}
function Gmap3($this) {
var self = this,
stack = new Stack(),
store = new Store(),
map = null,
task;
/**
* if not running, start next action in stack
**/
function run() {
if (!task && (task = stack.get())) {
task.run();
}
}
/**
* called when action in finished, to acknoledge the current in stack and start next one
**/
function end() {
task = null;
stack.ack();
run.call(self); // restart to high level scope
}
//-----------------------------------------------------------------------//
// Tools
//-----------------------------------------------------------------------//
/**
* execute callback functions
**/
function callback(args) {
var params,
cb = args.td.callback;
if (cb) {
params = Array.prototype.slice.call(arguments, 1);
if (isFunction(cb)) {
cb.apply($this, params);
} else if (isArray(cb)) {
if (isFunction(cb[1])) {
cb[1].apply(cb[0], params);
}
}
}
}
/**
* execute ending functions
**/
function manageEnd(args, obj, id) {
if (id) {
attachEvents($this, args, obj, id);
}
callback(args, obj);
task.ack(obj);
}
/**
* initialize the map if not yet initialized
**/
function newMap(latLng, args) {
args = args || {};
var opts = args.td && args.td.options ? args.td.options : 0;
if (map) {
if (opts) {
if (opts.center) {
opts.center = toLatLng(opts.center);
}
map.setOptions(opts);
}
} else {
opts = args.opts || $.extend(true, {}, defaults.map, opts || {});
opts.center = latLng || toLatLng(opts.center);
map = new defaults.classes.Map($this.get(0), opts);
}
}
/**
* store actions to execute in a stack manager
**/
self._plan = function (list) {
var k;
for (k = 0; k < list.length; k++) {
stack.add(new Task(self, end, list[k]));
}
run();
};
/**
* Initialize gm.Map object
**/
self.map = function (args) {
newMap(args.latLng, args);
attachEvents($this, args, map);
manageEnd(args, map);
};
/**
* destroy an existing instance
**/
self.destroy = function (args) {
store.clear();
$this.empty();
if (map) {
map = null;
}
manageEnd(args, true);
};
/**
* add an overlay
**/
self.overlay = function (args, internal) {
var objs = [],
multiple = "values" in args.td;
if (!multiple) {
args.td.values = [{latLng: args.latLng, options: args.opts}];
}
if (!args.td.values.length) {
manageEnd(args, false);
return;
}
if (!OverlayView.__initialised) {
OverlayView.prototype = new defaults.classes.OverlayView();
OverlayView.__initialised = true;
}
$.each(args.td.values, function (i, value) {
var id, obj, td = tuple(args, value),
$div = $(document.createElement("div")).css({
border: "none",
borderWidth: 0,
position: "absolute"
});
$div.append(td.options.content);
obj = new OverlayView(map, td.options, toLatLng(td) || toLatLng(value), $div);
objs.push(obj);
$div = null; // memory leak
if (!internal) {
id = store.add(args, "overlay", obj);
attachEvents($this, {td: td}, obj, id);
}
});
if (internal) {
return objs[0];
}
manageEnd(args, multiple ? objs : objs[0]);
};
/**
* Create an InternalClusterer object
**/
function createClusterer(raw) {
var internalClusterer = new InternalClusterer($this, map, raw),
td = {},
styles = {},
thresholds = [],
isInt = /^[0-9]+$/,
calculator,
k;
for (k in raw) {
if (isInt.test(k)) {
thresholds.push(1 * k); // cast to int
styles[k] = raw[k];
styles[k].width = styles[k].width || 0;
styles[k].height = styles[k].height || 0;
} else {
td[k] = raw[k];
}
}
thresholds.sort(function (a, b) { return a > b; });
// external calculator
if (td.calculator) {
calculator = function (indexes) {
var data = [];
$.each(indexes, function (i, index) {
data.push(internalClusterer.value(index));
});
return td.calculator.apply($this, [data]);
};
} else {
calculator = function (indexes) {
return indexes.length;
};
}
// set error function
internalClusterer.error(function () {
error.apply(self, arguments);
});
// set display function
internalClusterer.display(function (cluster) {
var i, style, atd, obj, offset, shadow,
cnt = calculator(cluster.indexes);
// look for the style to use
if (raw.force || cnt > 1) {
for (i = 0; i < thresholds.length; i++) {
if (thresholds[i] <= cnt) {
style = styles[thresholds[i]];
}
}
}
if (style) {
offset = style.offset || [-style.width/2, -style.height/2];
// create a custom overlay command
// nb: 2 extends are faster self a deeper extend
atd = $.extend({}, td);
atd.options = $.extend({
pane: "overlayLayer",
content: style.content ? style.content.replace("CLUSTER_COUNT", cnt) : "",
offset: {
x: ("x" in offset ? offset.x : offset[0]) || 0,
y: ("y" in offset ? offset.y : offset[1]) || 0
}
},
td.options || {});
obj = self.overlay({td: atd, opts: atd.options, latLng: toLatLng(cluster)}, true);
atd.options.pane = "floatShadow";
atd.options.content = $(document.createElement("div")).width(style.width + "px").height(style.height + "px").css({cursor: "pointer"});
shadow = self.overlay({td: atd, opts: atd.options, latLng: toLatLng(cluster)}, true);
// store data to the clusterer
td.data = {
latLng: toLatLng(cluster),
markers:[]
};
$.each(cluster.indexes, function(i, index){
td.data.markers.push(internalClusterer.value(index));
if (internalClusterer.markerIsSet(index)){
internalClusterer.marker(index).setMap(null);
}
});
attachEvents($this, {td: td}, shadow, undef, {main: obj, shadow: shadow});
internalClusterer.store(cluster, obj, shadow);
} else {
$.each(cluster.indexes, function (i, index) {
internalClusterer.marker(index).setMap(map);
});
}
});
return internalClusterer;
}
/**
* add a marker
**/
self.marker = function (args) {
var objs,
clusterer, internalClusterer,
multiple = "values" in args.td,
init = !map;
if (!multiple) {
args.opts.position = args.latLng || toLatLng(args.opts.position);
args.td.values = [{options: args.opts}];
}
if (!args.td.values.length) {
manageEnd(args, false);
return;
}
if (init) {
newMap();
}
if (args.td.cluster && !map.getBounds()) { // map not initialised => bounds not available : wait for map if clustering feature is required
gm.event.addListenerOnce(map, "bounds_changed", function () { self.marker.apply(self, [args]); });
return;
}
if (args.td.cluster) {
if (args.td.cluster instanceof Clusterer) {
clusterer = args.td.cluster;
internalClusterer = store.getById(clusterer.id(), true);
} else {
internalClusterer = createClusterer(args.td.cluster);
clusterer = new Clusterer(globalId(args.td.id, true), internalClusterer);
store.add(args, "clusterer", clusterer, internalClusterer);
}
internalClusterer.beginUpdate();
$.each(args.td.values, function (i, value) {
var td = tuple(args, value);
td.options.position = td.options.position ? toLatLng(td.options.position) : toLatLng(value);
if (td.options.position) {
td.options.map = map;
if (init) {
map.setCenter(td.options.position);
init = false;
}
internalClusterer.add(td, value);
}
});
internalClusterer.endUpdate();
manageEnd(args, clusterer);
} else {
objs = [];
$.each(args.td.values, function (i, value) {
var id, obj,
td = tuple(args, value);
td.options.position = td.options.position ? toLatLng(td.options.position) : toLatLng(value);
if (td.options.position) {
td.options.map = map;
if (init) {
map.setCenter(td.options.position);
init = false;
}
obj = new defaults.classes.Marker(td.options);
objs.push(obj);
id = store.add({td: td}, "marker", obj);
attachEvents($this, {td: td}, obj, id);
}
});
manageEnd(args, multiple ? objs : objs[0]);
}
};
/**
* return a route
**/
self.getroute = function (args) {
args.opts.origin = toLatLng(args.opts.origin, true);
args.opts.destination = toLatLng(args.opts.destination, true);
directionsService().route(
args.opts,
function (results, status) {
callback(args, status === gm.DirectionsStatus.OK ? results : false, status);
task.ack();
}
);
};
/**
* return the distance between an origin and a destination
*
**/
self.getdistance = function (args) {
var i;
args.opts.origins = array(args.opts.origins);
for (i = 0; i < args.opts.origins.length; i++) {
args.opts.origins[i] = toLatLng(args.opts.origins[i], true);
}
args.opts.destinations = array(args.opts.destinations);
for (i = 0; i < args.opts.destinations.length; i++) {
args.opts.destinations[i] = toLatLng(args.opts.destinations[i], true);
}
distanceMatrixService().getDistanceMatrix(
args.opts,
function (results, status) {
callback(args, status === gm.DistanceMatrixStatus.OK ? results : false, status);
task.ack();
}
);
};
/**
* add an infowindow
**/
self.infowindow = function (args) {
var objs = [],
multiple = "values" in args.td;
if (!multiple) {
if (args.latLng) {
args.opts.position = args.latLng;
}
args.td.values = [{options: args.opts}];
}
$.each(args.td.values, function (i, value) {
var id, obj,
td = tuple(args, value);
td.options.position = td.options.position ? toLatLng(td.options.position) : toLatLng(value.latLng);
if (!map) {
newMap(td.options.position);
}
obj = new defaults.classes.InfoWindow(td.options);
if (obj && (isUndefined(td.open) || td.open)) {
if (multiple) {
obj.open(map, td.anchor || undef);
} else {
obj.open(map, td.anchor || (args.latLng ? undef : (args.session.marker ? args.session.marker : undef)));
}
}
objs.push(obj);
id = store.add({td: td}, "infowindow", obj);
attachEvents($this, {td: td}, obj, id);
});
manageEnd(args, multiple ? objs : objs[0]);
};
/**
* add a circle
**/
self.circle = function (args) {
var objs = [],
multiple = "values" in args.td;
if (!multiple) {
args.opts.center = args.latLng || toLatLng(args.opts.center);
args.td.values = [{options: args.opts}];
}
if (!args.td.values.length) {
manageEnd(args, false);
return;
}
$.each(args.td.values, function (i, value) {
var id, obj,
td = tuple(args, value);
td.options.center = td.options.center ? toLatLng(td.options.center) : toLatLng(value);
if (!map) {
newMap(td.options.center);
}
td.options.map = map;
obj = new defaults.classes.Circle(td.options);
objs.push(obj);
id = store.add({td: td}, "circle", obj);
attachEvents($this, {td: td}, obj, id);
});
manageEnd(args, multiple ? objs : objs[0]);
};
/**
* returns address structure from latlng
**/
self.getaddress = function (args) {
callback(args, args.results, args.status);
task.ack();
};
/**
* returns latlng from an address
**/
self.getlatlng = function (args) {
callback(args, args.results, args.status);
task.ack();
};
/**
* return the max zoom of a location
**/
self.getmaxzoom = function (args) {
maxZoomService().getMaxZoomAtLatLng(
args.latLng,
function (result) {
callback(args, result.status === gm.MaxZoomStatus.OK ? result.zoom : false, status);
task.ack();
}
);
};
/**
* return the elevation of a location
**/
self.getelevation = function (args) {
var i,
locations = [],
f = function (results, status) {
callback(args, status === gm.ElevationStatus.OK ? results : false, status);
task.ack();
};
if (args.latLng) {
locations.push(args.latLng);
} else {
locations = array(args.td.locations || []);
for (i = 0; i < locations.length; i++) {
locations[i] = toLatLng(locations[i]);
}
}
if (locations.length) {
elevationService().getElevationForLocations({locations: locations}, f);
} else {
if (args.td.path && args.td.path.length) {
for (i = 0; i < args.td.path.length; i++) {
locations.push(toLatLng(args.td.path[i]));
}
}
if (locations.length) {
elevationService().getElevationAlongPath({path: locations, samples:args.td.samples}, f);
} else {
task.ack();
}
}
};
/**
* define defaults values
**/
self.defaults = function (args) {
$.each(args.td, function(name, value) {
if (isObject(defaults[name])) {
defaults[name] = $.extend({}, defaults[name], value);
} else {
defaults[name] = value;
}
});
task.ack(true);
};
/**
* add a rectangle
**/
self.rectangle = function (args) {
var objs = [],
multiple = "values" in args.td;
if (!multiple) {
args.td.values = [{options: args.opts}];
}
if (!args.td.values.length) {
manageEnd(args, false);
return;
}
$.each(args.td.values, function (i, value) {
var id, obj,
td = tuple(args, value);
td.options.bounds = td.options.bounds ? toLatLngBounds(td.options.bounds) : toLatLngBounds(value);
if (!map) {
newMap(td.options.bounds.getCenter());
}
td.options.map = map;
obj = new defaults.classes.Rectangle(td.options);
objs.push(obj);
id = store.add({td: td}, "rectangle", obj);
attachEvents($this, {td: td}, obj, id);
});
manageEnd(args, multiple ? objs : objs[0]);
};
/**
* add a polygone / polyline
**/
function poly(args, poly, path) {
var objs = [],
multiple = "values" in args.td;
if (!multiple) {
args.td.values = [{options: args.opts}];
}
if (!args.td.values.length) {
manageEnd(args, false);
return;
}
newMap();
$.each(args.td.values, function (_, value) {
var id, i, j, obj,
td = tuple(args, value);
if (td.options[path]) {
if (td.options[path][0][0] && isArray(td.options[path][0][0])) {
for (i = 0; i < td.options[path].length; i++) {
for (j = 0; j < td.options[path][i].length; j++) {
td.options[path][i][j] = toLatLng(td.options[path][i][j]);
}
}
} else {
for (i = 0; i < td.options[path].length; i++) {
td.options[path][i] = toLatLng(td.options[path][i]);
}
}
}
td.options.map = map;
obj = new gm[poly](td.options);
objs.push(obj);
id = store.add({td: td}, poly.toLowerCase(), obj);
attachEvents($this, {td: td}, obj, id);
});
manageEnd(args, multiple ? objs : objs[0]);
}
self.polyline = function (args) {
poly(args, "Polyline", "path");
};
self.polygon = function (args) {
poly(args, "Polygon", "paths");
};
/**
* add a traffic layer
**/
self.trafficlayer = function (args) {
newMap();
var obj = store.get("trafficlayer");
if (!obj) {
obj = new defaults.classes.TrafficLayer();
obj.setMap(map);
store.add(args, "trafficlayer", obj);
}
manageEnd(args, obj);
};
/**
* add a bicycling layer
**/
self.bicyclinglayer = function (args) {
newMap();
var obj = store.get("bicyclinglayer");
if (!obj) {
obj = new defaults.classes.BicyclingLayer();
obj.setMap(map);
store.add(args, "bicyclinglayer", obj);
}
manageEnd(args, obj);
};
/**
* add a ground overlay
**/
self.groundoverlay = function (args) {
args.opts.bounds = toLatLngBounds(args.opts.bounds);
if (args.opts.bounds){
newMap(args.opts.bounds.getCenter());
}
var id,
obj = new defaults.classes.GroundOverlay(args.opts.url, args.opts.bounds, args.opts.opts);
obj.setMap(map);
id = store.add(args, "groundoverlay", obj);
manageEnd(args, obj, id);
};
/**
* set a streetview
**/
self.streetviewpanorama = function (args) {
if (!args.opts.opts) {
args.opts.opts = {};
}
if (args.latLng) {
args.opts.opts.position = args.latLng;
} else if (args.opts.opts.position) {
args.opts.opts.position = toLatLng(args.opts.opts.position);
}
if (args.td.divId) {
args.opts.container = document.getElementById(args.td.divId);
} else if (args.opts.container) {
args.opts.container = $(args.opts.container).get(0);
}
var id, obj = new defaults.classes.StreetViewPanorama(args.opts.container, args.opts.opts);
if (obj) {
map.setStreetView(obj);
}
id = store.add(args, "streetviewpanorama", obj);
manageEnd(args, obj, id);
};
self.kmllayer = function (args) {
var objs = [],
multiple = "values" in args.td;
if (!multiple) {
args.td.values = [{options: args.opts}];
}
if (!args.td.values.length) {
manageEnd(args, false);
return;
}
$.each(args.td.values, function (i, value) {
var id, obj, options,
td = tuple(args, value);
if (!map) {
newMap();
}
options = td.options;
// compatibility 5.0-
if (td.options.opts) {
options = td.options.opts;
if (td.options.url) {
options.url = td.options.url;
}
}
// -- end --
options.map = map;
if (googleVersionMin("3.10")) {
obj = new defaults.classes.KmlLayer(options);
} else {
obj = new defaults.classes.KmlLayer(options.url, options);
}
objs.push(obj);
id = store.add({td: td}, "kmllayer", obj);
attachEvents($this, {td: td}, obj, id);
});
manageEnd(args, multiple ? objs : objs[0]);
};
/**
* add a fix panel
**/
self.panel = function (args) {
newMap();
var id, $content,
x = 0,
y = 0,
$div = $(document.createElement("div"));
$div.css({
position: "absolute",
zIndex: 1000,
visibility: "hidden"
});
if (args.opts.content) {
$content = $(args.opts.content);
$div.append($content);
$this.first().prepend($div);
if (!isUndefined(args.opts.left)) {
x = args.opts.left;
} else if (!isUndefined(args.opts.right)) {
x = $this.width() - $content.width() - args.opts.right;
} else if (args.opts.center) {
x = ($this.width() - $content.width()) / 2;
}
if (!isUndefined(args.opts.top)) {
y = args.opts.top;
} else if (!isUndefined(args.opts.bottom)) {
y = $this.height() - $content.height() - args.opts.bottom;
} else if (args.opts.middle) {
y = ($this.height() - $content.height()) / 2
}
$div.css({
top: y,
left: x,
visibility: "visible"
});
}
id = store.add(args, "panel", $div);
manageEnd(args, $div, id);
$div = null; // memory leak
};
/**
* add a direction renderer
**/
self.directionsrenderer = function (args) {
args.opts.map = map;
var id,
obj = new gm.DirectionsRenderer(args.opts);
if (args.td.divId) {
obj.setPanel(document.getElementById(args.td.divId));
} else if (args.td.container) {
obj.setPanel($(args.td.container).get(0));
}
id = store.add(args, "directionsrenderer", obj);
manageEnd(args, obj, id);
};
/**
* returns latLng of the user
**/
self.getgeoloc = function (args) {
manageEnd(args, args.latLng);
};
/**
* add a style
**/
self.styledmaptype = function (args) {
newMap();
var obj = new defaults.classes.StyledMapType(args.td.styles, args.opts);
map.mapTypes.set(args.td.id, obj);
manageEnd(args, obj);
};
/**
* add an imageMapType
**/
self.imagemaptype = function (args) {
newMap();
var obj = new defaults.classes.ImageMapType(args.opts);
map.mapTypes.set(args.td.id, obj);
manageEnd(args, obj);
};
/**
* autofit a map using its overlays (markers, rectangles ...)
**/
self.autofit = function (args) {
var bounds = new gm.LatLngBounds();
$.each(store.all(), function (i, obj) {
if (obj.getPosition) {
bounds.extend(obj.getPosition());
} else if (obj.getBounds) {
bounds.extend(obj.getBounds().getNorthEast());
bounds.extend(obj.getBounds().getSouthWest());
} else if (obj.getPaths) {
obj.getPaths().forEach(function (path) {
path.forEach(function (latLng) {
bounds.extend(latLng);
});
});
} else if (obj.getPath) {
obj.getPath().forEach(function (latLng) {
bounds.extend(latLng);
});
} else if (obj.getCenter) {
bounds.extend(obj.getCenter());
} else if (typeof Clusterer === "function" && obj instanceof Clusterer) {
obj = store.getById(obj.id(), true);
if (obj) {
obj.autofit(bounds);
}
}
});
if (!bounds.isEmpty() && (!map.getBounds() || !map.getBounds().equals(bounds))) {
if ("maxZoom" in args.td) {
// fitBouds Callback event => detect zoom level and check maxZoom
gm.event.addListenerOnce(
map,
"bounds_changed",
function () {
if (this.getZoom() > args.td.maxZoom) {
this.setZoom(args.td.maxZoom);
}
}
);
}
map.fitBounds(bounds);
}
manageEnd(args, true);
};
/**
* remove objects from a map
**/
self.clear = function (args) {
if (isString(args.td)) {
if (store.clearById(args.td) || store.objClearById(args.td)) {
manageEnd(args, true);
return;
}
args.td = {name: args.td};
}
if (args.td.id) {
$.each(array(args.td.id), function (i, id) {
store.clearById(id) || store.objClearById(id);
});
} else {
store.clear(array(args.td.name), args.td.last, args.td.first, args.td.tag);
store.objClear(array(args.td.name), args.td.last, args.td.first, args.td.tag);
}
manageEnd(args, true);
};
/**
* return objects previously created
**/
self.get = function (args, direct, full) {
var name, res,
td = direct ? args : args.td;
if (!direct) {
full = td.full;
}
if (isString(td)) {
res = store.getById(td, false, full) || store.objGetById(td);
if (res === false) {
name = td;
td = {};
}
} else {
name = td.name;
}
if (name === "map") {
res = map;
}
if (!res) {
res = [];
if (td.id) {
$.each(array(td.id), function (i, id) {
res.push(store.getById(id, false, full) || store.objGetById(id));
});
if (!isArray(td.id)) {
res = res[0];
}
} else {
$.each(name ? array(name) : [undef], function (i, aName) {
var result;
if (td.first) {
result = store.get(aName, false, td.tag, full);
if (result) {
res.push(result);
}
} else if (td.all) {
$.each(store.all(aName, td.tag, full), function (i, result) {
res.push(result);
});
} else {
result = store.get(aName, true, td.tag, full);
if (result) {
res.push(result);
}
}
});
if (!td.all && !isArray(name)) {
res = res[0];
}
}
}
res = isArray(res) || !td.all ? res : [res];
if (direct) {
return res;
} else {
manageEnd(args, res);
}
};
/**
* run a function on each items selected
**/
self.exec = function (args) {
$.each(array(args.td.func), function (i, func) {
$.each(self.get(args.td, true, args.td.hasOwnProperty("full") ? args.td.full : true), function (j, res) {
func.call($this, res);
});
});
manageEnd(args, true);
};
/**
* trigger events on the map
**/
self.trigger = function (args) {
if (isString(args.td)) {
gm.event.trigger(map, args.td);
} else {
var options = [map, args.td.eventName];
if (args.td.var_args) {
$.each(args.td.var_args, function (i, v) {
options.push(v);
});
}
gm.event.trigger.apply(gm.event, options);
}
callback(args);
task.ack();
};
}
$.fn.gmap3 = function () {
var i,
list = [],
empty = true,
results = [];
// init library
initDefaults();
// store all arguments in a td list
for (i = 0; i < arguments.length; i++) {
if (arguments[i]) {
list.push(arguments[i]);
}
}
// resolve empty call - run init
if (!list.length) {
list.push("map");
}
// loop on each jQuery object
$.each(this, function () {
var $this = $(this),
gmap3 = $this.data("gmap3");
empty = false;
if (!gmap3) {
gmap3 = new Gmap3($this);
$this.data("gmap3", gmap3);
}
if (list.length === 1 && (list[0] === "get" || isDirectGet(list[0]))) {
if (list[0] === "get") {
results.push(gmap3.get("map", true));
} else {
results.push(gmap3.get(list[0].get, true, list[0].get.full));
}
} else {
gmap3._plan(list);
}
});
// return for direct call only
if (results.length) {
if (results.length === 1) { // 1 css selector
return results[0];
}
return results;
}
return this;
};
})(jQuery);