Address Validation using the Google Maps API

I’ve never been a fan of address forms that look like this;

Bad street address form design

From a data entry perspective, it forces users to hit TAB constantly.  Furthermore, users who are less web savvy will transition between the mouse and the keyboard to negotiate these fields.  It’s not a natural way to type an address.

By contrast, here is a human friendly address form:

Good street address form design

While typing an address into this field, users can stay on the keyboard and simply type the address in a way that feels natural.  The problem [for programmers] is parsing and validating this raw text once the form has been submitted.

Client-side address validation using the Google Maps Geocode API

Converting raw text to a valid address

Overcoming this challenge was part of a recent project.  Thankfully, Google Maps does a great job of validating addresses.  Google has made these validation & geocoding services through an API.

My plan was to tackle this challenge with the following 3 ingredients:

  1. jQuery
  2. jQuery Validator
  3. Google Maps API

On paper, this appeared relatively straight-forward.  However, in practice I encountered several hurdles.  Below I’ve detailed how I overcame these hurdles.

Note: This is the 2nd version of this blog post.  In my original blog post, I described how to use a server-side proxy service to relay results from Google’s geocode web service.  Thor Mitchell, who is the Google Maps API product manager, posted a comment telling me that the proxy technique is unnecessarily difficult.  In addition, this technique is against Google’s Terms of Service and vulnerable to quota exhaustion.  The technique I describe below is in accordance best practices and the Terms of Service.

Creating the web form

To get started, I’ll create the simple web form shown in the screenshot above:

<form id="MyForm" name="MyForm" action="form.html">
    <div id="map_canvas" style="float: right; height: 200px; width: 400px;"></div>
    <div>
        <label for="Name">Name</label>
        <input id="Name" name="Name" type="text" />
    </div>
    <div>
        <label for="FullAddress">Address</label>
        <textarea id="FullAddress" name="FullAddress" cols="40" rows="5" class="fulladdressvalidator"></textarea>
    </div>
    <div>
        <input id="Submit" name="Submit" value="Submit" type="button" />
    </div>
</form>

Note, the Google Maps API Terms of Service requires that a map be displayed in conjunction with any Google Maps web services utilized.  In other words, you are not allowed to geocode an address using the Google Maps API and not display a Google map.  This is why I’ve included the map_canvas in the code above.

Creating an address jQuery Validator

Next, I’ll create a custom jQuery validator that performs the following tasks:

  1. Geocodes the address using the Google Maps API
  2. Return a true/false value indicating whether the address is valid

This seems simple, but it involves an asynchronous call to the Google Maps service.  This means processing continues after the initial request is made.  Before the result is received the validator needs to return a true/false value indicating whether the address is valid or not.

To overcome this challenge, I added an IsChecking flag using jQuery’s data method.  This flag is set to true when the request is sent to the Google Maps API.  When a form is submitted I’m able to check this flag to see whether the field has truly been validated.

Here is the code for this jQuery full address validator:

// FullAddress jQuery Validator
function FullAddressValidator(value, element, paras) {

    // Convert the value variable into something a bit more descriptive
    var CurrentAddress = value;

    // If the address is blank, then this is for the required validator to deal with.
    if (value.length == 0) {
        return true;
    }

    // If we've already validated this address, then just return the previous result
    if ($(element).data("LastAddressValidated") == CurrentAddress) {
        return $(element).data("IsValid");
    }

    // We have a new address to validate, set the IsChecking flag to true and set the LastAddressValidated to the CurrentAddress
    $(element).data("IsChecking", true);
    $(element).data("LastAddressValidated", CurrentAddress);

    // Google Maps doesn't like line-breaks, remove them
    CurrentAddress = CurrentAddress.replace(/\n/g, "");

    // Create a new Google geocoder
    var geocoder = new google.maps.Geocoder();
    geocoder.geocode({ 'address': CurrentAddress }, function (results, status) {

        // The code below only gets run after a successful Google service call has completed.
        // Because this is an asynchronous call, the validator has already returned a 'true' result
        // to supress an error message and then cancelled the form submission.  The code below
        // needs to fetch the true validation from the Google service and then re-execute the
        // jQuery form validator to display the error message.  Futhermore, if the form was
        // being submitted, the code below needs to resume that submit.

        // Google reported a valid geocoded address
        if (status == google.maps.GeocoderStatus.OK) {

            // Get the formatted Google result
            var address = results[0].formatted_address;

            // Count the commas in the fomatted address.
            // This doesn't look great, but it helps us understand how specific the geocoded address
            // is.  For example, "CA" will geocde to "California, USA".
            numCommas = address.match(/,/g).length;

            // A full street address will have at least 3 commas.  Alternate techniques involve
            // fetching the address_components returned by Google Maps.  That code looks even more ugly.
            if (numCommas >= 3) {

                // Replace the first comma found with a line-break
                address = address.replace(/, /, "\n");

                // Remove USA from the address (remove this, if this is important to you)
                address = address.replace(/, USA$/, "");

                // Check for the map_canvas, if it exists then position the Google Map
                if ($("#map_canvas").exists()) {
                    $("#map_canvas").show();
                    Map("map_canvas", results[0].geometry.location);
                }

                // Set the textarea value to the geocoded address
                $(element).val(address);

                // Cache this latest result
                $(element).data("LastAddressValidated", address);

                // We have a valid geocoded address
                $(element).data("IsValid", true);
            } else {
                // Google Maps was able to geocode the address, but it wasn't specific
                // enough (not enough commas) to be a valid street address.
                $(element).data("IsValid", false);
            }

            // Otherwise the address is invalid
        } else {
            $(element).data("IsValid", false);
        }

        // We're no longer in the midst of validating
        $(element).data("IsChecking", false);

        // Get the parent form element for this address field
        var form = $(element).parents('form:first');

        // This code is being run after the validation for this field,
        // if the form was being submitted before this validtor was
        // called then we need to re-submit the form.
        if ($(element).data("SubmitForm") == true) {
            form.submit();
        } else {
            // Re-validate this property so we can return the result.
            form.validate().element(element);
        }
    });

    // The FullAddress validator always returns 'true' when initially called.
    // The true result will be return later by the geocode function (above)
    return true;
}

// Define a new jQuery Validator method
$.validator.addMethod("fulladdress", FullAddressValidator);

Putting it all together

The steps above describe how to:

  1. Geocode addresses using the Google Maps API
  2. Create a custom jQuery Validator to use Google Maps API for address validation

The final step is to combine all of these pieces into a working form.  Below is the completed client-side code for the form shown above.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Example Form </title>
    <link href="./css/site.css" rel="stylesheet" type="text/css" />
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
    <script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery.validate/1.7/jquery.validate.min.js"></script>
    <script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>
</head>
<body>
    <div class="page">
        <div id="main">
            <form id="MyForm" name="MyForm" action="form.html">
                <div id="map_canvas" style="float: right; height: 200px; width: 400px;"></div>
                <div>
                    <label for="Name">
                        Name</label>
                    <input id="Name" name="Name" type="text" />
                </div>
                <div>
                    <label for="FullAddress">
                        Address</label>
                    <textarea id="FullAddress" name="FullAddress" cols="40" rows="5" class="fulladdressvalidator"></textarea>
                </div>
                <div>
                    <input id="Submit" name="Submit" value="Submit" type="button" />
                </div>
            </form>
        </div>
    </div>
    <script type="text/javascript">
        // The following code show execute only after the page is fully loaded
        $(document).ready(function () {
            if ($('#MyForm').exists()) {

                // Enable jQuery Validation for the form
                $("#MyForm").validate({ onkeyup: false });

                // Add validation rules to the FullAddress field
                $("#FullAddress").rules("add", {
                    fulladdress: true,
                    required: true,
                    messages: {
                        fulladdress: "Google cannot locate this address."
                    }
                });

                // This function will be executed when the form is submitted
                function FormSubmit() {
                    $.submitForm = true;
                    if (!$('#MyForm').valid()) {
                        return false;
                    } else {
                        if ($("#FullAddress").data("IsChecking") == true) {
                            $("#FullAddress").data("SubmitForm", true);
                            return false;
                        }

                        alert('Form Valid!  Submit!');
                        // return true;   // Uncomment to submit the form.
                        return false;     // Supress the form submission for test purpose.

                    }
                }

                // Attach the FormSubmit function to the Submit button
                if ($('#Submit').exists()) {
                    $("#Submit").click(FormSubmit);
                }

                // Execute the ForumSubmit function when the form is submitted
                $('#MyForm').submit(FormSubmit);
            }
        });

        // Create a jQuery exists method
        jQuery.fn.exists = function () { return jQuery(this).length > 0; }

        // Position the Google Map
        function Map(elementId, geolocation) {
            var myOptions = {
                zoom: 13,
                mapTypeId: google.maps.MapTypeId.ROADMAP
            }
            var map = new google.maps.Map(document.getElementById(elementId), myOptions);
            map.setCenter(geolocation);
        }

        // FullAddress jQuery Validator
        function FullAddressValidator(value, element, paras) {

            // Convert the value variable into something a bit more descriptive
            var CurrentAddress = value;

            // If the address is blank, then this is for the required validator to deal with.
            if (value.length == 0) {
                return true;
            }

            // If we've already validated this address, then just return the previous result
            if ($(element).data("LastAddressValidated") == CurrentAddress) {
                return $(element).data("IsValid");
            }

            // We have a new address to validate, set the IsChecking flag to true and set the LastAddressValidated to the CurrentAddress
            $(element).data("IsChecking", true);
            $(element).data("LastAddressValidated", CurrentAddress);

            // Google Maps doesn't like line-breaks, remove them
            CurrentAddress = CurrentAddress.replace(/\n/g, "");

            // Create a new Google geocoder
            var geocoder = new google.maps.Geocoder();
            geocoder.geocode({ 'address': CurrentAddress }, function (results, status) {

                // The code below only gets run after a successful Google service call has completed.
                // Because this is an asynchronous call, the validator has already returned a 'true' result
                // to supress an error message and then cancelled the form submission.  The code below
                // needs to fetch the true validation from the Google service and then re-execute the
                // jQuery form validator to display the error message.  Futhermore, if the form was
                // being submitted, the code below needs to resume that submit.

                // Google reported a valid geocoded address
                if (status == google.maps.GeocoderStatus.OK) {

                    // Get the formatted Google result
                    var address = results[0].formatted_address;

                    // Count the commas in the fomatted address.
                    // This doesn't look great, but it helps us understand how specific the geocoded address
                    // is.  For example, "CA" will geocde to "California, USA".
                    numCommas = address.match(/,/g).length;

                    // A full street address will have at least 3 commas.  Alternate techniques involve
                    // fetching the address_components returned by Google Maps.  That code looks even more ugly.
                    if (numCommas >= 3) {

                        // Replace the first comma found with a line-break
                        address = address.replace(/, /, "\n");

                        // Remove USA from the address (remove this, if this is important to you)
                        address = address.replace(/, USA$/, "");

                        // Check for the map_canvas, if it exists then position the Google Map
                        if ($("#map_canvas").exists()) {
                            $("#map_canvas").show();
                            Map("map_canvas", results[0].geometry.location);
                        }

                        // Set the textarea value to the geocoded address
                        $(element).val(address);

                        // Cache this latest result
                        $(element).data("LastAddressValidated", address);

                        // We have a valid geocoded address
                        $(element).data("IsValid", true);
                    } else {
                        // Google Maps was able to geocode the address, but it wasn't specific
                        // enough (not enough commas) to be a valid street address.
                        $(element).data("IsValid", false);
                    }

                    // Otherwise the address is invalid
                } else {
                    $(element).data("IsValid", false);
                }

                // We're no longer in the midst of validating
                $(element).data("IsChecking", false);

                // Get the parent form element for this address field
                var form = $(element).parents('form:first');

                // This code is being run after the validation for this field,
                // if the form was being submitted before this validtor was
                // called then we need to re-submit the form.
                if ($(element).data("SubmitForm") == true) {
                    form.submit();
                } else {
                    // Re-validate this property so we can return the result.
                    form.validate().element(element);
                }
            });

            // The FullAddress validator always returns 'true' when initially called.
            // The true result will be return later by the geocode function (above)
            return true;
        }

        // Define a new jQuery Validator method
        $.validator.addMethod("fulladdress", FullAddressValidator);
    </script>
</body>
</html>

All this work, just so I can do this:

Client-side address validation using the Google Maps Geocode API

At least I didn’t need to create a proxy service.  ;)

  • Abraham Estrada

    This violates the Google Maps APIs Terms of Service.

    http://code.google.com/apis/maps/terms.html#section_10_12

    10.12 use or display the Content without a corresponding Google map, unless you are explicitly permitted to do so in the Maps APIs Documentation, the Street View API Documentation, or through written permission from Google (for example, you must not use geocodes obtained through the Service except in conjunction with a Google map, but the Street View API Documentation explicitly permits you to display Street View imagery without a corresponding Google map);

  • Gabe Sumner

    Hi Abraham,

    The map is included in the code above. I didn't show it in the screenshot because it made the photo too wide.

  • John "Z-Bo" Zabroski

    This not only violates Google TOS, but it also lacks the sophistication of commercial Web APIs that do address validation.

    On the other hand, this points out one way Google is not monetizing its services: Why don't they offer Google Maps API for corporate firewalls, along with a side-by-side address validation service?

  • John "Z-Bo" Zabroski

    Sorry, what I meant was commercial Web APIs for address validation get their information from government offices, since address validation is primarily intended to allow corporations to do bulk mailing. The address is not only validated, but normalized to fit USPS guidelines for bulkmailing. Other countries have similar guidelines for bulkmail. They allow corporations discounts on their mail if it is pre-normalized and pre-sorted.

  • Gabe Sumner

    Hi John,

    Thanks for clarifying.

    Regarding the TOS:

    As I understand, if the geocoding is done in conjunction with a Google displayed map, there shouldn't be a problem. I understand displaying an address might be challenge for the mailing scenario you identified however.

    Regarding commercial address validation services:

    I've never been involved in bulk-mailing. That's an interesting twist on all of this. Google Maps does reformat the address, but I'm not sure if this reformatted address matches official postal guidelines.

    Thanks for the comment.

  • John "Z-Bo" Zabroski

    We aren't involved in bulk-mailing, either.

    We pay for address standardization as a value-added service to our SaaS platform. Every customer's contact database is address standardized. The side-effect of doing this is that it enables us to more easily catch duplicate data elements due to poorly integrated business processes (i.e., clients entering in their data more than once, signing up for a service more than once). It then lets our customers better track their clients.

  • John "Z-Bo" Zabroski

    Also, by corporate firewalls I meant that Google Maps API does not have a pay service for apps run with an encrypted (https://etc…) connection. The upshot of that is that all corporate intranet apps cannot use Google Maps API lawfully. Some B2B apps side-step this by having a public registration for viewing the B2B app, but for some apps it is more important that the app itself stay behind the corporate firewall.

  • John "Z-Bo" Zabroski

    Key point: Here is an example. If you buy a Garmin GPS device within the current Garmin product cycle (e.g., the most recent stuff), then Garmin GPSs won't be able to find half the streets in many parts of China. I believe the same to be true for Google Maps.

    Address standardization APIs on the other hand would use information from local chinese Governments and thus have the most current info, even before the maps are made for the area. Maps logically come after the gov't assigns street addresses.

  • Hosting 4 Developers

    If most forms were rewritten to use this, I envision a horde of support requests a la "your form won't let me enter my address."

    There are too many potential problems:
    - The user enters the name of a street that is valid but newly constructed (and hence not in Google's database)
    - The user misspells parts of the address badly enough for Google's code not to recognize the address
    - Google's service is slow to respond, or the API changes without notice
    - It's essentially impossible to use for an international site because address formats vary greatly from country to country. An example: In many European countries the house number is written *after* the street name ("Fake Street 1234").

    Having said that, there may be situations where it's imperative to validate an address like that, but I don't see the "normal" forms disappearing any time soon.

  • Mehdi

    Have you considered narrowing down what part of the address cannot be identified?

    e.g., if the user enters "123 QWERTY Lane, New York City", have the service only highlight "123 QWERTY Lane" instead of the full address?

  • Anonymous

    seriously, relying on google for this is not a good idea. for years, they refused to recognize my address, and worse, substituted a "best guess" which was completely wrong. "ohoh" i hear you say, "you must live in some weird neck of the woods" or "you must live on a new street."

    i live in new york city in a house that's been here, with the same address, for about 60 years.

  • Christopher Done

    Nothing wrong with using this and then the old fashioned street/city/country method after clicking 'Google cannot find my address, I'll enter it manually'. I've thought this was a good idea for a long time. Nice post.

  • Khumpty

    I replied to the post on reddit linking here, but I'll put it here, too, in case anyone is interested.

    I've done something similar, but I incorporated it into the jQuery autocomplete, so it returns Google's best guess for results as you type.
    Here are some screen shots and a sample of code.

    http://imgur.com/a/jeO6K

    //autocomplete for address search
    $('#address-search').autocomplete({
    source: function(req, add){

    var address = req.term;

    geocoder = new google.maps.Geocoder();

    var jsonResults = [];

    geocoder.geocode( { 'address': address}, function(results, status) {
    if (status == google.maps.GeocoderStatus.OK) {
    for (i = 0; i < results.length; i++) {
    jsonResults.push({id: i, value: results[i].formatted_address});
    }

    add(jsonResults);
    }
    });
    },
    select: function(event, ui){
    geocodeAddress(ui.item.value);
    },
    minLength: 1,
    autoFill: true,
    delay: 500
    });

    Like yours, mine is used in the administration side of my site, by me only. A problem with this approach for any client facing setup would be the limit on the number of geocoding requests Google allows you to make in a 24 hour period. 2500 I think. This would be reached fairly quickly by a site getting plenty of views b/c a request is made for every 500 millisecond delay after typing stops.

  • Anonymous

    Sorry, John "Z-Bo" Zabroski. Google DOES offer HTTPS services that corporations and other business entities can consume. I've personally done some integration with them.

    http://www.google.com/enterprise/earthmaps/maps.html

    It costs around 10K for a license though. Pretty pricey, but you'll find this is pretty standard in the industry (MapQuest, Yahoo, etc.)

    - Michael Donahoo
    http://donahoo-development.com

  • John "Z-Bo" Zabroski

    Thanks Michael. That must have changed since we last checked. Archive.org's first index was October 2009, which was after we switched to other services.

    Cheers,
    Z-Bo

  • Thor Mitchell

    A couple of comments. There is no need to parse the formatted_address to count the number of commas in order to determine whether you have matched a precise address. The geocoder returns an array of types for each result. If the type of the first result is street_address, you have a precise match.

    You may also want to offer users the option to use the formatted_address in place of the address they entered, as that should be formatted according to local convention, including handling "big-endian" addresses in countries such as those in Europe where these are used.

    I threw together a quick demo to illustrate:

    http://www.thialfi.org/work/api/validate.html

    You can also allow the user to enter the address as free form text, but then extract structured address information from the geocoded results by iterating over the address_components array. To visualise all the information returned by the geocoded, check out this tool:

    http://gmaps-samples-v3.googlecode.com/svn/trunk/geocoder/v3-geocoder-tool.html

    In relation to other comments above, Google does offer Maps API Premier, which permits use behind a firewall and enables https apps. Maps API Premier was previously known as Google Maps for Enterprise, and has been available since 2006.

    On the subject of quotas, the 2,500 daily limit applies to the geocoding web service, which was the service used by the first cut of this doc which relied on use of a proxy. By switching to this JS based approach the geocoding requests are made directly to Google from end user browsers rather than via the proxy, which addresses the quota issue (because each user has their own quota).

    Note however that a rate limit still applies, which means that implementing query as you type is not recommended.

    Many thanks,

    Thor Mitchell
    Product Manager
    Google Maps API

  • Anonymous

    The problem of determining whether we have a precise address is not solved completely by using "street_address". or even by using location type "ROOFTOP".

    I found that if you enter the correct street address but incorrect state or zip, these will still pass the validations.

    So I think that the correct way of determining a precise address is to use the partial_match property of the result object.

    http://in.linkedin.com/pub/abhinav-maheshwari/0/544/b39

  • shenoyjoseph

    in my college telnet for connecting the system

  • Simon de la Salle

    Thanks for the excellent article… this totally helped me improve the quality of Google maps being displayed on a client's site… keep up the good work.
    Regards,
    Simon de la Salle

  • Jonathan

    I realize I'm a bit late to the game, but I've seen the Google Maps approach advocated heavily, despite a number of drawbacks. It's good to see a balance of comments in the discussion advocating things like rate/query limits as well as licensing terms that a business/application might run into when implementing Google Maps.

    I do have to say, and perhaps I'm somewhat biased, that a true address verification solution that's worth anything should be able to tell you if an address is *real* beyond just formatting it to make it *look* real.

    In the interest of full disclosure, I'm the founder of SmartyStreets (http://www.smartystreets.com/). We do web-based address verification–hence the comment that I'm somewhat biased in this conversation. You're more than welcome to contact me personally with questions.

  • Andre

    Why doesn't this code work for South African addresses? Any ideas what else I can use?

  • Anonymous

    Hi Gabe, I tried the above code with an Australian address and it didn't seem to work.

    269 Flinders Street Melbourne, Melbourne, Victoria

    The Australia works fine in Google Maps.

    Does the code require further customization for non-US addresses?

  • Anonymous

    Sorry typo above address should be:

    269 Flinders Street, Melbourne, Victoria

    But still can't find it ;)

  • mobile tracker

    Very good interest to read. By Regards mobile tracker
    http://www.mobile–tracker.blogspot.com

  • http://jonathanoliver.com Jonathan Oliver

     The URL I posted above seems to be broken. Here’s the real URL: http://www.smartystreets.com

  • Mark Landry

    This is great.  Can you help me figure out how to throw an error if the address is not exactly correct?

  • Alex

    It doesn’t work at all. This way, the tutorial is useless.