25

I have the following code to parse the country when the autocomplete list is selected:

$('#spot_address').autocomplete({ // This bit uses the geocoder to fetch address values source: function(request, response) { geocoder.geocode( {'address': request.term }, function(results, status) { // Get address_components for (var i = 0; i < results[0].address_components.length; i++) { var addr = results[0].address_components[i]; var getCountry; if (addr.types[0] == 'country') getCountry = addr.long_name; } response($.map(results, function(item) { return { label: item.formatted_address, value: item.formatted_address, latitude: item.geometry.location.lat(), longitude: item.geometry.location.lng(), country: getCountry } })); }) }, // This bit is executed upon selection of an address select: function(event, ui) { // Get values $('#spot_country').val(ui.item.country); $('#spot_lat').val(ui.item.latitude); $('#spot_lng').val(ui.item.longitude); var location = new google.maps.LatLng(ui.item.latitude, ui.item.longitude); marker.setPosition(location); map.setCenter(location); }, // Changes the current marker when autocomplete dropdown list is focused focus: function(event, ui) { var location = new google.maps.LatLng(ui.item.latitude, ui.item.longitude); marker.setPosition(location); map.setCenter(location); } }); 

However, the code above doesn't work, and when the country is parsed, only the first result of the autocomplete is parsed no matter what, which is significant with the array results[0] because it only fetches the first result.

I tried to move it to the select function, but ui in select only contains formatted_address, longitude and latitude, but not the address_components.

What must I do to send the correct country when the autocomplete list item is selected?

Many thanks.

15 Answers 15

86

General solution:

var address_components = results[0].address_components; var components={}; jQuery.each(address_components, function(k,v1) {jQuery.each(v1.types, function(k2, v2){components[v2]=v1.long_name});}); 

Now your components looks like this:

street_number: "1100", route: "E Hector St", locality: "Conshohocken", political: "United States", administrative_area_level_3: "Whitemarsh"… administrative_area_level_1: "Pennsylvania" administrative_area_level_2: "Montgomery" administrative_area_level_3: "Whitemarsh" country: "United States" locality: "Conshohocken" political: "United States" postal_code: "19428" route: "E Hector St" street_number: "1100" 

Which you can query like this:

components.country 
Sign up to request clarification or add additional context in comments.

7 Comments

Nice one - in case anyone doesn't know that first line should be var address_components = results[0].address_components;
+1, simple but useful in most cases. Note that some info may be lost in the process (for example, when you have two components of types ['country', 'political'] and ['locality', 'political'], political gets overwritten)
Simple and brilliant
Great snippet. Will definitely use again. Is there a name for this pattern?
@plushyObject Maybe this is the visitor pattern
|
13

Here is my solution in typescript

interface AddressComponent { long_name: string; short_name: string; types: Array<string>; } interface Address { street_number?: string; street_name?: string; city?: string; state?: string; country?: string; postal_code?: string; } export class GoogleAddressParser { private address: Address = {}; constructor(private address_components: Array<AddressComponent>) { this.parseAddress(); } private parseAddress() { if (!Array.isArray(this.address_components)) { throw Error('Address Components is not an array'); } if (!this.address_components.length) { throw Error('Address Components is empty'); } for (let i = 0; i < this.address_components.length; i++) { const component: AddressComponent = this.address_components[i]; if (this.isStreetNumber(component)) { this.address.street_number = component.long_name; } if (this.isStreetName(component)) { this.address.street_name = component.long_name; } if (this.isCity(component)) { this.address.city = component.long_name; } if (this.isCountry(component)) { this.address.country = component.long_name; } if (this.isState(component)) { this.address.state = component.long_name; } if (this.isPostalCode(component)) { this.address.postal_code = component.long_name; } } } private isStreetNumber(component: AddressComponent): boolean { return component.types.includes('street_number'); } private isStreetName(component: AddressComponent): boolean { return component.types.includes('route'); } private isCity(component): boolean { return component.types.includes('locality'); } private isState(component): boolean { return component.types.includes('administrative_area_level_1'); } private isCountry(component): boolean { return component.types.includes('country'); } private isPostalCode(component): boolean { return component.types.includes('postal_code'); } result(): Address { return this.address; } } 

Usage:

const address = new GoogleAddressParser(results[0].address_components).result(); 

Comments

10

Here's an ES6 and jQuery-less solution (based on William Entriken's post), making use of the native reduce function and destructuring assignment syntax to unpack properties from objects, into distinct variables:

const address = address_components.reduce((seed, { long_name, types }) => { types.forEach(t => { seed[t] = long_name; }); return seed; }, {}); 

Or, the one-liner version (for what it's worth):

const address = address_components.reduce((seed, { long_name, types }) => (types.forEach(t => seed[t] = long_name), seed), {}); 

Which you can then use like:

address.street_number address.city 

Comments

8

Below is the full working code:

$('#spot_address').autocomplete({ // This bit uses the geocoder to fetch address values source: function(request, response) { geocoder.geocode( {'address': request.term }, function(results, status) { response($.map(results, function(item) { // Get address_components for (var i = 0; i < item.address_components.length; i++) { var addr = item.address_components[i]; var getCountry; if (addr.types[0] == 'country') getCountry = addr.long_name; } return { label: item.formatted_address, value: item.formatted_address, latitude: item.geometry.location.lat(), longitude: item.geometry.location.lng(), country: getCountry } })); }) }, // This bit is executed upon selection of an address select: function(event, ui) { // Get values $('#spot_country').val(ui.item.country); $('#spot_lat').val(ui.item.latitude); $('#spot_lng').val(ui.item.longitude); var location = new google.maps.LatLng(ui.item.latitude, ui.item.longitude); marker.setPosition(location); map.setCenter(location); }, // Changes the current marker when autocomplete dropdown list is focused focus: function(event, ui) { var location = new google.maps.LatLng(ui.item.latitude, ui.item.longitude); marker.setPosition(location); map.setCenter(location); } }); 

Comments

7

Here I made my own solution, as I wanted to get the city name and there may be more than one format for this, for example, the city name in some regions can be under the name of

 (locality, sublocality , sublocality_level_1, sublocality_level_2, sublocality_level_3 or sublocality_level_4) 

so I made this function

getAddressObject(address_components) { var ShouldBeComponent = { home: ["street_number"], postal_code: ["postal_code"], street: ["street_address", "route"], region: [ "administrative_area_level_1", "administrative_area_level_2", "administrative_area_level_3", "administrative_area_level_4", "administrative_area_level_5" ], city: [ "locality", "sublocality", "sublocality_level_1", "sublocality_level_2", "sublocality_level_3", "sublocality_level_4" ], country: ["country"] }; var address = { home: "", postal_code: "", street: "", region: "", city: "", country: "" }; address_components.forEach(component => { for (var shouldBe in ShouldBeComponent) { if (ShouldBeComponent[shouldBe].indexOf(component.types[0]) !== -1) { address[shouldBe] = component.long_name; } } }); console.log(address); return address; } 

Comments

5

Elaborating on @Full Decent's answer here a version for lodash:

_.each(address_components, function(k, v1) { _.each(address_components[v1].types, function(k2, v2){ components[address_components[v1].types[v2]] = address_components[v1].long_name }); }); 

Comments

3

In an AngularJS controller, it could be like this:

function NewController() { var vm = this; vm.address = null; vm.placeService = null; activate(); function activate() { vm.placeService = new google.maps.places.PlacesService(document.getElementById("map")); } function getAddressComponent(address, component, type) { var element = null; angular.forEach(address.address_components, function (address_component) { if (address_component.types[0] == component) { element = (type == 'short') ? address_component.short_name : address_component.long_name; } }); return element; } function getAddressDetail(addressId) { var request = { placeId: addressId }; vm.placeService.getDetails(request, function(place, status) { if (status == google.maps.places.PlacesServiceStatus.OK) { vm.address = { countryCode: getAddressComponent(place, 'country', 'short'), countryName: getAddressComponent(place, 'country', 'long'), cityCode: getAddressComponent(place, 'locality', 'short'), cityName: getAddressComponent(place, 'locality', 'long'), postalCode: getAddressComponent(place, 'postal_code', 'short'), streetNumber: getAddressComponent(place, 'street_number', 'short') }; console.log(vm.address); } }); } } 

Comments

1

This worked for me, in AngularJS;

// Function converts GPS co-ordinates to a locality name function showLocation(LatLng) { geocoder.geocode({'latLng': LatLng}, function (results, status) { if (status == google.maps.GeocoderStatus.OK) { console.log(results[0]); var myLocation; for (var i = 0; i < results[0].address_components.length; i++) { var addr = results[0].address_components[i]; var getCountry; var getAdministrative; var getLocality; if (addr.types[0] == 'locality') { getLocality = addr.long_name; console.log(getLocality); myLocation = getLocality+ ', '; } if (addr.types[0] == 'administrative_area_level_1') { getAdministrative = addr.long_name; console.log(getAdministrative); myLocation += getAdministrative + ', '; } if (addr.types[0] == 'country') { getCountry = addr.long_name; console.log(getCountry); myLocation += getCountry; } } $scope.locality = myLocation; console.log(myLocation); } }) }; 

Comments

1

Here is the same function as Full Decent's, but written for AngularJS:

function getAddressComponentByPlace(place) { var components; components = {}; angular.forEach(place.address_components, function(address_component) { angular.forEach(address_component.types, function(type) { components[type] = address_component.long_name; }); }); return components; } 

Comments

1

I've been using the following approach:

var city = getAddressComponent(place, 'locality', 'long_name'); var state = getAddressComponent(place, 'administrative_area_level_1', 'short_name'); var postalCode = getAddressComponent(place, 'postal_code', 'short_name'); var country = getAddressComponent(place, 'country', 'long_name'); function getAddressComponent(place, componentName, property) { var comps = place.address_components.filter(function(component) { return component.types.indexOf(componentName) !== -1; }); if(comps && comps.length && comps[0] && comps[0][property]) { return comps[0][property]; } else { return null; } } 

Comments

1

We can also extract country id and state code with this -

 const address_components = results[0].address_components; let components = {}; address_components.map((value, index) => { value.types.map((value2, index2) => { components[value2] = value.long_name; if (value2==='country') components.country_id = value.short_name; if (value2==='administrative_area_level_1') components.state_code = value.short_name; }) }) 

Returns this object -

{ administrative_area_level_1: "California" administrative_area_level_2: "Santa Clara County" country: "United States" country_id: "US" locality: "Mountain View" political: "United States" postal_code: "94043" route: "Amphitheatre Parkway" state_code: "CA" street_number: "1600" } 

Comments

1
function parseAddress(place) { const address = {} place.address_components.forEach(component => { let { long_name, types } = component if (types.includes('street_number')) { address.streetNumber = long_name } else if (types.includes('route')) { address.street = long_name } else if (types.includes('neighborhood')) { address.neighborhood = long_name } else if (types.includes('locality')) { address.city = long_name } else if (types.includes('administrative_area_level_2')) { address.county = long_name } else if (types.includes('administrative_area_level_1')) { address.state = long_name } else if (types.includes('country')) { address.country = long_name } else if (types.includes('postal_code')) { address.zip = long_name } }) return address } 

Comments

0

I think you need to split your response handler into a new function.

 source: function(request, response) { geocoder.geocode( {'address': request.term }, function(results, status) { // Get address_components for (var i = 0; i < results[0].address_components.length; i++) { var addr = results[0].address_components[i]; var getCountry; if (addr.types[0] == 'country') getCountry = addr.long_name; } response($.map(results, function(item) { getDetails(item); })); }) }, 

Move this outside of the .autocomplete function:

function getDetails(item) { return { label: item.formatted_address, value: item.formatted_address, latitude: item.geometry.location.lat(), longitude: item.geometry.location.lng(), country: getCountry } } 

Comments

0

Country is always last in array that is returned from Geocoder.

Here is my solution -

 geocoder.geocode( {'address': request.term }, function(results, status) { var location_country = results[0].address_components[results[0].address_components.length - 1].long_name; }); 

Hope this helps.

Comments

0

The solution no one asked for: @William Entriken's solution in PHP.

 $address_components = $geocodeResult[0]['address_components']; $components = []; foreach($address_components as $k1 => $v1) { foreach($v1['types'] as $k2 => $v2) { $components[$v2] = $v1['long_name']; } } dd($components); 
array:8 [ "street_number" => "78" "route" => "New Oxford Street" "postal_town" => "London" "administrative_area_level_2" => "Greater London" "political" => "United Kingdom" "administrative_area_level_1" => "England" "country" => "United Kingdom" "postal_code" => "WC1A 1HB" ] 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.