I am working on a pothole map, and am on the stage where I load pothole data from the server and put it on the map as markers. Since data retrieval and the APIs that my app relies on (Roads, Geolocation, etc.) are asynchronous, my code ended up refactored to run asynchronously. I refactored the code blocks that added the markers to the map to this:
/* put all potholes on the map
* Parameters:
* • callback : the function to call next
*/
function addPotholeMarkers(callback)
{
var DEBUG = false;
// guarantee that callback is function
if ((callback) && (typeof(callback) !== 'function')) throw new TypeError('callback is something, but not a function. Thrown from addPotholeMarkers().');
// add all the markers for them to the map
async.waterfall([
function(cb) {
async.eachOf(potholeAddresses, function(value, key) {
async.eachOf(value, function (v, k) {
addPotholeMarker(v, false);
})
})
if (cb && typeof cb === 'function') cb(null);
}, function(cb) {
async.eachOf(potholeCoordinates, function(value, key) {
async.eachOf(value, function(v, k) {
async.setImmediate(function() { addPotholeMarker(v); }); // This came from
})
})
}], function(err, results) {
console.log('trying to center map');
reCenterMap();
console.log('Map recentered');
if (callback) {
callback(err);
}
});
}
and addPotholeMarker() looks something like:
/* code was initially obtained from https://developers.google.com/maps/documentation/roads/inspector */
/* Adds marker to map.
* Parameters :
* • potholeData : a PotholeData (or PotholeDataFromCoords) object
* • snappedToRoad: boolean
* Returns :
* • the marker that was added to the map, or null if arguments invalid
*/
function addPotholeMarker(potholeData, snappedToRoad) {
// make sure potholeState is either falsy or contains iconURL string
if ((!potholeData.potholeState) || ((potholeData.potholeState) && (potholeData.potholeState.iconURL === undefined))) throw new Error('invalid potholeData');
// let's make sure to snap this to road if it isn't already...
var coords = new GPSCoordinates(potholeData.lat, potholeData.lng);
if (!snappedToRoad)
{
var potholeMarker = 'a garbage return value';
getRoadCoordinates(coords).done(function(response) {
var coords = response.snappedPoints[0].location;
potholeData.lat = coords.latitude;
potholeData.lng = coords.longitude;
return (potholeMarker = addPotholeMarker(potholeData, true));
/* potholeMarker = addPotholeMarker(potholeData, true);
return potholeMarker;*/
});
return;
//return potholeMarker;
}
var marker = new google.maps.Marker({
position: coords,
title: coords.toString(),
map: map,
opacity: 0.5,
icon: ((potholeData.potholeState.iconURL !== undefined) ? potholeData.potholeState.iconURL : PURPLE_MARKER)
});
// make marker have effect when mouseout,mouseover
marker.addListener('mouseover', function(mouseEvent) {
marker.setOpacity(1.0);
});
marker.addListener('mouseout', function(mouseEvent) {
marker.setOpacity(0.5);
});
var infoWindow = createInfoWindow(potholeData);
// save infoWindow for later reference
infoWindows[statesMap.get(getPotholeStateFor(potholeData.potholeState))].push(infoWindow);
// on click of marker, show infoWindow
marker.addListener('click', function(mouseEvent) {
infoWindow.open(map, marker);
});
// add this to potholeMarkers
potholeMarkers[statesMap.get(getPotholeStateFor(potholeData.potholeState))].push(marker);
return marker;
}
This app is hosted on Google Apps Script (you'll need Google account to run this), and uses the client-side async library
This code, when ran successfully, is supposed to re-center the map at the position-average of all the markers. reCenterMap() works as it should, so I omitted it in attempt at MVCE.
When I ran the code
During any tick of the asynchronous loop, the members of the potholeCoordinates object (which is an Object<Array<PotholeData> >) appear empty. How to fix this?
After showing a friend this problem, contemplating his advice on this, and staying up last night for a few hours to work on it myself, I made the following changes:
not related to this problem, but
I changed the index statesMap.get(getPotholeStateFor(potholeData.potholeState)) to statesMap.get(getPotholeStateFor(potholeData)). Turns out that I gave getPotholeStateFor() the wrong object, which made it return the wrong state.
I changed the signature of addPotholeMarker(potholeData, snappedToRoad) to addPotholeMarker(potholeData, snappedToRoad, callback); /* because apparently I forgot that, with async functions, callbacks must be passed to the lowest level functions and invoked there, with signature callback(err, results) or something similar */
Inside addPotholeMarker() I made sure to use callback, but in a modular way:
if (callback) return callback(null, potholeMarker);
return potholeMarker;
/* I applied this change to the if (!snappedToRoad), but that if-statement is still broken: it will return before it finishes its task of appending to array, and return callback(null, potholeMarker) will cause the callback to be invoked twice. I may end up having to refactor this whole function, especially to append to potholeMarkers (which, btw, is global) after this function (it will be the callback to this) */
The innermost async loop in addPotholeMarkers() got changed from :
async.eachOf(value, function(v, k) {
async.setImmediate(function() { addPotholeMarker(v); });
})
to
async.eachSeries(value, function(pothole, fn) {
addPotholeMarker(pothole,
true,
//pothole.isSnappedToRoad(),
fn);
})
NOTE pothole.isSnappedToRoad() is commented out because it returns false, and addPotholeMarker() doesn't work right in the main call stack with the second parameter false. Also, it should return true, but it isn't because this type of error happened in function prior to addPotholeMarkers(); /* I'm going to fix that next! */
Related
I'm working on a little web application which uses GPS. It uses Javascript to accomplish this. What I'm doing:
Defining an global array "positionCoords" with 2 empty indexes
(latitude / longitude).
On document ready, I am requesting latitude / longitude with the function "getGeoLocation()".
After the request, I am doing an simple console.log of the array. The array indexes are still empty.
Executing console.log(positionCoords) in the webbrowser console, gives me the correct values.
Could this has something to do with asynchronously? I'm quite new to this feature, any help would be appreciated!
// global variables
var positionCoords = {};
positionCoords['latitude'] = null;
positionCoords['longitude'] = null;
/*
on document ready, excecute
*/
$(document).ready(function() {
getGeolocation();
console.log(positionCoords);
});
/*
check if geolocation is supported and allowed, get coordinates
*/
function getGeolocation() {
if (navigator.geolocation) {
// geolocation is available
navigator.geolocation.getCurrentPosition(
function(position) {
// get coordinates
positionCoords['latitude'] = position.coords.latitude;
positionCoords['longitude'] = position.coords.longitude;
return true;
},
function(error){
handleGeolocationErrors(error);
return false;
}
);
} else {
// geolocation is not available
console.log("Browser does not support geolocation services.");
return false;
}
}
DEMO with Google Map
Since it is ASYNC call, you need to pass function-callback to getGeolocation, then , you will call it like the follwong :
ON Calling :
$(document).ready(function() {
getGeolocation(function(coords){
console.log(coords);
//====The resof your code which dependent on `coords` SHALL be here
//......
},function(err){console.log(err)});
});
To be able to call it like the above , you need to modify the implementation of getGeolocation by passing it two parameters :
Function Callback onSuccess.
Function Callback onError
Implementation :
/*
check if geolocation is supported and allowed, get coordinates
*/
function getGeolocation(fnsuccess,fnerror) {
if (navigator.geolocation) {
// geolocation is available
navigator.geolocation.getCurrentPosition(
function(position) {
if(typeof fnsuccess==='function'){
fnsuccess.call(null,position.coords); //--Call CallBack On Success
}
},
function(error){
if(typeof fnerror ==='function'){
fnerror.call(null,error); //--Call CallBack On Error
}
}
);
} else {
// geolocation is not available
console.log("Browser does not support geolocation services.");
return false;
}
}
DEMO :
the DEMO will ask you to share your location , if you accept , check the console of your browser , you will get Coords as you expect
DEMO with Google Map
I am trying to get a function to work that updates a L.Routing.line with Leaflet Routing Machine whenever a user adds or deletes a waypoint.
My code to date (slight adaptation from http://www.liedman.net/leaflet-routing-machine/interaction.html):
function routeImplement(){
if (routewpts.length >= 2) {
router.route(routewpts, function(err, routes) {
if (routeline) {
map.removeLayer(routeline);
};
if (err) {
alert(err);
} else {
routeline = L.Routing.line(routes[0]).addTo(map);
};
});
}
else{
if (routeline) {
map.removeLayer(routeline);
};
};
}
routewpts is an array of latLngs, routeline is supposed to be the L.Routing.line, router=L.Routing.osrm();, and map is the leaflet map (all globals). The function works fine for creating a line.
The issue that I am having is that the map.removeLayer(routeline); doesn't seem to work. There are two issues it seems: one is that L.Routing.line doesn't seem to return anything, so routeline is remaining undefined. Second, if I dispense with the use of a handle, and try to use map.removeLayer directly on L.Routing.line, I get some crazy error about a bad request to OSRM.org.
Thanks for any suggestions.
the first thing that i mooved forward is to use arrow function
function routeImplement(){
if (routewpts.length >= 2) {
router.route(routewpts, (err, routes) => { //arrow function
if (routeline) {
map.removeLayer(routeline);
};
if (err) {
alert(err);
} else {
routeline = L.Routing.line(routes[0]).addTo(map);
};
});
}
else{
if (routeline) {
map.removeLayer(routeline); // but still this line doesnt work for me
};
};
}
Well... It turns out the way to get this to work is to break up the L.Routing.line call:
routeline = L.Routing.line(routes[0]);
routeline.addTo(map);
With that the routeline handle works and everything else functions fine. I don't understand Leaflet (or the Routing Machine) well enough to explain why it gave up on returning anything if the line is immediately added to the map.
I am sure I am missing something obvious but I can't seem to make heads or tails of this problem. I have a web page that is being driven by javascript. The bindings are being provided by Knockout.js, the data is coming down from the server using Breeze.js, I am using modules tied together with Require.js. My goal is to load the html, load the info from Breeze.js, and then apply the bindings to show the data to the user. All of these things appear to be happening correctly, just not in the correct order which is leading to weird binding errors. Now on to the code.
I have a function that gets called after the page loads
function applyViewModel() {
var vm = viewModel();
vm.activate()
.then(
applyBindings(vm)
);
}
This should call activate, wait for activate to finish, then apply bindings....but it appears to be calling activate, not waiting for it to finish and then runs applybindings.
activate -
function activate() {
logger.log('Frames Admin View Activated', null, 'frames', false);
return datacontext.getAllManufacturers(manufacturers)
.then(function () {
manufacturer(manufacturers()[0]);
}).then(function () {
datacontext.getModelsWithSizes(modelsWithSizes, manufacturers()[0].manufacturerID())
.then(datacontext.getTypes(types));
});
}
datacontext.getAllManufacturers -
var getAllManufacturers = function (manufacturerObservable) {
var query = entityQuery.from('Manufacturers')
.orderBy('name');
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
if (manufacturerObservable) {
manufacturerObservable(data.results);
}
log('Retrieved [All Manufacturer] from remote data source',
data, true);
}
};
datacontext.getModelsWithSizes -
var getModelsWithSizes = function (modelsObservable, manufacturerId) {
var query = entityQuery.from('Models').where('manufactuerID', '==', manufacturerId)
.orderBy('name');
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
if (modelsObservable) {
for (var i = 0; i < data.results.length; i++) {
datacontext.getSizes(data.results[i].sizes, data.results[i].modelID());
// add new size function
data.results[i].addNewSize = function () {
var newValue = createNewSize(this.modelID());
this.sizes.valueHasMutated();
return newValue;
};
}
modelsObservable(data.results);
}
log('Retrieved [Models With Sizes] from remote data source',
data, false);
}
};
Any help on why this promise isn't working would be appreciated, as would any process to figure it out so I can help myself the next time I run into this.
A common mistake when working with promises is instead of specifying a callback, you specify the value returned from a callback:
function applyViewModel() {
var vm = viewModel();
vm.activate()
.then( applyBindings(vm) );
}
Note that when the callback returns a regular truthy value (number, object, string), this should cause an exception. However, if the callback doesn't return anything or it returns a function, this can be tricky to locate.
To correct code should look like this:
function applyViewModel() {
var vm = viewModel();
vm.activate()
.then(function() {
applyBindings(vm);
});
}
I am trying to implement the new structure of here map(asp.net web app) , however, I cannot see the features of passing the location information and retrieving the certain address via javascript and stick that info with a bubble on the marker.
Appreciate for any advises.
cheers
I think you are looking to make multiple concurrent reverse geocode requests. There is no problem in doing this, since the calls are asynchronous, you just need to keep a count on which requests have completed, and call your "do-after-all-completed" stuff once at the end:
e.g:
function concurrentSearch(map){
// we will put our address markers into this container
addressContainer = new nokia.maps.map.Container();
managersFinished = 0;
//Locations to be displayed
latLngs= [[10,30], [-117.5, 15.3] ... etc];
var i = latLngs.length;
while(i--) {
nokia.places.search.manager.reverseGeocode({
latitude: latLngs[i][0],
longitude: latLngs[i][1],
onComplete: processResults
});
}
}
With a callback function as shown:
function processResults(data, requestStatus) {
if (requestStatus == "OK") {
// if we are finished, we add a marker for the mapped position
var marker = new nokia.maps.map.StandardMarker(data.location[0].position);
marker.content = data.location[0].address.text; // add your content here
addressContainer.objects.add(
marker);
//increment the counter to notify another manager has finished
managersFinished++;
} else if(requestStatus === "ERROR") {
// we'll also increment in case of an error
managersFinished++;
}
// if all managers are finished, we call the final function
if(managersFinished === latLngs.length) {
onAllManagersFinished();
}
}
And the final clean up:
function onAllManagersFinished() {
//we get the bounding box of the container
var bbox = addressContainer.getBoundingBox();
// if the bounding box is null then there are no objects inside
// meaning no markers have been added to it
if (bbox != null) {
// we have at least one address mapped
// so we add the container and zoomTo it
map.objects.add(addressContainer);
map.zoomTo(bbox);
} else {
// otherwise we'll pop up an error message
alert("There are no addresses to show :(");
}
}
Futhermore, you could add a single event handler on the container, which could open the infobubbles for all of your locations:
addressContainer.addListener(
"click",
function (evt) {
infoBubbles.openBubble(evt.target.content, // content is your own stuff...
evt.target.coordinate);
}
);
If you need to pass in more of your information when reverse-geocoding, you can do it in the manner described in the question here
The solution to a similar problem (multiple geocoding requests) can be found here.
I have 2 js files, 1 is to store the location data in an object and the other is the event handler for the click events and such.
This works:
var Geology = {
coords: {},
error: '',
setPosition: function (position) {
Geology.coords = position.coords;
// DEBUG
for (var prop in Geology.coords)
console.log(prop + ': ' + Geology.coords[prop]);
},
setError: function (error) {
Geology.error = error;
}
};
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(Geology.setPosition,Geology.setError,{timeout:10000});
}
But i want to be able to update the location data if necessary by a click or timer. Any time i try to do that it doesn't assign the vars to Geology like it does initially.
like this:
jQuery(document).ready(function($){
$('.toggle').click(function(){
//- get geo location if available
if (typeof Geology === 'object') {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(Geology.setPosition,Geology.setError,{timeout:10000});
}
}
});
});
I don't get errors in the console but it just fails and does not run the callback. However, it does run the getCurrentPosition method because I've tested to see if it makes it through the conditionals.
I think you need to set the maximum_age parameter, otherwise the results will be cached forever :
navigator.geolocation.getCurrentPosition(
geo_success,
geo_error,
{ maximumAge:60000 } // set to 1min (in ms)
);
You also have two other parameters : enableHighAccuracy and timeout
More info: https://developer.mozilla.org/en-US/docs/Using_geolocation
EDIT : I just found this, it's probably related to your issue navigator.geolocation.getCurrentPosition sometimes works sometimes doesn't