I recently finished up a feature that required a search to find all zip codes within a specified number of miles. I've worked with geo-coding before, but fortunately I was able to leave all the hard math to an already existing API (Google Maps, Yahoo Maps, etc.). However, for this project, we couldn't use an existing API and, fortunately, I found the math to be pretty simple.

There were several ways to approach this problem, all of them with varying degrees of complexity. The more complex the algorithm, the more accurate the search would be, but accuracy is also a trade-off for performance. I settled for the easiest and quickest route possible which is a simple box search.

Basically, with the box search, you take the geocode of your search zip code as the center point, convert the radius of your search into degrees latitude and longitude and then add/subtract degrees latitude and longitude to find the four corners of the container box. Once you have the four corners of the box represented in geocodes you can perform a simple search against your table of zip codes with their related geo codes to come up with a fairly accurate result set. (See example below)

There are 2 obvious problems with this search, one of which can be circumvented fairly easily in rails.

1. The distance between the center point and any of the four corners of the surrounding box is greater than the specified radius of the search which will result in possible outliers in your search results. This is easily overcome by a simple where clause that calculates the distance of each zip code to the search zip code and weed out any that are further than the search radius.
2. The other blaring problem with this approach is its simplistic geometry that doesn't take into account the curvature of the earth. However, for smaller radius searches, I've found this method to be pretty accurate.

Now for some code. Please excuse the line wrapping. I added a Search module in my Rails lib directory

```module Search
# ----------------------------------------------------------------------
# returns a collection of zip codes that are within the specified
# radius (in miles) of the given search zip code
# ----------------------------------------------------------------------
#make sure we have valid parameters
#look up search zip_code in database
zip_code = ZipCode.find_by_zip_code(zip)
#make sure we found the zip code in the database first
unless zip_code.nil?
latitude_miles = 69.172  #this is constant
#longitude miles varies based on latitude, that is calculated here
longitude_miles = (latitude_miles *
Math.cos(zip_code.latitude * (Math::PI/180))).abs

#now set min and max lat and long accordingly
min_latitude = zip_code.latitude - latitude_degrees
max_latitude = zip_code.latitude + latitude_degrees
min_longitude = zip_code.longitude - longitude_degrees
max_longitude = zip_code.longitude + longitude_degrees

#now find all zip codes that are within
#these min/max lat/long bounds and return them
#weed out any zip codes that fall outside of the search radius
return ZipCode.find(:all,
:select => "id,
zip_code,
latitude,
longitude,
sqrt( pow(#{latitude_miles} *
(latitude-#{zip_code.latitude}),2) +
pow(#{longitude_miles} *
(longitude-#{zip_code.longitude}),2))
as distance",
:conditions => "(latitude BETWEEN #{min_latitude}
AND #{max_latitude})
AND (longitude BETWEEN #{min_longitude}
AND #{max_longitude})
AND sqrt(pow(#{latitude_miles} *
(latitude-#{zip_code.latitude}),2) +
pow(#{longitude_miles} *
(longitude-#{zip_code.longitude}),2))
:order => "distance")
else
return nil
end
else
return nill
end
end

end
```

Then in your controller you can simply make the call like so...

```zip_codes = zip_code_perimeter_search(params[:perimeter_zip_code],
params[:perimeter_distance])
```