i still have some problems with javascript closures, and input/output variables.
Im playing with google maps api for a no profit project: users will place the marker into a gmap, and I have to save the locality (with coordinates) in my db.
The problem comes when i need to do a second geocode in order to get a unique pairs of lat and lng for a location: lets say two users place the marker in the same town but in different places, I dont want to have the same locality twice in the database with differents coords.
I know i can do the second geocode after the user select the locality, but i want to understand what am i mistaking here:
// First geocoding, take the marker coords to get locality.
geocoder.geocode(
{
'latLng': new google.maps.LatLng($("#lat").val(), $("#lng").val()),
'language': 'it'
},
function(results_1, status_1){
// initialize the html var inside this closure
var html = '';
if(status_1 == google.maps.GeocoderStatus.OK)
{
// do stuff here
for(i = 0, geolen = results_1[0].address_components.length; i != geolen)
{
// Second type of geocoding: for each location from the first geocoding,
// i want to have a unique [lat,lan]
geocoder.geocode(
{
'address': results_1[0].address_components[i].long_name
},
function(results_2, status_2){
// Here come the problem. I need to have the long_name here, and
// 'html' var should increment.
coords = results_2[0].geometry.location.toUrlValue();
html += 'some html to let the user choose the locality';
}
);
}
// Finally, insert the 'html' variable value into my dom...
//but it never gets updated!
}
else
{
alert("Error from google geocoder:" + status_1)
}
}
);
I tryed with:
// Second type of geocoding: for each location from the first geocoding, i want
// to have a unique [lat,lan]
geocoder.geocode(
{
'address': results_1[0].address_components[i].long_name
},
(function(results_2, status_2, long_name){
// But in this way i'll never get results_2 or status_2, well, results_2
// get long_name value, status_2 and long_name is undefined.
// However, html var is correctly updated.
coords = results_2[0].geometry.location.toUrlValue();
html += 'some html to let the user choose the locality';
})(results_1[0].address_components[i].long_name)
);
And with:
// Second type of geocoding: for each location from the first geocoding, i want to have
// a unique [lat,lan]
geocoder.geocode(
{
'address': results_1[0].address_components[i].long_name
},
(function(results_2, status_2, long_name){
// But i get an obvious "results_2 is not defined" error (same for status_2).
coords = results_2[0].geometry.location.toUrlValue();
html += 'some html to let the user choose the locality, that can be more than one';
})(results_2, status_2, results_1[0].address_components[i].long_name)
);
Any suggestion?
EDIT:
My problem is how to pass an additional arguments to the geocoder inner function:
function(results_2, status_2, long_name){
//[...]
}
becose if i do that with a clousure, I mess with the original parameters (results_2 and status_2)
If I'm understanding you correctly:
Your problem in the first example is that the second (innermost) geocode's callback function (which appends the string to html) will not have executed by the time you reach the line with the comment Finally, insert the 'html' variable value into my dom....
You are effectively launching the second geocode request, and then inserting html before the operation has completed.
Regarding passing an extra argument to the callback, you could always make a function that creates and returns a function:
eg.
function(my_argument)
{
return(function(cb1,cb2) { ... });
}(my_argument_value);
Then you can pass in whatever you want for my_argument_value, and the innermost code (...) will see it as well as the two callback args.
The return value of this function is what you pass as the callback to the geocode call.
Related
Using the Google Geocoder v3, if I try to geocode 20 addresses, I get an OVER_QUERY_LIMIT unless I time them to be ~1 second apart, but then it takes 20 seconds before my markers are all placed.
Is there any other way to do it, other than storing the coordinates in advance?
No, there is not really any other way : if you have many locations and want to display them on a map, the best solution is to :
fetch the latitude+longitude, using the geocoder, when a location is created
store those in your database, alongside the address
and use those stored latitude+longitude when you want to display the map.
This is, of course, considering that you have a lot less creation/modification of locations than you have consultations of locations.
Yes, it means you'll have to do a bit more work when saving the locations -- but it also means :
You'll be able to search by geographical coordinates
i.e. "I want a list of points that are near where I'm now"
Displaying the map will be a lot faster
Even with more than 20 locations on it
Oh, and, also (last but not least) : this will work ;-)
You will less likely hit the limit of X geocoder calls in N seconds.
And you will less likely hit the limit of Y geocoder calls per day.
You actually do not have to wait a full second for each request. I found that if I wait 200 miliseconds between each request I am able to avoid the OVER_QUERY_LIMIT response and the user experience is passable. With this solution you can load 20 items in 4 seconds.
$(items).each(function(i, item){
setTimeout(function(){
geoLocate("my address", function(myLatlng){
...
});
}, 200 * i);
}
Unfortunately this is a restriction of the Google maps service.
I am currently working on an application using the geocoding feature, and I'm saving each unique address on a per-user basis. I generate the address information (city, street, state, etc) based on the information returned by Google maps, and then save the lat/long information in the database as well. This prevents you from having to re-code things, and gives you nicely formatted addresses.
Another reason you want to do this is because there is a daily limit on the number of addresses that can be geocoded from a particular IP address. You don't want your application to fail for a person for that reason.
I'm facing the same problem trying to geocode 140 addresses.
My workaround was adding usleep(100000) for each loop of next geocoding request. If status of the request is OVER_QUERY_LIMIT, the usleep is increased by 50000 and request is repeated, and so on.
And of cause all received data (lat/long) are stored in XML file not to run request every time the page is loading.
EDIT:
Forgot to say that this solution is in pure js, the only thing you need is a browser that supports promises https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/Promise
For those who still needs to accomplish such, I've written my own solution that combines promises with timeouts.
Code:
/*
class: Geolocalizer
- Handles location triangulation and calculations.
-- Returns various prototypes to fetch position from strings or coords or dragons or whatever.
*/
var Geolocalizer = function () {
this.queue = []; // queue handler..
this.resolved = [];
this.geolocalizer = new google.maps.Geocoder();
};
Geolocalizer.prototype = {
/*
#fn: Localize
#scope: resolve single or multiple queued requests.
#params: <array> needles
#returns: <deferred> object
*/
Localize: function ( needles ) {
var that = this;
// Enqueue the needles.
for ( var i = 0; i < needles.length; i++ ) {
this.queue.push(needles[i]);
}
// return a promise and resolve it after every element have been fetched (either with success or failure), then reset the queue.
return new Promise (
function (resolve, reject) {
that.resolveQueueElements().then(function(resolved){
resolve(resolved);
that.queue = [];
that.resolved = [];
});
}
);
},
/*
#fn: resolveQueueElements
#scope: resolve queue elements.
#returns: <deferred> object (promise)
*/
resolveQueueElements: function (callback) {
var that = this;
return new Promise(
function(resolve, reject) {
// Loop the queue and resolve each element.
// Prevent QUERY_LIMIT by delaying actions by one second.
(function loopWithDelay(such, queue, i){
console.log("Attempting the resolution of " +queue[i-1]);
setTimeout(function(){
such.find(queue[i-1], function(res){
such.resolved.push(res);
});
if (--i) {
loopWithDelay(such,queue,i);
}
}, 1000);
})(that, that.queue, that.queue.length);
// Check every second if the queue has been cleared.
var it = setInterval(function(){
if (that.queue.length == that.resolved.length) {
resolve(that.resolved);
clearInterval(it);
}
}, 1000);
}
);
},
/*
#fn: find
#scope: resolve an address from string
#params: <string> s, <fn> Callback
*/
find: function (s, callback) {
this.geolocalizer.geocode({
"address": s
}, function(res, status){
if (status == google.maps.GeocoderStatus.OK) {
var r = {
originalString: s,
lat: res[0].geometry.location.lat(),
lng: res[0].geometry.location.lng()
};
callback(r);
}
else {
callback(undefined);
console.log(status);
console.log("could not locate " + s);
}
});
}
};
Please note that it's just a part of a bigger library I wrote to handle google maps stuff, hence comments may be confusing.
Usage is quite simple, the approach, however, is slightly different: instead of looping and resolving one address at a time, you will need to pass an array of addresses to the class and it will handle the search by itself, returning a promise which, when resolved, returns an array containing all the resolved (and unresolved) address.
Example:
var myAmazingGeo = new Geolocalizer();
var locations = ["Italy","California","Dragons are thugs...","China","Georgia"];
myAmazingGeo.Localize(locations).then(function(res){
console.log(res);
});
Console output:
Attempting the resolution of Georgia
Attempting the resolution of China
Attempting the resolution of Dragons are thugs...
Attempting the resolution of California
ZERO_RESULTS
could not locate Dragons are thugs...
Attempting the resolution of Italy
Object returned:
The whole magic happens here:
(function loopWithDelay(such, queue, i){
console.log("Attempting the resolution of " +queue[i-1]);
setTimeout(function(){
such.find(queue[i-1], function(res){
such.resolved.push(res);
});
if (--i) {
loopWithDelay(such,queue,i);
}
}, 750);
})(that, that.queue, that.queue.length);
Basically, it loops every item with a delay of 750 milliseconds between each of them, hence every 750 milliseconds an address is controlled.
I've made some further testings and I've found out that even at 700 milliseconds I was sometimes getting the QUERY_LIMIT error, while with 750 I haven't had any issue at all.
In any case, feel free to edit the 750 above if you feel you are safe by handling a lower delay.
Hope this helps someone in the near future ;)
I have just tested Google Geocoder and got the same problem as you have.
I noticed I only get the OVER_QUERY_LIMIT status once every 12 requests
So I wait for 1 second (that's the minimum delay to wait)
It slows down the application but less than waiting 1 second every request
info = getInfos(getLatLng(code)); //In here I call Google API
record(code, info);
generated++;
if(generated%interval == 0) {
holdOn(delay); // Every x requests, I sleep for 1 second
}
With the basic holdOn method :
private void holdOn(long delay) {
try {
Thread.sleep(delay);
} catch (InterruptedException ex) {
// ignore
}
}
Hope it helps
This worked well for me, after intermittent trial and error over the past couple days. I am using react instant-search-hooks via Algolia with Nextjs and Sanity for a new jobs site for a large company.
Postal Code is a facet for filtering/sorting/query matching that is defined in the algolia index. In another script file, I map out all of these facets (postal code, city, etc); Now that I have 100 returned files they can be mapped out by iterating through a mapped asynchronous import and the lat/lng coords matched to the corresponding zip codes defining a job posting (there are ~2500 postings but only ~100 zip codes to narrow down the coordinates of)
import * as dotenv from "dotenv";
dotenv.config();
import {
googleNetwork,
axiosConfig as googleAxiosConfig
} from "../utils/google-axios";
import JSONData from "../../public/data/postalCode/2022/05/26.json";
import fs from "fs";
import { join } from "path";
import type { GeneratedGeolocData } from "../types/algolia";
import { timezoneHelper } from "../utils/timezone-helper";
import { Unenumerate } from "../types/helpers";
let i = 0;
i < JSONData.postalCodes.facetHits.length;
i++;
const getGeoCode = (
record: Unenumerate<typeof JSONData.postalCodes.facetHits>
) =>
function () {
return JSONData.postalCodes.facetHits.map(async (data = record, u) => {
const googleBase = process.env.NEXT_PUBLIC_GOOGLE_MAPS_BASE_PATH ?? "";
const googleApiKey =
process.env.NEXT_PUBLIC_TAKEDA_JOBS_GOOGLE_SERVICES ?? "";
const params: (string | undefined)[][] = [
["address", data.value],
["key", googleApiKey]
];
const query = params
.reduce<string[]>((arr, [k, v]) => {
if (v) arr.push(`${k}=${encodeURIComponent(v)}`);
return arr;
}, [])
.join("&");
return await googleNetwork("GET")
.get(`${googleBase}geocode/json?${query}`, googleAxiosConfig)
.then(dat => {
const geoloc = dat.data as GeneratedGeolocData;
const {
[0]: Year,
[2]: Month,
[4]: Day
} = new Date(Date.now())
.toISOString()
.split(/(T)/)[0]
.split(/([-])/g);
const localizedTimestamp = timezoneHelper({
dateField: new Date(Date.now()),
timezone: "America/Chicago"
});
return setTimeout(
() =>
fs.appendFileSync(
join(
process.cwd(),
`public/data/geoloc/${Year}/${Month}/${Day}-${[i]}.json`
),
JSON.stringify(
{
generated: localizedTimestamp,
_geoloc: {
postalCode: data.value,
geolocation: geoloc
}
},
null,
2
)
),
1000
);
});
});
};
getGeoCode(JSONData.postalCodes.facetHits[i]);
It took a lot less time than anticipated -- under 4 seconds for 100 unique results to generate
Context on the Unenumerate type -- Unenumerate strips the internal repeating unit within an array:
type Unenumerate<T> = T extends Array<infer U> ? U : T;
I have an asynchronous loop that sends the index value to save the results. However, for some reason, the first time this item loops, it records the value properly. The second time it loops. it changes the value in the first loop and the value in the second loop, and so forth and so on. It seems to do this inconsistently depending on the input.
Below is the code
promises[address_key] = addresses[address_key].nearest.map(function(currentValue, charger_key) { // This loops through items creating promises
return new Promise(function(resolve) {
addresses[address_key].nearest[charger_key].directions = get_driving_directions(addresses[address_key].nearest[charger_key]);
addresses[address_key].nearest[charger_key].map_img = get_static_map_url(addresses[address_key].nearest[charger_key]);
get_driving_distance(address_key, charger_key, resolve); // this calls the get_driving_distance function below
});
});
/* more code here */
// Calls distance matrix from Google's api.
function get_driving_distance(address_key, charger_key, resolve) {
var distance_matrix = new google.maps.DistanceMatrixService();
distance_matrix.getDistanceMatrix( // calls distance matrix in Google API
{
origins: [addresses[address_key].address],
destinations: [new google.maps.LatLng(addresses[address_key].nearest[charger_key].latitude, addresses[address_key].nearest[charger_key].longitude)],
unitSystem: google.maps.UnitSystem.IMPERIAL,
travelMode: 'DRIVING'
}, process_distance_matrix(address_key, charger_key, resolve) // calls process_distance_matrix function sending over variables necessary.
);
}
// processes data from the distance matrix in the function get_driving_distance
function process_distance_matrix(address_key, charger_key, callback) {
return function(response, status) {
if (response.rows[0].elements[0].status == 'OK') {
console.log("response", response, 'status', status, 'address_key', address_key, 'charger_key', charger_key);
console.log("Records before:", addresses[0].nearest[0].distance, response.rows[0].elements[0].distance.text, 'address_key', address_key, 'charger_key', charger_key);
// Update the global variable with the distance data. This is recording data in wrong fields.
addresses[address_key].nearest[charger_key].distance = {
'text': response.rows[0].elements[0].distance.text,
'number': response.rows[0].elements[0].distance.value / 1609.344,
'over_limit': (max_driving_distance ? (response.rows[0].elements[0].distance.value / 1609.344 > max_driving_distance) : false)
};
console.log("Records after:", addresses[0].nearest[0].distance, response.rows[0].elements[0].distance.text, 'address_key', address_key, 'charger_key', charger_key);
} else {
display_error(address_key + ') ' + addresses[address_key].address + ' - Error getting driving distance');
addresses[address_key].errors.push('Error getting driving distance');
progress_status.error++;
}
callback();
}
}
The rest of the code is too much to post, so here is a link to the rest of the code: https://jsfiddle.net/RobertoMejia/cqyyLh27/
The original loop is a for loop on line 68. This loops through addresses and passes address_key to refer to the global object.
There is a second loop on line 183. It is a .map loop. This runs through chargers, and passes charger_key to refer to the global object.
Notice the console.logs in the middle of the function. Those are to show how the variable changes where it shouldn't. The display the object in question before and after the declaration each time. It also shows the address key and the charger key at the time of execution.
Any help would be appreciated.
I think the problem is here in process_addresses:
addresses[address_key].nearest = charger_data.sort( sort_by_closest( addresses[address_key].geo ) );
// Takes the top results based on the number of results wanted.
addresses[address_key].nearest = addresses[address_key].nearest.slice(0, $('#number_of_results').val() );
If the same charger is near multiple addresses, that charger will be in the nearest array for all of them. So when you add the driving directions to the charger from one address, you're replacing the directions from the previous address.
There are two solutions:
The simplest is to clone the charger objects before putting them into the nearest array.
addresses[address_key].nearest = addresses[address_key].nearest.slice(0, $('#number_of_results').val()).map(function(charger) {
return $.extend({}, charger);
});
Another way is to use a different object to hold the directions, and make the charger a property of it:
addresses[address_key].nearest = addresses[address_key].nearest.slice(0, $('#number_of_results').val()).map(function(charger) {
return { charger: charger };
});
This is an application of Wheeler's famous aphorism: "All problems in computer science can be solved by another level of indirection"
Modified fiddle (using the first solution)
Hello smarter than me people. ;)
I really cannot figure this weird behaviour of my code out.
What it is supposed to do is to execute the function fixedlocation() when a button is pressed in order to check if a valid address can be found and then execute some code.
In order to check if it works properly I have an alert in the code which gives me the state of the Boolean that should be changed. Now I really dont get why, but it only works from the second time the button is pressed onwards. Meaning that even if a valid address can be found, the first time the alert will pop up with a false...
At first I thought it must have something to do with where I defined the variable but then it couldnt work the second time could it?
Any help or pointing in the right direction would be much appreciated.
var geoinfobool=new Boolean();
function fixedlocation()
{
var addresscoordinates = new google.maps.LatLng(document.getElementById('addresslat').value,document.getElementById('addresslng').value)
geocoder.geocode({'latLng': addresscoordinates}, function(results, status)
{
if (status == google.maps.GeocoderStatus.OK)
{
geoinfoavailable(true);
//do other stuff in here//
}
else
{
geoinfoavailable(false);
//do other stuff in here//
}
});
alert(geoinfobool);
}
function geoinfoavailable(state)
{
geoinfobool = state;
}
In your code geocoder is not defined .
Try
var addresscoordinates = new google.maps.LatLng(document.getElementById('addresslat').value,document.getElementById('addresslng').value);
var geocoder = new google.maps.Geocoder();
geocoder.geocode({'latLng': addresscoordinates}, function(results, status)
{
I'm developing with Nokia Maps (a wonderful option I really love them) but I'm only able to get the location (latitude and longitude) with HTML5 but I can't the name where I am :/, maybe somebody could give an idea, how to do it, thank you very mach for your help.
Maps API for JavaScript 3.x
The current 3.x JavaScript API offers a thin wrapper around the REST Geocoder API. You need to make a ReverseGeocode search, and then extract the data from the Location object(s) found in the result.
A fully working reverse geocoding example can be found here, but the important bit (getting the address) can be see below:
function reverseGeocode(platform) {
var geocoder = platform.getGeocodingService(),
reverseGeocodingParameters = {
prox: '52.5309,13.3847,150', // Location
mode: 'retrieveAddresses',
maxresults: '1',
jsonattributes : 1
};
geocoder.reverseGeocode(
reverseGeocodingParameters,
function (result) {
var locations = result.response.view[0].result;
// ... etc.
},
function (error) {
alert('Ooops!');
}
);
}
Maps API for JavaScript 2.x (deprecated)
With the recently deprecated 2.x JavaScript API, again you need to make a ReverseGeocode search, and then extract the data from the Address object found in the result.
The code is a bit longer, but the important bit (getting the address) can be seen below:
// Function for receiving search results from places search and process them
var processResults = function (data, requestStatus, requestId) {
var i, len, locations, marker;
if (requestStatus == "OK") {
// The function findPlaces() and reverseGeoCode() of return results in slightly different formats
locations = data.results ? data.results.items : [data.location];
// We check that at least one location has been found
if (locations.length > 0) {
for (i = 0, len = locations.length; i < len; i++) {
alert(locations[i].address.street);
alert(locations[i].address.state);
}
} else {
alert("Your search produced no results!");
}
} else {
alert("The search request failed");
}
};
/* We perform a reverse geocode search request: translating a given
* latitude & longitude into an address
*/
var reverseGeoCodeTerm = new nokia.maps.geo.Coordinate(
52.53099,
13.38455
);
nokia.places.search.manager.reverseGeoCode({
latitude: reverseGeoCodeTerm.latitude,
longitude: reverseGeoCodeTerm.longitude,
onComplete: processResults
});
I wonder whether someone may be able to help me please.
This page allows users to filter (via checkboxes), markers which are placed on the map. Clicking on any marker performs a 'reverse geocode' action and in conjunction with selecting the 'Search Locations' button, the user can then see POI's within a given radius of the clicked marker.
For demo purposes, if you select the 'Coin' checkbox, click the green marker, then select the 'Search Locations' button, the marker will bounce and the right hand sidebar will be populated with POI's.
The problem I'm having is that in Internet Explorer everything works fine, but when I try to run this in Chrome, the marker looses the 'bounce' functionality, the 'reverse geocode' doesn't run and in the error console I receive the following error:
Uncaught ReferenceError: reversegeocode is not defined at line 55 of my code which is:
reversegeocode(); I've done some reading on this and other sites and from reading the guidance I've tried changing this part of my code to this:
function geocodePosition(pos) {
var clickListener =
document.getElementById('findosgb36lat').value = this.mylat;
document.getElementById('findosgb36lon').value = this.mylon;
document.getElementById('address').value = this.myaddress;
if(bouncingMarker)
bouncingMarker.setAnimation(null);
if(bouncingMarker != this) {
this.setAnimation(google.maps.Animation.BOUNCE);
bouncingMarker = this;
}
else bouncingMarker = null;
}
geocoder.geocode({latLng: pos }, function(responses) {
if (responses && responses.length > 0) {
updateMarkerAddress(responses[0].formatted_address);
} else {
updateMarkerAddress('Cannot determine address at this location.');
}
});
}
function updateMarkerAddress(str) {
document.getElementById('address').value = str;
}
function getAddress(latlng) {
if (!geocoder) {
geocoder = new google.maps.Geocoder();
}
geocoder.geocode({ 'latLng': latlng }, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
// Looping through the result
for (var i = 0; i < results.length; i++) {
if (results[0].formatted_address) {
document.getElementById('address').value =
results[0].formatted_address;
}
}
}
}
)
}
But unfortunately, this doesn't work and actually creates more problems with syntax errors.
I'm relatively new to Javascript, so perhaps I've totally misunderstood,. But I just wondered whether someone could possibly take a look at this please and let me know where I'm going wrong?
Many thanks and kind regards
Code feedback:
What is the second line in your code: var clickListener = meant to do (something is missing)?
Both of your calls to geocoder.geocode pass an object with a property named: latLng; the object passed should match the structure of google.maps.GeocoderRequestapi-doc, which has properties named: address, bounds, location, and region. I suggest you change the name of these properties from: latLng to location.
I'm not sure of the context of the code shown, but the usage of this in this code section looks suspicious:
if(bouncingMarker != this) {
this.setAnimation(google.maps.Animation.BOUNCE);
bouncingMarker = this;
}
It looks like you are within a global function, so it looks like the use of this may not make sense here?
That's all I see after a first pass; hope this helps you -