21

I'm just looking for suggestions on the best way to do this...

I need to create a search function that searches for "users" within a 50 mile radius of a zip code. I have a zip code table that contains all the U.S. zip codes with their latitude/longitude but I'm just trying to figure out the best way to structure and query my data...

Should I add latitude/longitude columns to the users table and query it for all the users within the radius of the given zip code? Or should I query the zip codes table for all the zip codes that fall within the radius then query the users table for all the users with the results(zip codes)? Or... ??? I am open to any suggestions at this point!

Thanks!

mike
  • 8,041
  • 19
  • 53
  • 68

7 Answers7

25

Here is the best way I have found. Of course it will require that you have all of your zipcodes lat/lon encoded in the database.

// get all the zipcodes within the specified radius - default 20
function zipcodeRadius($lat, $lon, $radius)
{
    $radius = $radius ? $radius : 20;
    $sql = 'SELECT distinct(ZipCode) FROM zipcode  WHERE (3958*3.1415926*sqrt((Latitude-'.$lat.')*(Latitude-'.$lat.') + cos(Latitude/57.29578)*cos('.$lat.'/57.29578)*(Longitude-'.$lon.')*(Longitude-'.$lon.'))/180) <= '.$radius.';';
    $result = $this->db->query($sql);
    // get each result
    $zipcodeList = array();
    while($row = $this->db->fetch_array($result))
    {
        array_push($zipcodeList, $row['ZipCode']);
    }
    return $zipcodeList;
}

You should be able to just drop in this function. Pass it the $lat and $lon of the zipcode you want the radius for, include the optional radius, and get a list of zipcodes back.

You could very easily modify this to get all users where zipcode IN (radius_sql) and get your list users back.

Happy Coding!

Chuck Burgess
  • 11,600
  • 5
  • 41
  • 74
  • 1
    This would be great but how would you suggest ordering them by distance? – mike Mar 10 '10 at 15:27
  • 2
    So the only way you could is if you are comparing an address (lat/lon) to surrounding addresses (lat/lon). That being said, you can still order them by closest to farthest zipcode. But keep in mind, this will only specify the "area" of the zipcode, not necessarily proximity to the other addresses. I would say adding to the select: (3958*3.1415926*sqrt((Latitude-'.$lat.')*(Latitude-'.$lat.') + cos(Latitude/57.29578)*cos('.$lat.'/57.29578)*(Longitude-'.$lon.')*(Longitude-'.$lon.'))/180) as LOC then on the end ORDER BY LOC. – Chuck Burgess Mar 10 '10 at 17:33
  • Just to let people know... US Census has this avail here: http://www.census.gov/tiger/tms/gazetteer/zips.txt – r109 Mar 21 '13 at 06:10
  • Simple and clear - when you need to find 'within a radius of the destination' (i.e., 'zip code' and not the 'destination from the point' (i.e., 'coffee shop') and all you have is lat/long. No spatial geometry or all that.... Thrilled to find this for use on an 'advertising' lookup (where we know where the 'user' is and need to see if the advertising lat/long+radius extends out that far. Solved a big need for us. – Apps-n-Add-Ons Aug 20 '20 at 16:18
4

http://www.micahcarrick.com/04-19-2005/php-zip-code-range-and-distance-calculation.html

I found this very awesome.

"query the zip codes table for all the zip codes that fall within the radius then query the users table for all the users with the results(zip codes)"

I found this is the best way to do it unless you need to put the users on a google map. If you're just listing the users in the mileage range it should be pretty easy to query the database (using the class) for a list of zips then select all users in those zipcodes.

Select * from Users where zip_code IN (19125,19081,19107.........);

That should do it.

mmundiff
  • 3,875
  • 7
  • 32
  • 46
  • After playing with things a little. I think this would be the best option but I'm not sure how I would order the final results(the users) by distance.... and, that's the site I got my zip code data from! ;) – mike Mar 10 '10 at 15:29
  • This has a dead link. – Ael Oct 13 '17 at 00:42
2

Check out the proximity search featured here:

Using PHP/MySQL with Google Maps

If your data is in the same notation / projection / format (whatever that's called), it may work for you.

Michael
  • 2,016
  • 5
  • 35
  • 51
Pekka
  • 442,112
  • 142
  • 972
  • 1,088
  • 2
    Link is dead ... And what if I have only ZIP codes .. no Long-Lati info – Umair Ayub Nov 10 '14 at 10:36
  • @Chinu fair point - but there's so many other good answers here, I'll ask the OP to remove the check mark so I can delete the answer – Pekka Jun 16 '15 at 05:59
2

Start here but note that the solution isn't very fast:

PHP Zip Code Range and Distance Calculation

Now to make it fast - we are going to replace the lookup to use a spatial index :)

  1. Use MySQL

  2. Add a column to the database called location and make it type POINT

  3. Make sure it accepts nulls right now

  4. Run the following SQL Query

    UPDATE zip_code SET location = PointFromText(CONCAT('POINT(',lon,' ',lat,')'));

  5. Now, make the column not accept nulls

  6. Add a spatial index to the location column

  7. In the code from the above project replace the function 'get_zips_in_range' with the following:

    function get_zips_in_range($zip, $range, $sort=1, $include_base) 
         {
    
    
        // returns an array of the zip codes within $range of $zip. Returns
        // an array with keys as zip codes and values as the distance from
        // the zipcode defined in $zip.
    
        $this->chronometer();                     // start the clock
    
        $details = $this->get_zip_point($zip);  // base zip details
        if ($details == false) return false;
    
        // This portion of the routine  calculates the minimum and maximum lat and
        // long within a given range.  This portion of the code was written
        // by Jeff Bearer (http://www.jeffbearer.com). This significanly decreases
        // the time it takes to execute a query.  My demo took 3.2 seconds in
        // v1.0.0 and now executes in 0.4 seconds!  Greate job Jeff!
    
        // Find Max - Min Lat / Long for Radius and zero point and query
        // only zips in that range.
        $lat = $details[0];
        $lon = $details[1];
    
        $return = array();    // declared here for scope
    
        $first = true;
        $radius = $range/69.172;
        $boundary = "POLYGON((";
        for($i=0; $i <= 360; $i += 360/24)
        {
            if($first)
            {
                $first = false;
            }
            else
            {
                $boundary .= ', ';
            }
    
            $clon = $radius*cos(deg2rad($i)) + $lon;
            $clat = $radius*sin(deg2rad($i)) + $lat;
            $boundary .= "$clon $clat" ;
        }
    
        $boundary  .= '))';
    
        $sql = "SELECT zip_code, city, county, state_name, state_prefix, area_code, time_zone, lat, lon FROM zip_code WHERE MBRContains(GeomFromText('$boundary'), location);";
    
        //echo $sql;
        $r = mysql_query($sql);
    
        if (!$r) {    // sql error
    
            $this->last_error = mysql_error();
            return false;
    
        } else {
    
            while ($row = mysql_fetch_row($r)) {
    
                // loop through the results to get the milage from src
                $dist = $this->calculate_mileage($details[0],$row[7],$details[1],$row[8]);
                if ($this->units == _UNIT_KILOMETERS) $dist = $dist * _M2KM_FACTOR;
                $return[str_pad($row[0].', '.$row[1], 5, "0", STR_PAD_LEFT)] = round($dist, $this->decimals);
    
            }
            mysql_free_result($r);
        }
    
        // sort array
        switch($sort)
        {
            case _ZIPS_SORT_BY_DISTANCE_ASC:
                asort($return);
                break;
    
            case _ZIPS_SORT_BY_DISTANCE_DESC:
                arsort($return);
                break;
    
            case _ZIPS_SORT_BY_ZIP_ASC:
                ksort($return);
                break;
    
            case _ZIPS_SORT_BY_ZIP_DESC:
                krsort($return);
                break;
        }
    
        $this->last_time = $this->chronometer();
    
        if (empty($return)) return false;
        return $return;
       }
    
Mr.Wizard
  • 24,179
  • 5
  • 44
  • 125
Introgy
  • 541
  • 2
  • 5
  • 11
2

I would first do a search for all the zipcodes in the radius of the target. Then compare all the returned zipcodes to your user table zipcodes. Pull out the matching users.

It find the zipcodes in a radius, found this MySQL call:

$query = 'SELECT zzip FROM ' . table . 
            ' WHERE (POW((69.1*(zlongitude-"' . 
            $long . '")*cos(' . $long . 
            '/57.3)),"2")+POW((69.1*(zlatitude-"' . 
            $lat . '")),"2"))<(' . $radius . 
            '*' . $radius . ')';

MySQL does all the math for you.

I found a class that uses this here: http://www.nucleusdevelopment.com/code/do/zipcode

Hope that helps.

Mr.Wizard
  • 24,179
  • 5
  • 44
  • 125
1

I'd consider reducing the number of candidates with a bounding square first, then worrying about the radius as a second step. You start off with the coordinates of the zipcode, then calculate the long/lat of 50 miles in all 4 directions, then select only candidates within that box using simple greater/less than criteria. If your user base is well spread out this reduces your candidate set considerably, then you only have to do vector distance math to eliminate the "corners".

Steve Prior
  • 322
  • 2
  • 14
0

The lat/long you have for each zip code is a geographic center for that zip, right? So if you first find zip codes with geographic centers within 50 miles, then users in those zip codes, you could easily be returning users more than 50 miles away. So you'd sacrifice some accuracy doing it that way.

But if you have a lot of users (more than the number of zip codes), this would be faster, since you'd query against the smaller zip codes table first. And you could index zip codes in the users table, so finding users with a particular zip code would be fast.

Just some thoughts! So, if you are expecting a lot of users and the radius of 50 miles doesn't need to be exact, I would find zip codes within 50 miles, then users within those zip codes.

Derek Kurth
  • 1,771
  • 1
  • 19
  • 19