summaryrefslogtreecommitdiffstats
path: root/admin/survey/script/Maps
diff options
context:
space:
mode:
Diffstat (limited to 'admin/survey/script/Maps')
-rw-r--r--admin/survey/script/Maps/CenterControl.js138
-rw-r--r--admin/survey/script/Maps/VnaprejMarkers.js621
-rw-r--r--admin/survey/script/Maps/mapInBox.js343
-rw-r--r--admin/survey/script/Maps/markerclusterer.js1318
4 files changed, 2420 insertions, 0 deletions
diff --git a/admin/survey/script/Maps/CenterControl.js b/admin/survey/script/Maps/CenterControl.js
new file mode 100644
index 0000000..3d69965
--- /dev/null
+++ b/admin/survey/script/Maps/CenterControl.js
@@ -0,0 +1,138 @@
+// Author: Uroš Podkrižnik (23.1.2017)
+// Tip vprasanja = 26
+
+//SET CONTROLS FOR SETTING FOCUS ON MAP
+
+/**
+ * Used in Branching.php for setting Map focus on Map
+ * @param {type} controlDiv - div to put in custom settings - buttons
+ * @param {type} map - google map
+ * @param {type} spremenljivka - id spremenljivke
+ * @returns {undefined}
+ */
+function centerControl(controlDiv, map, spremenljivka) {
+
+ // Set CSS for the control border.
+ var controlUI = document.createElement('div');
+ controlUI.style.backgroundColor = '#fff';
+ controlUI.style.border = '2px solid #fff';
+ controlUI.style.borderRadius = '3px';
+ controlUI.style.boxShadow = '0 2px 6px rgba(0,0,0,.3)';
+ controlUI.style.cursor = 'pointer';
+ controlUI.style.marginBottom = '22px';
+ controlUI.style.textAlign = 'center';
+ controlUI.style.display = 'inline-block';
+ controlUI.title = lang['srv_vprasanje_fokus_button_map_set_title'];
+ controlDiv.appendChild(controlUI);
+
+ // Set CSS for the control interior.
+ var controlText = document.createElement('div');
+ controlText.style.color = 'rgb(25,25,25)';
+ controlText.style.fontFamily = 'Roboto,Arial,sans-serif';
+ controlText.style.fontSize = '16px';
+ controlText.style.lineHeight = '28px';
+ controlText.style.paddingLeft = '5px';
+ controlText.style.paddingRight = '5px';
+ controlText.innerHTML = lang['srv_vprasanje_fokus_button_map_set'];
+ controlUI.appendChild(controlText);
+
+ //cancel button
+ var controlUI1 = document.createElement('div');
+ controlUI1.style.backgroundColor = '#fff';
+ controlUI1.style.border = '2px solid #fff';
+ controlUI1.style.borderRadius = '3px';
+ controlUI1.style.boxShadow = '0 2px 6px rgba(0,0,0,.3)';
+ controlUI1.style.cursor = 'pointer';
+ controlUI1.style.marginBottom = '22px';
+ controlUI1.style.textAlign = 'center';
+ controlUI1.style.display = 'none';
+ controlUI1.style.marginLeft = '5px';
+ controlUI1.title = lang['srv_vprasanje_fokus_button_map_cancel_title'];
+ controlDiv.appendChild(controlUI1);
+
+ //cancel button
+ var controlText1 = document.createElement('div');
+ controlText1.style.color = 'rgb(25,25,25)';
+ controlText1.style.fontFamily = 'Roboto,Arial,sans-serif';
+ controlText1.style.fontSize = '16px';
+ controlText1.style.lineHeight = '28px';
+ controlText1.style.paddingLeft = '5px';
+ controlText1.style.paddingRight = '5px';
+ controlText1.innerHTML = lang['srv_vprasanje_fokus_button_map_cancel'];
+ controlUI1.appendChild(controlText1);
+
+ // Setup the click event listeners
+ controlUI.addEventListener('click', function() {
+ if(controlText.innerHTML === lang['srv_vprasanje_fokus_button_map_set']){
+ controlText.innerHTML = lang['srv_vprasanje_fokus_button_map_save'];
+ controlUI.title = lang['srv_vprasanje_fokus_button_map_save_title'];
+
+ setMapMovable(map);
+
+ controlUI1.style.display = 'inline-block';
+ }
+ else{
+ //shrani spremembe v bazo
+ set_fokus_koordiante_map(spremenljivka, map.getCenter().lat(),
+ map.getCenter().lng(), map.getZoom(), '');
+ set_fokus_string_map(spremenljivka, '')
+
+ var fokus_mape_settings = document.getElementById("fokus_mape_"+spremenljivka);
+ if(fokus_mape_settings)
+ //v nastavitvah vprasanja pobrisi value textboxa za fokus
+ document.getElementById("fokus_mape_"+spremenljivka).value = "";
+
+ setMapNoFocus(map);
+ }
+ });
+
+ // Setup the click event listeners for cancel
+ controlUI1.addEventListener('click', function() {
+ setMapNoFocus(map);
+ vprasanje_fullscreen(spremenljivka);
+ });
+
+ //set map to not focusing mode
+ function setMapNoFocus(map){
+ setMapFixed(map);
+ controlUI1.style.display = 'none';
+ controlText.innerHTML = lang['srv_vprasanje_fokus_button_map_set'];
+ controlUI.title = lang['srv_vprasanje_fokus_button_map_set_title'];
+ }
+}
+
+function set_fokus_koordiante_map(spremenljivka, lat, lng, zoom, fokus){
+ //kreiraj json za kasnejsi fokus mape - da se ne porabljajo kvote za geocoding
+ var fokusJSON = {koordinate:{center:{lat:null, lng:null}, zoom:null, fokus:fokus},
+ spr_id:spremenljivka, anketa: srv_meta_anketa_id};
+ fokusJSON.koordinate.center.lat = lat;
+ fokusJSON.koordinate.center.lng = lng;
+ fokusJSON.koordinate.zoom = zoom;
+
+ //shrani parametre v bazo - BranchingAjax.php -> ajax_fokus_koordiante_map()
+ $.post('ajax.php?t=branching&a=fokus_koordiante_map', fokusJSON);
+}
+
+function set_fokus_string_map(spremenljivka, fokus){
+ //spremeni fokus string v praznega, saj je custom narejen fokus
+ var fokusJSON2 = {fokus:fokus, spr_id:spremenljivka, anketa: srv_meta_anketa_id};
+ $.post('ajax.php?t=branching&a=fokus_string_map', fokusJSON2);
+}
+
+function setMapMovable(map){
+ map.set('zoomControl', true);
+ map.set('disableDoubleClickZoom', false);
+ map.set('scrollwheel', true);
+ map.set('navigationControl', true);
+ map.set('draggable', true);
+ map.set('mapTypeControl', true);
+}
+
+function setMapFixed(map){
+ map.set('zoomControl', false);
+ map.set('disableDoubleClickZoom', true);
+ map.set('scrollwheel', false);
+ map.set('navigationControl', false);
+ map.set('draggable', false);
+ map.set('mapTypeControl', false);
+} \ No newline at end of file
diff --git a/admin/survey/script/Maps/VnaprejMarkers.js b/admin/survey/script/Maps/VnaprejMarkers.js
new file mode 100644
index 0000000..3bca4e6
--- /dev/null
+++ b/admin/survey/script/Maps/VnaprejMarkers.js
@@ -0,0 +1,621 @@
+// Author: Uroš Podkrižnik (22.2.2017)
+// Tip vprasanja = 26
+
+//MARKERS - adding markers in branching
+
+//storing active shape, to for instance disable edit mode when needed
+var active_shape;
+
+/**
+ * Ce obstajajo podatki v bazi (rec. uporabnik klikne 'Prejsnja stran').
+ * Kreirajo se markerji, shranjeni v bazi.
+ * @param spremenljivka int id spremenljivke
+ * @param map_data JSON Object
+ * @param locked boolean true, if survey is locked in branching
+ */
+function map_data_fill_vnaprej_mrkerji(spremenljivka, map_data, locked) {
+ for (var row in map_data) {
+ var row_object = map_data[row];
+ var pos = {lat: row_object.lat, lng: row_object.lng};
+
+ createMarkerVnaprej(spremenljivka, row_object.address, row_object.naslov,
+ ustvari_basic_marker(spremenljivka, pos, false), row_object.id, true, locked);
+ }
+}
+
+/**
+ * Ce obstajajo podatki v bazi izrisi info shapes
+ * Kreirajo se markerji, shranjeni v bazi.
+ * @param spremenljivka int id spremenljivke
+ * @param map_data JSON Object
+ * @param locked boolean true, if survey is locked in branching
+ * @returns {int} ID of last shape
+ */
+function map_data_fill_vnaprej_shapes(spremenljivka, map_data, locked) {
+ //pridobi mapo spremenljivke
+ var map = document.getElementById("br_map_" + spremenljivka).gMap;
+
+ var shapeOption = polylineOptions(map);
+ var last_id = 0;
+ for (var i = 0; i < map_data.length; i++)
+ {
+
+ last_id = map_data[i].overlay_id;
+ var color = mapShapeColors[map_data[i].overlay_id-1 % mapShapeColors.length];
+
+ //set and show the shape
+ shapeOption.path = map_data[i].path;
+ shapeOption.strokeColor = '#' + color;
+ shapeOption.strokeColorOrg = '#' + color;
+ shapeOption.editable = false;
+ var shape = new google.maps.Polyline(shapeOption);
+
+ shape.overlay_id = map_data[i].overlay_id;
+ shape.address = map_data[i].address;
+ shape.infowin = makeShapeInfoWindow(spremenljivka, shape, locked);
+ setListenersShapeVM(spremenljivka, shape, map, locked);
+
+ //google.maps.event.addListener(shape, 'mouseover', function() { this.setOptions({strokeWeight: 6, strokeColor: 'black', zIndex: 2}); });
+ //google.maps.event.addListener(shape, 'mouseout', function() { this.setOptions({strokeWeight: 4, strokeColor: this.strokeColorOrg , zIndex: 1}); });
+ }
+ return last_id;
+}
+
+/**
+ * Ustvari in vrne basic marker, na mapo spremenljivke z danimi koordinatami
+ * @param {type} spremenljivka - int - id spremenljivke
+ * @param {type} pos - koordinate - objekt {lat: ???, lng: ???}
+ * @param {string} addressFromSearchBox - address if adding marker from searchBox, null otherwise
+ * (used for ignoring duplicates)
+ * @returns {google.maps.Marker}
+ */
+function ustvari_basic_marker(spremenljivka, pos, addressFromSearchBox) {
+ var marker = addressFromSearchBox ? findMarkerFromAddress(addressFromSearchBox, spremenljivka) : null;
+
+ if (!marker) {
+ //pridobi mapo spremenljivke
+ var map = document.getElementById("br_map_" + spremenljivka).gMap;
+
+ //kreiraj marker
+ var marker = new google.maps.Marker({
+ position: new google.maps.LatLng(pos.lat, pos.lng),
+ map: map,
+ identifier: getMarkerUniqueId(pos.lat, pos.lng, spremenljivka),
+ icon: {url: "img_0/marker_text_off.png"}
+ });
+
+ //hide warning text for 'no values yet'
+ var no_value_warning = document.getElementById("variabla_no_value_" + spremenljivka);
+ no_value_warning.style.display = "none";
+
+ allMarkers[spremenljivka].push(marker);
+
+ return marker;
+ } else {
+ //trigger click on marker, to focus it and open infowindow
+ google.maps.event.trigger(marker, 'click');
+ return null
+ }
+}
+
+/**
+ * funkcija, ki kreira osnovni marker (rdec)
+ * @param {type} spremenljivka - int - id spremenljivke
+ * @param {type} add - String - label o informacijah markerja
+ * @param {type} text - string - text za windowinfo textarea
+ * @param {type} marker - google maps new created marker
+ * @returns {undefined}
+ */
+function shraniMarker(spremenljivka, add, text, marker) {
+ if (marker != null) {
+ //save new marker in DB
+ $.post('ajax.php?t=branching&a=save_marker', {spr_id: spremenljivka, address: add,
+ lat: marker.getPosition().lat(), lng: marker.getPosition().lng(), anketa: srv_meta_anketa_id},
+ function (id) {
+ //id - id markerja v bazi
+ createMarkerVnaprej(spremenljivka, add, text, marker, id, false);
+ });
+ }
+}
+
+/**
+ * funkcija, ki kreira osnovni marker (rdec)
+ * @param {type} spremenljivka - int - id spremenljivke
+ * @param {type} add - String - label o informacijah markerja
+ * @param {type} naslov - string - text za windowinfo textarea
+ * @param {type} marker - google maps new created marker
+ * @param {type} id - id vrednosti markerja iz tabele srv_vrednost
+ * @param {type} fromLoad - boolean true, if data for map already axists and
+ * this function was used to load a map with existing markers
+ * @param locked boolean true, if survey is locked in branching
+ * @returns {undefined}
+ */
+function createMarkerVnaprej(spremenljivka, add, naslov, marker, id, fromLoad, locked) {
+ if (marker != null) {
+ //pridobi mapo spremenljivke
+ var map = document.getElementById("br_map_" + spremenljivka).gMap;
+
+ marker.address = add;
+ marker.id = id;
+
+ bounds[spremenljivka].extend(marker.position);
+
+ //markers[markerId] = marker; // cache marker in markers object
+ //dont set listeners if survey is locked
+ if (!locked)
+ bindMarkerEventsVM(spremenljivka, marker, map); // bind right click event to marker
+
+ //Create a div element for container.
+ var container = document.createElement("div");
+ //margin, da se pri italic ne odreze zadja crka
+ container.style.cssText = 'margin-right:1px';
+
+ //Create a textarea for the input text
+ var textBoxTitle = document.createElement("input");
+ textBoxTitle.style.cssText = "font-weight:bold; font-size:1em";
+ textBoxTitle.type = "text";
+ textBoxTitle.value = naslov;
+ textBoxTitle.id = "input_map_vre_" + id;
+ textBoxTitle.className = "input_title";
+ container.appendChild(textBoxTitle);
+ container.appendChild(document.createElement("br"));
+ //ce je anketa locked, disablaj urejanje naslova
+ if (locked)
+ textBoxTitle.disabled = "disabled";
+
+ //ko se spremeni textarea v windowinfo, spremeni value inputa za text
+ google.maps.event.addDomListener(textBoxTitle, "change", function () {
+ //save title of marker in DB
+ $.post('ajax.php?t=vprasanjeinline&a=inline_vrednost_naslov_save', {
+ spremenljivka: spremenljivka, vrednost: id, naslov: textBoxTitle.value,
+ anketa: srv_meta_anketa_id, lang_id: srv_meta_lang_id});
+ });
+
+ //Create a label element for address.
+ var address = document.createElement("label");
+ address.innerHTML = '<i>' + add + '</i>';
+ address.style.cssText = 'font-size:0.85em;';
+ container.appendChild(address);
+ container.appendChild(document.createElement("br"));
+
+ //create label for subquestion if exist or is not empty string
+ if (podvprasanje_naslov[spremenljivka]) {
+ container.appendChild(document.createElement("br"));
+ //Create a label element for subquestion.
+ var podvprasanje = document.createElement("label");
+ podvprasanje.innerHTML = '<b>' + podvprasanje_naslov[spremenljivka] + '</b>';
+ podvprasanje.style.cssText = 'font-size:0.95em';
+ container.appendChild(podvprasanje);
+ container.appendChild(document.createElement("br"));
+ }
+
+ var textBox = document.createElement("textarea");
+ //textBox.setAttribute("id", markerId + "_textarea");
+ textBox.style.width = "100%";
+ textBox.className = "boxsizingBorder";
+ textBox.disabled = "disabled";
+ textBox.style.height = "50px";
+ textBox.style.resize = "none";
+ container.appendChild(textBox);
+
+ //listener ob kliku na marker
+ google.maps.event.addListener(marker, 'click', function () {
+ //if infowindow in opened, blur focus, so saving in DB can be triggered
+ blurTitleInfowindow(false);
+
+ infowindow.setContent(container);
+ infowindow.open(map, marker);
+ $("#input_map_vre_" + id).focus();
+ });
+
+ if (!fromLoad) {
+ //if infowindow in opened, blur focus, so saving in DB can be triggered
+ blurTitleInfowindow(true);
+
+ //nastavi label
+ infowindow.setContent(container);
+ //odpre marker - prikaze label (kot da bi ga kliknil)
+ infowindow.open(map, marker);
+ //fokus na input
+ $("#input_map_vre_" + id).focus();
+ } else {
+ //zemljevid se prilagodi okviru
+ map.fitBounds(bounds[spremenljivka]);
+ }
+
+ st_markerjev[spremenljivka]++;
+ }
+}
+
+/**
+ * handler za desni klik in dvojni klik na markerja
+ * @param {type} spremenljivka - int - id spremenljivke
+ * @param {type} marker
+ * @param {type} map - google map
+ */
+function bindMarkerEventsVM(spremenljivka, marker, map) {
+ google.maps.event.addListener(marker, "rightclick", function () {
+ //removeMarker(spremenljivka, marker); // remove marker from array
+ deleteMenu.open(map, null, null, spremenljivka, null, null, marker);
+ });
+ google.maps.event.addListener(marker, "dblclick", function () {
+ //removeMarker(spremenljivka, marker); // remove marker from array
+ deleteMenu.open(map, null, null, spremenljivka, null, null, marker);
+ });
+}
+
+/**
+ * All necessaries to remove marker - from map, DB, global variables
+ * @param {type} spremenljivka - int - id spremenljivke
+ * @param {type} marker
+ */
+function removeMarkerVM(spremenljivka, marker) {
+ function callback() {
+ marker.setMap(null); // set markers setMap to null to remove it from map
+ //remove marker from marker array
+ var index = allMarkers[spremenljivka].indexOf(marker);
+ if (index > -1)
+ allMarkers[spremenljivka].splice(index, 1);
+ st_markerjev[spremenljivka]--;
+ if (st_markerjev[spremenljivka] < 1) {
+ //show warning text for 'no values yet'
+ var no_value_warning = document.getElementById("variabla_no_value_" + spremenljivka);
+ no_value_warning.style.display = "inline-block";
+ }
+ }
+ inline_vrednost_delete(spremenljivka, marker.id, 0, callback);
+}
+
+/**
+ * Function to set parameters for drawing a polygon on map
+ *
+ * @param {type} spremenljivka - id spremenljivke
+ * @returns {undefined}
+ */
+function drawMarkers(spremenljivka) {
+ var map = document.getElementById("br_map_" + spremenljivka).gMap;
+
+ var startDrawingMode = google.maps.drawing.OverlayType.MARKER;
+
+ var icon = {
+ url: "img_0/marker_text_off.png"
+ };
+
+ var drawingManager = new google.maps.drawing.DrawingManager({
+ drawingMode: startDrawingMode,
+ drawingControl: true,
+ drawingControlOptions: {
+ position: google.maps.ControlPosition.TOP_RIGHT,
+ drawingModes: ['marker', 'polyline']
+ },
+ markerOptions: {icon: icon},
+ polylineOptions: polylineOptions(map)
+ });
+
+ //listener when user creates a marker
+ google.maps.event.addListener(drawingManager, 'markercomplete', function (marker) {
+
+ //hide warning text for 'no values yet'
+ var no_value_warning = document.getElementById("variabla_no_value_" + spremenljivka);
+ no_value_warning.style.display = "none";
+
+ //koordinate
+ var pos = {
+ lat: marker.getPosition().lat(),
+ lng: marker.getPosition().lng()
+ };
+
+ marker.indentifier = getMarkerUniqueId(pos.lat, pos.lng, spremenljivka);
+ allMarkers[spremenljivka].push(marker);
+
+ // naslov se pridobi, da se klice geocoding
+ GeocodingF(pos, function (data) {
+ //ce ne vrne null - je nasel naslov
+ if (data != null) {
+ //createMarkerVnaprej(spremenljivka, data.formatted_address, '', marker);
+ shraniMarker(spremenljivka, data.formatted_address, '', marker);
+ } else {
+ marker.setMap(null);
+ //odpre se okno, ce je prislo do napake - null - (mozen je tudi prekratek delay med geocoding requesti)
+ alert(lang['srv_resevanje_alert_location_not_found_map']);
+ }
+ });
+ });
+
+ //listener when user creates a line
+ google.maps.event.addListener(drawingManager, 'polylinecomplete', function (line) {
+ afterShapeCompleteSettingsVM(line, spremenljivka, map);
+ });
+
+ drawingManager.setMap(map);
+
+ google.maps.event.addListener(map, 'click', function () {
+ changeFocusLine(null);
+ });
+}
+
+/**
+ * Do some things when focus is changed when inculdin lines
+ * @param {type} shape - shape that is clicked or wanted to be in focus
+ * @returns {undefined}
+ */
+function changeFocusLine(shape) {
+ if (active_shape != shape) {
+ if (active_shape)
+ active_shape.setOptions({editable: false});
+ active_shape = shape;
+ }
+ blurTitleInfowindow(true);
+}
+
+/**
+ * Trigger a blur() on titlein ifnowindow
+ * @param {type} closeInfowindow - true to close infowindow afterwards
+ * @returns {undefined}
+ */
+function blurTitleInfowindow(closeInfowindow) {
+ if (infowindow.getMap()) {
+ //if infowindow in opened, blur focus, so saving in DB can be triggered
+ infowindow.content.getElementsByClassName("input_title")[0].blur();
+ if (closeInfowindow)
+ infowindow.close();
+ }
+}
+
+/**
+ * Polyline options holder
+ * @param map google map, to put polyline in it
+ * @returns {polylineOptions.PolylineAnonym$6}
+ */
+function polylineOptionsVM(map) {
+ return {
+ strokeColor: 'black',
+ strokeColorOrg: 'black',
+ strokeWeight: 4,
+ clickable: true,
+ editable: !viewMode,
+ zIndex: 1,
+ map: map
+ };
+}
+
+/**
+ * Sets all kind of settings after shape is completed
+ *
+ * @param {google's shape} shape - polyline or polygon if already exists
+ * @param {type} spremenljivka - id spremenljivke
+ * @param {type} map - google map
+ * @returns {undefined}
+ */
+function afterShapeCompleteSettingsVM(shape, spremenljivka, map) {
+ st_shapes[spremenljivka]['last_id']++;
+ shape.overlay_id = st_shapes[spremenljivka]['last_id'];
+ shape.address = '';
+ shape.infowin = makeShapeInfoWindow(spremenljivka, shape);
+
+ saveNewLineInDB(spremenljivka, shape.getPath().getArray(), st_shapes[spremenljivka]['last_id']);
+
+ var color = mapShapeColors[shape.overlay_id-1 % mapShapeColors.length];
+ st_shapes[spremenljivka]['count']++;
+
+ //set shape
+ shape.setOptions({strokeColor: '#' + color});
+
+ changeFocusLine(shape);
+
+ //open infowindow
+ infowindow.setContent(shape.infowin);
+ var pathArr = shape.getPath().getArray();
+ //set position to rounded middle point
+ infowindow.setPosition(pathArr[Math.round((pathArr.length - 1) / 2)]);
+ infowindow.open(map);
+
+ setListenersShapeVM(spremenljivka, shape, map);
+
+ //focus on input
+ $("#map_input_overlay_id_" + spremenljivka + "_" + shape.overlay_id).focus();
+}
+
+/**
+ * Creates and returns a html container for infowindow
+ * @param {type} spremenljivka - id spremenljivke
+ * @param {type} shape - googles shape - polyline or polygon
+ * @param locked boolean true, if survey is locked in branching
+ * @returns {makeShapeInfoWindow.container|Element}
+ */
+function makeShapeInfoWindow(spremenljivka, shape, locked) {
+ //Create a div element for container.
+ var container = document.createElement("div");
+ //margin, da se pri italic ne odreze zadja crka
+ container.style.cssText = 'margin-right:1px';
+
+ //Create a textarea for the input text
+ var textBoxTitle = document.createElement("input");
+ textBoxTitle.style.cssText = "font-weight:bold; font-size:1em !important; display: block; float: left;";
+ textBoxTitle.type = "text";
+ textBoxTitle.value = shape.address;
+ textBoxTitle.className = "input_title";
+ textBoxTitle.id = "map_input_overlay_id_" + spremenljivka + "_" + shape.overlay_id;
+ container.appendChild(textBoxTitle);
+ //container.appendChild(document.createElement("br"));
+ //disable changing title if locked
+ if (locked)
+ textBoxTitle.disabled = "disabled";
+
+ //dont show delete image if survey is locked and no need for listeners
+ if (!locked) {
+ var deleteImg = document.createElement("span");
+ deleteImg.className = "faicon delete icon-grey_dark_link";
+ deleteImg.title = lang['srv_vprasanje_delete_line_map'];
+ deleteImg.style.cssText = "height:1.65em; display: block; float: left; margin-left:7px; cursor: pointer;";
+ container.appendChild(deleteImg);
+
+ //ko se spremeni textarea v windowinfo, spremeni value inputa za text
+ google.maps.event.addDomListener(deleteImg, "click", function () {
+ if (confirm(lang['srv_vprasanje_delete_line_confirm_map']))
+ deleteLineInDB(spremenljivka, shape);
+ });
+
+ //ko se spremeni textarea v windowinfo, spremeni value inputa za text
+ google.maps.event.addDomListener(textBoxTitle, "change", function () {
+ //save title of SHAPE in DB
+ var lineData = {anketa: srv_meta_anketa_id, spr_id: spremenljivka,
+ overlay_id: shape.overlay_id, address: textBoxTitle.value};
+ $.post('ajax.php?t=branching&a=edit_naslov_polyline', lineData);
+ });
+ }
+
+ return container;
+}
+
+/**
+ * Set Liteners when editing a shape
+ * @param {type} spremenljivka - id spremenljivke
+ * @param {type} shape - googles shape to set listeners on
+ * @param {type} map - google map
+ * @param locked boolean true, if survey is locked in branching
+ * @returns {undefined}
+ */
+function setListenersShapeVM(spremenljivka, shape, map, locked) {
+ //listener ob kliku na marker
+ google.maps.event.addListener(shape, 'click', function (ev) {
+ //pusti editable false, ce je locked
+ if (!locked)
+ this.setOptions({editable: true});
+ changeFocusLine(this);
+
+ //open infowindow
+ infowindow.setContent(this.infowin);
+ infowindow.setPosition(ev.latLng);
+ infowindow.open(map);
+
+ $("#map_input_overlay_id_" + spremenljivka + "_" + shape.overlay_id).focus();
+ });
+
+ //no need for listeners if survey is locked
+ if (!locked) {
+ //when editing end or start point
+ google.maps.event.addListener(shape.getPath(), 'set_at', function (index, obj) {
+ saveEditedLineInDB(spremenljivka, shape.getPath().getArray(), shape.overlay_id);
+ });
+ //when inserting new point (break old one)
+ google.maps.event.addListener(shape.getPath(), 'insert_at', function (index, obj) {
+ saveEditedLineInDB(spremenljivka, shape.getPath().getArray(), shape.overlay_id);
+ });
+ //when deleting a point -> undo
+ google.maps.event.addListener(shape.getPath(), 'remove_at', function (index, obj) {
+ saveEditedLineInDB(spremenljivka, shape.getPath().getArray(), shape.overlay_id);
+ });
+
+ setDeleteMenu(shape, map, spremenljivka)
+ }
+}
+
+/**
+ * Sets rightclick and dblclick listeners to open delete menu
+ * @param {type} shape - shape to set listeners on
+ * @param {type} map - map on which to hover menu (shape's map)
+ * @param {type} spremenljivka - id of variable
+ * @returns {undefined}
+ */
+function setDeleteMenu(shape, map, spremenljivka) {
+ google.maps.event.addListener(shape, 'rightclick', function (e) {
+ // Check if click was on a vertex control point
+ if (e.vertex == undefined) {
+ return;
+ }
+ //removeVertexVM(spremenljivka, shape, e.vertex);
+ deleteMenu.open(map, shape, e.vertex, spremenljivka, null, null);
+ });
+ google.maps.event.addListener(shape, 'dblclick', function (e) {
+ // Check if click was on a vertex control point
+ if (e.vertex == undefined) {
+ return;
+ }
+ //removeVertexVM(spremenljivka, shape, e.vertex);
+ deleteMenu.open(map, shape, e.vertex, spremenljivka, null, null);
+ });
+}
+
+/**
+ * Creates json data of shape to later insert or update DB
+ * @param {type} spremenljivka - id spremenljivke
+ * @param {type} pathArray - array of path of shape (shape.getPath().getArray())
+ * @param {type} overlay_id - id of overlay
+ * @returns {createLineData.lineData}
+ */
+function createLineData(spremenljivka, pathArray, overlay_id) {
+ var lineData = {anketa: srv_meta_anketa_id, spr_id: spremenljivka, overlay_id: overlay_id, path: []};
+
+ for (var i = 0, n = pathArray.length; i < n; i++) {
+ var latLng = pathArray[i];
+ var vrstni_red = i + 1;
+ lineData.path[i] = {lat: latLng.lat(), lng: latLng.lng(), vrstni_red: i + 1};
+ }
+
+ return lineData;
+}
+
+/**
+ * Saves newly created line in DB
+ * @param {type} spremenljivka - id spremenljivke
+ * @param {type} pathArray - array of path of shape (shape.getPath().getArray())
+ * @param {type} overlay_id - id of overlay
+ * @returns {undefined}
+ */
+function saveNewLineInDB(spremenljivka, pathArray, overlay_id) {
+ var lineData = createLineData(spremenljivka, pathArray, overlay_id);
+ $.post('ajax.php?t=branching&a=save_polyline', lineData);
+}
+
+/**
+ * Saves edited line in DB
+ * @param {type} spremenljivka - id spremenljivke
+ * @param {type} pathArray - array of path of shape (shape.getPath().getArray())
+ * @param {type} overlay_id - id of overlay
+ * @returns {undefined}
+ */
+function saveEditedLineInDB(spremenljivka, pathArray, overlay_id) {
+ var lineData = createLineData(spremenljivka, pathArray, overlay_id);
+ $.post('ajax.php?t=branching&a=edit_polyline', lineData);
+}
+
+/**
+ * Deletes line in DB
+ * @param {type} spremenljivka - id spremenljivke
+ * @param {type} shape - shape to be deleted
+ * @returns {undefined}
+ */
+function deleteLineInDB(spremenljivka, shape) {
+ shape.setMap(null);
+ st_shapes[spremenljivka]['count']--;
+ blurTitleInfowindow(true);
+ var lineData = {anketa: srv_meta_anketa_id, spr_id: spremenljivka, overlay_id: shape.overlay_id};
+ $.post('ajax.php?t=branching&a=delete_polyline', lineData);
+}
+
+/**
+ * Removes a vertex/point of shape and if it after last it clears the map
+ * and sets drawing mode on again
+ * @param {type} spremenljivka - id spremenljivke
+ * @param {google's shape} shape - polyline or polygon if already exists
+ * @param {type} vertex - vertex or point which was selected
+ * @returns {undefined}
+ */
+/*function removeVertexVM(spremenljivka, shape, vertex) {
+ if (!shape || vertex == undefined){
+ //closes menu
+ this.close();
+ return;
+ }
+
+ shape.getPath().removeAt(vertex);
+ saveEditedLineInDB(spremenljivka, shape.getPath().getArray(), shape.overlay_id);
+
+ if (shape.getPath().length < 2) {
+ //remove shape
+ deleteLineInDB(spremenljivka, shape);
+ }
+ //closes menu
+ this.close();
+}*/ \ No newline at end of file
diff --git a/admin/survey/script/Maps/mapInBox.js b/admin/survey/script/Maps/mapInBox.js
new file mode 100644
index 0000000..03bb139
--- /dev/null
+++ b/admin/survey/script/Maps/mapInBox.js
@@ -0,0 +1,343 @@
+//map_data_holder postane holder id-jev potrebnih za prikaz markerjev
+var map_data_holder = {};
+//id spremenljivke, ki bo prikazana v pojavnem oknu
+var spremenljivka_box = 0;
+
+//Procedura, ki preveri ce je gogle maps API ze includan
+function googleMapsAPIProcedura(after){
+ //preveri, ce je google API ze includan (ce se je vedno nanovo icludal, je prislo do errorjev)
+ if((typeof google === 'object' && typeof google.maps === 'object')){
+ //API je naloadan, inicializiraj mapo
+ after();
+ }
+ else{
+ mapsAPIseNi(after);
+ }
+}
+
+//kreiraj colorbox (popup, kjer bo zemljevid)
+$(document).ready(function () {
+ $(".fMap").colorbox({
+ scrolling:false,
+ width:"80%",
+ height:"80%",
+ title: "",
+ html:'<div id="map_canvas_all" style="width:100%;height:100%"></div>',
+ onComplete:function(){
+ if(!$(".fMap").hasClass("rawData")){
+ $('#colorbox').addClass("divPopUp");
+ getMapDataAjax();
+ }
+ else{
+ googleMapsAPIProcedura(initializeMapGeneralForIPs);
+ }
+ }
+ });
+});
+
+/**
+ * ob kliku na link prenesi podatke o zeljeni spremenljivki
+ * @param {type} sprid - int - id spremenljivke
+ * @param {type} usrid - int - id userja
+ * @param {type} loopid - int - id loopa
+ * @param {type} ankid - int - id ankete
+ * @param {type} ajaxCall - string - call to ajax function mapData or mapDataAll
+ * @returns {undefined}
+ */
+function passMapData(sprid, usrid, loopid, ankid, ajaxCall){
+ if(!ajaxCall)
+ ajaxCall = "mapData";
+ //map_data_holder postane holder id-jev potrebnih za prikaz markerjev kasneje
+ map_data_holder = {spr_id: sprid, usr_id: usrid, loop_id: loopid, ank_id: ankid, ajaxCall: ajaxCall};
+ spremenljivka_box = sprid;
+}
+
+/**
+ * Passing raw json data for google maps (originaly used for mod IP location)
+ * @param {type} rawData - json - [{"Modena":{"cnt":2,"lat":44.666698455810546875,"lng":10.9167003631591796875}}]
+ * @returns {undefined}
+ */
+function passMapDataRaw(rawData){
+ //map_data_holder postane holder id-jev potrebnih za prikaz markerjev kasneje
+ map_data_holder = rawData;
+ spremenljivka_box = 'IPloc';
+}
+
+ //inicializiraj mapo / nastavi mapo v colorboxu
+function initializeMapGeneral() {
+
+ var mapOptions = {
+ mapTypeId: google.maps.MapTypeId.ROADMAP
+ };
+
+ //pri vpogledu podatkov je viewMode vedno true
+ viewMode = true;
+
+ //infowindow iz API-ja, za prikaz markerja in informacije o markerju
+ infowindow = new google.maps.InfoWindow();
+ //deklaracija zemljevida
+ var mapdiv = document.getElementById("map_canvas_all");
+ var map = new google.maps.Map(mapdiv, mapOptions);
+ //to se kasneje uporabi za pridobitev mape z id-em spremenljivke
+ mapdiv.gMap = map;
+
+ //deklaracija mej/okvira prikaza na zemljevidu
+ bounds[spremenljivka_box] = new google.maps.LatLngBounds();
+ //to store combined bounds of all polylines or polygons
+ bounds['all'] = new google.maps.LatLngBounds();
+
+ //get input type (marker, polyline, polygon)
+ var input_type = map_data_holder.input_type;
+ delete map_data_holder.input_type;
+
+ //get subtype (mylocation-1, multilocation-2, chooselocation-3)
+ var enota = map_data_holder.enota;
+ delete map_data_holder.enota;
+
+ //podvprasanja v infowindow
+ podvprasanje[spremenljivka_box] = map_data_holder.podvprasanje;
+ delete map_data_holder.podvprasanje;
+
+ //naslov podvprasanja v infowindow
+ podvprasanje_naslov[spremenljivka_box] = map_data_holder.podvprasanje_naslov;
+ delete map_data_holder.podvprasanje_naslov;
+
+ max_mark[spremenljivka_box] = map_data_holder.length;
+ ml_sprem[spremenljivka_box] = false;
+ st_markerjev[spremenljivka_box] = 0;
+ allMarkers[spremenljivka_box] = [];
+
+ if(enota == 3){
+ map_data_fill(spremenljivka_box, map_data_holder.data, enota);
+ map_data_fill_info_shapes(spremenljivka_box, map_data_holder.info_shapes);
+ }
+ else{
+ if(input_type == 'marker'){
+ //nafilaj mapo z markerji
+ map_data_fill(spremenljivka_box, map_data_holder);
+
+ //var soda = spremenljivka_box % 2 == 0;
+
+ //if(soda){
+ // Add a marker clusterer to manage the markers.
+ var markerCluster = new MarkerClusterer(map, allMarkers[spremenljivka_box],
+ {imagePath: srv_site_url + 'admin/survey/img_0/markerclusterer/m',
+ maxZoom: 15});
+ //}
+ }
+ else if(input_type == 'polyline')
+ map_data_fill_shape(spremenljivka_box, map_data_holder.data, input_type);
+ else if(input_type == 'polygon')
+ map_data_fill_shape(spremenljivka_box, map_data_holder.data, input_type);
+ }
+ //hide loader
+ $("#cboxLoadingOverlay").hide();
+};
+
+//inicializiraj mapo / nastavi mapo v colorboxu za mod IP location
+function initializeMapGeneralForIPs() {
+ var mapOptions = {
+ mapTypeId: google.maps.MapTypeId.ROADMAP,
+ streetViewControl: false
+ };
+
+ //infowindow iz API-ja, za prikaz markerja in informacije o markerju
+ infowindow = new google.maps.InfoWindow();
+ //deklaracija zemljevida
+
+ var mapdiv = document.getElementById("map_canvas_all");
+ if(!mapdiv)
+ mapdiv = document.getElementById("map_ip");
+ var map = new google.maps.Map(mapdiv, mapOptions);
+ //to se kasneje uporabi za pridobitev mape z id-em spremenljivke
+ mapdiv.gMap = map;
+
+ //deklaracija mej/okvira prikaza na zemljevidu
+ bounds[spremenljivka_box] = new google.maps.LatLngBounds();
+
+ max_mark[spremenljivka_box] = map_data_holder.length;
+ ml_sprem[spremenljivka_box] = false;
+ st_markerjev[spremenljivka_box] = 0;
+ allMarkers[spremenljivka_box] = [];
+
+ //close infowindow on zoom because info can change due to clustering
+ google.maps.event.addListener(map, 'zoom_changed', function() {
+ infowindow.close();
+ });
+
+ Object.keys(map_data_holder).forEach(function(key) {
+ //pomnozi markerje glede na frekvence
+ for(var i=0; i<map_data_holder[key].cnt; i++)
+ //nafilaj mapo z markerji
+ if(key!=='')
+ createMarkerForIps(key, map_data_holder[key].lat, map_data_holder[key].lng);
+ //ce ni podatka o imenu mesta aka neznana lokacija
+ else{
+
+ delete map_data_holder['']['cnt'];
+ Object.keys(map_data_holder['']).forEach(function(key) {
+ //pomnozi markerje glede na frekvence
+ for(var i=0; i<map_data_holder[''][key].cnt; i++)
+ //nafilaj mapo z markerji
+ createMarkerForIps('N/A', map_data_holder[''][key].lat, map_data_holder[''][key].lng);
+ });
+ }
+ });
+
+ // Add a marker clusterer to manage the markers.
+ var markerCluster = new MarkerClusterer(map, allMarkers[spremenljivka_box],
+ {imagePath: srv_site_url + 'admin/survey/img_0/markerclusterer/m',
+ maxZoom: 30,
+ zoomOnClick: false});
+
+ google.maps.event.addListener(markerCluster, 'clusterclick', function(cluster) {
+ var markers = cluster.getMarkers();
+
+ var array = [];
+ for (var i = 0; i < markers.length; i++) {
+ if(array[markers[i].getTitle()])
+ array[markers[i].getTitle()]++;
+ else
+ array[markers[i].getTitle()]=1;
+ }
+ //array["N/A"] = array[""];
+ //delete array[""];
+
+ var text = '';
+ Object.keys(array).forEach(function(key) {
+ text += '<b>'+array[key]+'</b> '+key+'<br>';
+ });
+
+ if (map.getZoom() <= markerCluster.getMaxZoom()) {
+ infowindow.setContent(text);
+ infowindow.setPosition(cluster.getCenter());
+ infowindow.open(map);
+ }
+ });
+
+ //hide loader
+ $("#cboxLoadingOverlay").hide();
+};
+
+function createMarkerForIps(key, lat, lng) {
+ //pridobi mapo spremenljivke
+ var map = document.getElementById("map_canvas_all");
+ if(!map)
+ map = document.getElementById("map_ip").gMap;
+ else
+ map = document.getElementById("map_canvas_all").gMap;
+
+ //var path_img_dir = srv_site_url + 'admin/survey/img_0/';
+ var bigIcon = 1;
+ /*if(map_data_holder[key].cnt > 9999)
+ bigIcon = 5;
+ else if(map_data_holder[key].cnt > 999)
+ bigIcon = 4;
+ else if(map_data_holder[key].cnt > 99)
+ bigIcon = 3;
+ else if(map_data_holder[key].cnt > 9)
+ bigIcon = 2;*/
+
+ var icon = {
+ //fillColor: '#FF5555',
+ //url: path_img_dir + 'marker_default.svg',
+ url: srv_site_url + 'admin/survey/img_0/markerclusterer/m'+bigIcon+'.png',
+ fillOpacity: 1,
+ strokeWeight: 1
+ };
+
+ //nastavitve markerja
+ var marker = new google.maps.Marker({
+ position: new google.maps.LatLng(lat, lng),
+ title: key,
+ map: map,
+ icon: icon,
+ label: {
+ text: /*map_data_holder[key].cnt+''*/'1'
+ // Add in the custom label here
+ //fontFamily: 'Roboto, Arial, sans-serif, custom-label-' + map_data_holder[key].cnt
+ }
+ });
+
+ allMarkers[spremenljivka_box].push(marker);
+
+ //Create a div element for container.
+ var container = document.createElement("div");
+
+ //Create a label element for address.
+ var label = document.createElement("label");
+ label.innerHTML = '<b>' + 1 + '</b> '+key;
+ container.appendChild(label);
+
+ //listener ob kliku na marker - focus input mora biti po nastavljanju bounds ali zoom
+ google.maps.event.addListener(marker, 'click', function () {
+ infowindow.setContent(container);
+ infowindow.open(map, marker);
+ });
+
+ //nastavi label
+ infowindow.setContent(container);
+ //odpre marker - prikaze label (kot da bi ga kliknil)
+ //infowin.open(map, marker);
+
+ //v okvir se doda nov marker
+ bounds[spremenljivka_box].extend(marker.position);
+
+ //ce je samo eden marker, ga malo odzoomaj
+ if (st_markerjev[spremenljivka_box] == 0) {
+ map.setCenter(marker.position);
+ map.setZoom(17);
+ } else if (st_markerjev[spremenljivka_box] > 0) {
+ //zemljevid se prilagodi okviru
+ map.fitBounds(bounds[spremenljivka_box]);
+ //ce je v viewMode - v podatkih ali analizah - se odzooma za 1 samo pri zadnjem markerju
+ if (max_mark[spremenljivka_box] == st_markerjev[spremenljivka_box] + 1) {
+ //zmanjsaj zoom za 1, ker google naredi prevec oddaljeno
+ google.maps.event.addListenerOnce(map, "bounds_changed", function () {
+ map.setZoom(map.getZoom() - 1);
+ });
+ }
+ }
+
+ //stevilo markerjev se poveca
+ st_markerjev[spremenljivka_box]++;
+}
+
+//pridobi markerje iz baze
+function getMapDataAjax() {
+ //show loader
+ $("#cboxLoadingOverlay").show();
+
+ $.ajax({
+ cache: false,
+ crossDomain: true,
+ type: 'post',
+ dataType: "json",
+ url: 'ajax.php?t='+map_data_holder.ajaxCall,
+ data: { map_data: map_data_holder },
+ error: function(response) {
+ console.log("Error in Ajax connection! Please try later!");
+ },
+ success: function(response) {
+ //holder markerjev
+ map_data_holder = response;
+ googleMapsAPIProcedura(initializeMapGeneral);
+ }
+ });
+}
+
+//for IP location navigation
+function geoip_map_navigation_toggle(el, data){
+ var elid = $(el).attr('id');
+ if(!$(el).hasClass("active")){
+ if(elid === 'geoip_countries')
+ document.getElementById("geoip_cities").classList.remove("active");
+ else
+ document.getElementById("geoip_countries").classList.remove("active");
+
+ $(el).addClass('active');
+
+ passMapDataRaw(data);
+ googleMapsAPIProcedura(initializeMapGeneralForIPs);
+ }
+} \ No newline at end of file
diff --git a/admin/survey/script/Maps/markerclusterer.js b/admin/survey/script/Maps/markerclusterer.js
new file mode 100644
index 0000000..a27298c
--- /dev/null
+++ b/admin/survey/script/Maps/markerclusterer.js
@@ -0,0 +1,1318 @@
+// ==ClosureCompiler==
+// @compilation_level ADVANCED_OPTIMIZATIONS
+// @externs_url http://closure-compiler.googlecode.com/svn/trunk/contrib/externs/maps/google_maps_api_v3_3.js
+// ==/ClosureCompiler==
+
+/**
+ * @name MarkerClusterer for Google Maps v3
+ * @version version 1.0.1
+ * @author Luke Mahe
+ * @fileoverview
+ * The library creates and manages per-zoom-level clusters for large amounts of
+ * markers.
+ * <br/>
+ * This is a v3 implementation of the
+ * <a href="http://gmaps-utility-library-dev.googlecode.com/svn/tags/markerclusterer/"
+ * >v2 MarkerClusterer</a>.
+ */
+
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/**
+ * A Marker Clusterer that clusters markers.
+ *
+ * @param {google.maps.Map} map The Google map to attach to.
+ * @param {Array.<google.maps.Marker>=} opt_markers Optional markers to add to
+ * the cluster.
+ * @param {Object=} opt_options support the following options:
+ * 'gridSize': (number) The grid size of a cluster in pixels.
+ * 'maxZoom': (number) The maximum zoom level that a marker can be part of a
+ * cluster.
+ * 'zoomOnClick': (boolean) Whether the default behaviour of clicking on a
+ * cluster is to zoom into it.
+ * 'imagePath': (string) The base URL where the images representing
+ * clusters will be found. The full URL will be:
+ * {imagePath}[1-5].{imageExtension}
+ * Default: '../images/m'.
+ * 'imageExtension': (string) The suffix for images URL representing
+ * clusters will be found. See _imagePath_ for details.
+ * Default: 'png'.
+ * 'averageCenter': (boolean) Whether the center of each cluster should be
+ * the average of all markers in the cluster.
+ * 'minimumClusterSize': (number) The minimum number of markers to be in a
+ * cluster before the markers are hidden and a count
+ * is shown.
+ * 'styles': (object) An object that has style properties:
+ * 'url': (string) The image url.
+ * 'height': (number) The image height.
+ * 'width': (number) The image width.
+ * 'anchor': (Array) The anchor position of the label text.
+ * 'textColor': (string) The text color.
+ * 'textSize': (number) The text size.
+ * 'backgroundPosition': (string) The position of the backgound x, y.
+ * @constructor
+ * @extends google.maps.OverlayView
+ */
+function MarkerClusterer(map, opt_markers, opt_options) {
+ // MarkerClusterer implements google.maps.OverlayView interface. We use the
+ // extend function to extend MarkerClusterer with google.maps.OverlayView
+ // because it might not always be available when the code is defined so we
+ // look for it at the last possible moment. If it doesn't exist now then
+ // there is no point going ahead :)
+ this.extend(MarkerClusterer, google.maps.OverlayView);
+ this.map_ = map;
+
+ /**
+ * @type {Array.<google.maps.Marker>}
+ * @private
+ */
+ this.markers_ = [];
+
+ /**
+ * @type {Array.<Cluster>}
+ */
+ this.clusters_ = [];
+
+ this.sizes = [53, 56, 66, 78, 90];
+
+ /**
+ * @private
+ */
+ this.styles_ = [];
+
+ /**
+ * @type {boolean}
+ * @private
+ */
+ this.ready_ = false;
+
+ var options = opt_options || {};
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.gridSize_ = options['gridSize'] || 60;
+
+ /**
+ * @private
+ */
+ this.minClusterSize_ = options['minimumClusterSize'] || 2;
+
+
+ /**
+ * @type {?number}
+ * @private
+ */
+ this.maxZoom_ = options['maxZoom'] || null;
+
+ this.styles_ = options['styles'] || [];
+
+ /**
+ * @type {string}
+ * @private
+ */
+ this.imagePath_ = options['imagePath'] ||
+ this.MARKER_CLUSTER_IMAGE_PATH_;
+
+ /**
+ * @type {string}
+ * @private
+ */
+ this.imageExtension_ = options['imageExtension'] ||
+ this.MARKER_CLUSTER_IMAGE_EXTENSION_;
+
+ /**
+ * @type {boolean}
+ * @private
+ */
+ this.zoomOnClick_ = true;
+
+ if (options['zoomOnClick'] != undefined) {
+ this.zoomOnClick_ = options['zoomOnClick'];
+ }
+
+ /**
+ * @type {boolean}
+ * @private
+ */
+ this.averageCenter_ = false;
+
+ if (options['averageCenter'] != undefined) {
+ this.averageCenter_ = options['averageCenter'];
+ }
+
+ this.setupStyles_();
+
+ this.setMap(map);
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.prevZoom_ = this.map_.getZoom();
+
+ // Add the map event listeners
+ var that = this;
+ google.maps.event.addListener(this.map_, 'zoom_changed', function() {
+ // Determines map type and prevent illegal zoom levels
+ var zoom = that.map_.getZoom();
+ var minZoom = that.map_.minZoom || 0;
+ var maxZoom = Math.min(that.map_.maxZoom || 100,
+ that.map_.mapTypes[that.map_.getMapTypeId()].maxZoom);
+ zoom = Math.min(Math.max(zoom,minZoom),maxZoom);
+
+ if (that.prevZoom_ != zoom) {
+ that.prevZoom_ = zoom;
+ that.resetViewport();
+ }
+ });
+
+ google.maps.event.addListener(this.map_, 'idle', function() {
+ that.redraw();
+ });
+
+ // Finally, add the markers
+ if (opt_markers && (opt_markers.length || Object.keys(opt_markers).length)) {
+ this.addMarkers(opt_markers, false);
+ }
+}
+
+
+/**
+ * The marker cluster image path.
+ *
+ * @type {string}
+ * @private
+ */
+MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_PATH_ = '../images/m';
+
+
+/**
+ * The marker cluster image path.
+ *
+ * @type {string}
+ * @private
+ */
+MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_EXTENSION_ = 'png';
+
+
+/**
+ * Extends a objects prototype by anothers.
+ *
+ * @param {Object} obj1 The object to be extended.
+ * @param {Object} obj2 The object to extend with.
+ * @return {Object} The new extended object.
+ * @ignore
+ */
+MarkerClusterer.prototype.extend = function(obj1, obj2) {
+ return (function(object) {
+ for (var property in object.prototype) {
+ this.prototype[property] = object.prototype[property];
+ }
+ return this;
+ }).apply(obj1, [obj2]);
+};
+
+
+/**
+ * Implementaion of the interface method.
+ * @ignore
+ */
+MarkerClusterer.prototype.onAdd = function() {
+ this.setReady_(true);
+};
+
+/**
+ * Implementaion of the interface method.
+ * @ignore
+ */
+MarkerClusterer.prototype.draw = function() {};
+
+/**
+ * Sets up the styles object.
+ *
+ * @private
+ */
+MarkerClusterer.prototype.setupStyles_ = function() {
+ if (this.styles_.length) {
+ return;
+ }
+
+ for (var i = 0, size; size = this.sizes[i]; i++) {
+ this.styles_.push({
+ url: this.imagePath_ + (i + 1) + '.' + this.imageExtension_,
+ height: size,
+ width: size
+ });
+ }
+};
+
+/**
+ * Fit the map to the bounds of the markers in the clusterer.
+ */
+MarkerClusterer.prototype.fitMapToMarkers = function() {
+ var markers = this.getMarkers();
+ var bounds = new google.maps.LatLngBounds();
+ for (var i = 0, marker; marker = markers[i]; i++) {
+ bounds.extend(marker.getPosition());
+ }
+
+ this.map_.fitBounds(bounds);
+};
+
+
+/**
+ * Sets the styles.
+ *
+ * @param {Object} styles The style to set.
+ */
+MarkerClusterer.prototype.setStyles = function(styles) {
+ this.styles_ = styles;
+};
+
+
+/**
+ * Gets the styles.
+ *
+ * @return {Object} The styles object.
+ */
+MarkerClusterer.prototype.getStyles = function() {
+ return this.styles_;
+};
+
+
+/**
+ * Whether zoom on click is set.
+ *
+ * @return {boolean} True if zoomOnClick_ is set.
+ */
+MarkerClusterer.prototype.isZoomOnClick = function() {
+ return this.zoomOnClick_;
+};
+
+/**
+ * Whether average center is set.
+ *
+ * @return {boolean} True if averageCenter_ is set.
+ */
+MarkerClusterer.prototype.isAverageCenter = function() {
+ return this.averageCenter_;
+};
+
+
+/**
+ * Returns the array of markers in the clusterer.
+ *
+ * @return {Array.<google.maps.Marker>} The markers.
+ */
+MarkerClusterer.prototype.getMarkers = function() {
+ return this.markers_;
+};
+
+
+/**
+ * Returns the number of markers in the clusterer
+ *
+ * @return {Number} The number of markers.
+ */
+MarkerClusterer.prototype.getTotalMarkers = function() {
+ return this.markers_.length;
+};
+
+
+/**
+ * Sets the max zoom for the clusterer.
+ *
+ * @param {number} maxZoom The max zoom level.
+ */
+MarkerClusterer.prototype.setMaxZoom = function(maxZoom) {
+ this.maxZoom_ = maxZoom;
+};
+
+
+/**
+ * Gets the max zoom for the clusterer.
+ *
+ * @return {number} The max zoom level.
+ */
+MarkerClusterer.prototype.getMaxZoom = function() {
+ return this.maxZoom_;
+};
+
+
+/**
+ * The function for calculating the cluster icon image.
+ *
+ * @param {Array.<google.maps.Marker>} markers The markers in the clusterer.
+ * @param {number} numStyles The number of styles available.
+ * @return {Object} A object properties: 'text' (string) and 'index' (number).
+ * @private
+ */
+MarkerClusterer.prototype.calculator_ = function(markers, numStyles) {
+ var index = 0;
+ var count = markers.length;
+ var dv = count;
+ while (dv !== 0) {
+ dv = parseInt(dv / 10, 10);
+ index++;
+ }
+
+ index = Math.min(index, numStyles);
+ return {
+ text: count,
+ index: index
+ };
+};
+
+
+/**
+ * Set the calculator function.
+ *
+ * @param {function(Array, number)} calculator The function to set as the
+ * calculator. The function should return a object properties:
+ * 'text' (string) and 'index' (number).
+ *
+ */
+MarkerClusterer.prototype.setCalculator = function(calculator) {
+ this.calculator_ = calculator;
+};
+
+
+/**
+ * Get the calculator function.
+ *
+ * @return {function(Array, number)} the calculator function.
+ */
+MarkerClusterer.prototype.getCalculator = function() {
+ return this.calculator_;
+};
+
+
+/**
+ * Add an array of markers to the clusterer.
+ *
+ * @param {Array.<google.maps.Marker>} markers The markers to add.
+ * @param {boolean=} opt_nodraw Whether to redraw the clusters.
+ */
+MarkerClusterer.prototype.addMarkers = function(markers, opt_nodraw) {
+ if (markers.length) {
+ for (var i = 0, marker; marker = markers[i]; i++) {
+ this.pushMarkerTo_(marker);
+ }
+ } else if (Object.keys(markers).length) {
+ for (var marker in markers) {
+ this.pushMarkerTo_(markers[marker]);
+ }
+ }
+ if (!opt_nodraw) {
+ this.redraw();
+ }
+};
+
+
+/**
+ * Pushes a marker to the clusterer.
+ *
+ * @param {google.maps.Marker} marker The marker to add.
+ * @private
+ */
+MarkerClusterer.prototype.pushMarkerTo_ = function(marker) {
+ marker.isAdded = false;
+ if (marker['draggable']) {
+ // If the marker is draggable add a listener so we update the clusters on
+ // the drag end.
+ var that = this;
+ google.maps.event.addListener(marker, 'dragend', function() {
+ marker.isAdded = false;
+ that.repaint();
+ });
+ }
+ this.markers_.push(marker);
+};
+
+
+/**
+ * Adds a marker to the clusterer and redraws if needed.
+ *
+ * @param {google.maps.Marker} marker The marker to add.
+ * @param {boolean=} opt_nodraw Whether to redraw the clusters.
+ */
+MarkerClusterer.prototype.addMarker = function(marker, opt_nodraw) {
+ this.pushMarkerTo_(marker);
+ if (!opt_nodraw) {
+ this.redraw();
+ }
+};
+
+
+/**
+ * Removes a marker and returns true if removed, false if not
+ *
+ * @param {google.maps.Marker} marker The marker to remove
+ * @return {boolean} Whether the marker was removed or not
+ * @private
+ */
+MarkerClusterer.prototype.removeMarker_ = function(marker) {
+ var index = -1;
+ if (this.markers_.indexOf) {
+ index = this.markers_.indexOf(marker);
+ } else {
+ for (var i = 0, m; m = this.markers_[i]; i++) {
+ if (m == marker) {
+ index = i;
+ break;
+ }
+ }
+ }
+
+ if (index == -1) {
+ // Marker is not in our list of markers.
+ return false;
+ }
+
+ marker.setMap(null);
+
+ this.markers_.splice(index, 1);
+
+ return true;
+};
+
+
+/**
+ * Remove a marker from the cluster.
+ *
+ * @param {google.maps.Marker} marker The marker to remove.
+ * @param {boolean=} opt_nodraw Optional boolean to force no redraw.
+ * @return {boolean} True if the marker was removed.
+ */
+MarkerClusterer.prototype.removeMarker = function(marker, opt_nodraw) {
+ var removed = this.removeMarker_(marker);
+
+ if (!opt_nodraw && removed) {
+ this.resetViewport();
+ this.redraw();
+ return true;
+ } else {
+ return false;
+ }
+};
+
+
+/**
+ * Removes an array of markers from the cluster.
+ *
+ * @param {Array.<google.maps.Marker>} markers The markers to remove.
+ * @param {boolean=} opt_nodraw Optional boolean to force no redraw.
+ */
+MarkerClusterer.prototype.removeMarkers = function(markers, opt_nodraw) {
+ // create a local copy of markers if required
+ // (removeMarker_ modifies the getMarkers() array in place)
+ var markersCopy = markers === this.getMarkers() ? markers.slice() : markers;
+ var removed = false;
+
+ for (var i = 0, marker; marker = markersCopy[i]; i++) {
+ var r = this.removeMarker_(marker);
+ removed = removed || r;
+ }
+
+ if (!opt_nodraw && removed) {
+ this.resetViewport();
+ this.redraw();
+ return true;
+ }
+};
+
+
+/**
+ * Sets the clusterer's ready state.
+ *
+ * @param {boolean} ready The state.
+ * @private
+ */
+MarkerClusterer.prototype.setReady_ = function(ready) {
+ if (!this.ready_) {
+ this.ready_ = ready;
+ this.createClusters_();
+ }
+};
+
+
+/**
+ * Returns the number of clusters in the clusterer.
+ *
+ * @return {number} The number of clusters.
+ */
+MarkerClusterer.prototype.getTotalClusters = function() {
+ return this.clusters_.length;
+};
+
+
+/**
+ * Returns the google map that the clusterer is associated with.
+ *
+ * @return {google.maps.Map} The map.
+ */
+MarkerClusterer.prototype.getMap = function() {
+ return this.map_;
+};
+
+
+/**
+ * Sets the google map that the clusterer is associated with.
+ *
+ * @param {google.maps.Map} map The map.
+ */
+MarkerClusterer.prototype.setMap = function(map) {
+ this.map_ = map;
+};
+
+
+/**
+ * Returns the size of the grid.
+ *
+ * @return {number} The grid size.
+ */
+MarkerClusterer.prototype.getGridSize = function() {
+ return this.gridSize_;
+};
+
+
+/**
+ * Sets the size of the grid.
+ *
+ * @param {number} size The grid size.
+ */
+MarkerClusterer.prototype.setGridSize = function(size) {
+ this.gridSize_ = size;
+};
+
+
+/**
+ * Returns the min cluster size.
+ *
+ * @return {number} The grid size.
+ */
+MarkerClusterer.prototype.getMinClusterSize = function() {
+ return this.minClusterSize_;
+};
+
+/**
+ * Sets the min cluster size.
+ *
+ * @param {number} size The grid size.
+ */
+MarkerClusterer.prototype.setMinClusterSize = function(size) {
+ this.minClusterSize_ = size;
+};
+
+
+/**
+ * Extends a bounds object by the grid size.
+ *
+ * @param {google.maps.LatLngBounds} bounds The bounds to extend.
+ * @return {google.maps.LatLngBounds} The extended bounds.
+ */
+MarkerClusterer.prototype.getExtendedBounds = function(bounds) {
+ var projection = this.getProjection();
+
+ // Turn the bounds into latlng.
+ var tr = new google.maps.LatLng(bounds.getNorthEast().lat(),
+ bounds.getNorthEast().lng());
+ var bl = new google.maps.LatLng(bounds.getSouthWest().lat(),
+ bounds.getSouthWest().lng());
+
+ // Convert the points to pixels and the extend out by the grid size.
+ var trPix = projection.fromLatLngToDivPixel(tr);
+ trPix.x += this.gridSize_;
+ trPix.y -= this.gridSize_;
+
+ var blPix = projection.fromLatLngToDivPixel(bl);
+ blPix.x -= this.gridSize_;
+ blPix.y += this.gridSize_;
+
+ // Convert the pixel points back to LatLng
+ var ne = projection.fromDivPixelToLatLng(trPix);
+ var sw = projection.fromDivPixelToLatLng(blPix);
+
+ // Extend the bounds to contain the new bounds.
+ bounds.extend(ne);
+ bounds.extend(sw);
+
+ return bounds;
+};
+
+
+/**
+ * Determins if a marker is contained in a bounds.
+ *
+ * @param {google.maps.Marker} marker The marker to check.
+ * @param {google.maps.LatLngBounds} bounds The bounds to check against.
+ * @return {boolean} True if the marker is in the bounds.
+ * @private
+ */
+MarkerClusterer.prototype.isMarkerInBounds_ = function(marker, bounds) {
+ return bounds.contains(marker.getPosition());
+};
+
+
+/**
+ * Clears all clusters and markers from the clusterer.
+ */
+MarkerClusterer.prototype.clearMarkers = function() {
+ this.resetViewport(true);
+
+ // Set the markers a empty array.
+ this.markers_ = [];
+};
+
+
+/**
+ * Clears all existing clusters and recreates them.
+ * @param {boolean} opt_hide To also hide the marker.
+ */
+MarkerClusterer.prototype.resetViewport = function(opt_hide) {
+ // Remove all the clusters
+ for (var i = 0, cluster; cluster = this.clusters_[i]; i++) {
+ cluster.remove();
+ }
+
+ // Reset the markers to not be added and to be invisible.
+ for (var i = 0, marker; marker = this.markers_[i]; i++) {
+ marker.isAdded = false;
+ if (opt_hide) {
+ marker.setMap(null);
+ }
+ }
+
+ this.clusters_ = [];
+};
+
+/**
+ *
+ */
+MarkerClusterer.prototype.repaint = function() {
+ var oldClusters = this.clusters_.slice();
+ this.clusters_.length = 0;
+ this.resetViewport();
+ this.redraw();
+
+ // Remove the old clusters.
+ // Do it in a timeout so the other clusters have been drawn first.
+ window.setTimeout(function() {
+ for (var i = 0, cluster; cluster = oldClusters[i]; i++) {
+ cluster.remove();
+ }
+ }, 0);
+};
+
+
+/**
+ * Redraws the clusters.
+ */
+MarkerClusterer.prototype.redraw = function() {
+ this.createClusters_();
+};
+
+
+/**
+ * Calculates the distance between two latlng locations in km.
+ * @see http://www.movable-type.co.uk/scripts/latlong.html
+ *
+ * @param {google.maps.LatLng} p1 The first lat lng point.
+ * @param {google.maps.LatLng} p2 The second lat lng point.
+ * @return {number} The distance between the two points in km.
+ * @private
+*/
+MarkerClusterer.prototype.distanceBetweenPoints_ = function(p1, p2) {
+ if (!p1 || !p2) {
+ return 0;
+ }
+
+ var R = 6371; // Radius of the Earth in km
+ var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
+ var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
+ var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
+ Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) *
+ Math.sin(dLon / 2) * Math.sin(dLon / 2);
+ var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+ var d = R * c;
+ return d;
+};
+
+
+/**
+ * Add a marker to a cluster, or creates a new cluster.
+ *
+ * @param {google.maps.Marker} marker The marker to add.
+ * @private
+ */
+MarkerClusterer.prototype.addToClosestCluster_ = function(marker) {
+ var distance = 40000; // Some large number
+ var clusterToAddTo = null;
+ var pos = marker.getPosition();
+ for (var i = 0, cluster; cluster = this.clusters_[i]; i++) {
+ var center = cluster.getCenter();
+ if (center) {
+ var d = this.distanceBetweenPoints_(center, marker.getPosition());
+ if (d < distance) {
+ distance = d;
+ clusterToAddTo = cluster;
+ }
+ }
+ }
+
+ if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) {
+ clusterToAddTo.addMarker(marker);
+ } else {
+ var cluster = new Cluster(this);
+ cluster.addMarker(marker);
+ this.clusters_.push(cluster);
+ }
+};
+
+
+/**
+ * Creates the clusters.
+ *
+ * @private
+ */
+MarkerClusterer.prototype.createClusters_ = function() {
+ if (!this.ready_) {
+ return;
+ }
+
+ // Get our current map view bounds.
+ // Create a new bounds object so we don't affect the map.
+ var mapBounds = new google.maps.LatLngBounds(this.map_.getBounds().getSouthWest(),
+ this.map_.getBounds().getNorthEast());
+ var bounds = this.getExtendedBounds(mapBounds);
+
+ for (var i = 0, marker; marker = this.markers_[i]; i++) {
+ if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) {
+ this.addToClosestCluster_(marker);
+ }
+ }
+};
+
+
+/**
+ * A cluster that contains markers.
+ *
+ * @param {MarkerClusterer} markerClusterer The markerclusterer that this
+ * cluster is associated with.
+ * @constructor
+ * @ignore
+ */
+function Cluster(markerClusterer) {
+ this.markerClusterer_ = markerClusterer;
+ this.map_ = markerClusterer.getMap();
+ this.gridSize_ = markerClusterer.getGridSize();
+ this.minClusterSize_ = markerClusterer.getMinClusterSize();
+ this.averageCenter_ = markerClusterer.isAverageCenter();
+ this.center_ = null;
+ this.markers_ = [];
+ this.bounds_ = null;
+ this.clusterIcon_ = new ClusterIcon(this, markerClusterer.getStyles(),
+ markerClusterer.getGridSize());
+}
+
+/**
+ * Determins if a marker is already added to the cluster.
+ *
+ * @param {google.maps.Marker} marker The marker to check.
+ * @return {boolean} True if the marker is already added.
+ */
+Cluster.prototype.isMarkerAlreadyAdded = function(marker) {
+ if (this.markers_.indexOf) {
+ return this.markers_.indexOf(marker) != -1;
+ } else {
+ for (var i = 0, m; m = this.markers_[i]; i++) {
+ if (m == marker) {
+ return true;
+ }
+ }
+ }
+ return false;
+};
+
+
+/**
+ * Add a marker the cluster.
+ *
+ * @param {google.maps.Marker} marker The marker to add.
+ * @return {boolean} True if the marker was added.
+ */
+Cluster.prototype.addMarker = function(marker) {
+ if (this.isMarkerAlreadyAdded(marker)) {
+ return false;
+ }
+
+ if (!this.center_) {
+ this.center_ = marker.getPosition();
+ this.calculateBounds_();
+ } else {
+ if (this.averageCenter_) {
+ var l = this.markers_.length + 1;
+ var lat = (this.center_.lat() * (l-1) + marker.getPosition().lat()) / l;
+ var lng = (this.center_.lng() * (l-1) + marker.getPosition().lng()) / l;
+ this.center_ = new google.maps.LatLng(lat, lng);
+ this.calculateBounds_();
+ }
+ }
+
+ marker.isAdded = true;
+ this.markers_.push(marker);
+
+ var len = this.markers_.length;
+ if (len < this.minClusterSize_ && marker.getMap() != this.map_) {
+ // Min cluster size not reached so show the marker.
+ marker.setMap(this.map_);
+ }
+
+ if (len == this.minClusterSize_) {
+ // Hide the markers that were showing.
+ for (var i = 0; i < len; i++) {
+ this.markers_[i].setMap(null);
+ }
+ }
+
+ if (len >= this.minClusterSize_) {
+ marker.setMap(null);
+ }
+
+ this.updateIcon();
+ return true;
+};
+
+
+/**
+ * Returns the marker clusterer that the cluster is associated with.
+ *
+ * @return {MarkerClusterer} The associated marker clusterer.
+ */
+Cluster.prototype.getMarkerClusterer = function() {
+ return this.markerClusterer_;
+};
+
+
+/**
+ * Returns the bounds of the cluster.
+ *
+ * @return {google.maps.LatLngBounds} the cluster bounds.
+ */
+Cluster.prototype.getBounds = function() {
+ var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
+ var markers = this.getMarkers();
+ for (var i = 0, marker; marker = markers[i]; i++) {
+ bounds.extend(marker.getPosition());
+ }
+ return bounds;
+};
+
+
+/**
+ * Removes the cluster
+ */
+Cluster.prototype.remove = function() {
+ this.clusterIcon_.remove();
+ this.markers_.length = 0;
+ delete this.markers_;
+};
+
+
+/**
+ * Returns the number of markers in the cluster.
+ *
+ * @return {number} The number of markers in the cluster.
+ */
+Cluster.prototype.getSize = function() {
+ return this.markers_.length;
+};
+
+
+/**
+ * Returns a list of the markers in the cluster.
+ *
+ * @return {Array.<google.maps.Marker>} The markers in the cluster.
+ */
+Cluster.prototype.getMarkers = function() {
+ return this.markers_;
+};
+
+
+/**
+ * Returns the center of the cluster.
+ *
+ * @return {google.maps.LatLng} The cluster center.
+ */
+Cluster.prototype.getCenter = function() {
+ return this.center_;
+};
+
+
+/**
+ * Calculated the extended bounds of the cluster with the grid.
+ *
+ * @private
+ */
+Cluster.prototype.calculateBounds_ = function() {
+ var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
+ this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds);
+};
+
+
+/**
+ * Determines if a marker lies in the clusters bounds.
+ *
+ * @param {google.maps.Marker} marker The marker to check.
+ * @return {boolean} True if the marker lies in the bounds.
+ */
+Cluster.prototype.isMarkerInClusterBounds = function(marker) {
+ return this.bounds_.contains(marker.getPosition());
+};
+
+
+/**
+ * Returns the map that the cluster is associated with.
+ *
+ * @return {google.maps.Map} The map.
+ */
+Cluster.prototype.getMap = function() {
+ return this.map_;
+};
+
+
+/**
+ * Updates the cluster icon
+ */
+Cluster.prototype.updateIcon = function() {
+ var zoom = this.map_.getZoom();
+ var mz = this.markerClusterer_.getMaxZoom();
+
+ if (mz && zoom > mz) {
+ // The zoom is greater than our max zoom so show all the markers in cluster.
+ for (var i = 0, marker; marker = this.markers_[i]; i++) {
+ marker.setMap(this.map_);
+ }
+ return;
+ }
+
+ if (this.markers_.length < this.minClusterSize_) {
+ // Min cluster size not yet reached.
+ this.clusterIcon_.hide();
+ return;
+ }
+
+ var numStyles = this.markerClusterer_.getStyles().length;
+ var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles);
+ this.clusterIcon_.setCenter(this.center_);
+ this.clusterIcon_.setSums(sums);
+ this.clusterIcon_.show();
+};
+
+
+/**
+ * A cluster icon
+ *
+ * @param {Cluster} cluster The cluster to be associated with.
+ * @param {Object} styles An object that has style properties:
+ * 'url': (string) The image url.
+ * 'height': (number) The image height.
+ * 'width': (number) The image width.
+ * 'anchor': (Array) The anchor position of the label text.
+ * 'textColor': (string) The text color.
+ * 'textSize': (number) The text size.
+ * 'backgroundPosition: (string) The background postition x, y.
+ * @param {number=} opt_padding Optional padding to apply to the cluster icon.
+ * @constructor
+ * @extends google.maps.OverlayView
+ * @ignore
+ */
+function ClusterIcon(cluster, styles, opt_padding) {
+ cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView);
+
+ this.styles_ = styles;
+ this.padding_ = opt_padding || 0;
+ this.cluster_ = cluster;
+ this.center_ = null;
+ this.map_ = cluster.getMap();
+ this.div_ = null;
+ this.sums_ = null;
+ this.visible_ = false;
+
+ this.setMap(this.map_);
+}
+
+
+/**
+ * Triggers the clusterclick event and zoom's if the option is set.
+ */
+ClusterIcon.prototype.triggerClusterClick = function() {
+ var markerClusterer = this.cluster_.getMarkerClusterer();
+
+ // Trigger the clusterclick event.
+ google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster_);
+
+ if (markerClusterer.isZoomOnClick()) {
+ // Zoom into the cluster.
+ this.map_.fitBounds(this.cluster_.getBounds());
+ }
+};
+
+
+/**
+ * Adding the cluster icon to the dom.
+ * @ignore
+ */
+ClusterIcon.prototype.onAdd = function() {
+ this.div_ = document.createElement('DIV');
+ if (this.visible_) {
+ var pos = this.getPosFromLatLng_(this.center_);
+ this.div_.style.cssText = this.createCss(pos);
+ this.div_.innerHTML = this.sums_.text;
+ }
+
+ var panes = this.getPanes();
+ panes.overlayMouseTarget.appendChild(this.div_);
+
+ var that = this;
+ google.maps.event.addDomListener(this.div_, 'click', function() {
+ that.triggerClusterClick();
+ });
+};
+
+
+/**
+ * Returns the position to place the div dending on the latlng.
+ *
+ * @param {google.maps.LatLng} latlng The position in latlng.
+ * @return {google.maps.Point} The position in pixels.
+ * @private
+ */
+ClusterIcon.prototype.getPosFromLatLng_ = function(latlng) {
+ var pos = this.getProjection().fromLatLngToDivPixel(latlng);
+ pos.x -= parseInt(this.width_ / 2, 10);
+ pos.y -= parseInt(this.height_ / 2, 10);
+ return pos;
+};
+
+
+/**
+ * Draw the icon.
+ * @ignore
+ */
+ClusterIcon.prototype.draw = function() {
+ if (this.visible_) {
+ var pos = this.getPosFromLatLng_(this.center_);
+ this.div_.style.top = pos.y + 'px';
+ this.div_.style.left = pos.x + 'px';
+ }
+};
+
+
+/**
+ * Hide the icon.
+ */
+ClusterIcon.prototype.hide = function() {
+ if (this.div_) {
+ this.div_.style.display = 'none';
+ }
+ this.visible_ = false;
+};
+
+
+/**
+ * Position and show the icon.
+ */
+ClusterIcon.prototype.show = function() {
+ if (this.div_) {
+ var pos = this.getPosFromLatLng_(this.center_);
+ this.div_.style.cssText = this.createCss(pos);
+ this.div_.style.display = '';
+ }
+ this.visible_ = true;
+};
+
+
+/**
+ * Remove the icon from the map
+ */
+ClusterIcon.prototype.remove = function() {
+ this.setMap(null);
+};
+
+
+/**
+ * Implementation of the onRemove interface.
+ * @ignore
+ */
+ClusterIcon.prototype.onRemove = function() {
+ if (this.div_ && this.div_.parentNode) {
+ this.hide();
+ this.div_.parentNode.removeChild(this.div_);
+ this.div_ = null;
+ }
+};
+
+
+/**
+ * Set the sums of the icon.
+ *
+ * @param {Object} sums The sums containing:
+ * 'text': (string) The text to display in the icon.
+ * 'index': (number) The style index of the icon.
+ */
+ClusterIcon.prototype.setSums = function(sums) {
+ this.sums_ = sums;
+ this.text_ = sums.text;
+ this.index_ = sums.index;
+ if (this.div_) {
+ this.div_.innerHTML = sums.text;
+ }
+
+ this.useStyle();
+};
+
+
+/**
+ * Sets the icon to the the styles.
+ */
+ClusterIcon.prototype.useStyle = function() {
+ var index = Math.max(0, this.sums_.index - 1);
+ index = Math.min(this.styles_.length - 1, index);
+ var style = this.styles_[index];
+ this.url_ = style['url'];
+ this.height_ = style['height'];
+ this.width_ = style['width'];
+ this.textColor_ = style['textColor'];
+ this.anchor_ = style['anchor'];
+ this.textSize_ = style['textSize'];
+ this.backgroundPosition_ = style['backgroundPosition'];
+};
+
+
+/**
+ * Sets the center of the icon.
+ *
+ * @param {google.maps.LatLng} center The latlng to set as the center.
+ */
+ClusterIcon.prototype.setCenter = function(center) {
+ this.center_ = center;
+};
+
+
+/**
+ * Create the css text based on the position of the icon.
+ *
+ * @param {google.maps.Point} pos The position.
+ * @return {string} The css style text.
+ */
+ClusterIcon.prototype.createCss = function(pos) {
+ var style = [];
+ style.push('background-image:url(' + this.url_ + ');');
+ var backgroundPosition = this.backgroundPosition_ ? this.backgroundPosition_ : '0 0';
+ style.push('background-position:' + backgroundPosition + ';');
+
+ if (typeof this.anchor_ === 'object') {
+ if (typeof this.anchor_[0] === 'number' && this.anchor_[0] > 0 &&
+ this.anchor_[0] < this.height_) {
+ style.push('height:' + (this.height_ - this.anchor_[0]) +
+ 'px; padding-top:' + this.anchor_[0] + 'px;');
+ } else {
+ style.push('height:' + this.height_ + 'px; line-height:' + this.height_ +
+ 'px;');
+ }
+ if (typeof this.anchor_[1] === 'number' && this.anchor_[1] > 0 &&
+ this.anchor_[1] < this.width_) {
+ style.push('width:' + (this.width_ - this.anchor_[1]) +
+ 'px; padding-left:' + this.anchor_[1] + 'px;');
+ } else {
+ style.push('width:' + this.width_ + 'px; text-align:center;');
+ }
+ } else {
+ style.push('height:' + this.height_ + 'px; line-height:' +
+ this.height_ + 'px; width:' + this.width_ + 'px; text-align:center;');
+ }
+
+ var txtColor = this.textColor_ ? this.textColor_ : 'black';
+ var txtSize = this.textSize_ ? this.textSize_ : 11;
+
+ style.push('cursor:pointer; top:' + pos.y + 'px; left:' +
+ pos.x + 'px; color:' + txtColor + '; position:absolute; font-size:' +
+ txtSize + 'px; font-family:Arial,sans-serif; font-weight:bold');
+ return style.join('');
+};
+
+
+// Export Symbols for Closure
+// If you are not going to compile with closure then you can remove the
+// code below.
+window['MarkerClusterer'] = MarkerClusterer;
+MarkerClusterer.prototype['addMarker'] = MarkerClusterer.prototype.addMarker;
+MarkerClusterer.prototype['addMarkers'] = MarkerClusterer.prototype.addMarkers;
+MarkerClusterer.prototype['clearMarkers'] =
+ MarkerClusterer.prototype.clearMarkers;
+MarkerClusterer.prototype['fitMapToMarkers'] =
+ MarkerClusterer.prototype.fitMapToMarkers;
+MarkerClusterer.prototype['getCalculator'] =
+ MarkerClusterer.prototype.getCalculator;
+MarkerClusterer.prototype['getGridSize'] =
+ MarkerClusterer.prototype.getGridSize;
+MarkerClusterer.prototype['getExtendedBounds'] =
+ MarkerClusterer.prototype.getExtendedBounds;
+MarkerClusterer.prototype['getMap'] = MarkerClusterer.prototype.getMap;
+MarkerClusterer.prototype['getMarkers'] = MarkerClusterer.prototype.getMarkers;
+MarkerClusterer.prototype['getMaxZoom'] = MarkerClusterer.prototype.getMaxZoom;
+MarkerClusterer.prototype['getStyles'] = MarkerClusterer.prototype.getStyles;
+MarkerClusterer.prototype['getTotalClusters'] =
+ MarkerClusterer.prototype.getTotalClusters;
+MarkerClusterer.prototype['getTotalMarkers'] =
+ MarkerClusterer.prototype.getTotalMarkers;
+MarkerClusterer.prototype['redraw'] = MarkerClusterer.prototype.redraw;
+MarkerClusterer.prototype['removeMarker'] =
+ MarkerClusterer.prototype.removeMarker;
+MarkerClusterer.prototype['removeMarkers'] =
+ MarkerClusterer.prototype.removeMarkers;
+MarkerClusterer.prototype['resetViewport'] =
+ MarkerClusterer.prototype.resetViewport;
+MarkerClusterer.prototype['repaint'] =
+ MarkerClusterer.prototype.repaint;
+MarkerClusterer.prototype['setCalculator'] =
+ MarkerClusterer.prototype.setCalculator;
+MarkerClusterer.prototype['setGridSize'] =
+ MarkerClusterer.prototype.setGridSize;
+MarkerClusterer.prototype['setMaxZoom'] =
+ MarkerClusterer.prototype.setMaxZoom;
+MarkerClusterer.prototype['onAdd'] = MarkerClusterer.prototype.onAdd;
+MarkerClusterer.prototype['draw'] = MarkerClusterer.prototype.draw;
+
+Cluster.prototype['getCenter'] = Cluster.prototype.getCenter;
+Cluster.prototype['getSize'] = Cluster.prototype.getSize;
+Cluster.prototype['getMarkers'] = Cluster.prototype.getMarkers;
+
+ClusterIcon.prototype['onAdd'] = ClusterIcon.prototype.onAdd;
+ClusterIcon.prototype['draw'] = ClusterIcon.prototype.draw;
+ClusterIcon.prototype['onRemove'] = ClusterIcon.prototype.onRemove;
+
+Object.keys = Object.keys || function(o) {
+ var result = [];
+ for(var name in o) {
+ if (o.hasOwnProperty(name))
+ result.push(name);
+ }
+ return result;
+};