We help IT Professionals succeed at work.

Street Level Address Validation - PHP

Hello,

I have a form where I collect data, street address, unit number, zip code.

I need to validate the address so that it is correct, I have tried to use google to validate it but it does not work 100% of the time.

Is there another way to validate it that is more effective?

Thank you
<label>Street Address<br />
<input class="input2" type="text" name="street_address"  value=""  /></label><br /><br />
<label>Unit Number<br />
<input class="input2" type="text" name="unit"  value=""  /></label><br /><br />
<label>Zip<span style="color:#F50">*</span><br />

Open in new window

Comment
Watch Question

Commented:
Hello,

Same address can given in many no of ways. The best thing I will suggest is try providing the most of the things like up till city in drop down menu. Btw try sending the details to the courier service api like USPS. And test the results :P
Most Valuable Expert 2011
Top Expert 2016
Commented:
Here is my geocoder class.  It will validate the addresses and return a precision value.  Please look it over and post back with any questions.  FWIW, I have found that Yahoo may give you a better indicator when the address is totally bogus.  Google tries to be a little creative about how it finds addresses.  The only good way I found to handle this was a bit of a pain - I would geocode the zip code, then geocode the address separately.  If they were more than 100 miles apart, it was probably an error!

HTH, ~Ray
<?php // RAY_class_SimpleGeoCoder.php
error_reporting(E_ALL);

// API KEYS, ETC., IF REQUIRED
// require_once('_config.php');


// PUT UP A FORM TO RECEIVE THE ADDRESS INFORMATION
if (empty($_GET))
{
?>

<html><head><title>Yahoo/Google SimpleGeoCoder Demo</title></head>
<body>
<form method="get">
Addr: <input type="text" name="a" autocomplete="off" />
City: <input type="text" name="c" autocomplete="off" />
ST:   <input type="text" name="s" autocomplete="off" size="2" />
Zip:  <input type="text" name="z" autocomplete="off" size="8" />
<input type="submit" name="_go" value="go" />
</form>
</body>
</html>

<?php
    die();
}


// A FREEFORM LOCATION STATEMENT IS OPTIONAL - GET ADDRESS, CITY, STATE, ZIP FROM THE URL
$location = '';
if ($_GET["a"] != '') { $location .= $_GET["a"] . ' '; }
if ($_GET["c"] != '') { $location .= $_GET["c"] . ' '; }
if ($_GET["s"] != '') { $location .= $_GET["s"] . ' '; }
if ($_GET["z"] != '') { $location .= $_GET["a"] . ' '; }
$location = trim($location);


// PREPARE THE GEOCODER
$demo          = new SimpleGeoCoder;
$demo->address = $_GET["a"];
$demo->city    = $_GET["c"];
$demo->state   = $_GET["s"];
$demo->zip     = $_GET["z"];

// TEST THE YAHOO! GEOCODER
$demo->geocodeYahoo();
echo "<pre>\n\nYAHOO! ";
print_rr($demo);
echo "</pre>\n";

// TEST THE GOOGLE GEOCODER
$demo->geocodeGoogle();
echo "<pre>\n\nGOOGLE ";
print_rr($demo);
echo "</pre>\n";


// ALL DONE
die();



// SIMPLE GEOCODER CLASS
class SimpleGeoCoder
{

// DECLARE THE INPUT DATA
    public $location; // USE THIS FOR A FREEFORM QUERY, OR USE THE PARTS
    public $address;
    public $city;
    public $state;
    public $zip;

// DECLARE THE WORKING DATA
    private $precis; // TEXTUAL PRECISION FROM YAHOO
    private $yfp, $yahooUrl,  $yahooResponse,  $ydata;
    private $gfp, $googleUrl, $googleResponse, $gdata;

// DECLARE THE OUTPUT DATA
    public $latitude;
    public $longitude;
    public $precision;
    public $warning;
    public $geocoder;

// DECLARE THE CONSTRUCTOR
    public function __construct()
    {
        $this->latitude  = 0.0;
        $this->longitude = 0.0;
        $this->precision = FALSE; // WANT A VALUE OF 5 OR HIGHER, HIGHER IS BETTER
        $this->warning   = '';
        $this->geocoder  = '';
        unset($this->precis);
    }

// DECLARE THE DATA-CLEANUP
    private function _cleanup($str)
    {
        $str = preg_replace('/[^\' a-zA-Z0-9&!#$%()"+:?\/@,_\.\-]/', '', $str);
        return trim(preg_replace('/\s\s+/', ' ', $str));
    }

// DECLARE THE YAHOO! VERSION OF THE WORKHORSE
    public function geocodeYahoo()
    {
        if (!defined('YAHOO_API')) define('YAHOO_API', 'YAHOO_API');
        $this->geocoder = 'Yahoo!';
        $yahooUrl       = "http://local.yahooapis.com/MapsService/V1/geocode?&appid=" . YAHOO_API;
        if ($this->location != '') // YOU CAN ASK FOR A FREEFORM QUERY
        {
            $yahooUrl .= "&location=" . urlencode($this->_cleanup($this->location));
        }
        else // YOU CAN ASK FOR INDIVIDUAL PIECES OF AN ADDRESS
        {
            $yahooUrl .= "&street=" . urlencode($this->_cleanup($this->address));
            $yahooUrl .= "&city="   . urlencode($this->_cleanup($this->city));
            $yahooUrl .= "&state="  . urlencode($this->_cleanup($this->state));
            $yahooUrl .= "&zip="    . urlencode($this->_cleanup($this->zip));
        }

// EXECUTE YAHOO GEOCODER QUERY
// NOTE - USE ERROR SUPPRESSION OR IT WILL BARK OUT THE YAHOO API KEY - ON FAILURE RETURNS HTTP 400 BAD REQUEST
        if ($yfp = @fopen($yahooUrl, 'r'))
        {
            $yahooResponse = '';
            while (!feof($yfp)) { $yahooResponse .= fgets($yfp); }
            fclose($yfp);
        }
        else
        {
            return FALSE;
        }

// EXAMINE THE RESULT
        if ($yahooResponse != '') // NOT EMPTY, WE GOT DATA
        {
            $ydata    = new SimpleXMLElement($yahooResponse);

// CHECK FOR ANY ERROR MESSAGE, IF NONE, EXTRACT THE DATA POINTS
            $yerror    = $ydata->Message;
            if ($yerror == '')
            {
                $this->precis    = (string)$ydata->Result["precision"];
                $this->warning   = (string)$ydata->Result["warning"];
                $this->latitude  = (string)$ydata->Result->Latitude;
                $this->longitude = (string)$ydata->Result->Longitude;

// THESE STATEMENTS CAN BE USED TO RETURN NORMALIZED ADDRESS
                $this->address   = (string)$ydata->Result->Address;
                $this->city      = (string)$ydata->Result->City;
                $this->state     = (string)$ydata->Result->State;
                $this->zip       = (string)$ydata->Result->Zip;

// SET PRECISION TO A NUMBER VALUE
                if ($this->precis == 'zip')     { $this->precision = "5"; }
                if ($this->precis == 'street')  { $this->precision = "6"; }
                if ($this->precis == 'address') { $this->precision = "8"; }
            }
            else
            {
                return FALSE;
            }
        }
        else  // NO RESULT - SOMETHING IS SICK AT YAHOO
        {
            return FALSE;
        }
        return TRUE;
    } // END function geocodeYahoo



// DECLARE THE GOOGLE VERSION OF THE WORKHORSE
    public function geocodeGoogle()
    {
        if (!defined('GOOGLE_API')) define('GOOGLE_API', 'GOOGLE_API');
        $this->geocoder = 'Google';
        $googleUrl      = "http://maps.google.com/maps/geo?key=" . GOOGLE_API . "&output=csv";

        if ($this->location != '') // YOU CAN ASK FOR A FREEFORM QUERY
        {
            $googleUrl .= "&q=" . urlencode($this->_cleanup($this->location));
        }
        else // YOU CAN ASK FOR INDIVIDUAL PIECES OF AN ADDRESS
        {
            $googleUrl .= "&q="
                       . urlencode(trim(
                         $this->_cleanup($this->address) . ' '
                       . $this->_cleanup($this->city)    . ' '
                       . $this->_cleanup($this->state)   . ' '
                       . $this->_cleanup($this->zip)
                       ));
        }

// EXECUTE GOOGLE GEOCODER QUERY
        if ($gfp = @fopen($googleUrl, 'r'))
        {
            $googleResponse = '';
            while (!feof($gfp)) { $googleResponse .= fgets($gfp); }
            fclose($gfp);
        }
        else
        {
            return FALSE;
        }

// EXTRACT THE DATA
        $gdata    = explode(',',$googleResponse);
        if ($gdata[0] != '200') // RESPONSE CODE SHOULD BE '200' -- IF 602 - BAD ZIP CODE OR UNUSABLE ADDRESS
        {
            return FALSE;
        }
        $this->precision = $gdata[1]; // GEOCODE ACCURACY - ZIP CODE = 5, HIGHER NUMBERS ARE BETTER
        $this->latitude  = $gdata[2];
        $this->longitude = $gdata[3];
        return TRUE;
    } // END function geocodeGoogle
} // END class SimpleGeocoder



// A QUICK AND DIRTY FUNCTION TO MAKE THE OUTPUT SHOW THE PUBLIC INFORMATION ONLY
function print_rr($thing)
{
    $str = print_r($thing, TRUE);
    $arr = explode("\n", $str);
    $num = count($arr) -1;
    foreach ($arr as $ptr => $txt)
    {
        if (preg_match('/:private]/', $txt)) { unset($arr[$ptr]); }
    }
    echo implode("\n", $arr);
}

Open in new window

Author

Commented:
Thanks Ray,

I am actually using yours to begin with and here is my problem, for example google does not accept this address:

953 Sweetwater Dr, Lake Ozark, MO, 65049

Because the correct address is:

953 Sweetwater Dr, Village of Four Seasons, MO, 65049

And the geo coding of the street address and zip equals the city name to be 'Lake Ozark'

Hope that makes sense.

Author

Commented:
It looks like my solution is to make it so that it allows for customers to enter their own city in instead of me using the zip to do so. I am now going to make it so that someone can enter in the zip and I auto populate a drop down with the city I come up with and they can make the change themselves.

Thanks for your help, I will split the points.
Most Valuable Expert 2011
Top Expert 2016

Commented:
You can get a ZIP code directory and use it to look up the cities, however ZIP codes and city names have a many-to-many relationship.  You are better off letting the client enter the data.

I had no trouble at all with the address you posted above.  Here is the link:
http://www.laprbass.com/RAY_class_SimpleGeoCoder.php?a=953+Sweetwater+Dr&c=Lake+Ozark&s=MO&z=65049&_go=go

Thanks for the points, and good luck with the project, ~Ray