--- /dev/null
+## FEATURE SUMMARY
+
+This plugin provides key functionality for location-oriented Rails applications:
+
+- Distance calculations, for both flat and spherical environments. For example,
+ given the location of two points on the earth, you can calculate the miles/KM
+ between them.
+- ActiveRecord distance-based finders. For example, you can find all the points
+ in your database within a 50-mile radius.
+- Geocoding from multiple providers. It currently supports Google, Yahoo,
+ Geocoder.us, and Geocoder.ca geocoders, and it provides a uniform response
+ structure from all of them. It also provides a fail-over mechanism, in case
+ your input fails to geocode in one service.
+- IP-based location lookup utilizing hostip.info. Provide an IP address, and get
+ city name and latitude/longitude in return
+- A before_filter helper to geocoder the user's location based on IP address,
+ and retain the location in a cookie.
+
+The goal of this plugin is to provide the common functionality for location-oriented
+applications (geocoding, location lookup, distance calculation) in an easy-to-use
+package.
+
+## A NOTE ON TERMINOLOGY
+
+Throughout the code and API of this, latitude and longitude are referred to as lat
+and lng. We've found over the long term the abbreviation saves lots of typing time.
+
+## DISTANCE CALCULATIONS AND QUERIES
+
+If you want only distance calculation services, you need only mix in the Mappable
+module like so:
+
+ class Location
+ include GeoKit::Mappable
+ end
+
+After doing so, you can do things like:
+
+ Location.distance_between(from, to)
+
+with optional parameters :units and :formula. Values for :units can be :miles or
+:kms with :miles as the default. Values for :formula can be :sphere or :flat with
+:sphere as the default. :sphere gives you Haversine calculations, while :flat
+gives the Pythagoreum Theory. These defaults persist through out the plug-in.
+
+You can also do:
+
+ location.distance_to(other)
+
+The real power and utility of the plug-in is in its query support. This is
+achieved through mixing into an ActiveRecord model object:
+
+ class Location < ActiveRecord::Base
+ acts_as_mappable
+ end
+
+The plug-in uses the above-mentioned defaults, but can be modified to use
+different units and a different formulae. This is done through the :default_units
+and :default_formula keys which accept the same values as mentioned above.
+
+The plug-in creates a calculated column and potentially a calculated condition.
+By default, these are known as "distance" but this can be changed through the
+:distance_field_name key.
+
+So, an alternative invocation would look as below:
+
+ class Location < ActiveRecord::Base
+ acts_as_mappable :default_units => :kms,
+ :default_formula => :flat,
+ :distance_field_name => :distance
+ end
+
+You can also define alternative column names for latitude and longitude using
+the :lat_column_name and :lng_column_name keys. The defaults are 'lat' and
+'lng' respectively.
+
+Thereafter, a set of finder methods are made available. Below are the
+different combinations:
+
+Origin as a two-element array of latititude/longitude:
+
+ find(:all, :origin => [37.792,-122.393])
+
+Origin as a geocodeable string:
+
+ find(:all, :origin => '100 Spear st, San Francisco, CA')
+
+Origin as an object which responds to lat and lng methods,
+or latitude and longitude methods, or whatever methods you have
+specified for lng_column_name and lat_column_name:
+
+ find(:all, :origin=>my_store) # my_store.lat and my_store.lng methods exist
+
+Often you will need to find within a certain distance. The prefered syntax is:
+
+ find(:all, :origin => @somewhere, :within => 5)
+
+. . . however these syntaxes will also work:
+
+ find_within(5, :origin => @somewhere)
+ find(:all, :origin => @somewhere, :conditions => "distance < 5")
+
+Note however that the third form should be avoided. With either of the first two,
+GeoKit automatically adds a bounding box to speed up the radial query in the database.
+With the third form, it does not.
+
+If you need to combine distance conditions with other conditions, you should do
+so like this:
+
+ find(:all, :origin => @somewhere, :within => 5, :conditions=>['state=?',state])
+
+If :origin is not provided in the finder call, the find method
+works as normal. Further, the key is removed
+from the :options hash prior to invoking the superclass behavior.
+
+Other convenience methods work intuitively and are as follows:
+
+ find_within(distance, :origin => @somewhere)
+ find_beyond(distance, :origin => @somewhere)
+ find_closest(:origin => @somewhere)
+ find_farthest(:origin => @somewhere)
+
+where the options respect the defaults, but can be overridden if
+desired.
+
+Lastly, if all that is desired is the raw SQL for distance
+calculations, you can use the following:
+
+ distance_sql(origin, units=default_units, formula=default_formula)
+
+Thereafter, you are free to use it in find_by_sql as you wish.
+
+There are methods available to enable you to get the count based upon
+the find condition that you have provided. These all work similarly to
+the finders. So for instance:
+
+ count(:origin, :conditions => "distance < 5")
+ count_within(distance, :origin => @somewhere)
+ count_beyond(distance, :origin => @somewhere)
+
+## FINDING WITHIN A BOUNDING BOX
+
+If you are displaying points on a map, you probably need to query for whatever falls within the rectangular bounds of the map:
+
+ Store.find :all, :bounds=>[sw_point,ne_point]
+
+The input to :bounds can be array with the two points or a Bounds object. However you provide them, the order should always be the southwest corner, northeast corner of the rectangle. Typically, you will be getting the sw_point and ne_point from a map that is displayed on a web page.
+
+If you need to calculate the bounding box from a point and radius, you can do that:
+
+ bounds=Bounds.from_point_and_radius(home,5)
+ Store.find :all, :bounds=>bounds
+
+## USING INCLUDES
+
+You can use includes along with your distance finders:
+
+ stores=Store.find :all, :origin=>home, :include=>[:reviews,:cities] :within=>5, :order=>'distance'
+
+*However*, ActiveRecord drops the calculated distance column when you use include. So, if you need to
+use the distance column, you'll have to re-calculate it post-query in Ruby:
+
+ stores.sort_by_distance_from(home)
+
+In this case, you may want to just use the bounding box
+condition alone in your SQL (there's no use calculating the distance twice):
+
+ bounds=Bounds.from_point_and_radius(home,5)
+ stores=Store.find :all, :include=>[:reviews,:cities] :bounds=>bounds
+ stores.sort_by_distance_from(home)
+
+## IP GEOCODING
+
+You can obtain the location for an IP at any time using the geocoder
+as in the following example:
+
+ location = IpGeocoder.geocode('12.215.42.19')
+
+where Location is a GeoLoc instance containing the latitude,
+longitude, city, state, and country code. Also, the success
+value is true.
+
+If the IP cannot be geocoded, a GeoLoc instance is returned with a
+success value of false.
+
+It should be noted that the IP address needs to be visible to the
+Rails application. In other words, you need to ensure that the
+requesting IP address is forwarded by any front-end servers that
+are out in front of the Rails app. Otherwise, the IP will always
+be that of the front-end server.
+
+## IP GEOCODING HELPER
+
+A class method called geocode_ip_address has been mixed into the
+ActionController::Base. This enables before_filter style lookup of
+the IP address. Since it is a filter, it can accept any of the
+available filter options.
+
+Usage is as below:
+
+ class LocationAwareController < ActionController::Base
+ geocode_ip_address
+ end
+
+A first-time lookup will result in the GeoLoc class being stored
+in the session as :geo_location as well as in a cookie called
+:geo_session. Subsequent lookups will use the session value if it
+exists or the cookie value if it doesn't exist. The last resort is
+to make a call to the web service. Clients are free to manage the
+cookie as they wish.
+
+The intent of this feature is to be able to provide a good guess as
+to a new visitor's location.
+
+## INTEGRATED FIND AND GEOCODING
+
+Geocoding has been integrated with the finders enabling you to pass
+a physical address or an IP address. This would look the following:
+
+ Location.find_farthest(:origin => '217.15.10.9')
+ Location.find_farthest(:origin => 'Irving, TX')
+
+where the IP or physical address would be geocoded to a location and
+then the resulting latitude and longitude coordinates would be used
+in the find. This is not expected to be common usage, but it can be
+done nevertheless.
+
+## ADDRESS GEOCODING
+
+GeoKit can geocode addresses using multiple geocodeing web services.
+Currently, GeoKit supports Google, Yahoo, and Geocoder.us geocoding
+services.
+
+These geocoder services are made available through three classes:
+GoogleGeocoder, YahooGeocoder, and UsGeocoder. Further, an additional
+geocoder class called MultiGeocoder incorporates an ordered failover
+sequence to increase the probability of successful geocoding.
+
+All classes are called using the following signature:
+
+ include GeoKit::Geocoders
+ location = XxxGeocoder.geocode(address)
+
+where you replace Xxx Geocoder with the appropriate class. A GeoLoc
+instance is the result of the call. This class has a "success"
+attribute which will be true if a successful geocoding occurred.
+If successful, the lat and lng properties will be populated.
+
+Geocoders are named with the naming convention NameGeocoder. This
+naming convention enables Geocoder to auto-detect its sub-classes
+in order to create methods called name_geocoder(address) so that
+all geocoders are called through the base class. This is done
+purely for convenience; the individual geocoder classes are expected
+to be used independently.
+
+The MultiGeocoder class requires the configuration of a provider
+order which dictates what order to use the various geocoders. Ordering
+is done through the PROVIDER_ORDER constant found in environment.rb.
+
+On installation, this plugin appends a template for your API keys to
+your environment.rb.
+
+Make sure your failover configuration matches the usage characteristics
+of your application -- for example, if you routinely get bogus input to
+geocode, your code will be much slower if you have to failover among
+multiple geocoders before determining that the input was in fact bogus.
+
+The Geocoder.geocode method returns a GeoLoc object. Basic usage:
+
+ loc=Geocoder.geocode('100 Spear St, San Francisco, CA')
+ if loc.success
+ puts loc.lat
+ puts loc.lng
+ puts loc.full_address
+ end
+
+## INTEGRATED FIND WITH ADDRESS GEOCODING
+
+Just has you can pass an IP address directly into an ActiveRecord finder
+as the origin, you can also pass a physical address as the origin:
+
+ Location.find_closest(:origin => '100 Spear st, San Francisco, CA')
+
+where the physical address would be geocoded to a location and then the
+resulting latitude and longitude coordinates would be used in the
+find.
+
+Note that if the address fails to geocode, the find method will raise an
+ActiveRecord::GeocodeError you must be prepared to catch. Alternatively,
+You can geocoder the address beforehand, and pass the resulting lat/lng
+into the finder if successful.
+
+## Auto Geocoding
+
+If your geocoding needs are simple, you can tell your model to automatically
+geocode itself on create:
+
+ class Store < ActiveRecord::Base
+ acts_as_mappable :auto_geocode=>true
+ end
+
+It takes two optional params:
+
+ class Store < ActiveRecord::Base
+ acts_as_mappable :auto_geocode=>{:field=>:address, :error_message=>'Could not geocode address'}
+ end
+
+. . . which is equivilent to:
+
+ class Store << ActiveRecord::Base
+ acts_as_mappable
+ before_validation_on_create :geocode_address
+
+ private
+ def geocode_address
+ geo=GeoKit::Geocoders::MultiGeocoder.geocode (address)
+ errors.add(:address, "Could not Geocode address") if !geo.success
+ self.lat, self.lng = geo.lat,geo.lng if geo.success
+ end
+ end
+
+If you need any more complicated geocoding behavior for your model, you should roll your own
+before_validate callback.
+
+
+## Distances, headings, endpoints, and midpoints
+
+ distance=home.distance_from(work, :units=>:miles)
+ heading=home.heading_to(work) # result is in degrees, 0 is north
+ endpoint=home.endpoint(90,2) # two miles due east
+ midpoing=home.midpoint_to(work)
+
+## Cool stuff you can do with bounds
+
+ bounds=Bounds.new(sw_point,ne_point)
+ bounds.contains?(home)
+ puts bounds.center
+
+
+HOW TO . . .
+=================================================================================
+
+## How to install the GeoKit plugin
+ cd [APP_ROOT]
+ ruby script/plugin install svn://rubyforge.org/var/svn/geokit/trunk
+ or, to install as an external (your project must be version controlled):
+ ruby script/plugin install -x svn://rubyforge.org/var/svn/geokit/trunk
+
+## How to find all stores within a 10-mile radius of a given lat/lng
+1. ensure your stores table has lat and lng columns with numeric or float
+ datatypes to store your latitude/longitude
+
+3. use acts_as_mappable on your store model:
+ class Store < ActiveRecord::Base
+ acts_as_mappable
+ ...
+ end
+3. finders now have extra capabilities:
+ Store.find(:all, :origin =>[32.951613,-96.958444], :within=>10)
+
+## How to geocode an address
+
+1. configure your geocoder key(s) in environment.rb
+
+2. also in environment.rb, make sure that PROVIDER_ORDER reflects the
+ geocoder(s). If you only want to use one geocoder, there should
+ be only one symbol in the array. For example:
+ PROVIDER_ORDER=[:google]
+
+3. Test it out in script/console
+ include GeoKit::Geocoders
+ res = MultiGeocoder.geocode('100 Spear St, San Francisco, CA')
+ puts res.lat
+ puts res.lng
+ puts res.full_address
+ ... etc. The return type is GeoLoc, see the API for
+ all the methods you can call on it.
+
+## How to find all stores within 10 miles of a given address
+
+1. as above, ensure your table has the lat/lng columns, and you've
+ applied acts_as_mappable to the Store model.
+
+2. configure and test out your geocoder, as above
+
+3. pass the address in under the :origin key
+ Store.find(:all, :origin=>'100 Spear st, San Francisco, CA',
+ :within=>10)
+
+4. you can also use a zipcode, or anything else that's geocodable:
+ Store.find(:all, :origin=>'94117',
+ :conditions=>'distance<10')
+
+## How to sort a query by distance from an origin
+
+You now have access to a 'distance' column, and you can use it
+as you would any other column. For example:
+ Store.find(:all, :origin=>'94117', :order=>'distance')
+
+## How to elements of an array according to distance from a common point
+
+Usually, you can do your sorting in the database as part of your find call.
+If you need to sort things post-query, you can do so:
+
+ stores=Store.find :all
+ stores.sort_by_distance_from(home)
+ puts stores.first.distance
+
+Obviously, each of the items in the array must have a latitude/longitude so
+they can be sorted by distance.
+
+
+HIGH-LEVEL NOTES ON WHAT'S WHERE
+=================================================================================
+
+acts_as_mappable.rb, as you'd expect, contains the ActsAsMappable
+module which gets mixed into your models to provide the
+location-based finder goodness.
+
+mappable.rb contains the Mappable module, which provides basic
+distance calculation methods, i.e., calculating the distance
+between two points.
+
+mappable.rb also contains LatLng, GeoLoc, and Bounds.
+LatLng is a simple container for latitude and longitude, but
+it's made more powerful by mixing in the above-mentioned Mappable
+module -- therefore, you can calculate easily the distance between two
+LatLng ojbects with distance = first.distance_to(other)
+
+GeoLoc (also in mappable.rb) represents an address or location which
+has been geocoded. You can get the city, zipcode, street address, etc.
+from a GeoLoc object. GeoLoc extends LatLng, so you also get lat/lng
+AND the Mappable modeule goodness for free.
+
+geocoders.rb contains the geocoder classes.
+
+ip_geocode_lookup.rb contains the before_filter helper method which
+enables auto lookup of the requesting IP address.
+
+## IMPORTANT NOTE: We have appended to your environment.rb file
+
+Installation of this plugin has appended an API key template
+to your environment.rb file. You *must* add your own keys for the various
+geocoding services if you want to use geocoding. If you need to refer to the original
+template again, see the api_keys_template file in the root of the plugin.