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.
Related
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! */
I have to convert longitude and latitude, into a useful address (reverse geocode), I found a good script at [Geo Scipt][1]
I am having an issue with too many requests per day, my question is how can I have this run only once (when the data is imported from ifttt) and not every time I open this google sheet?
function getLat(address) {
if (address == '') {
Logger.log("Must provide an address");
return;
}
var geocoder = Maps.newGeocoder();
var location;
// Geocode the address and plug the lat, lng pair into the
// 2nd and 3rd elements of the current range row.
location = geocoder.geocode(address);
// Only change cells if geocoder seems to have gotten a
// valid response.
if (location.status == 'OK') {
lat = location["results"][0]["geometry"]["location"]["lat"];
return lat;
}
};
function getLon(address) {
if (address == '') {
Logger.log("Must provide an address");
return;
}
var geocoder = Maps.newGeocoder();
var location;
// Geocode the address and plug the lat, lng pair into the
// 2nd and 3rd elements of the current range row.
location = geocoder.geocode(address);
// Only change cells if geocoder seems to have gotten a
// valid response.
if (location.status == 'OK') {
lng = location["results"][0]["geometry"]["location"]["lng"];
return lng;
}
};
function getAdd(lat, lng) {
// Return Address by taking the coordinates and reverse geocoding.
if (lat == "") {
return "You have to provide latitudinal coordinates to the place"
} if (lng == ""){
return "You have to provide longitudinal coordinates to the place"
}
var response = Maps.newGeocoder().reverseGeocode(lat, lng); //Call the reverse Geocode service
for (var i = 0; i < response.results.length; i++) {
var result = response.results[i];
return result.formatted_address; //Output full address in cell
Utilities.sleep(Math.random() * 500);
}
};
The Service invoked too many times for one day indicates that the given service exceed the total allowable execution time for one day. It most commonly occurs for scripts that run on a trigger, which have a lower daily limit than scripts executed manually. Instead of Google Sheet, try to use Fusion Tables
Each time a custom function is used in a spreadsheet, Google Sheets makes a separate call to the Apps Script server. If your spreadsheet contains dozens (or hundreds, or thousands!) of custom function calls, this process can be quite slow. It could be helpful to read Map your data with the Maps API and Fusion Table
If a script reaches a quota or limitation, it will throw an exception with a message, for more information regarding this, follow this link: https://developers.google.com/apps-script/guides/services/quotas#current_limitations
Trying to fetch all the restaurants within a city using Google Places API.
Even though there are 100 records returned, only 20 markers are displayed on map.
Below is the piece of code where we are creating markers. Please tell me if I am missing something here. You may refer to below link and give input as restaurants in a 'Santa Clara' jsfiddle.net/r8g42046
//function used to create marker based on Location of a place.
function createMarker(place){
var place = place.geometry.location;
var marker = new google.maps.Marker({
map: map,
position: place
});
markers.push(marker);
google.maps.event.addListener(marker, 'click', function() {
infoWindow.setContent(place.name);
infoWindow.open(map, this);
});
}
// Callback function based on results fetched from Places library based on type : restaurant.
function callback(results, status, next_page_token) {
if (results != null){
resultlist.push(results);
}
if (next_page_token != undefined){
textSearchrequest.pagetoken = next_page_token;
service.textSearch(textSearchrequest,callback);
}
else{
findLocation(resultlist[0][0].place_id);
for(var page = 0;page < resultlist.length;page++)
{
for (var i = 0; i < resultlist[page].length; i++) {
var place = resultlist[page][i];
console.log(place.name); // Displays 100 restaurant names
createMarker(place); //Call create marker function
}
}
}
}
This line is completely useless:
textSearchrequest.pagetoken = next_page_token;
pagetoken is a URL-parameter for the places-webservice and has no meaning in the places-library
it will not lead you to the next page of results.
You always get the same set of(20) results and draw the same set of markers again and again, until you hit the QUERY_LIMIT. When you hit the QUERY_LIMIT after 10 requests, you draw 200, when you hit it after 12 request 240, but no matter how often you may run the search, you never get more than 20 different places(markers)
Although you draw 200 (or more)markers you only see 20 markers, because they are at the same locations.
Use the method pagination.nextPage() to access the next set of results.
I have a javascript function that creates a leaflet layer group, adds it to a map, removes it and then repeats. Each time, it goes through a json array that has the data which is placed in the layer group. The effect is an animation of data points on the map.
There are two data sets which are formatted to be animated with this function. Let's call them Nowcast and Forecast. In my app, if I run the animations in the following sequence:
Nowcast, Forecast, Nowcast, Forecast, Nowcast
On the third Nowcast run, it looks like some of the data from the Forecast shows up along with the Nowcast data. Not all the data, and only for a few iterations, but then it just sits there and does not disappear when the animation is over. The only way to get this weird residual data is to run the Forecast animation again. (running the Nowcast again does not remove it).
Anyway, here is the function:
function animateAshData(inputData,dataLayer) {
var currentTime = inputData.ashData[t];
var currentAshData = currentTime.concentrations;
window.Android.toLogCat("t = " + currentTime.date + " " + t);
window.Android.toLogCat("Current set size= " + currentAshData.length);
if (dataLayer != undefined) { //The map has a data layer already been created from previous time step, so remove it
removeDataLayer(dataLayer);
}
var currentAshLayerGroup = new L.LayerGroup();
for (j = 0; j<currentAshData.length; j++) {
L.marker([currentAshData[j].lat, currentAshData[j].lon],{icon: ashIcon}).addTo(currentAshLayerGroup);
map.addLayer(currentAshLayerGroup);
};
t = t+1;
// Queue up the animation to the next time step
tid = setTimeout(function(){
if (t > finalt) {
//end of animation
window.Android.toLogCat("Done with animation");
removeDataLayer(currentAshLayerGroup);
window.clearTimeout(tid);
} else {
// delete currentTime.concentrations;
clearMap();
animateAshData(inputData,currentAshLayerGroup);
}
}, 100);
My first thought was that the layer groups were somehow persisting, even though I had a map.removeLayer(layer) in there (although it is mysterious why it takes 3 iterations instead of just 2 for this to show up...). So, I made a removeDataLayer() function and added a layer.clearLayers(). This did not work either. I finally added the function from here Clear all polylines from leaflet map to just try clearing everything, but this still did not work. Here is my removeDataLayer function:
function removeDataLayer(layer){
map.removeLayer(layer);
layer.clearLayers();
clearMap();
};
function clearMap(){
for(i in map._layers){
if(map._layers[i]._path != undefined)
{
try{
map.removeLayer(map._layers[i]);
}
catch(e){
console.log("problem with " + e + map._layers[i]);
}
}
}
};
Anyway, I can't imagine I am the only one with problems with leaflet layers interfering with each other and/or problems removing them completely. Thanks in advance.
I've got a program which generates KML files, and now want to cycle through the placemarks in order to find the nearest one. But I'm having a problem cycling through them.
Basically, I parse the KML into a Google Earth plugin. Clicking on one of the placemarks in the Google Earth plugin gets it's lat/lon (as StartLat and StartLon) and starts up this code:
function GetDir()
{
var PlaceMarks=ge.getElementsByType('KmlPlacemark');
i=0;
GetNext=1;
while(i<3)
{
if(GetNext==1)
{
GetNext=0;
var PM2 = PlaceMarks.item(i);
var request=
{
origin: StartLat + ", " + StartLon,
destination: PM2.getGeometry().getLatitude() + ", " + PM2.getGeometry().getLongitude(),
travelMode: google.maps.DirectionsTravelMode.DRIVING
};
directionsService.route(request, function(result, status)
{
if (status == google.maps.DirectionsStatus.OK)
{
directionDisplay.setDirections(result);
}
});
}
}
}
I've got an "idle" listener on the map so, theoretically, after it's found the directions and displayed them, i is incremented by 1, so we move to the next placemark, and GetNext is set to 1, so we call the directions again.
At the moment it snarls up and IE8 (the only browser our IT dept will let us use) says that the script is running slowly. I've got the feeling that, using this loop, it can't listen for a response.
EDIT - sorry, should have mentioned, there's also a map on the same page that shows the result. It does work when dealing with only one (outside of this loop), whilst at the moment this loop only deals with three (hence the while(i<3)). The intention is for this to deal with lots of placemarks, and I can get the amount of placemarks in a KML file no problem.
Solved it. Instead of incrementing everything on the Idle listener, I instead made the Idle listener call the procedure which moved to the next point. Relevant code:
This puts the KML file into the Google Earth API, and attaches the code to start cycling through:
function GetKML()
{
if (currentKmlObject)
ge.getFeatures().removeChild(currentKmlObject);
var KmlBox = document.getElementById('kml-box');
var KmlString = KmlBox.value;
try
{
currentKmlObject=ge.parseKml(KmlString);
ge.getFeatures().appendChild(currentKmlObject);
google.earth.addEventListener(currentKmlObject, 'click', function(event)
{
var placemark = event.getTarget();
StartLat = placemark.getGeometry().getLatitude();
StartLon = placemark.getGeometry().getLongitude();
alert('Start point set. Latitude: ' + StartLat + ' Longitude: ' + StartLon + '. Finding nearest...');
AreWeSearching=1;
i=0;
GetDir();
});
var PlaceMarks=ge.getElementsByType('KmlPlacemark');
KMLLength=PlaceMarks.getLength();
alert('KML parsed and added. ' + KMLLength + ' points available. Please click which placemark you would like as your START point on the EARTH screen.');
}
catch(ex)
{
alert('Parse error');
}
}
This is the code for the GetDir function which interrogates the placemarks. At the moment this only goes up to 10, but I'll just use the PlaceMarks length:
function GetDir()
{
if(i==10){AreWeSearching=0;}
if(AreWeSearching==1)
{
var PlaceMarks=ge.getElementsByType('KmlPlacemark');
var PM2 = PlaceMarks.item(i);
var request=
{
origin: StartLat + ", " + StartLon,
destination: PM2.getGeometry().getLatitude() + ", " + PM2.getGeometry().getLongitude(),
travelMode: google.maps.DirectionsTravelMode.DRIVING
};
directionsService.route(request, function(result, status)
{
if (status == google.maps.DirectionsStatus.OK)
{
directionDisplay.setDirections(result);
}
});
i=i+1;
}
}
On the Maps API's idle listener it calls the GetDir function again, so essentially it's called for a first time and, whenever it is idle, gets called again, until we stop going through it.
Basically, what I'm building it for (for anyone who hasn't worked it out already) is so that I can find the nearest placemark to a placemark that I click. Using a bit of extra code it'll allow me to route my way around a KML file's placemarks, starting at a selected placemark.
So, for anyone wanting a way of routing a KML file FROM YOUR HARD DRIVE, create a page with a Google Earth plugin and a Google Maps plugin. Load the KML into Google Earth, as I've done above, then use Google Maps and the Idle listener to cycle through.