24

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
7
  • 1
    Nice one - in case anyone doesn't know that first line should be var address_components = results[0].address_components; Commented Feb 19, 2014 at 1:59
  • 4
    +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)
    – m_x
    Commented Jan 20, 2015 at 13:40
  • Simple and brilliant
    – ChrisRich
    Commented May 21, 2016 at 6:10
  • Great snippet. Will definitely use again. Is there a name for this pattern? Commented Jan 4, 2017 at 3:37
  • @plushyObject Maybe this is the visitor pattern Commented Jan 4, 2017 at 3:40
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();
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
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);
  }
});
0
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;
}
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
    });
});
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);
      }
    });
  }
}
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);
        }
    })
};
0
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;
}
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;
  }
}
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"
}
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
            }
          }
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.

0
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
}
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"
]

Not the answer you're looking for? Browse other questions tagged or ask your own question.