Background to Question
I have an array which includes latitude and longitude values. I have the below code which places a marker for each iteration. I am using a Ruby gem Gon to pass values from the database to javascript. The below is working as expected:
function populateMap(map){
var index;
for (index = 0; index < gon.length; ++index) {
var latlng = new google.maps.LatLng(gon.murals[index].lat, gon.murals[index].long);
var marker = new google.maps.Marker({
position: latlng,
map: map
});
}
}
However I want to have an info window for each marker with the address. This is done by reverse geo-coding. https://developers.google.com/maps/documentation/javascript/examples/geocoding-reverse.
The below code works for reverse geocoding 1 marker:
function getReverseGeocodingData(geocoder, map, infowindow) {
var latlng = new google.maps.LatLng(gon.murals[0].lat, gon.murals[0].long);
geocoder.geocode({'location': latlng}, function(results, status) {
if (status === 'OK') {
if (results[1]) {
var marker = new google.maps.Marker({
position: latlng,
map: map
});
google.maps.event.addListener(marker, 'mouseover', function () {
infowindow.open(map, marker);
document.getElementById("address").innerHTML = results[1].formatted_address ;
});
} else {
window.alert('No results found');
}
} else {
window.alert('Geocoder failed due to: ' + status);
}
});
}
Actual Question
When I add the for loop to the reverse geo0code function it only places the marker of the last iteration.
function populateMapTest(map, geocoder, infowindow){
var index;
for (index = 0; index < gon.murals.length; ++index) {
var latlng = new google.maps.LatLng(gon.murals[index].lat, gon.murals[index].long);
alert("start of iteration: " + index);
geocoder.geocode({'location': latlng}, function(results, status){
alert("middle of iteration: " + index);
if (status === 'OK') {
if (results[1]) {
var marker = new google.maps.Marker({
position: latlng,
map: map
});
google.maps.event.addListener(marker, 'mouseover', function () {
infowindow.open(map, marker);
document.getElementById("address").innerHTML = results[1].formatted_address ;
});
} else {
window.alert('No results found');
}
} else {
window.alert('Geocoder failed due to: ' + status);
}
});
alert("end of iteration: " + index);
}
}
For each iteration the alerts are in the following order: Start of iteration, end of iteration, middle of iteration. It seems to be skipping over the code contained in the geocoder brackets till all the iterations are done. I think?
Any help appreciated.
This sounds like a class closure problem, which relates to the scope of a variable that is declared in a high scope but used in functions that are in a lower scope and persist longer than the higher scope where the variable was actually declared.
Change:
var index;
for (index = 0; index < gon.murals.length; ++index) {
to:
for (let index = 0; index < gon.murals.length; ++index) {
This will give index block level scope and each iteration of the loop will have its own value for index. Instead of all iterations of the loop sharing the same index value, each will get its own.
It does seem like a closure issue. But I think it could be because of the variable latlng instead of the index, (or both).
var latlng = new google.maps.LatLng(gon.murals[index].lat, gon.murals[index].long);
The latlng above is updated throughout the loop but eventually the function uses only the last iteration's latlng. The variables inside the closure (the "middle" function) are referenced and will all be updated to the last value when the function actually executes. (I guess a different way of thinking about it, is that it really only looks at the value during execution instead of declaration)
var marker = new google.maps.Marker({
position: latlng,
map: map
});
And at the end, the marker would just be created at the same position, index times.
As an example, the code below will print ten 9s even if you expect it to print an increasing x
function foo() {
for (let index = 0; index < 10; ++index) {
var x = index;
setTimeout(function bar() {
console.log(x)
}, 10)
}
}
foo()
But this will print it correctly if it was immediately invoked (but of course, this isn't an option for your case)
function foo() {
for (let index = 0; index < 10; ++index) {
var x = index;
setTimeout(function bar() {
console.log(x)
}(), 10)
}
}
foo()
You could move the latlng declaration inside the middle function. (Do check the value of the index too though, because that suffers the same issue)
How about this :
geocoder.geocode({'location': latlng}, (function(indexCopy){
return function(results, status) {
alert("middle of iteration: " + indexCopy);
if (status === 'OK') {
if (results[1]) {
var marker = new google.maps.Marker({
position: latlng,
map: map
});
google.maps.event.addListener(marker, 'mouseover', function () {
infowindow.open(map, marker);
document.getElementById("address").innerHTML = results[1].formatted_address ;
});
} else {
window.alert('No results found');
}
} else {
window.alert('Geocoder failed due to: ' + status);
}
};
})(index));
Just a thought...
Related
I am trying to put multiple markers on a Google map. I have JS that builds an array and passes it to the Google function that handles markers.
Problem is, when I try to access the supposed array, I just get the first character as if it's a string.
$(document).ready(function () {
// initialize map to center on florida.
initializeGMap();
var locations = [];
#foreach (var item in Model)
{
<text>
locations.push(#Html.Raw(Json.Encode("'" + item.Name + "'," + item.Location.Latitude + "," + item.Location.Longitude + "")));
</text>
}
addMarker(locations);
});
I've tried several (read: 20+) variations of this including JSON.stringify it before sending, after sending, etc. Here's the function it gets passed too:
function addMarker(locations) {
var locations = JSON.stringify(locations);
alert(locations + '\n' + locations[0][0] + '\n' + locations[0][1]);
var infowindow = new google.maps.InfoWindow();
var marker, i;
for (i = 0; i < locations.length; i++) {
marker = new google.maps.Marker({
position: new google.maps.LatLng(locations[i][1], locations[i][2]),
map: map
});
google.maps.event.addListener(marker, 'click', (function (marker, i) {
return function () {
infowindow.setContent(locations[i][0]);
infowindow.open(map, marker);
}
})(marker, i));
}
alert("done");
}
When it gets to the line with 'locations[x][x]' all I ever get back in '[' which is the first character of the JSON string. It's not being treated at an array.
What am I missing?
I solved it via:
$(document).ready(function () {
// initialize map to center on florida.
initializeGMap();
// serialize model locations
var locationsToPass = #Html.Raw(Json.Encode(Model.Select(x => new { x.Name, x.Location.Latitude, x.Location.Longitude })));
addMarker(locationsToPass);
});
and in the receiving function:
function addMarker(locations) {
var infowindow = new google.maps.InfoWindow();
var marker, i;
for (i = 0; i < locations.length; i++) {
marker = new google.maps.Marker({
position: new google.maps.LatLng(locations[i].Latitude, locations[i].Longitude),
map: map
});
google.maps.event.addListener(marker, 'click', (function (marker, i) {
return function () {
infowindow.setContent(locations[i].Name);
infowindow.open(map, marker);
}
})(marker, i));
}
}
The key to troubleshooting was using this to detect if I was passing an array or not:
variable.constructor === Array
which I got here.
In the example fiddle, how can I get the total number of markers displayed on the map? I'm pushing each of the markers into an array like this:
markers.push(marker)
And attempting to get the total number of markers like this:
$('.marker-count span').html(markers.length);
Unfortunately, "markers.length" is returning 0 when it should be returning at least 3.
I have example code here: http://jsfiddle.net/287C7/
How can I display the total number of markers? Is it not possible to add each marker to my array?
I need to know the amount of markers shown so that I can alert the user if there are none.
Thanks,
In case you don't want to view the code on jsfiddle.net, here it is:
var map, places, tmpLatLng, markers = [];
var pos = new google.maps.LatLng(51.5033630,-0.1276250);
var mapOptions = {
zoom: 8,
center: new google.maps.LatLng(51.5033630,-0.1276250)
};
map = new google.maps.Map(document.getElementById('map-canvas'),
mapOptions);
// create the map and reference the div#map-canvas container
var markerBounds = new google.maps.LatLngBounds();
var service = new google.maps.places.PlacesService(map);
// fetch the existing places (ajax)
// and put them on the map
var request = {
location: pos,
radius: 48000, // Max radius
name: "mc donalds"
};
function callback(results, status) {
if (status == google.maps.places.PlacesServiceStatus.OK) {
for (var i = 0; i < results.length; i++) {
createMarker(results[i]);
}
$('#map-canvas').attr("data-markers",results.length);
$('.marker-count span').html(markers.length);
} else {
console.log("Places request failed: "+status);
}
} // end callback
function createMarker(place) {
var prequest = {
reference: place.reference
};
var tmpLatLng = place.geometry.location;
var marker = new google.maps.Marker({
map: map,
position: place.geometry.location
});
markers.push(marker);
markerBounds.extend( tmpLatLng );
} // end createMarker
service.nearbySearch(request, callback);
the placesSearch call is asynchronous, when you run your code:
$('.marker-count span').html(markers.length);
the result hasn't come back from the server yet. You need to do that in the call back after you update the markers array.
function callback(results, status) {
if (status == google.maps.places.PlacesServiceStatus.OK) {
for (var i = 0; i < results.length; i++) {
createMarker(results[i]);
}
$('#map-canvas').attr("data-markers",results.length);
$('.marker-count span').html(markers.length);
} else {
console.log("Places request failed: "+status);
}
} // end callback
working fiddle
I've been working on this very simple Google Places search and I cannot get anything but a ZERO_RESULTS. It makes no sense to me at this point as my map is working and displays markers from my database within a separate AJAX function. I've logged my objects and variables and all seem to be just fine.
Why does the success callback go right to my else statement with ZERO_RESULTS?
$( "#submit3" ).click(function(e) {
e.preventDefault();
findPlaces();
$('#results').text("Triggah3!");
});
function findPlaces() {
var lat = document.getElementById("latitude").value;
var lng = document.getElementById("longitude").value;
var cur_location = new google.maps.LatLng(lat, lng);
// prepare request to Places
var request = {
location: cur_location,
radius: 50000,
types: 'bank'
};
// send request
service = new google.maps.places.PlacesService(map);
service.search(request, createMarkers);
}
// create markers (from 'findPlaces' function)
function createMarkers(results, status) {
if (status == google.maps.places.PlacesServiceStatus.OK) { //ZERO_RESULTS
// if we have found something - clear map (overlays)
clearOverlays();
// and create new markers by search result
for (var i = 0; i < results.length; i++) {
createMarker(results[i]);
}
} else if (status == google.maps.places.PlacesServiceStatus.ZERO_RESULTS) {
alert('Sorry, nothing is found');
}
}
// create single marker function
function createMarker(obj) {
// prepare new Marker object
var mark = new google.maps.Marker({
position: obj.geometry.location,
map: map,
title: obj.name
});
markers.push(mark);
// prepare info window
var infowindow = new google.maps.InfoWindow({
content: '<img src="' + obj.icon + '" /><font style="color:#000;">' + obj.name +
'<br />Rating: ' + obj.rating + '<br />Vicinity: ' + obj.vicinity + '</font>'
});
// add event handler to current marker
google.maps.event.addListener(mark, 'click', function() {
clearInfos();
infowindow.open(map,mark);
});
infos.push(infowindow);
}
types is expected to be an array.
Use:
types: ['bank']
Here is the code:
<script type="text/javascript">
var offender_locations = [
["10010", "xxxxx", 3],
["10001", "xxxxx", 2],
["10002", "zzzzz", 1]
];
for (i = 0; i < offender_locations.length; i++) {
var address = offender_locations[i][0];
var icon_img = offender_locations[i][1];
}
</script>
This is the output:
1) 10010 - zzzzz
2) 10001 - zzzzz
3) 10002 - zzzzz
AS you can see var address outputs the correct value, but *var icon_img* does always repeat the same value.
I am a Javascript beginner and I have tried all ways I can think of but I still get the same results.
P.S. I have pasted the full script here :
<script type="text/javascript">
var offender_locations = [
["10010", "offender_icon.png", 3],
["10001", "offender_icon.png", 2],
["10002", "visitor_icon.png", 1]
];
var myOptions = {
zoom: 10,
center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById("map"), myOptions);
var latlng = new google.maps.LatLng(0, 0);
for (i = 0; i < offender_locations.length; i++) {
var infowindow = new google.maps.InfoWindow();
var geocoder_map = new google.maps.Geocoder();
var address = offender_locations[i][0];
var icon_img = offender_locations[i][1];
geocoder_map.geocode({
'address': address
}, function (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
map.setCenter(results[0].geometry.location);
var marker = new google.maps.Marker({
map: map,
position: map.getCenter(),
icon: icon_img
});
google.maps.event.addListener(marker, 'click', (function (marker, i) {
return function () {
infowindow.setContent(offender_locations[i][0]);
infowindow.open(map, marker);
}
})(marker, i));
} else {
alert("The requested offender is not mappable !")
};
});
}
</script>
The markers in this script are all # the correct postal code, but they all show the same icon (visitor_icon.png) !
The problem is that you are creating a function in a loop. JavaScript has only function scope, not block scope. I.e. variables you create in a loop exist only once in the whole function, just the values changes per iteration.
At the time icon_img is evaluated (in the callback passed to geocode), the outer for loop already finished and icon_img has the value of the last iteration. It works for address because it is evaluated inside the loop, not later.
You have to 'capture' the current value of icon_img and you can do so by using an immediate function:
for (i = 0; i < offender_locations.length; i++) {
var infowindow = new google.maps.InfoWindow(),
geocoder_map = new google.maps.Geocoder(),
address = offender_locations[i][0],
icon_img = offender_locations[i][1];
(function(addr, img) { // <-- immediate function call
geocoder_map.geocode({'address': addr}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
map.setCenter(results[0].geometry.location);
var marker = new google.maps.Marker({
map: map,
position: map.getCenter(),
icon: img
});
google.maps.event.addListener(marker, 'click', function() {
infowindow.setContent(addr);
infowindow.open(map, marker);
});
} else {
alert("The requested offender is not mappable !");
}
});
}(address, icon_img)); // <-- immediate function call
}
Maybe you hav to do this for even more variables... not sure.
I'm sure this is really simple but I haven't had much luck figuring out what's wrong. I'm creating an empty array (locations), filling it with location objects in the getPartnerLocations function and then trying to plot the locations on the map with the drop function. The problem I'm having is that inside the drop function the locations array which has stuff in it is returning a length of zero so the loop in the isn't working. Any tips or ideas about what's going on here would be greatly appreciated.
var markers = [];
var locations = [];
var iterator = 0;
var map;
var geocoder;
var newYork = new google.maps.LatLng(40.7143528, -74.0059731);
function initialize() {
var mapOptions = {
zoom: 12,
mapTypeId: google.maps.MapTypeId.ROADMAP,
center: newYork
};
map = new google.maps.Map(document.getElementById("map_canvas"),mapOptions);
}
function getPartnerLocations() {
geocoder = new google.maps.Geocoder();
$('.partner').each(function(index){
var street = $('.partner-streetaddress',this).text();
var city = $('.partner-city',this).text();
var state = $('.partner-state',this).text();
var country = $('.partner-country',this).text();
var address = street + ', ' + city + ', ' + state + ', ' + country;
geocoder.geocode( { 'address': address}, function(results, status)
{
if (status == google.maps.GeocoderStatus.OK)
{
locations.push( results[0].geometry.location );
console.log(locations[index]);
}
else
{
console.log('failed to geocode address: ' + address);
}
});
});
initialize();
drop();
}
function addMarker() {
console.log('add marker function');
markers.push(new google.maps.Marker({
position: locations[iterator],
map: map,
draggable: false,
animation: google.maps.Animation.DROP
}));
iterator++;
}
function drop()
{
console.log(locations.length);
for (var i = 0; i < locations.length; i++) {
setTimeout(function() {
addMarker();
}, i * 200);
}
}
getPartnerLocations();
geocode is an asynchronous function.
The callback doesn't execute until some time after you call drop.
Therefore, when you call drop, the array is still empty.
You need to call initialize and drop after the last AJAX call replies, in the geocode callback.