I am using renderDirections and requestDirections methods to create multiple routes called in a loop, now i want to clear them before calling that loop because even when there is no route data it shows that of past data.
PS: The methods are not called when no data is there but it shows previous routes
Below is my sample code to set routes:
function renderDirections(result, Driver) {
var directionsRenderer = new google.maps.DirectionsRenderer({ suppressMarkers: false,preserveViewport: true, polylineOptions: { strokeColor: colors[cur] } });
directionsRenderer.setMap(map);
directionsRenderer.setDirections(result);
var leg = result.routes[0].legs[0];
makeMarker(leg.start_location, Driver);
makeMarker(leg.end_location, Driver);
cur++;
}
function requestDirections(start, end, wps, Driver) {
var directionsService = new google.maps.DirectionsService;
directionsService.route(
{
origin: start,
destination: end,
waypoints: wps,
travelMode: google.maps.DirectionsTravelMode.DRIVING
}, function (result) {
renderDirections(result, Driver);
});
}
You can simply put your routes in an array, when you initialize it, like this: routes.push (myRoute) and next use the method route.setMap () with the argument null to disappear them of the map. You can also remove them if you reset the array like this: routes = []; or routes[2]=null for one element. I give you some methods to remove or just disappear all routes.
// Sets the map on all routes in the array.
function setMapOnAll(map) {
for (var i = 0; i < markers.length; i++) {
routes[i].setMap(map);
}
}
// Removes the routes from the map, but keeps them in the array.
function clearMarkers() {
setMapOnAll(null);
}
// Shows any routes currently in the array.
function showMarkers() {
setMapOnAll(map);
}
// Deletes all routes in the array by removing references to them.
function clearAllMarkers() {
clearMarkers();
routes = [];
}
This worked for me: declaring directionsRender[] globally and setting up a counter to loop on old routes to clear it `
function renderDirections(result, Driver) {
directionsRenderer[cnt] = new google.maps.DirectionsRenderer();
directionsRenderer[cnt].setMap(map);
directionsRenderer[cnt].setDirections(result);
cnt++;
}`
function clearRoutes() {
for (var i = 0; i < cnt; i++) {
directionsRenderer[i].setMap(null);
}
}
Related
I am building a web app that drops markers on a map based on the viewport bounds. The markers are dropping as expected when the user moves the map, but they drop simultaneously and I would like them to drop consecutively.
To solve this, I am attempting to use the window.setTimeout() (as per Google Maps API docs) but I am having trouble making it work with my data which is added to a Set by an AJAX call.
I am not too sure how to structure the window.setTimeout() function within my JS code, which is more complicated than Google's example. I have tried dozens of different variations with no success.
Here is the JavaScript:
var markers = new Set();
var marker, i;
[...]
google.maps.event.addListener(map, "bounds_changed", () => {
var lat0 = map.getBounds().getNorthEast().lat();
var lng0 = map.getBounds().getNorthEast().lng();
var lat1 = map.getBounds().getSouthWest().lat();
var lng1 = map.getBounds().getSouthWest().lng();
$.ajax({
type: 'GET',
url: '/electra/marker_info/',
data: {
'neLat': parseFloat(lat0),
'neLng': parseFloat(lng0),
'swLat': parseFloat(lat1),
'swLng': parseFloat(lng1)
},
success: function (data) {
add_markers(data, i * 200);
}
});
});
[...]
function add_markers(ajaxData, timeout) {
window.setTimeout(() => {
for (i = 0; i < ajaxData.length; i++) { //puts markers in the markers set
if(! markers.has(JSON.stringify(ajaxData[i]))) {
marker = new google.maps.Marker({
position: new google.maps.LatLng(ajaxData[i][2], ajaxData[i][3]),
map: map,
animation: google.maps.Animation.DROP,
})
[...]
markers.add(JSON.stringify(ajaxData[i]));
}
}
}, timeout);
}
Currently you have a single timeout for all the markers.
There is an example in the documentation: Marker Animations With setTimeout() which includes a setMarkerWithTimeout function:
function addMarkerWithTimeout(position, timeout) {
window.setTimeout(() => {
markers.push(
new google.maps.Marker({
position: position,
map,
animation: google.maps.Animation.DROP,
})
);
}, timeout);
}
that can be used in your code, but you will need to change the markers array it uses, as you have a markers array that contains different data and which is used for a different purpose (you can probably remove the markers.push from that function if you don't need references to the google.maps.Marker objects).
function add_markers(ajaxData, timeout) {
for (i = 0; i < ajaxData.length; i++) { //puts markers in the markers set
if(! markers.has(JSON.stringify(ajaxData[i]))) {
addMarkerWithTimeout(new google.maps.LatLng(ajaxData[i][2], ajaxData[i][3]), timeout);
[...]
markers.add(JSON.stringify(ajaxData[i]));
}
}
}
I would change your add_markers function in this way:
function add_markers(ajaxData, timeout) {
for (let i = 0; i < ajaxData.length; i++) { //puts markers in the markers set
if(! markers.has(JSON.stringify(ajaxData[i]))) {
let marker = new google.maps.Marker({
position: new google.maps.LatLng(ajaxData[i][2], ajaxData[i][3]),
map: map,
animation: google.maps.Animation.DROP,
})
[...]
// here you increase the delay based on current number or markers
addMarker(JSON.stringify(ajaxData[i]), timeout * markers.size);
}
}
and define a new function to insert a marker at the wanted time:
function addMarker(marker, interval) {
setTimeout(function() { markers.add(marker); }, interval);
}
Basically you move setTimeout to the function responsible of actually inserting the marker from the function processing the data you get from the Ajax call.
The line below, will make sure that the first marker will be inserted immediately (marker.size being 0); the next ones will be inserted with a timeout distance from each other.
addMarker(JSON.stringify(ajaxData[i]), timeout * markers.size);
I have a React app that displays a map with some markers on it. The map markers are refreshed by clicking a button that fetches new locations from the Google Maps API. I want to remove the previous location markers from the map on each refresh.
import React, { useEffect, useState } from 'react';
function Map(props) {
const [markers, setMarkers] = useState();
function clearMarkers() {
for(let i = 0; i < markers.length; i++) {
markers[i].setMap(null);
}
}
useEffect(() => {
clearMarkers();
if(props.locations.length) {
const googleMarkers = [];
for(let i = 0; i < props.locations.length; i++) {
const marker = new window.google.maps.Marker({...});
googleMarkers.push(marker);
}
setMarkers(googleMarkers);
}
}, [props.locations, props.map]);
}
I have it working, but I am getting a warning from React.
React Hook useEffect has a missing dependency: 'clearMarkers'. Either include it or remove the dependency array
I need the dependency array, so the markers only refresh when there are new props.locations, but when I include it in the dependency array, I get an infinite loop.
How can I clear the markers off the map before adding new ones without React throwing a warning? Or should I not be concerned with the warning?
You could consider to store markers via mutable ref object (as described here):
const prevMarkersRef = useRef([]);
to distinguish previous markers. And then clear previous markers once locations prop gets updated:
useEffect(() => {
//clear prev markers
clearMarkers(prevMarkersRef.current);
//render markers
for (let loc of props.locations) {
const marker = createMarker({ lat: loc.lat, lng: loc.lng }, map);
prevMarkersRef.current.push(marker);
}
});
where
function createMarker(position, map) {
return new window.google.maps.Marker({
position: position,
map: map
});
}
function clearMarkers(markers) {
for (let m of markers) {
m.setMap(null);
}
}
Here is a demo
You can try and memoize your clearMarkers function and add it to the dependency array of useEffect using useCallback
As your code reads you are creating a new function on each render so the effect is triggered every time if you add the function to the dependency array
this should work
const cb = useCallback(function clearMarkers() {
for(let i = 0; i < markers.length; i++) {
markers[i].setMap(null);
}
}, [markers.length]);
useEffect(() => {
cb();
if (props.locations.length) {
const googleMarkers = [];
for (let i = 0; i < props.locations.length; i++) {
const marker = `marker #${i}`;
googleMarkers.push(marker);
}
setMarkers(googleMarkers);
}
}, [props.locations, cb]);
but you can also just add the clearing loop inside useEffect and add the markers.length to the dependency array
I'm a having some trouble with google maps V3 API.
Here's my AngularJS Directive:
;
(function () {
"use strict";
angular.module('LoVendoApp.directives')
.directive('map', ['SimpleRETS', 'InfoWindowService', 'McOptions', 'ModalOptions', '$rootScope', '$parse', '$uibModal', '$timeout', 'SafetyFilter',
function (SimpleRETS, InfoWindowService, McOptions, ModalOptions, $rootScope, $parse, $uibModal, $timeout, SafetyFilter) {
return {
restrict: 'A',
scope: {
requestObj: '='
},
link: function (scope, el, attrs) {
//Creating map instance with GoogleMaps API
var map = new google.maps.Map(el[0], {
center: {
lat: 25.7742700,
lng: -80.1936600
},
zoom: 8
});
//Markers array
var markers = [];
console.log('on init = ', markers.length);
/**
* Creates new google maps
* marker
*
* #param {Object} param
*
*/
function _newGoogleMapsMarker(param) {
var r = new google.maps.Marker({
map: param._map,
position: new google.maps.LatLng(param._lat, param._lng),
title: param._head,
icon: param._icon
});
if (param._data) {
google.maps.event.addListener(r, 'click', function () {
// this -> the marker on which the onclick event is being attached
if (!this.getMap()._infoWindow) {
this.getMap()._infoWindow = new google.maps.InfoWindow();
}
this.getMap()._infoWindow.close();
var content = InfoWindowService.getContent(param._data);
this.getMap()._infoWindow.setContent(content);
//Creates event listener for InfoWindow insances
google.maps.event.addListener(this.getMap()._infoWindow, 'domready', function () {
$("#iw_container")
.off("click")
.on("click", modalListener);
//Opens modal when click is listened
function modalListener() {
var modalOptions = ModalOptions.getHouseDetailOptions(param._data);
var modalInstance = $uibModal.open(modalOptions);
}
});
this.getMap()._infoWindow.open(this.getMap(), this);
});
}
return r;
}
//Handling request with SimpleRETS service factory
scope.$on('loadMap', function () {
SimpleRETS.requestHandler(scope.requestObj).then(dataReceived, dataError);
function dataReceived(res) {
if (markers.length > 0) {
for (var k = 0; k > markers.length; k++) {
markers[k].setMap(null);
console.log('removing! #', k);
}
markers = [];
console.log('removed!');
}
var results = res.filter(SafetyFilter.filterData);
$rootScope.globalHousesData = results;
if (markers.length == 0)
$timeout(loadMarkers(results), 1000);
}
function dataError(error) {
console.log('mapError', error);
}
});
//Randomizes position for matching coordinates
function randomPos() {
return Math.random() * (0.0001 - 0.00005) + 0.00005;
}
function loadMarkers(results) {
// Fetching marker options from service
var options = McOptions.getOptions;
for (var i = 0; i < results.length; i++) {
var marker = _newGoogleMapsMarker({
_map: map,
_icon: 'assets/images/icon.png',
_lat: results[i].geo.lat,
_lng: results[i].geo.lng,
_head: '|' + new google.maps.LatLng(results[i].geo.lat, results[i].geo.lng),
_data: results[i]
});
markers.push(marker);
}
var markerCluster = new MarkerClusterer(map, markers, options);
}
//Initializes event to load m
scope.$broadcast('loadMap');
}
}
}
]);
})();
As you can see I've been going nuts with console.logs to see what is wrong with my logic. It may look a bit overwhelming but I simply have 4 main components or actions. 1st I initialize the map:
var map = new google.maps.Map(el[0], {
center: {
lat: 25.7742700,
lng: -80.1936600
},
zoom: 8
});
The I instantiate a markers array: var markers = [];
Then I have 2 main functions. One that create new markers: _newGoogleMapMarkers(param)
This function takes a param object that basically contain the map object, position coordinates, an icon, title and some content that should be available in the InfoWindow. Also, I'm attatching an onClick listener to all InfoWindows to open an external modal in my app.
The other function should be triggered when the markers array is empty and it loads the new data from the external API I'm using (SimpleRETS)
This is all triggered by an event `$scope.$broadcast('loadMap') that is triggered everytime filters are changed, the user interacts with some stuff, etc.
The problem is that even though I am calling the loadMarkers() function when the markers array is empty (and after iterating and doing markers[k].setMap(null)), the old markers stay in the map, creating a massive bloab of markers when loading 500 markers with each load. I even tried setting a timeout of 1000ms after the markers array is empty before the next load, to see if it helped in any way, but it doesn't.
So what do you guys think? I'd appreciate any suggestions you might have.
For anyone that might be having the same problem. If you are using the markerclusterer jQuery Plugin you must markerCluster.clearMarkers(); before emptying the markers array after a new load of markers.
So basically it'll end up looking something like this
if (markerCluster) {
markerCluster.clearMarkers();
markers = [];
}
//Load new markers in the markers array
[...]
//Creating Cluster
markerCluster = new MarkerClusterer(map, markers, options);
Then you would do whatever you need to fetch data and replace the markers, since from this point on the map would be clear of markers and clusters.
The Senario:
I have a google map and one of its functions is to show routing information from a central point to several diffrent locations on the same map.
I know that each direction object can only show one set of routes at time so I have a function that create a render object each time it is called and places the route on the map and call it for each location.
The code:
the function to calculate and display the route:
function calculateRoot (startLocation,wayPoints,endLocation) {
var selectedMode = $("#travelMode").val();
// create a new directions service object to handle directions requests
var directionsService = new google.maps.DirectionsService();
// create a directions display to display route info on the map
var directionsDisplay = new google.maps.DirectionsRenderer();
directionsDisplay.setMap(map);
// Stops the default googlemarkers from showing
directionsDisplay.suppressMarkers = true;
directionsDisplay.setPanel(document.getElementById("directions"));
// create a request object
var request = {
origin:startLocation,
waypoints: wayPoints,
destination:endLocation,
travelMode: google.maps.TravelMode[selectedMode],
optimizeWaypoints:true,
provideRouteAlternatives:true,
transitOptions: {
departureTime: new Date()
}
};
directionsService.route(request, function(result,status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsDisplay.setDirections(result);
return directionsDisplay;
}
else if (status == google.maps.DirectionsStatus.ZERO_RESULTS){
alert ('No routing can be found for this Journey');
return -1;
}
else {
alert ('The following error occoured while attempting to obtain directions information:'+'\n'+status + '\n' + 'For:'+ ' '+ request.destination);
return -1;
}
});
}
The all locations function:
function showAllRoutes(){
if ( ! directionsArray.length < 1) {
// if directions are already displayed clear the route info
clearRoots();
}
$('#directions').empty();
// craete an empty waypoint array just to pass to the function
var wayPoints = [];
for (var i = 0; i< markerArray.length; i++) {
var directions = calculateRoot(startLatLng,wayPoints,markerArray[i].position);
directionsArray.push(directions);
}
sizeMap();
$('#directions').show();
}
The function to clear the route(s)
function clearRoutes() {
if (directionsArray.length <1 ) {
alert ("No directions have been set to clear");
return;
}
else {
$('#directions').hide();
for (var i = 0;i< directionsArray.length; i ++) {
if (directionsArray [i] !== -1) {
directionsArray [i].setMap(null);
}
}
directionsArray.clear();
map.setZoom(5);
return;
}
}
The problem:
While generating and displaying the routes seems to work fine no matter what I do I cant clear the routes from the map unless I refresh.
Am I missing something simple or can what I need to do not be done in this way, I've been trying to get this working for more than a day now and I'm stumped.
Can anyone help?
Thanks in advance
The directions service is asynchronous, calculateRoot (or calculateRoute or whatever it is really called) can't return anything, you need to push the routes into a global array in the callback function.
This is your callback function:
function(result,status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsDisplay.setDirections(result);
return directionsDisplay;
}
change it to something like:
function(result,status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsDisplay.setDirections(result);
directionsArray.push(directionsDisplay);
}
I'm trying to sort through an array of distances generated by google maps. I need to order my list, closest to furthest. I can get all of the directions and distances displayed just fine with the directionsService api example, but I cannot figure out how to retrieve that info outside of the function so that I can sort it.
function calcDistances() {
for (var x = 0; x < wineries.length; x++) {
var winery = wineries[x];
var trdistances = [];
var request = {
origin: map.getCenter(),
destination: new google.maps.LatLng(winery[1], winery[2]),
travelMode: google.maps.DirectionsTravelMode.DRIVING
};
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var route = response.routes[0];
var summaryPanel = document.getElementById("tasting_rooms_panel");
// For each route, display summary information.
for (var i = 0; i < route.legs.length; i++) {
//this works fine and displays properly
summaryPanel.innerHTML += route.legs[i].distance.text;
//I want to store to this array so that I can sort
trdistances.push(route.legs[i].distance.text);
}
}
});
alert(trdistances[0]);//debug
}
}
As commented in the code, I can populate summaryPanel.innerHTML, but when I populate the array trdistances, the alert gives me "undefined". Is this some rookie javascript coding error? I read up on the scope of variables and this should work. Help me oh wise ones.
function calcDistances() {
for (var x = 0; x < wineries.length; x++) {
var winery = wineries[x];
var trdistances = [];
var request = {
origin: map.getCenter(),
destination: new google.maps.LatLng(winery[1], winery[2]),
travelMode: google.maps.DirectionsTravelMode.DRIVING
};
//Using Closure to get the right X and store it in index
(function(index){
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var route = response.routes[0];
var summaryPanel = document.getElementById("tasting_rooms_panel");
// For each route, display summary information.
for (var i = 0; i < route.legs.length; i++) {
//this works fine and displays properly
summaryPanel.innerHTML += route.legs[i].distance.text;
//I want to store to this array so that I can sort
trdistances.push(route.legs[i].distance.text);
}
if(index == wineries.length-1){ //check to see if this is the last x callback
console.log(trdistances); //this should print the result
//or in your case you can create global function that gets called here like sortMahDistance(trdistances); where the function does what you want.
printMyDistances(trdistances); //calls global function and prints out content of trdistances console.log();
}
}
});
})(x); //pass x into closure as index
}
}
//on global scope
function printMyDistances(myArray){
console.log(myArray);
}
The problem is scope to keep track of the for loop's X. Basically, you have to make sure all the callbacks are done before you can get the final result of trdistances. So, you'll have to use closure to achieve this. By storing first for loops' X into index within closure by passing X in as index, you can check to see if the callback is the last one, and if it is then your trdistances should be your final result. This mod of your code should work, but if not please leave comment.
Furthermore, I marked up my own version of google map using closure to resolve using async directionServices within for loop, and it worked. Here is my demo jsfiddle. Trace the output of console.log(); to see X vs index within the closure.