Reverse geolocate using lat/lon to get street address - javascript

I created a function making use of Googles Geocoder API to perform a reverse geolocation. It pulls lat/lon from the browser's native functions and sends them through to Google. The intent of the function is to get the approximate street address of the user (ie. 123 Fake Street). The City/Municipality, Province/State, Country and Post code are not wanted, just the street address.
function geoSuccess(pos) {
Geocoder = new google.maps.Geocoder;
Geocoder.geocode({location: {lat:pos.coords.latitude,lng:pos.coords.longitude}}, function(r, s) {
if (s === 'OK') {
var found = false,
x = 0;
do {
if (r[x].address_components[0].types.indexOf('route') !== -1) {
found = true;
}
x++;
} while(!found && x < r.length);
alert(r[x-1].address_components[0].long_name);
}
});
}
Based on how the function works, it scans through the resulting JSON to find the route. However, there is a discrepancy for mobile devices where the accuracy is different than desktops. Despite searching for route, frequently mobile devices will return either the wrong data (closest highway).

After looking at the comments made (and revising the question), the function was rewritten. Nested loops through the JSON were made to tally up all of the unique data from the query. From their, street address was manually recreated instead of relying on Google to provide the desired result.
function geoSuccess(pos) {
var Geocoder = new google.maps.Geocoder;
Geocoder.geocode({location: {lat:pos.coords.latitude,lng:pos.coords.longitude}}, function(r, s) {
if (s === 'OK') {
var addr = [];
r.each(function(o) {
o.address_components.each(function(a) {
addr[a.types.join('.')] = a.long_name
});
});
alert(addr['street_number']+' '+addr['route']);
}
});
}
So far this stands as the best way I found to get the data I wanted.

Related

Reverse place id using PlacesService

I am looking for a way to avoid being charged for
Places API Atmosphere Data
Places API Contact Data
I only need to reverse the place id to its formatted_address. Any idea about what changes I need to do in the code bellow?
Related question, although I am not using Autocomplete but PlacesService.
$('.company_location_id').each(function(i, obj) {
if ($(this).length > 0) {
if ($(this).val()) {
var request = {
placeId: $(this).val()
};
service = new google.maps.places.PlacesService(document.getElementsByClassName($(this).attr('class'))[0]);
service.getDetails(request, callback);
var new_text = $(this)
function callback(place, status) {
if (status == google.maps.places.PlacesServiceStatus.OK) {
$(new_text).next().text(place.formatted_address);
}
}
}
}
});
In order to avoid charges for Atmosphere Data and Contact Data using the PlacesService, you should specify a list of fields to be retrieved in the PlaceDetailsRequest object that you pass as a first parameter in getDetails() function.
Have a look at the documentation of PlaceDetailsRequest interface:
https://developers.google.com/maps/documentation/javascript/reference/places-service#PlaceDetailsRequest
As you can see this interface has fields property
fields - Fields to be included in the details response. For a list of fields see PlaceResult. Nested fields can be specified with dot-paths (for example, "geometry.location").
So, you should define request in your code as
var request = {
placeId: $(this).val(),
fields: ["formatted_address"]
};
I hope this helps!

How to delay a function like this? [duplicate]

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;

Leaflet Geocoder restrict results by Country

I am using the Leaflet plugin leaflet-control-geocoder found here: https://github.com/perliedman/leaflet-control-geocoder
My aim is to restrict the results by country = UK and city = london.
My current code which works fine but am getting results also from outside UK
var geocoder = L.Control.geocoder({position:'topleft', geocode:'countrycodes=gb'});
geocode.addTo(map);
The geocode uses Nominatim to respond to geocoding queries. Not sure why its not working
The way i have restrict country
var options = {
position: 'topright',
geocoder: new L.Control.Geocoder.nominatim({
geocodingQueryParams: {
"countrycodes": "gb"
}
})
};
var geocoder = L.Control.geocoder(options).addTo(map);
I believe that you need to write L.Control.Geocoder.nominatim({position:'topleft', geocode:'countrycodes=gb'}) to specify that you're using Nominatim.
That is what they do in the demo at this link: https://github.com/perliedman/leaflet-control-geocoder/blob/master/demo/index.html
If you use geocoder without nominatim(), then you can do the country filter as the following. The loops are ugly I know, it could be better solved with mapping to object. It's just for demonstration.
geocoder.query('pass-a-search-string-criteria', showResult);
function showResult(err, data) {
if (!map) {
map = L.mapbox.map('your-map-id', 'mapbox.streets');
}
if (data.lbounds) {
[].slice.call(data.results.features).forEach(function(place){
[].slice.call(place.context).some(function(ccode, i) {
if (i === 3 && ccode.short_code ==='gb') {
console.log(place.place_name);
}
});
});
map.fitBounds(data.lbounds);
} else if (data.latlng) {
map.setView([data.latlng[0], data.latlng[1]], 13);
}
}

How to get the name of a country or a city or a state with Nokia Maps?

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
});

What is causing this Google Maps marker click error in Chrome?

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 -

Categories

Resources