Why can't Google Sheets find reverseGeocode method from Maps API? - google-apps-script

I'm using a Google Sheets function to reverse geocode a list lat/long coordinates. It looks like this:
function getAdd(lat, lng) {
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);
for (var i = 0; i < response.results.length; i++) {
var result = response.results[i];
Utilities.sleep(1000);
return result.formatted_address;
}
};
Question 1: Why is Google Sheets giving me the following error: "Cannot find method reverseGeocode(object,(class))"?
Question 2: Once I fix that, how can fetch country names from the result array instead of the complete address from the results?

You're trying to return a result for each result in the response object. Instead you have to choose one:
function getAdd(lat, lng) {
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);
return response.results[0].formatted_address;
};
If you're looking for just the country, the format of the result object is here:
https://developers.google.com/maps/documentation/geocoding/#ReverseGeocoding
In that case, you should iterate through the results[0] object and test to see if the types includes "Country". If it does, select the results[0].address_components[i].short_name where i is your iterator. Or use long_name instead.

Ok, figured it out. Here's how I eventually got country from lat/long:
function getAdd(lat, lng) {
if (lat == "") {
return "Insert latitude."
}
if (lng == ""){
return "Insert longitude."
}
var response = Maps.newGeocoder().reverseGeocode(lat,lng);
Utilities.sleep(1000); //in order not to exeed api calls per second
for (var i in response.results) {
var result = response.results[i];
}
for (var j in result.address_components) {
for (var k in result.address_components[j].types) {
if (result.address_components[j].types[k] == "country") {
return result.address_components[j].long_name;
}
}
}
};

Thank you for posting this it just helped me a lot with what I was trying to do. Just a quick note that your first loop simply returns the last record and not all data is in the last record but the country seems to always be there. If anyone (like me) is looking for locality names you can simply choose the first record and more data is available to you.
I just changed:
for (var i in response.results) {
var result = response.results[i];
}
to:
var result = response.results[0];
Full Code returns the locality and country:
function getAdd(lat, lng) {
var response = Maps.newGeocoder().reverseGeocode(lat,lng);
var country="";
var place="";
Utilities.sleep(1000); //in order not to exeed api calls per second
var result = response.results[0];
for (var j in result.address_components) {
for (var k in result.address_components[j].types) {
if (result.address_components[j].types[k] == "country") {
country = result.address_components[j].long_name;
}
if(result.address_components[j].types[k] == "locality") {
place = result.address_components[j].long_name;
}
}
}
return [place, country];
};

Related

Google Places API, return a list of nearby places from a list of lat/longs

I am trying to print out a list of places around certain addresses (~200 addresses). However, when I run this code I only get place results for 6 addresses and then in the console I get the message 'Uncaught RangeError: Maximum call stack size exceeded'. I know that my lat and long values are formatted correctly, and set a TimeOut in case I was going over my query limit, does anyone have any ideas on how I can get responses for all 200 addresses? Thanks
function run(){
for (var i=100; i<latss.length;i ++){
var lat = data[i].Lats;
var long = data[i].Longs;
var latlng = new google.maps.LatLng(lat, long);
//var address= data[i].Address
GetJsonResults(latlng, address)
}
}
run();
function GetJsonResults(latlng){
var service = new google.maps.places.PlacesService(map);
service.nearbySearch({
location: latlng,
radius: 50,
type: ['type']
},
function callback(results, status) {
var GoodTypes= [];
if (status === google.maps.places.PlacesServiceStatus.OK) {
for (var i = 0; i < results.length; i++) {
var type =results[i].types;
var name= results[i].name
console.log(results[i])
}
}
else {setTimeout(run(), 200)}
})
}

GMap Api geocoder.getLatLng (address) function , Doesn't work good when call it within loop

I'm trying this recursion function to find LatLng of 2088 diffrent addresses and replay me with only about 180 results . although all addresses are valid on google maps website .
function test(i)
{
if(i >= jsArray.length){return;}
var geocoder = new GClientGeocoder();
geocoder.getLatLng(jsArray[i], function (current) {
return function(point) {
if (!point) {
data.push("null");
//nulls.push(myindex);
} else {
data.push(point);
//alert("done");
}
test(i+1,jsArray);
}
}(i));
}
test(0);
i have developed this recursive function but it's need about 30 mins till get good results ,
function test2(i)
{
geocoder.getLocations(jsArray[i], function (current) {
return function(response) {
if (!response || response.Status.code != 200) {
//alert(address + " not found");
//test(i,jsArray);
// data.push("null");
//nulls.push(myindex);
test2(i);
} else {
var len = response.Placemark[0];
point2 = new GLatLng(
len.Point.coordinates[1],
len.Point.coordinates[0]
);
data[i] = point2;
}
}
}(i));
}
for(i =0 ; i<=jsArray.length; i++)
{
if(i==jsArray.length){
alert(data.length);
/// $("#maintable").show(100) ;
/// $("#loading").hide(100) ;
}else{
test2(i);
}
}
i still need expert one to help me :) :D
The geocoder is asynchronous (which makes using it in loops problematic) and subject to a quota and rate limits. It is not intended for displaying lots of known addresses on a map, it is intended for user entered data.
You really should geocode your points off line, save the resulting coordinates and use those coordinates to display markers on your map.
If you are using it in a loop, you shouldn't use the getLatLng method, you should use the getLocations method, which contains a status code that will let you know why it is failing (G_GEO_TOO_MANY_QUERIES
= 620, would mean you could throttle your requests and potentially get a useful result)
// jsArray is array of addresses . the length of this array is 2087 element, all addresses got from google maps .
function MYFunction(i)
{
geocoder.getLocations(jsArray[i], function (current) {
return function(response) {
if (!response || response.Status.code != 200) {
test2(i); // recursive calling
} else {
var len = response.Placemark[0];
point2 = new GLatLng(
len.Point.coordinates[1],
len.Point.coordinates[0]
);
data[i] = point2;
}
}
}(i));
} /// end of y Function
//// loop for each address and pass it to MyFunction function and start recursive function .
for(i =0 ; i<=jsArray.length; i++)
{
MYFunction(i);
}

Country name from latitude and longitude

This question may look familiar: I have the latitude and longitude of a place. I need to get the name of country. I know I have to use reverse geo coding for this. But my problem is that sometimes it returns the short form of the area or country (for example US for United State or CA for California). Is there any way that I can get the full name of the country? I can't perform a match operation by this short forms with my prestored country database.
I have already gone through this, this. But it's not much help for my problem.
The geocoder response usually returns several results which include street corners, intersections, counties, and other alternate representation names. I found that results[0] is the best description.
The trick is searching for "country" in the results. Then the long_name can be retrieved.
Click on the map
function getCountry(latLng) {
geocoder.geocode( {'latLng': latLng},
function(results, status) {
if(status == google.maps.GeocoderStatus.OK) {
if(results[0]) {
for(var i = 0; i < results[0].address_components.length; i++) {
if(results[0].address_components[i].types[0] == "country") {
alert(results[0].address_components[i].long_name);
}
}
}
else {
alert("No results");
}
}
else {
alert("Status: " + status);
}
}
);
}
The JSON array normally includes both long_name and short_name. You should be able to extract both...
Here is an JSON/XML parser that works for both google street maps and open street maps.
(the only problem is that it requires a JSON or XML object as the "reply" its tested on version 3 google and 0.6 open street maps and it works good)
NOTE: it returns an object location.lat or location.lon you can also have it return whatever other field you want.
JSON.parse(text) // where text is the reply from google or open street maps
XML.parse(text) // you can make your own to convert the reply to XML or use regex to parse it. If someone has a regex version to parse the text reply that may also be helpful.
// Parser(ajax reply object, google/open, json/xml);
// takes the reply from google maps or open street maps and creates an object with location[lat/lon]
function Parser(reply, provider, type) {
var location = {};
if(reply != null) {
if(provider == "google") { // Google Street Maps
switch(type) {
case "xml":
location["lat"] = reply.getElementsByTagName("lat")[0].textContent;
location["lon"] = reply.getElementsByTagName("lng")[0].textContent;
break;
default: // json
location["lat"] = reply.results[0].geometry.location.lat;
location["lon"] = reply.results[0].geometry.location.lng;
}
}
else { // Open Street Maps
switch(type) {
case "xml":
location["lat"] = reply.getElementsByTagName("place")[0].getAttribute("lat");
location["lon"] = reply.getElementsByTagName("place")[0].getAttribute("lon");
break;
default: // json
location["lat"] = reply[0].lat;
location["lon"] = reply[0].lon;
}
}
}
return location;
}
function getLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function (position) {
$.post("https://maps.googleapis.com/maps/api/geocode/json?latlng=" + position.coords.latitude + "," + position.coords.longitude + "&sensor=false", function (result) {
for (var i = 0; i < result['results'][0]['address_components'].length; i++) {
if (result['results'][0]['address_components'][i]['types'][0] == "country") {
alert(result['results'][0]['address_components'][i]['long_name']);
}
}
});
});
}
}
getLocation();

Trying to create a function which extracts a URL from an array. JavaScript

So basically I would like to create a function that when alerted, returns the URL from an array (in this case the array is declared as 'websites'). The function has two parameters 'websites' and 'searchTerm'.
I'm struggling to make the function behave, so that when i type yahoo or google or bing in the searchTerm parameter for the function; I want it to return the corresponding URL.
Any help or support would be greatly appreciated.
Sorry if I have not made myself clear in my explanation, if this is the case, let me know and I will try and be clearer in my explanation.
Thanks in advance!
Try something more like:
var websites = {google: 'www.google.com', yahoo: 'www.yahoo.com'};
function filterURL(websites,searchTerm)
{
return websites[searchTerm] || 'www.defaultsearchwebstirehere.com';
}
** Update following comment **
Build up your websites object like so (where input is your array of key values seperated by pipe characters):
var websites = {};
for (var i = 0; i < input.length; i++) {
var siteToSearchTerm = input[i].split('|');
websites[siteToSearchTerm[1]] = siteToSearchTerm[0];
}
Here is how:
var websites = ["www.google.com|Google" , "www.yahoo.com|Yahoo" , "www.bing.com|Bing"];
function filterURL(websites,searchTerm)
{
for (var i = 0; i < websites.length; i++) {
if (websites[i].split('|')[1] === searchTerm) {
return websites[i].split('|')[0];
}
}
}
Working Example
You can also validate and improve function:
function filterURL(websites,searchTerm)
{
if (typeof websites != 'Array' || ! searchTerm) return false;
for (var i = 0; i < websites.length; i++) {
if (websites[i].split('|')[1] === searchTerm) {
return websites[i].split('|')[0];
}
}
return false;
}
Why not just use an object?
var websites = {
Google: 'www.google.com',
Yahoo: 'www.yahoo.com'
};
function filterURL(sites, searchTerm) {
if (sites[searchTerm]) {
return sites[searchTerm];
} else {
// What do you want to do when it can't be found?
}
}
alert(filterURL(websites, 'Google')); // alerts 'www.google.com'
You should really be using a hash-table like structure so that you don't have to search through the whole array every time. Something like this:
var websites = {
"Google": "www.google.com",
"Yahoo": "www.yahoo.com",
"Bing": "www.bing.com"
};
function filterURL(websites, searchTerm) {
if (websites[searchTerm] !== undefined)
return websites[searchTerm];
else
return null;
}
I'm not sure why you want to use an array for this, as what you're really doing fits a key-value pair better; however, here's how I'd do it:
function filterURL(websites, searchTerm) {
var i = 0,
parts;
for (i = 0; i < websites.length; i++) {
parts = websites[i].split("|");
if (parts[1].toLowerCase() === searchTerm) {
return parts[0];
}
}
}
But consider if you used a proper JavaScript Object instead:
var websites = {
Google: "www.google.com",
Yahoo: "www.yahoo.com",
Bing: "www.bing.com"
}
// Now it's much simpler:
function filterURL(websites, searchTerm) {
// key has first letter capitalized…
return websites[searchTerm.charAt(0).toUpperCase() + searchTerm.slice(1).toLowerCase()];
}

Retrieving Postal Code with Google Maps Javascript API V3 Reverse Geocode

I'm trying to submit a query using the postal code to my DB whenever the googlemaps viewport center changes. I know that this can be done with reverse geocoding with something like:
google.maps.event.addListener(map, 'center_changed', function(){
newCenter();
});
...
function newCenter(){
var newc = map.getCenter();
geocoder.geocode({'latLng': newc}, function(results, status){
if (status == google.maps.GeocoderStatus.OK) {
var newzip = results[0].address_components['postal_code'];
}
});
};
Of course, this code doesn't actually work. So I was wondering how I would need to change this in order to extract the postal code from the results array.
Thanks
What I've realized so far is that in most cases the ZIPCODE is always the last value inside each returned address, so, if you want to retrieve the very first zipcode (this is my case), you can use the following approach:
var address = results[0].address_components;
var zipcode = address[address.length - 1].long_name;
You can do this pretty easily using the underscore.js libraray: http://documentcloud.github.com/underscore/#find
_.find(results[0].address_components, function (ac) { return ac.types[0] == 'postal_code' }).short_name
Using JQuery?
var searchAddressComponents = results[0].address_components,
searchPostalCode="";
$.each(searchAddressComponents, function(){
if(this.types[0]=="postal_code"){
searchPostalCode=this.short_name;
}
});
short_name or long_name will work above
the "searchPostalCode" var will contain the postal (zip?) code IF
and only IF you get one from the Google Maps API.
Sometimes you DO NOT get a "postal_code" in return for your query.
Alright, so I got it. The solution is a little uglier than I'd like, and I probably don't need the last for loop, but here's the code for anyone else who needs to extract crap from address_components[]. This is inside the geocoder callback function
// make sure to initialize i
for(i=0; i < results.length; i++){
for(var j=0;j < results[i].address_components.length; j++){
for(var k=0; k < results[i].address_components[j].types.length; k++){
if(results[i].address_components[j].types[k] == "postal_code"){
zipcode = results[i].address_components[j].short_name;
}
}
}
}
$.each(results[0].address_components,function(index,value){
if(value.types[0] === "postal_code"){
$('#postal_code').val(value.long_name);
}
});
You can also use JavaScript .find method which is similar to underscore _.find method but it is native and require no extra dependency.
const zip_code = results[0].address_components.find(addr => addr.types[0] === "postal_code").short_name;
This takes only two for loops. The "results" array gets updated once we found the first "type" to be "postal_code".
It then updates the original array with the newly found array set and loops again.
var i, j,
result, types;
// Loop through the Geocoder result set. Note that the results
// array will change as this loop can self iterate.
for (i = 0; i < results.length; i++) {
result = results[i];
types = result.types;
for (j = 0; j < types.length; j++) {
if (types[j] === 'postal_code') {
// If we haven't found the "long_name" property,
// then we need to take this object and iterate through
// it again by setting it to our master loops array and
// setting the index to -1
if (result.long_name === undefined) {
results = result.address_components;
i = -1;
}
// We've found it!
else {
postcode = result.long_name;
}
break;
}
}
}
You can also use this code, this function will help to get zip on button click or onblur or keyup or keydown.
Just pass the address to this function.
use google api with valid key and sensor option removed as it doesn't required now.
function callZipAPI(addSearchZip)
{
var geocoder = new google.maps.Geocoder();
var zipCode = null;
geocoder.geocode({ 'address': addSearchZip }, function (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
//var latitude = results[0].geometry.location.lat();
//var longitude = results[0].geometry.location.lng();
var addressComponent = results[0].address_components;
for (var x = 0 ; x < addressComponent.length; x++) {
var chk = addressComponent[x];
if (chk.types[0] == 'postal_code') {
zipCode = chk.long_name;
}
}
if (zipCode) {
alert(zipCode);
}
else {
alert('No result found!!');
}
} else {
alert('Enter proper address!!');
}
});
}
I use this code to get "Postal code" and "locality", but you can use it to get any other field just changing the value of type:
JAVASCRIPT
var address = results[0].address_components;
var zipcode = '';
var locality = '';
for (var i = 0; i < address.length; i++) {
if (address[i].types.includes("postal_code")){ zipcode = address[i].short_name; }
if (address[i].types.includes("locality")){ locality = address[i].short_name; }
}
I think rather than depending on the index it better checks address type key inside the component. I solved this issue by using a switch case.
var address = '';
var pin = '';
var country = '';
var state = '';
var city = '';
var streetNumber = '';
var route ='';
var place = autocomplete.getPlace();
for (var i = 0; i < place.address_components.length; i++) {
var component = place.address_components[i];
var addressType = component.types[0];
switch (addressType) {
case 'street_number':
streetNumber = component.long_name;
break;
case 'route':
route = component.short_name;
break;
case 'locality':
city = component.long_name;
break;
case 'administrative_area_level_1':
state = component.long_name;
break;
case 'postal_code':
pin = component.long_name;
break;
case 'country':
country = component.long_name;
break;
}
}
places.getDetails( request_details, function(results_details, status){
// Check if the Service is OK
if (status == google.maps.places.PlacesServiceStatus.OK) {
places_postal = results_details.address_components
places_phone = results_details.formatted_phone_number
places_phone_int = results_details.international_phone_number
places_format_address = results_details.formatted_address
places_google_url = results_details.url
places_website = results_details.website
places_rating = results_details.rating
for (var i = 0; i < places_postal.length; i++ ) {
if (places_postal[i].types == "postal_code"){
console.log(places_postal[i].long_name)
}
}
}
});
This seems to work very well for me, this is with the new Google Maps API V3. If this helps anyone, write a comment, i'm writing my script as we speak... so it might change.
Using JSONPath, it's easily done with one line of code:
var zip = $.results[0].address_components[?(#.types=="postal_code")].long_name;
In PHP I use this code. Almost in every conditions it works.
$zip = $data["results"][3]["address_components"];
$zip = $index[0]["short_name"];
Romaine M. — thanks! If you just need to find the postal code in the first returned result from Google, you can do just 2 loops:
for(var j=0;j < results[0].address_components.length; j++){
for(var k=0; k < results[0].address_components[j].types.length; k++){
if(results[0].address_components[j].types[k] == "postal_code"){
zipcode = results[0].address_components[j].long_name;
}
}
}
In a word, that's a lot of effort. At least with the v2 API, I could retrieve those details thusly:
var place = response.Placemark[0];
var point = new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]);
myAddress = place.AddressDetails.Country.AdministrativeArea.SubAdministrativeArea.Locality.Thoroughfare.ThoroughfareName
myCity = place.AddressDetails.Country.AdministrativeArea.SubAdministrativeArea.Locality.LocalityName
myState = place.AddressDetails.Country.AdministrativeArea.AdministrativeAreaName
myZipCode = place.AddressDetails.Country.AdministrativeArea.SubAdministrativeArea.Locality.PostalCode.PostalCodeNumber
There has got to be a more elegant way to retrieve individual address_components without going through the looping jujitsu you just went through.
This simple code works for me
for (var i = 0; i < address.length; i++) {
alert(address[i].types);
if (address[i].types == "postal_code")
$('#postalCode').val(address[i].long_name);
if (address[i].types == "")
$('#country').val(address[i].short_name);
}
Using Jquery
You cant be sure in which location in the address_components array the postal code is stored. Sometimes in address_components.length - 1 > pincode may not be there. This is true in "Address to latlng" geocoding.
You can be sure that Postal code will contain a "postal_code" string. So best way is to check for that.
var postalObject = $.grep(results[0].address_components, function(n, i) {
if (n.types[0] == "postal_code") {
return n;
} else {
return null;
}
});
$scope.query.Pincode = postalObject[0].long_name;
return $http.get('//maps.googleapis.com/maps/api/geocode/json', {
params: {
address: val,
sensor: false
}
}).then(function (response) {
var model= response.data.results.map(function (item) {
// return item.address_components[0].short_name;
var short_name;
var st= $.each(item.address_components, function (value, key) {
if (key.types[0] == "postal_code") {
short_name= key.short_name;
}
});
return short_name;
});
return model;
});
//autocomplete is the text box where u will get the suggestions for an address.
autocomplete.addListener('place_changed', function () {
//Place will get the selected place geocode and returns with the address
//and marker information.
var place = autocomplete.getPlace();
//To select just the zip code of complete address from marker, below loop //will help to find. Instead of y.long_name you can also use y.short_name.
var zipCode = null;
for (var x = 0 ; x < place.address_components.length; x++) {
var y = place.address_components[x];
if (y.types[0] == 'postal_code') {
zipCode = y.long_name;
}
}
});
It seems that nowadays it's better to get it from the restful API, simply try:
https://maps.googleapis.com/maps/api/geocode/json?latlng=40.714224,-73.961452&key=YOUR_KEY_HERE
Using an AJAX GET call works perfect!
Something like:
var your_api_key = "***";
var f_center_lat = 40.714224;
var f_center_lon = -73.961452;
$.ajax({ url: "https://maps.googleapis.com/maps/api/geocode/json?latlng="+f_center_lat+","+f_center_lon+"&key="+your_api_key,
method: "GET"
})
.done(function( res ) { if (debug) console.log("Ajax result:"); console.log(res);
var zipCode = null;
var addressComponent = res.results[0].address_components;
for (var x = 0 ; x < addressComponent.length; x++) {
var chk = addressComponent[x];
if (chk.types[0] == 'postal_code') {
zipCode = chk.long_name;
}
}
if (zipCode) {
//alert(zipCode);
$(current_map_form + " #postalcode").val(zipCode);
}
else {
//alert('No result found!!');
if (debug) console.log("Zip/postal code not found for this map location.")
}
})
.fail(function( jqXHR, textStatus ) {
console.log( "Request failed (get postal code via geocoder rest api). Msg: " + textStatus );
});
As I got it zip is the last or the one that before last.
That why this is my solution
const getZip = function (arr) {
return (arr[arr.length - 1].types[0] === 'postal_code') ? arr[arr.length - 1].long_name : arr[arr.length - 2].long_name;
};
const zip = getZip(place.address_components);
i think this is the most accurate solution:
zipCode: result.address_components.find(item => item.types[0] === 'postal_code').long_name;
Just search for postal_code in all types, and return when found.
const address_components = [{"long_name": "2b","short_name": "2b","types": ["street_number"]}, { "long_name": "Louis Schuermanstraat","short_name": "Louis Schuermanstraat", "types": ["route"]},{"long_name": "Gent","short_name": "Gent","types": ["locality","political" ]},{"long_name": "Oost-Vlaanderen","short_name": "OV","types": ["administrative_area_level_2","political"]},{"long_name": "Vlaanderen","short_name": "Vlaanderen","types": ["administrative_area_level_1","political"]},{"long_name": "België","short_name": "BE","types": ["country","political"]},{"long_name": "9040","short_name": "9040","types": ["postal_code"]}];
// address_components = results[0]address_components
console.log({
'object': getByGeoType(address_components),
'short_name': getByGeoType(address_components).short_name,
'long_name': getByGeoType(address_components).long_name,
'route': getByGeoType(address_components, ['route']).long_name,
'place': getByGeoType(address_components, ['locality', 'political']).long_name
});
function getByGeoType(components, type = ['postal_code']) {
let result = null;
$.each(components,
function() {
if (this.types.some(r => type.indexOf(r) >= 0)) {
result = this;
return false;
}
});
return result;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>