Google Maps setTimeout() not working with an AJAX call - javascript

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);

Related

Google maps add multiple markers and info windows for each one?

I have my code like this:
function algolia_search(position) {
clearOverlays();
var APPLICATION_ID = 'PSH...VAKL';
var SEARCH_ONLY_API_KEY = '2eb...efa8';
var INDEX_NAME = 'entities';
var PARAMS = { hitsPerPage: 150 };
// Client + Helper initialization
var algolia = algoliasearch(APPLICATION_ID, SEARCH_ONLY_API_KEY);
var algoliaHelper = algoliasearchHelper(algolia, INDEX_NAME, PARAMS);
// Map initialization
algoliaHelper.on('result', function(content) {
renderHits(content);
var i;
// Add the markers to the map
for (i = 0; i < content.hits.length; ++i) {
var hit = content.hits[i];
var infowindow = new google.maps.InfoWindow({
    content: hit.name
    });
var marker = new google.maps.Marker({
position: {lat: hit._geoloc.lat, lng: hit._geoloc.lng},
map: map,
label: hit.name,
animation: google.maps.Animation.DROP,
index: i
});
    marker.addListener('click', function() {
    infowindow.open(map, marker);
    });
addListenerOnPoint(marker);
markers.push(marker);
infos.push(infowindow);
}
});
function renderHits(content) {
$('#container').html(JSON.stringify(content, null, 2));
}
algoliaHelper.setQueryParameter('aroundRadius', 200);
algoliaHelper.search();
}
Now the markers works absolutely fine, however the info windows doesn't. No matter which marker I click, it shows me an info windows from one marker, however I don't exactly know how to solve it.
Explanation of the problem
The infowindow shows you data from one markerbecause when you click on them they call:
infowindow.open(map, marker);
At the point they are clicked, waht do you think the values for infowindow,map and marker are respectively?
Your assumption is that they will hold the values that you set as the for loop is running.
What is happening is that the for loop has finished running completely by the time you click on the the marker. The values of infowindow and marker will happen to be set to the last value of the for loop.
Suggested solution(s)
The solution for this is to find a way to encapsulate the data for each iteration of the loop that will persist when the listener is called.
Two ways to do this are to use the ES6 block scoped let instead of var OR use n IIFE (Immediate Invoked Function Expression) to encapsulate the state of the infowindow and marker for each step of the loop.
Using let:
for(i = 0; i < content.hits.length; ++i) {
// rest of code above --^
let infowindow = new google.maps.InfoWindow({content: hit.name});
let marker = new google.maps.Marker({...});
marker.addListener('click', function() {
infowindow.open(map, marker);
});
// rest of code below --v
}
The above works because what effectively happens is that a new
variable is created for each infowindow and marker variable for
each iteration, almost like creating a new vairable name for each iteration(marker0, marker1, etc)
Using an IIFE:
for(i = 0; i < content.hits.length; ++i) {
// rest of code above --^
var infowindow = new google.maps.InfoWindow({content: hit.name});
var marker = new google.maps.Marker({...});
function(inf, mar){
marker.addListener('click', function() {
inf.open(map, mar);
})(infowindow, marker);
}
// rest of code below --v
}

how to remove all routes in google map?

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);
}
}

Can't remove Google Map Markers when reloading data

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.

How to remove duplicated function in javascript listener?

Objective
I wish to remove the duplication code brought by the anonymous function calls.
Background
I am doing a very simple project where I use Google Maps API to show a map with two searchboxes. The user puts a start address and an end address in those boxes and I show markers in the map.
To achieve this I have two anonymous functions for the listeners, which are exactly equal except for one point - one uses the startSearchBox and the other one the endSearchBox.
What I tried
This duplication of code is unnecessary, and so I tried to pass the searchboxes as a parameter to the anonymous function, however that didn't work.
I also considered creating the searchboxes as global variables, but that is a bad practice I wish to avoid.
How can I eliminate the duplication in this code?
Code
function initSearchBoxes() {
// Create the search box and link it to the UI element.
let startInput = document.getElementById('start-input');
let startSearchBox = new google.maps.places.SearchBox(startInput);
let endInput = document.getElementById('end-input');
let endSearchBox = new google.maps.places.SearchBox(endInput);
// Bias the SearchBox results towards current map's viewport.
map.addListener('bounds_changed', function() {
startSearchBox.setBounds(map.getBounds());
endSearchBox.setBounds(map.getBounds());
});
startSearchBox.addListener('places_changed', function() {
deleteAllMarkers();
let places = startSearchBox.getPlaces();
if (places.length == 0) {
return;
}
// For each place, get the icon, name and location.
let bounds = new google.maps.LatLngBounds();
places.forEach(function(place) {
// // Create a marker for each place.
let newMarker = createMarker(place.geometry.location, place.name, markerLabels.nextSymbol(), true);
markerLib.trackMarker(newMarker);
newMarker.setMap(map);
if (place.geometry.viewport) {
// Only geocodes have viewport.
bounds.union(place.geometry.viewport);
}
else {
bounds.extend(place.geometry.location);
}
});
map.fitBounds(bounds);
});
endSearchBox.addListener('places_changed', function() {
deleteAllMarkers();
let places = endSearchBox.getPlaces();
if (places.length == 0) {
return;
}
// For each place, get the icon, name and location.
let bounds = new google.maps.LatLngBounds();
places.forEach(function(place) {
// // Create a marker for each place.
let newMarker = createMarker(place.geometry.location, place.name, markerLabels.nextSymbol(), true);
markerLib.trackMarker(newMarker);
newMarker.setMap(map);
if (place.geometry.viewport) {
// Only geocodes have viewport.
bounds.union(place.geometry.viewport);
}
else {
bounds.extend(place.geometry.location);
}
});
map.fitBounds(bounds);
});
}
You can wrap your callback function in another "factory" function. The factory will take a parameter (the search box reference) and then it will return the actual handler:
function makeSearchHandler(searchBox) {
return function() {
deleteAllMarkers();
let places = searchBox.getPlaces();
if (places.length == 0) {
return;
}
// For each place, get the icon, name and location.
let bounds = new google.maps.LatLngBounds();
places.forEach(function(place) {
// // Create a marker for each place.
let newMarker = createMarker(place.geometry.location, place.name, markerLabels.nextSymbol(), true);
markerLib.trackMarker(newMarker);
newMarker.setMap(map);
if (place.geometry.viewport) {
// Only geocodes have viewport.
bounds.union(place.geometry.viewport);
}
else {
bounds.extend(place.geometry.location);
}
});
map.fitBounds(bounds);
};
}
That function contains the code from your original, but instead of directly referring to either startSearchBox or endSearchBox, it uses the parameter passed to the factory. The returned function will therefore work like yours, but the code is only present once.
You can then use that function to create the callbacks:
startSearchBox.addListener('places_changed', makeSearchHandler(startSearchBox));
endSearchBox.addListener('places_changed', makeSearchHandler(endSearchBox));

Getting Data into the JSON

I am trying to visulize Coordinates on the map . Data is Coming form json.
I am getting and error Push is undefined. I am passing the Array but getting error
Here is the Code
var testCtrl = this;
testCtrl.allOrgUnits = response.organisationUnits;
console.log(testCtrl.allOrgUnits)
for (i = 0; i < testCtrl.allOrgUnits.length; i++) {
if(testCtrl.allOrgUnits[i].coordinates != undefined && testCtrl.allOrgUnits[i].coordinates.length < 200) {
testCtrl.geoCoords.push(new Array(testCtrl.allOrgUnits[i].name, testCtrl.allOrgUnits[i].coordinates.substring(1,testCtrl.allOrgUnits[i].coordinates.length-1).split(",")));
}
}
// Add the coordinates to the map.
addMarkers(testCtrl.geoCoords);
});
JSON Data is like
organization units [{ "name":david , "coordinates""[ 10.24 ,23.80] { "name":phil , "coordinates""[ 35.80 ,23.80]
Here is the Function addMarkers
function addMarkers(coordinates) {
var marker;
markers = [];
for (i = 0; i < coordinates.length; i++) {
// Create and add a new marker per coordinate.
marker = new google.maps.Marker({
position: new google.maps.LatLng(coordinates[i][1][1], coordinates[i][1][0]),
map: map,
title: coordinates[i][0],
icon: blueMarker,
current: false,
});
markers.push(marker);
// Add a listener to each marker, so that they will display the name of the facility when clicked.
google.maps.event.addListener(marker, 'click', (function(marker, i) {
return function() {
infowindow.setContent("<div class='info'><h4>" + coordinates[i][0] + "</h4>Facility</div>");
infowindow.open(map, marker);
}
})(marker, i));
}
You probably need to initialize geoCords to be an array before trying to push something.
testCtrl.geoCoords = [];
You are trying to call push method on undefined
You are doing this
testCtrl.geoCoords.push()
where testCtrl has no property geoCoords, so first initialize this property like
testCtrl.geoCoords = []
Now it is an empty array , you can use push method on this
You could transform your json into Object, it might be easier after that.
private ObjectMapper mapper = new ObjectMapper();
listPersonCoordinates = mapper.readValue(query_result, new TypeReference<ArrayList<PersonCoordinate>>(){})
for (PersonCoordinate pc : listPersonCoordinate) {
pc.getName();
pc.getCoordinate();
// Do your stuff here
}
and
public Class PersonCoordinate {
String name;
ArrayList<double> coordinate;
// getters + setters + constructor
}
Hope this help :)
Edit : Your JSON looks strangely formatted. misses some "

Categories

Resources