I have made a API meant to fetch Electrical Vehicles Charging Points on a map and create a route.
The route should automatically add charging points if the vehicle's capacity is not enough to finish the trip.
I fetch a Json and create an array of Marker objects. This works fine.
I then pass it through a loop that should splice all markers too far from the starting point to be reached and then another loop selecting the marker closest to destination.
For a reason I do not understand, though the array seems to have been trimed correctly, the result is a marker just next to destination and totally out of range from the starting point.
I am using the following libraries from Google: Directions, Geocoding, Maps Javascript...
I hope someone can help me because I am totally stuck here.
EDIT (further explanations:
The program is meant to add a charging stopover when the travel gets longer than the vehicle capacity (which is is 220 km.)
All the eligible stopovers are shown on the map and pushed in an array (as marker objects).
The function first splice all the stopovers further than 220 km. from the array, then in another loop I chose the stopover closest to destination.
But, if you look closer at the route you will see the charging stopover (point B) is actually 550 km. from departure and 2 km. from arrival.
This Marker should have been spliced and not be in the array anymore.
function selectMarkerClosestToDestination(vehicle) {
//Selecting the closest marker to destination as long as it is not out of the vehicle capacity range
//CURRENTLY BUGGED
let waypoints = chargingPointsMarkers;
for (let x = waypoints.length -1; x > 0; x--) {
if(calculateDistance(waypoints[x], start) > (vehicle.status*vehicle.consumption)){
console.log(calculateDistance(waypoints[x], start))
console.log(vehicle.status*vehicle.consumption)
waypoints.splice(x, 1)
console.log(waypoints)
}
}
console.log(waypoints)
for (let x = waypoints.length - 1; x > 0; x--) {
if (calculateDistance(waypoints[x], end) > (calculateDistance(waypoints[x-1], end))) {
waypoints.splice(x, 1);
} else {
waypoints.splice(x - 1, 1);
}
}
console.log(waypoints)
return waypoints[0];
}
function calculateDistance(p1, p2) {
//Uses the Google geometry library to calculate distance between two Markers
let a = p1.getPosition();
let b = p2.getPosition();
let distance = (google.maps.geometry.spherical.computeDistanceBetween(a, b) / 1000).toFixed(2);
return distance;
}
This is the full code (code snippet):
let map;
let mapCenter = { lat: 59.428, lng: 24.76};
let start;
let end;
let chargingPointsMarkers = [];
let markerArray = [];
let stopoverMarkers = []
let vehicle1 = {capacity: 33, status: 33, consumption: 6.6666} //1KW = 6.6666 Km; Capacity in KM = status*consumption;
function initMap(listener) {
//Create the map, the DirectionsService, the DirectionsRenderer and an eventListener for the GO button
//If I chose to implement a detailed steps display it would also be created here
const directionsService = new google.maps.DirectionsService();
const mapOptions = {
center: mapCenter,
zoom: 7,
}
map = new google.maps.Map(document.getElementById("map"), mapOptions);
const directionsRenderer = new google.maps.DirectionsRenderer({map: map});
//const stepDisplay = new google.maps.InfoWindow();
const geocoder = new google.maps.Geocoder();
document.getElementById("submit").addEventListener("click", () => {
launcher(geocoder, directionsRenderer, directionsService);
});
}
async function launcher(geocoder, directionsRenderer, directionsService){
//the method is used to be launched by the eventListener
//it sets up the start and end points, fetches the EV markers and launches the route calculation process though a callback function
resetMarkers();
const startEndPointsArray = await setupRoutingProcess(geocoder);
await callbackHandler(startEndPointsArray,directionsRenderer,
directionsService, calculateAndDisplayRoute);
}
function setMapOnAll(map){
// Sets the map on all markers in the array.
for (let i = 0; i < markerArray.length; i++) {
markerArray[i].setMap(map);
}
}
function clearMarkers() {
// Removes the markers from the map, but keeps them in the array.
setMapOnAll(null);
}
function resetMarkers(){
// Pushes all visible markers to a same array,
// launches the different reset processes and
// deletes all markers in the arrays by removing references to them.
for (let i = 0; i < chargingPointsMarkers.length; i++) {
markerArray.push(chargingPointsMarkers[i])
}
chargingPointsMarkers = [];
for (let j = 0; j < stopoverMarkers.length; j++) {
markerArray.push(stopoverMarkers[j])
}
stopoverMarkers = [];
clearMarkers();
markerArray = []
}
async function setupRoutingProcess(geocoder){
//launches the setGeocodeAddress method for both start and end points and stores them in an array
start = await setGeocodeAddress(geocoder, map, "start");
end = await setGeocodeAddress(geocoder, map, "end");
let startEndPointsArray = [start];
startEndPointsArray.push(end);
return startEndPointsArray;
}
async function setGeocodeAddress(geocoder, resultsMap, elementId) {
//Retrieve the addresses (strings) from the html text boxes and uses Geocoder to Google Markers objects.
//it pushes those markers in an array later used to delete the markers on the map
const address = document.getElementById(elementId).value;
return new Promise(resolve => geocoder.geocode({address: address},
(results, status) => {
if (status === "OK") {
resultsMap.setCenter(results[0].geometry.location);
const marker = new google.maps.Marker({
map: resultsMap,
position: results[0].geometry.location,
title: elementId,
});
resolve(marker)
markerArray.push(marker);
} else {
alert("Trip Route finder was not successful for the following reason: " + status);
}
}));
}
async function callbackHandler (startEndPointsArray,
directionsRenderer,
directionsService,
calculateAndDisplayRoute){
//
let jsonChargingPoints = await setChargingStationsMarkers(startEndPointsArray, directionsRenderer,
directionsService, calculateAndDisplayRoute);
await createChargerPointMarkers(jsonChargingPoints)
calculateAndDisplayRoute(
directionsRenderer,
directionsService,
jsonChargingPoints
);
}
async function setChargingStationsMarkers(startEndPointsArray, directionsRenderer,
directionsService, calculateAndDisplayRoute) {
//Creates an encoded polyline to be passed as an Url argument to limit the results
//fetches the EV Charging Points as Json response
const polyline = await createPolyline(startEndPointsArray);
const baseUrl = 'https://api.openchargemap.io/v3/poi/?output=json&maxresults=200&includecomments=true';
const queryUrl = baseUrl + '&polyline=' + polyline + '&distance=50';
let data = await fetch(queryUrl)
.then((response) => response.json())
.then((data) => {return data})
return data;
}
async function createPolyline(startEndPointsArray){
//Creates a polyline and encodes it
try {
position = startEndPointsArray[0].getPosition();
position2 = startEndPointsArray[1].getPosition();
const initialPath = [position, position2];
const poly = new google.maps.Polyline({
path: initialPath,
strokeColor: '#ff0000',
strokeOpacity: 0.00001,
strokeWeight: 0,
});
const path = poly.getPath();
const encodedPath = await google.maps.geometry.encoding.encodePath(path);
return encodedPath;
}catch (error){
throw error ('Failed to create polyline');
}
}
function createChargerPointMarkers(jsonChargingPoints) {
//Loop through the Json response and launch the PlaceMarkers function
for (let x = 0; x < jsonChargingPoints.length; x++) {
const LatLng = new google.maps.LatLng(parseFloat(jsonChargingPoints[x].AddressInfo.Latitude), parseFloat(jsonChargingPoints[x].AddressInfo.Longitude));
placeMarker(LatLng);
}
}
function placeMarker(location) {
//Convert the Json response elements to Google Markers, places them on the Map and pushes them to an array.
let marker = new google.maps.Marker({
position: location,
map,
draggable: false,
});
chargingPointsMarkers.push(marker)
}
async function calculateAndDisplayRoute(
directionsRenderer,
directionsService,
jsonChargingPoints,
stepDisplay,
map) {
if (!compareVehicleCapacityToDistance(vehicle1, start)) {
setChargeCheckpoint(vehicle1)
}
directionsService.route(setRequest(),
function (result, status) {
if (status === "OK") {
directionsRenderer.setDirections(result);
// showSteps(result, markerArray, stepDisplay, map);
} else {
window.alert("Directions request failed due to " + status);
}
});
}
function setRequest(){
//prepares the request sent to the Directions service
let stopovers = [];
for (let x = 0; x < stopoverMarkers.length; x++){
let latLng = stopoverMarkers[x].getPosition();
let waypoint = {
location: latLng,
stopover: true
};
stopovers.push(waypoint)
}
const request = {
origin: start.getPosition(),
destination: end.getPosition(),
waypoints: stopovers,
travelMode: google.maps.TravelMode.DRIVING,
unitSystem: google.maps.UnitSystem.METRIC
};
return request;
}
function compareVehicleCapacityToDistance(vehicle, p1){
//Checks if the distance to destination is greater than the vehicle capacity
if (calculateDistance(p1, end) > (vehicle.status*vehicle.consumption)){
return false
}return true;
}
function setChargeCheckpoint(vehicle){
//launches the function selecting the closest marker to destination
//Setting a marker of the selected marker on the map (might be redundant)
//Pushes it to markerArray for later deletion (might be redundant)
//Pushes it to stopoverMarkers to be used by the Directions service to setup a route
let waypoint = selectMarkerClosestToDestination(vehicle);
const waypointLocation = waypoint.getPosition();
const marker = new google.maps.Marker({
position: waypointLocation,
stopover: true,
draggable: false,
title: "EV charging stopover"
});
markerArray.push(marker)
stopoverMarkers.push(marker)
}
function selectMarkerClosestToDestination(vehicle) {
//Selecting the closest marker to destination as long as it is not out of the vehicle capacity range
//CURRENTLY BUGGED
let waypoints = chargingPointsMarkers;
for (let x = waypoints.length -1; x > 0; x--) {
if(calculateDistance(waypoints[x], start) > (vehicle.status*vehicle.consumption)){
console.log(calculateDistance(waypoints[x], start))
console.log(vehicle.status*vehicle.consumption)
waypoints.splice(x, 1)
console.log(waypoints)
}
}
console.log(waypoints)
for (let x = waypoints.length - 1; x > 0; x--) {
if (calculateDistance(waypoints[x], end) > (calculateDistance(waypoints[x-1], end))) {
waypoints.splice(x, 1);
} else {
waypoints.splice(x - 1, 1);
}
}
console.log(waypoints)
return waypoints[0];
}
function calculateDistance(p1, p2) {
//Uses the Google geometry library to calculate distance between two Markers
let a = p1.getPosition();
let b = p2.getPosition();
let distance = (google.maps.geometry.spherical.computeDistanceBetween(a, b) / 1000).toFixed(2);
return distance;
}
function showSteps(directionResult, stepDisplay, map) {
// For each step, place a marker, and add the text to the marker's infowindow.
// Also attach the marker to an array so we can keep track of it and remove it
// when calculating new routes.
//NOT CURRENTLY IMPLEMENTED/USED
const myRoute = directionResult.routes[0].legs[0];
for (let i = 0; i < myRoute.steps.length; i++) {
const marker = (markerArray[i] =
markerArray[i] || new google.maps.Marker());
marker.setMap(map);
marker.setPosition(myRoute.steps[i].start_location);
attachInstructionText(
stepDisplay,
marker,
myRoute.steps[i].instructions,
map
);
}
}
function attachInstructionText(stepDisplay, marker, text, map) {
google.maps.event.addListener(marker, "click", () => {
// Open an info window when the marker is clicked on, containing the text
// of the step.
//NOT CURRENTLY IMPLEMENTED/USED
stepDisplay.setContent(text);
stepDisplay.open(map, marker);
});
}
/* Always set the map height explicitly to define the size of the div
* element that contains the map. */
#map {
height: 100%;
}
/* Optional: Makes the sample page fill the window. */
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
#floating-panel {
position: absolute;
top: 10px;
left: 25%;
z-index: 5;
background-color: #fff;
padding: 5px;
border: 1px solid #999;
text-align: center;
font-family: "Roboto", "sans-serif";
line-height: 30px;
padding-left: 10px;
}
#warnings-panel {
width: 100%;
height: 10%;
text-align: center;
}
<!DOCTYPE html>
<html>
<head>
<title>EV Trip Route Finder</title>
<script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
<script
src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&callback=initMap&libraries=&v=weekly"
defer
></script>
<!-- jsFiddle will insert css and js -->
</head>
<body>
<div id="floating-panel" >
<b>Start: </b>
<input id="start" type="text" value="Tallinn">
<b>End: </b>
<input id="end" type="text" value="Vilnius">
<input id="submit" type="button" value="GO" />
</div>
<div id="map"></div>
<div id="warnings-panel"></div>
</body>
</html>
There is only one small mistake in your code, that is in your for loop where you exclude waypoints that are over the vehicle capacity.
Basically, you are doing the following, which as you can see doesn't output what you'd expect:
const a = [1, 2, 3, 4, 5];
for (let i = a.length - 1; i > 0; i--) {
console.log(a[i])
}
Instead, you want to use i >= 0 to loop through all elements within your array:
const a = [1, 2, 3, 4, 5];
for (let i = a.length - 1; i >= 0; i--) {
console.log(a[i])
}
Changing this one statement seems to correct the issue because you were not iterating through the entire array and therefore you were leaving 1 element at 528km from start in the first loop, which would end up being the one you select in the second loop (in the selectMarkerClosestToDestination() function).
let map;
let mapCenter = { lat: 59.428, lng: 24.76};
let start;
let end;
let chargingPointsMarkers = [];
let markerArray = [];
let stopoverMarkers = []
let vehicle1 = {capacity: 33, status: 33, consumption: 6.6666} //1KW = 6.6666 Km; Capacity in KM = status*consumption;
function initMap(listener) {
//Create the map, the DirectionsService, the DirectionsRenderer and an eventListener for the GO button
//If I chose to implement a detailed steps display it would also be created here
const directionsService = new google.maps.DirectionsService();
const mapOptions = {
center: mapCenter,
zoom: 7,
}
map = new google.maps.Map(document.getElementById("map"), mapOptions);
const directionsRenderer = new google.maps.DirectionsRenderer({map: map});
//const stepDisplay = new google.maps.InfoWindow();
const geocoder = new google.maps.Geocoder();
document.getElementById("submit").addEventListener("click", () => {
launcher(geocoder, directionsRenderer, directionsService);
});
}
async function launcher(geocoder, directionsRenderer, directionsService){
//the method is used to be launched by the eventListener
//it sets up the start and end points, fetches the EV markers and launches the route calculation process though a callback function
resetMarkers();
const startEndPointsArray = await setupRoutingProcess(geocoder);
await callbackHandler(startEndPointsArray,directionsRenderer,
directionsService, calculateAndDisplayRoute);
}
function setMapOnAll(map){
// Sets the map on all markers in the array.
for (let i = 0; i < markerArray.length; i++) {
markerArray[i].setMap(map);
}
}
function clearMarkers() {
// Removes the markers from the map, but keeps them in the array.
setMapOnAll(null);
}
function resetMarkers(){
// Pushes all visible markers to a same array,
// launches the different reset processes and
// deletes all markers in the arrays by removing references to them.
for (let i = 0; i < chargingPointsMarkers.length; i++) {
markerArray.push(chargingPointsMarkers[i])
}
chargingPointsMarkers = [];
for (let j = 0; j < stopoverMarkers.length; j++) {
markerArray.push(stopoverMarkers[j])
}
stopoverMarkers = [];
clearMarkers();
markerArray = []
}
async function setupRoutingProcess(geocoder){
//launches the setGeocodeAddress method for both start and end points and stores them in an array
start = await setGeocodeAddress(geocoder, map, "start");
end = await setGeocodeAddress(geocoder, map, "end");
let startEndPointsArray = [start];
startEndPointsArray.push(end);
return startEndPointsArray;
}
async function setGeocodeAddress(geocoder, resultsMap, elementId) {
//Retrieve the addresses (strings) from the html text boxes and uses Geocoder to Google Markers objects.
//it pushes those markers in an array later used to delete the markers on the map
const address = document.getElementById(elementId).value;
return new Promise(resolve => geocoder.geocode({address: address},
(results, status) => {
if (status === "OK") {
resultsMap.setCenter(results[0].geometry.location);
const marker = new google.maps.Marker({
map: resultsMap,
position: results[0].geometry.location,
title: elementId,
});
resolve(marker)
markerArray.push(marker);
} else {
alert("Trip Route finder was not successful for the following reason: " + status);
}
}));
}
async function callbackHandler (startEndPointsArray,
directionsRenderer,
directionsService,
calculateAndDisplayRoute){
//
let jsonChargingPoints = await setChargingStationsMarkers(startEndPointsArray, directionsRenderer,
directionsService, calculateAndDisplayRoute);
await createChargerPointMarkers(jsonChargingPoints)
calculateAndDisplayRoute(
directionsRenderer,
directionsService,
jsonChargingPoints
);
}
async function setChargingStationsMarkers(startEndPointsArray, directionsRenderer,
directionsService, calculateAndDisplayRoute) {
//Creates an encoded polyline to be passed as an Url argument to limit the results
//fetches the EV Charging Points as Json response
const polyline = await createPolyline(startEndPointsArray);
const baseUrl = 'https://api.openchargemap.io/v3/poi/?output=json&maxresults=200&includecomments=true';
const queryUrl = baseUrl + '&polyline=' + polyline + '&distance=50';
let data = await fetch(queryUrl)
.then((response) => response.json())
.then((data) => {return data})
return data;
}
async function createPolyline(startEndPointsArray){
//Creates a polyline and encodes it
try {
position = startEndPointsArray[0].getPosition();
position2 = startEndPointsArray[1].getPosition();
const initialPath = [position, position2];
const poly = new google.maps.Polyline({
path: initialPath,
strokeColor: '#ff0000',
strokeOpacity: 0.00001,
strokeWeight: 0,
});
const path = poly.getPath();
const encodedPath = await google.maps.geometry.encoding.encodePath(path);
return encodedPath;
}catch (error){
throw error ('Failed to create polyline');
}
}
function createChargerPointMarkers(jsonChargingPoints) {
//Loop through the Json response and launch the PlaceMarkers function
for (let x = 0; x < jsonChargingPoints.length; x++) {
const LatLng = new google.maps.LatLng(parseFloat(jsonChargingPoints[x].AddressInfo.Latitude), parseFloat(jsonChargingPoints[x].AddressInfo.Longitude));
placeMarker(LatLng);
}
}
function placeMarker(location) {
//Convert the Json response elements to Google Markers, places them on the Map and pushes them to an array.
let marker = new google.maps.Marker({
position: location,
map,
draggable: false,
});
chargingPointsMarkers.push(marker)
}
async function calculateAndDisplayRoute(
directionsRenderer,
directionsService,
jsonChargingPoints,
stepDisplay,
map) {
if (!compareVehicleCapacityToDistance(vehicle1, start)) {
setChargeCheckpoint(vehicle1)
}
directionsService.route(setRequest(),
function (result, status) {
if (status === "OK") {
directionsRenderer.setDirections(result);
// showSteps(result, markerArray, stepDisplay, map);
} else {
window.alert("Directions request failed due to " + status);
}
});
}
function setRequest(){
//prepares the request sent to the Directions service
let stopovers = [];
for (let x = 0; x < stopoverMarkers.length; x++){
let latLng = stopoverMarkers[x].getPosition();
let waypoint = {
location: latLng,
stopover: true
};
stopovers.push(waypoint)
}
const request = {
origin: start.getPosition(),
destination: end.getPosition(),
waypoints: stopovers,
travelMode: google.maps.TravelMode.DRIVING,
unitSystem: google.maps.UnitSystem.METRIC
};
return request;
}
function compareVehicleCapacityToDistance(vehicle, p1){
//Checks if the distance to destination is greater than the vehicle capacity
if (calculateDistance(p1, end) > (vehicle.status*vehicle.consumption)){
return false
}return true;
}
function setChargeCheckpoint(vehicle){
//launches the function selecting the closest marker to destination
//Setting a marker of the selected marker on the map (might be redundant)
//Pushes it to markerArray for later deletion (might be redundant)
//Pushes it to stopoverMarkers to be used by the Directions service to setup a route
let waypoint = selectMarkerClosestToDestination(vehicle);
const waypointLocation = waypoint.getPosition();
const marker = new google.maps.Marker({
position: waypointLocation,
stopover: true,
draggable: false,
title: "EV charging stopover"
});
markerArray.push(marker)
stopoverMarkers.push(marker)
}
function selectMarkerClosestToDestination(vehicle) {
//Selecting the closest marker to destination as long as it is not out of the vehicle capacity range
//CURRENTLY BUGGED
let waypoints = chargingPointsMarkers;
for (let x = waypoints.length -1; x >= 0; x--) {
if(calculateDistance(waypoints[x], start) > (vehicle.status*vehicle.consumption)){
console.log(calculateDistance(waypoints[x], start))
console.log(vehicle.status*vehicle.consumption)
waypoints.splice(x, 1)
console.log(waypoints)
}
}
console.log(waypoints)
for (let x = waypoints.length - 1; x > 0; x--) {
if (calculateDistance(waypoints[x], end) > (calculateDistance(waypoints[x-1], end))) {
waypoints.splice(x, 1);
} else {
waypoints.splice(x - 1, 1);
}
}
console.log(waypoints)
return waypoints[0];
}
function calculateDistance(p1, p2) {
//Uses the Google geometry library to calculate distance between two Markers
let a = p1.getPosition();
let b = p2.getPosition();
let distance = (google.maps.geometry.spherical.computeDistanceBetween(a, b) / 1000).toFixed(2);
return distance;
}
function showSteps(directionResult, stepDisplay, map) {
// For each step, place a marker, and add the text to the marker's infowindow.
// Also attach the marker to an array so we can keep track of it and remove it
// when calculating new routes.
//NOT CURRENTLY IMPLEMENTED/USED
const myRoute = directionResult.routes[0].legs[0];
for (let i = 0; i < myRoute.steps.length; i++) {
const marker = (markerArray[i] =
markerArray[i] || new google.maps.Marker());
marker.setMap(map);
marker.setPosition(myRoute.steps[i].start_location);
attachInstructionText(
stepDisplay,
marker,
myRoute.steps[i].instructions,
map
);
}
}
function attachInstructionText(stepDisplay, marker, text, map) {
google.maps.event.addListener(marker, "click", () => {
// Open an info window when the marker is clicked on, containing the text
// of the step.
//NOT CURRENTLY IMPLEMENTED/USED
stepDisplay.setContent(text);
stepDisplay.open(map, marker);
});
}
/* Always set the map height explicitly to define the size of the div
* element that contains the map. */
#map {
height: 100%;
}
/* Optional: Makes the sample page fill the window. */
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
#floating-panel {
position: absolute;
top: 10px;
left: 25%;
z-index: 5;
background-color: #fff;
padding: 5px;
border: 1px solid #999;
text-align: center;
font-family: "Roboto", "sans-serif";
line-height: 30px;
padding-left: 10px;
}
#warnings-panel {
width: 100%;
height: 10%;
text-align: center;
}
<!DOCTYPE html>
<html>
<head>
<title>EV Trip Route Finder</title>
<script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
<script
src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&callback=initMap&libraries=&v=weekly"
defer
></script>
<!-- jsFiddle will insert css and js -->
</head>
<body>
<div id="floating-panel" >
<b>Start: </b>
<input id="start" type="text" value="Tallinn">
<b>End: </b>
<input id="end" type="text" value="Vilnius">
<input id="submit" type="button" value="GO" />
</div>
<div id="map"></div>
<div id="warnings-panel"></div>
</body>
</html>
I am trying to return the markers as the object but when i run the function it just returns [ ], but printing it inside i can see the object data, can anyone explain how to return the object batch2 please?
google.maps.event.addListener(mgr, 'loaded', function(){
mgr.addMarkers(getMarkers(),6); //add all the markers! documentation for viewports with totals for city count, look at viewport
mgr.addMarkers(getMarkers2(),14); //get markers for zoomed out place, add click function to zoom in
//mgr.addMarkers(getMarkers(1000), 8);
console.log("added");
mgr.refresh();
});
function getMarkers2() {
var batch2 = [];
var clusters = new Parse.Query("cityfreqcoords");
var clusterresults = new Parse.Object("cityfreqcoords");
clusters.find({
success: function (results) {
for (i = 1; i < results.length; i++) {
var city = (results[i]["attributes"]["city"]);
var count = (results[i]["attributes"]["count"]);
var lat = (results[i]["attributes"]["lat"]);
var lng = (results[i]["attributes"]["lng"]);
var markerLatlong = new google.maps.LatLng(lat, lng);
//icon =
//adding the marker
var marker2 = new google.maps.Marker({
position: markerLatlong,
title: city,
clickable: true,
animation: google.maps.Animation.DROP
//icon:icon
});
//adding the click event and info window
google.maps.event.addListener(marker2, 'click', function () {
map.setZoom(6);
map.setCenter(marker2.getPosition());
});
batch2.push(marker2);
}
}
})
return batch2;
}
It would appear that clusters.find is asynchronous. You return batch2 before cluster.find succeeds. There are a handful of patterns for working with asynchronous code in JavaScript -- one common one is to use a callback. You would need to rewrite your code like so:
function getMarkers2(callback) {
var batch2 = [];
var clusters = new Parse.Query("cityfreqcoords");
var clusterresults = new Parse.Object("cityfreqcoords");
clusters.find({
success: function (results) {
for (i = 1; i < results.length; i++) {
var city = (results[i]["attributes"]["city"]);
var count = (results[i]["attributes"]["count"]);
var lat = (results[i]["attributes"]["lat"]);
var lng = (results[i]["attributes"]["lng"]);
var markerLatlong = new google.maps.LatLng(lat, lng);
//icon =
//adding the marker
var marker2 = new google.maps.Marker({
position: markerLatlong,
title: city,
clickable: true,
animation: google.maps.Animation.DROP
//icon:icon
});
//adding the click event and info window
google.maps.event.addListener(marker2, 'click', function () {
map.setZoom(6);
map.setCenter(marker2.getPosition());
});
batch2.push(marker2);
}
}
callback(batch2);
})
}
Then call it like so:
getMarkers2(function(markers) {
mgr.addMarkers(markers, 14);
});
If you're interested, take a look at how promises work as you might prefer that approach over using callbacks.
With callbacks in javascript, you generally don't return data. You pass in another function reference into the handler as a callback.
EG:
function getMarkers2(f) {
// do stuff
//when done
f(batch2)
}
Ended up just passing making the marker manager global and passing mgr into the query which worked, probably not the most efficient way to do it
function getMarkers2(mgr) {
Parse.initialize("X", "Y");
var batch2 = [];
var clusters = new Parse.Query("cityfrequency2");
var clusterresults = new Parse.Object("cityfrequency2");
clusters.find({
success: function (results) {
for (i = 0; i < (results.length); i++) {
var city = (results[i]["attributes"]["city"]);
var lat = (results[i]["attributes"]["lat"]);
var lng = (results[i]["attributes"]["lng"]);
var markerLatlong = new google.maps.LatLng(lat, lng);
var image = {
url: 'warning.png',
size: new google.maps.Size(50, 46),
// The origin
origin: new google.maps.Point(0, 0),
// The anchor
anchor: new google.maps.Point(25, 0)
};
//adding the marker
var marker2 = new google.maps.Marker({
position: markerLatlong,
title: city,
clickable: true,
animation: google.maps.Animation.DROP,
icon:image
});
//adding the click event and info window
google.maps.event.addListener(marker2, 'click', function () {
map.setZoom(6);
map.setCenter();
});
batch2.push(marker2);
mgr.addMarkers(batch2,0,6);
mgr.refresh();
}
}
})
}
function setupMarkers() {
var mgrOptions = { borderPadding: 50, maxZoom: 15, trackMarkers: true };
mgr = new MarkerManager(map,mgrOptions);
google.maps.event.addListener(mgr, 'loaded', function(){
getMarkers2(mgr);
getMarkers(mgr);
console.log("added");
});
}
I've got a map with a few MarkerWithLabel objects on it (http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerwithlabel/1.1.9/). The labels, in this case, are integers.
I also have a MarkerClustererPlus (http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerclustererplus/2.1.2/) which also works fine.
However, I want to change the text on Clusters to show the sum of those integers on labels for every MarkerWithLabel inside a Cluster.
I did that by binding this function to the end of clustering:
function calculateClusterLabels() {
$.each(markerCluster.clusters_, function(i, cluster){
var sum = 0;
var cluster_markers = cluster.getMarkers();
$.each(cluster_markers, function(j, marker) {
sum += marker.labelContent;
});
cluster.clusterIcon_.sums_['text'] = sum;
cluster.updateIcon(); // also tried cluster.repaint();
});
}
And that works - at least for the Cluster text. But now we are getting to the real problem: it freezes the whole Map. Raven.js catches this: Uncaught TypeError: undefined is not a function. But nothing clearer than this.
Any ideas?
EDIT:
Some more code. Data is fetched with ajax and then the markers are set in a loop:
$.each(us_data, function(k, v) {
var markerPosition = new google.maps.LatLng(us_data[k]['lat'], us_data[k]['lon']);
var marker = new MarkerWithLabel({
position: markerPosition,
draggable: false,
map: map,
labelContent: us_data[k]['count'],
labelAnchor: anchor,
labelClass: "marker-with-label"
});
markers.push(marker);
});
And then I make the Clusters and bind the event:
markerCluster = new MarkerClusterer(map, markers, {imagePath: 'https://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclustererplus/images/m'});
google.maps.event.addListener(markerCluster, 'clusteringend', function() {
calculateClusterLabels();
});
This all happens inside the .done() of ajax, but markerCluster and markers are visible outside.
To answer myself: well, it was fairly simple after all:
function calculateClusterLabels() {
$.each(markerCluster.clusters_, function(i, cluster){
var sum = 0;
var cluster_markers = cluster.getMarkers();
$.each(cluster_markers, function(j, marker) {
sum += marker.labelContent;
});
if (cluster.clusterIcon_.sums_ != null) {
cluster.clusterIcon_.sums_['text'] = sum;
}
});
}
What I did - I added a simple if statement to check whether the .sums_ object is not null (because it wasn't null only on the visible clusters that had markers inside) and I omitted the .updateIcon call and everything works perfectly, no errors.
An even better solution, for my case, was to simply change the markerclusterer.js source file:
MarkerClusterer.CALCULATOR = function (markers, numStyles) {
var index = 0;
var title = "";
var count = 0;
if (typeof markers[0].labelContent != 'undefined') {
var sum = 0;
var i;
for (i = 0; i < markers.length; ++i) {
if (!isNaN(markers[i].labelContent) {
sum += markers[i].labelContent;
} else {
// whatever we need, perhaps we want to calculate it differently
}
}
count = sum.toString();
} else {
count = markers.length.toString();
}
var dv = count;
while (dv !== 0) {
dv = parseInt(dv / 10, 10);
index++;
}
index = Math.min(index, numStyles);
return {
text: count,
index: index,
title: title
};
};
This was even better because it updated Cluster styles, as well and it works both with regular Marker and MarkerWithLabel objects.
This question already has an answer here:
Remove all markers google map v3
(1 answer)
Closed 8 years ago.
Is there a way to do this instruction on google map v3 ? I have a button on the map, I want ,on the first click, to show markers and ,on the second, to remove them from map. Thank you for advance.
//Add hotel's markers and infowindows to the map
google.maps.event.addDomListener(hotel, 'click', function() {
for (var i = 0; i < len; i++) {
marker = new google.maps.Marker({
position: new google.maps.LatLng(results.rows.item(i).lat,results.rows.item(i).long),
map: map,
icon : icons[1],
animation: google.maps.Animation.DROP,
});
markers.push(marker);
google.maps.event.addListener(marker, 'click', (function(marker, i) {
return function() {
//if we create the infowindow here, all the windows 'll stay shown
infowindow.setContent("<div style='background-color:red;'><h3>"+results.rows.item(i).nom+"</h3><br/><center>"+"<img src='"+results.rows.item(i).img+"' style='width:20px; height:20px;' /></center><br/></div>")
infowindow.open(map,marker);
}
})(marker,i));
}
});
Create a global showMarkers variable
Create a function toggleMarkers and call it when you click on the button
Here is a quick example:
var showMarkers = false;
function toggleMarkers() {
if (showMarkers === false) {
for (var i=0; i<markers.length; i++) {
markers[i].setMap(map);
}
showMarkers = true;
} else {
for (var i=0; i<markers.length; i++) {
markers[i].setMap(null);
}
showMarkers = false;
}
}
I have to manage a map of about 80.000 markers concentrated in France.
To do that, I decided to get the bounds of the viewport and call a dynamic-JSON (with PHP) which contains the markers inside the viewport. And this on the "idle" event.
I faced a problem with this solution. Indeed, the markers which already exist was re-plotted (at the same position), which consequently weigh the map for nothing...
To solve it, the markers list before and after the JSON query are compared (thanks to jQuery), in order to plot only the new markers. And it works!
Now, I would want to remove the markers which are not currently shown on the map. Or a list of markers (I get it thanks to jQuery) designated by an ID which is also the title of the marker. So, how can a delete markers like that ? I specify that I am using MarkerManager.
Otherwise, you guess that if I do not remove these markers, they will be re-plotted in some cases... For example, you are viewing the city A, you move the map to view the city B, and you get back to the city A...
Here is the code:
var map;
var mgr;
var markers = [];
function initialize(){
var mapOptions = {
zoom: 6,
center: new google.maps.LatLng(46.679594, 2.109375)
};
map = new google.maps.Map(document.getElementById('map_canvas'), mapOptions);
var mgrOptions = { borderPadding: 50, maxZoom: 15, trackMarkers: false };
mgr = new MarkerManager(map, mgrOptions);
google.maps.event.addListener(map, 'idle', function() {
mapEvent();
});
}
function mapEvent(){
if( map.getZoom() >= 8 ){
var bounds = map.getBounds();
getSupports(bounds.getNorthEast(), bounds.getSouthWest());
} else {
// Todo
}
}
var markerslistID = new Array();
var markerslistData = {};
function getSupports(ne, sw){
newMarkerslistID = new Array();
newMarkerslistData = {};
// Getting the markers of the current view
$.getJSON('./markerslist.php?nelat='+ne.lat()+'&nelng='+ne.lng()+'&swlat='+sw.lat()+'&swlng='+sw.lng(), function(data) {
for (var i = 0; i < data.points.length; i++) {
var val = data.points[i];
newMarkerslistID.push(val.id);
newMarkerslistData[val.id] = new Array(val.lat, val.lng, val.icon);
}
// List of New Markers TO PLOT
var diffNewMarkers = $(newMarkerslistID).not(markerslistID).get();
// List of Old markers TO REMOVE
var diffOldMarkers = $(markerslistID).not(newMarkerslistID).get();
// Plotting the NEW MARKERS
for( var i = 0; i < diffNewMarkers.length; i++ ){
var marker = new google.maps.Marker({
position: new google.maps.LatLng(newMarkerslistData[diffNewMarkers[i]][0], newMarkerslistData[diffNewMarkers[i]][1]),
title : diffNewMarkers[i],
icon : './images/'+newMarkerslistData[diffNewMarkers[i]][2]+'.png'
});
mgr.addMarker(marker, 0);
}
/*****************************************
HERE WE HAVE TO REMOVE
THE MARKERS CONTAINED IN diffOldMarkers
*****************************************/
mgr.refresh();
// Switching the new list to the old, for the next event
markerslistID = newMarkerslistID;
markerslistData = newMarkerslistData;
});
}
Thank you for your help.
A one-liner to hide all markers that ar not in the current viewport.
!map.getBounds().contains(marker.getPosition()) && marker.setVisible(false);
Or,
if (map.getBounds().contains(marker.getPosition()) && !marker.getVisible()) {
marker.setVisible(true);
}
else if (!map.getBounds().contains(marker.getPosition()) && marker.getVisible()) {
marker.setVisible(false);
}