Question

I hope the title was even somewhat comprehensible. I have a function that compares two values, one of which is an average of all the values of the first.

Basically, I want to find the distance between the Long, Lat of n number of markers and a central marker. I have a loop that pushes my "results" variable to an array and, at the end of the loop, finds an average Lat, Long that creates a center point. The problem is that I want to find the distance between each of these "results" and the center point, but mid-loop, the average marker isn't finalized yet.

Harder to explain than to show; I have a demo set up here; to see what I mean, click the Update Button in the results pane: http://jsfiddle.net/Cory2711/m55Ef/. You'll see that the circle emanating from California is correct, since it's the last one made (with the full average point found). The circle coming from New York is made mid-loop, and thus has an incomplete average marker to measure to.

My Javascript (The issues are focused around the "markers" section:

//GLOBAL VARIABLES
var gMapAPIKey = 'AIzaSyBUlnM6c7LqCR79UM9mTKvSNK6V7gzKsmE';
var namearray = document.getElementsByName("name");
var form = document.getElementById("addressform");
var addBtn = document.getElementById("addperson");


//GOOGLE MAPS API SCRIPTS
var streetarray = document.getElementsByName("street");
var cityarray = document.getElementsByName("city");
var geocoder;
var map;
var results;
var mapArray = new Array();
var AvgLat = 0;
var AvgLng = 0;
var j;
var newLatLng;
var dynamicRadius;




function initialize() {
geocoder = new google.maps.Geocoder();
var latlng = new google.maps.LatLng(-34.397, 150.644);
var mapOptions = {
    zoom: 3,
    //centered on Carrollton, Texas -- Change lat,long values to change initial map area
    center: new google.maps.LatLng(32.999173, -96.897413)
}
//change Id reference to the div/container that contains the google map
map = new google.maps.Map(document.getElementById('map'), mapOptions);
}

function codeAddress() {
//Loop through and concate street and city values to find long,lat values for all fields
for (var cName = 0; cName < namearray.length; cName++) {
    var address = streetarray[cName].value + cityarray[cName].value;

    //start geocode copy  & paste text from API reference
    geocoder.geocode({
        'address': address
    }, function (results, status) {
        if (status == google.maps.GeocoderStatus.OK) {
            var results = results[0].geometry.location;
            map.setCenter(results);
            console.log(results);
            var marker = new google.maps.Marker({
                map: map,
                position: results,

            });
            //Push lng/lat locations to the mapArray and find AvgLat/Lngs
            mapArray.push(results);
            var j = mapArray.length;
            AvgLat = (AvgLat + results.lat());
            AvgLng = (AvgLng + results.lng());

            console.log(mapArray, AvgLat, AvgLng, j)

            //Begin Marker Scripts

            var markerCircle = new google.maps.Circle({
                center: results,
                radius: 15000,
                strokeColor: "#0000FF",
                strokeOpacity: 0.8,
                strokeWeight: 2,
                fillColor: "#0000FF",
                fillOpacity: 0.4


            })
            var centerLocation = {
                    lat: AvgLat / j,
                    lng: AvgLng / j,
                }

           var newLatLng = new google.maps.LatLng(centerLocation.lat,     centerLocation.lng);
           var dynamicRadius = google.maps.geometry.spherical.computeDistanceBetween     (results, newLatLng);
            if (j == namearray.length) {

            markerCircle.setRadius(dynamicRadius*1.15)                    
                map.setCenter(newLatLng);
                var centerMarker = new google.maps.Marker({
                    position: newLatLng,
                    map: map,
                    title: "Center Marker",
                })
                var centerCircle = new google.maps.Circle({
                    center: newLatLng,
                    radius: 15000,
                    strokeColor: "#0000FF",
                    strokeOpacity: 0.8,
                    strokeWeight: 2,
                    fillColor: "#0000FF",
                    fillOpacity: 0.4,
                    map: map,
                })


            }
            markerCircle.setRadius(dynamicRadius*1.15)
            markerCircle.setMap(map);


        } else {
            alert('Geocode was not successful for the following reason: ' + status);
        }
    });


}
}

//Initialize the first map on load -- When form is submitted geocode addresses and   places markers on the map
google.maps.event.addDomListener(window, 'load', initialize);
form.addEventListener("submit", codeAddress, false);
 addBtn.addEventListener("click", addPerson, false);
Was it helpful?

Solution

My first thought was that you're not limited to looping over your data only once. The data will still be there after you loop over it (unless you change or destroy it in the process).

So, you'd use two loops.

First loop calculates the center point.

Second loop calculates all of the distances from that point.

But as I look over your code I see there's a bit more to it than that.

Inside your main loop where you iterate over namearray, the loop calls geocoder.geocode() for each element of this array. Then inside the geocoder callback is where the work is done.

The interesting thing is that this code is not actually part of your main loop at all. It appears inside that loop, but it's called much later, as the geocode responses come back one by one.

So let's assume that there are no errors in the geocoding (bad assumption, but we'll run with it for a moment). Your geocoder callback already pushes each geocoding result onto mapArray. So what you could do is have this callback code only do this and not do any of the center/circle calculations. So your geocode call might look more like this:

    geocoder.geocode({
        'address': address
    }, function (results, status) {
        if (status == google.maps.GeocoderStatus.OK) {
            mapArray.push({ latLng: results[0].geometry.location });
            AvgLat += results.lat();
            AvgLng += results.lng();
        } else {
            mapArray.push({ error: status });
            ++nErrors;
        }

        // Might want to update a progress indicator here
        // if the geocoding takes a while?

        if( mapArray.length == namearray.length ) {
            // Everything is geocoded, now get to work
            drawMap();
        }
    });

(nErrors should be defined above the loop where you initialize your other variables with var nErrors = 0;)

Then completely outside your current main loop, you add this function:

function drawMap() {
    if( nErrors ) {
        // complain about error
        return;
    }

    var center = {
        lat: AvgLat / j,
        lng: AvgLng / j,
    };
    map.setCenter( new google.maps.LatLng( center.lat, center.lng ) );

    for( var i = 0;  i < mapArray.length;  i++ ) {
        // Create marker and draw circle here
    }
}

So a couple of other points. First, calculating the average lat/lng this way doesn't sound quite right. I would give some thought (and testing) to cases where markers are different sides of the 0° and 180° meridians. You may be OK there, I just never count on it!

Also is the "average" lat/long actually what you want here? (It may be, just asking.) If you have many markers near each other and very few markers at some distance (e.g. 10 markers in CA and 1 in NY), the "average" will be very near to that cluster of markers. That may be what you want, but if not, there are other ways to calculate various kinds of "centers".

For example, one common way is to create an empty LatLngBounds object, and then extend those boundaries with each position you receive. So up at the top with your other variable initialization you'd add:

var bounds = new google.maps.LatLngBounds;

And then inside each successful geocode callback you'd replace this:

            AvgLat += results.lat();
            AvgLng += results.lng();

with:

            bounds.extend( results );

Then in drawMap() you'd replace this:

    var center = {
        lat: AvgLat / j,
        lng: AvgLng / j,
    };
    map.setCenter( new google.maps.LatLng( center.lat, center.lng ) );

with:

    map.fitBounds( bounds );
    var center = bounds.getCenter();

and use center.lat() and center.lng() if you need those individual values - but in the places where you have newLatLng you can now just use center directly because it is a LatLng object.

Even if you do want to use an "average" of the points for your circle calculations, you may want to use this bounding box approach for the map centering instead of centering the map on the calculated average.

One last thing is to watch out for rate limits and throttling on the geocoding. Maybe you're OK for a small number of markers, but it's possible that you may need to make the geocode calls with a time interval between them instead of firing them all of in a hard loop.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top