Commit 26982e37628b31c14f7701a3b92b9fe4b5b31af4

Authored by MoisesMachado
1 parent 76644e68

ActionItem261: added the geokit plugin


git-svn-id: https://svn.colivre.coop.br/svn/noosfero/trunk@1650 3f533792-8f58-4932-b0fe-aaf55b0a4547
Showing 32 changed files with 3287 additions and 0 deletions   Show diff stats
vendor/plugins/geokit/MIT-LICENSE 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +Copyright (c) 2007 Bill Eisenhauer & Andre Lewis
  2 +
  3 +Permission is hereby granted, free of charge, to any person obtaining
  4 +a copy of this software and associated documentation files (the
  5 +"Software"), to deal in the Software without restriction, including
  6 +without limitation the rights to use, copy, modify, merge, publish,
  7 +distribute, sublicense, and/or sell copies of the Software, and to
  8 +permit persons to whom the Software is furnished to do so, subject to
  9 +the following conditions:
  10 +
  11 +The above copyright notice and this permission notice shall be
  12 +included in all copies or substantial portions of the Software.
  13 +
  14 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  15 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  16 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  17 +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  18 +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  19 +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  20 +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
0 21 \ No newline at end of file
... ...
vendor/plugins/geokit/README 0 → 100644
... ... @@ -0,0 +1,451 @@
  1 +## FEATURE SUMMARY
  2 +
  3 +This plugin provides key functionality for location-oriented Rails applications:
  4 +
  5 +- Distance calculations, for both flat and spherical environments. For example,
  6 + given the location of two points on the earth, you can calculate the miles/KM
  7 + between them.
  8 +- ActiveRecord distance-based finders. For example, you can find all the points
  9 + in your database within a 50-mile radius.
  10 +- Geocoding from multiple providers. It currently supports Google, Yahoo,
  11 + Geocoder.us, and Geocoder.ca geocoders, and it provides a uniform response
  12 + structure from all of them. It also provides a fail-over mechanism, in case
  13 + your input fails to geocode in one service.
  14 +- IP-based location lookup utilizing hostip.info. Provide an IP address, and get
  15 + city name and latitude/longitude in return
  16 +- A before_filter helper to geocoder the user's location based on IP address,
  17 + and retain the location in a cookie.
  18 +
  19 +The goal of this plugin is to provide the common functionality for location-oriented
  20 +applications (geocoding, location lookup, distance calculation) in an easy-to-use
  21 +package.
  22 +
  23 +## A NOTE ON TERMINOLOGY
  24 +
  25 +Throughout the code and API of this, latitude and longitude are referred to as lat
  26 +and lng. We've found over the long term the abbreviation saves lots of typing time.
  27 +
  28 +## DISTANCE CALCULATIONS AND QUERIES
  29 +
  30 +If you want only distance calculation services, you need only mix in the Mappable
  31 +module like so:
  32 +
  33 + class Location
  34 + include GeoKit::Mappable
  35 + end
  36 +
  37 +After doing so, you can do things like:
  38 +
  39 + Location.distance_between(from, to)
  40 +
  41 +with optional parameters :units and :formula. Values for :units can be :miles or
  42 +:kms with :miles as the default. Values for :formula can be :sphere or :flat with
  43 +:sphere as the default. :sphere gives you Haversine calculations, while :flat
  44 +gives the Pythagoreum Theory. These defaults persist through out the plug-in.
  45 +
  46 +You can also do:
  47 +
  48 + location.distance_to(other)
  49 +
  50 +The real power and utility of the plug-in is in its query support. This is
  51 +achieved through mixing into an ActiveRecord model object:
  52 +
  53 + class Location < ActiveRecord::Base
  54 + acts_as_mappable
  55 + end
  56 +
  57 +The plug-in uses the above-mentioned defaults, but can be modified to use
  58 +different units and a different formulae. This is done through the :default_units
  59 +and :default_formula keys which accept the same values as mentioned above.
  60 +
  61 +The plug-in creates a calculated column and potentially a calculated condition.
  62 +By default, these are known as "distance" but this can be changed through the
  63 +:distance_field_name key.
  64 +
  65 +So, an alternative invocation would look as below:
  66 +
  67 + class Location < ActiveRecord::Base
  68 + acts_as_mappable :default_units => :kms,
  69 + :default_formula => :flat,
  70 + :distance_field_name => :distance
  71 + end
  72 +
  73 +You can also define alternative column names for latitude and longitude using
  74 +the :lat_column_name and :lng_column_name keys. The defaults are 'lat' and
  75 +'lng' respectively.
  76 +
  77 +Thereafter, a set of finder methods are made available. Below are the
  78 +different combinations:
  79 +
  80 +Origin as a two-element array of latititude/longitude:
  81 +
  82 + find(:all, :origin => [37.792,-122.393])
  83 +
  84 +Origin as a geocodeable string:
  85 +
  86 + find(:all, :origin => '100 Spear st, San Francisco, CA')
  87 +
  88 +Origin as an object which responds to lat and lng methods,
  89 +or latitude and longitude methods, or whatever methods you have
  90 +specified for lng_column_name and lat_column_name:
  91 +
  92 + find(:all, :origin=>my_store) # my_store.lat and my_store.lng methods exist
  93 +
  94 +Often you will need to find within a certain distance. The prefered syntax is:
  95 +
  96 + find(:all, :origin => @somewhere, :within => 5)
  97 +
  98 +. . . however these syntaxes will also work:
  99 +
  100 + find_within(5, :origin => @somewhere)
  101 + find(:all, :origin => @somewhere, :conditions => "distance < 5")
  102 +
  103 +Note however that the third form should be avoided. With either of the first two,
  104 +GeoKit automatically adds a bounding box to speed up the radial query in the database.
  105 +With the third form, it does not.
  106 +
  107 +If you need to combine distance conditions with other conditions, you should do
  108 +so like this:
  109 +
  110 + find(:all, :origin => @somewhere, :within => 5, :conditions=>['state=?',state])
  111 +
  112 +If :origin is not provided in the finder call, the find method
  113 +works as normal. Further, the key is removed
  114 +from the :options hash prior to invoking the superclass behavior.
  115 +
  116 +Other convenience methods work intuitively and are as follows:
  117 +
  118 + find_within(distance, :origin => @somewhere)
  119 + find_beyond(distance, :origin => @somewhere)
  120 + find_closest(:origin => @somewhere)
  121 + find_farthest(:origin => @somewhere)
  122 +
  123 +where the options respect the defaults, but can be overridden if
  124 +desired.
  125 +
  126 +Lastly, if all that is desired is the raw SQL for distance
  127 +calculations, you can use the following:
  128 +
  129 + distance_sql(origin, units=default_units, formula=default_formula)
  130 +
  131 +Thereafter, you are free to use it in find_by_sql as you wish.
  132 +
  133 +There are methods available to enable you to get the count based upon
  134 +the find condition that you have provided. These all work similarly to
  135 +the finders. So for instance:
  136 +
  137 + count(:origin, :conditions => "distance < 5")
  138 + count_within(distance, :origin => @somewhere)
  139 + count_beyond(distance, :origin => @somewhere)
  140 +
  141 +## FINDING WITHIN A BOUNDING BOX
  142 +
  143 +If you are displaying points on a map, you probably need to query for whatever falls within the rectangular bounds of the map:
  144 +
  145 + Store.find :all, :bounds=>[sw_point,ne_point]
  146 +
  147 +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.
  148 +
  149 +If you need to calculate the bounding box from a point and radius, you can do that:
  150 +
  151 + bounds=Bounds.from_point_and_radius(home,5)
  152 + Store.find :all, :bounds=>bounds
  153 +
  154 +## USING INCLUDES
  155 +
  156 +You can use includes along with your distance finders:
  157 +
  158 + stores=Store.find :all, :origin=>home, :include=>[:reviews,:cities] :within=>5, :order=>'distance'
  159 +
  160 +*However*, ActiveRecord drops the calculated distance column when you use include. So, if you need to
  161 +use the distance column, you'll have to re-calculate it post-query in Ruby:
  162 +
  163 + stores.sort_by_distance_from(home)
  164 +
  165 +In this case, you may want to just use the bounding box
  166 +condition alone in your SQL (there's no use calculating the distance twice):
  167 +
  168 + bounds=Bounds.from_point_and_radius(home,5)
  169 + stores=Store.find :all, :include=>[:reviews,:cities] :bounds=>bounds
  170 + stores.sort_by_distance_from(home)
  171 +
  172 +## IP GEOCODING
  173 +
  174 +You can obtain the location for an IP at any time using the geocoder
  175 +as in the following example:
  176 +
  177 + location = IpGeocoder.geocode('12.215.42.19')
  178 +
  179 +where Location is a GeoLoc instance containing the latitude,
  180 +longitude, city, state, and country code. Also, the success
  181 +value is true.
  182 +
  183 +If the IP cannot be geocoded, a GeoLoc instance is returned with a
  184 +success value of false.
  185 +
  186 +It should be noted that the IP address needs to be visible to the
  187 +Rails application. In other words, you need to ensure that the
  188 +requesting IP address is forwarded by any front-end servers that
  189 +are out in front of the Rails app. Otherwise, the IP will always
  190 +be that of the front-end server.
  191 +
  192 +## IP GEOCODING HELPER
  193 +
  194 +A class method called geocode_ip_address has been mixed into the
  195 +ActionController::Base. This enables before_filter style lookup of
  196 +the IP address. Since it is a filter, it can accept any of the
  197 +available filter options.
  198 +
  199 +Usage is as below:
  200 +
  201 + class LocationAwareController < ActionController::Base
  202 + geocode_ip_address
  203 + end
  204 +
  205 +A first-time lookup will result in the GeoLoc class being stored
  206 +in the session as :geo_location as well as in a cookie called
  207 +:geo_session. Subsequent lookups will use the session value if it
  208 +exists or the cookie value if it doesn't exist. The last resort is
  209 +to make a call to the web service. Clients are free to manage the
  210 +cookie as they wish.
  211 +
  212 +The intent of this feature is to be able to provide a good guess as
  213 +to a new visitor's location.
  214 +
  215 +## INTEGRATED FIND AND GEOCODING
  216 +
  217 +Geocoding has been integrated with the finders enabling you to pass
  218 +a physical address or an IP address. This would look the following:
  219 +
  220 + Location.find_farthest(:origin => '217.15.10.9')
  221 + Location.find_farthest(:origin => 'Irving, TX')
  222 +
  223 +where the IP or physical address would be geocoded to a location and
  224 +then the resulting latitude and longitude coordinates would be used
  225 +in the find. This is not expected to be common usage, but it can be
  226 +done nevertheless.
  227 +
  228 +## ADDRESS GEOCODING
  229 +
  230 +GeoKit can geocode addresses using multiple geocodeing web services.
  231 +Currently, GeoKit supports Google, Yahoo, and Geocoder.us geocoding
  232 +services.
  233 +
  234 +These geocoder services are made available through three classes:
  235 +GoogleGeocoder, YahooGeocoder, and UsGeocoder. Further, an additional
  236 +geocoder class called MultiGeocoder incorporates an ordered failover
  237 +sequence to increase the probability of successful geocoding.
  238 +
  239 +All classes are called using the following signature:
  240 +
  241 + include GeoKit::Geocoders
  242 + location = XxxGeocoder.geocode(address)
  243 +
  244 +where you replace Xxx Geocoder with the appropriate class. A GeoLoc
  245 +instance is the result of the call. This class has a "success"
  246 +attribute which will be true if a successful geocoding occurred.
  247 +If successful, the lat and lng properties will be populated.
  248 +
  249 +Geocoders are named with the naming convention NameGeocoder. This
  250 +naming convention enables Geocoder to auto-detect its sub-classes
  251 +in order to create methods called name_geocoder(address) so that
  252 +all geocoders are called through the base class. This is done
  253 +purely for convenience; the individual geocoder classes are expected
  254 +to be used independently.
  255 +
  256 +The MultiGeocoder class requires the configuration of a provider
  257 +order which dictates what order to use the various geocoders. Ordering
  258 +is done through the PROVIDER_ORDER constant found in environment.rb.
  259 +
  260 +On installation, this plugin appends a template for your API keys to
  261 +your environment.rb.
  262 +
  263 +Make sure your failover configuration matches the usage characteristics
  264 +of your application -- for example, if you routinely get bogus input to
  265 +geocode, your code will be much slower if you have to failover among
  266 +multiple geocoders before determining that the input was in fact bogus.
  267 +
  268 +The Geocoder.geocode method returns a GeoLoc object. Basic usage:
  269 +
  270 + loc=Geocoder.geocode('100 Spear St, San Francisco, CA')
  271 + if loc.success
  272 + puts loc.lat
  273 + puts loc.lng
  274 + puts loc.full_address
  275 + end
  276 +
  277 +## INTEGRATED FIND WITH ADDRESS GEOCODING
  278 +
  279 +Just has you can pass an IP address directly into an ActiveRecord finder
  280 +as the origin, you can also pass a physical address as the origin:
  281 +
  282 + Location.find_closest(:origin => '100 Spear st, San Francisco, CA')
  283 +
  284 +where the physical address would be geocoded to a location and then the
  285 +resulting latitude and longitude coordinates would be used in the
  286 +find.
  287 +
  288 +Note that if the address fails to geocode, the find method will raise an
  289 +ActiveRecord::GeocodeError you must be prepared to catch. Alternatively,
  290 +You can geocoder the address beforehand, and pass the resulting lat/lng
  291 +into the finder if successful.
  292 +
  293 +## Auto Geocoding
  294 +
  295 +If your geocoding needs are simple, you can tell your model to automatically
  296 +geocode itself on create:
  297 +
  298 + class Store < ActiveRecord::Base
  299 + acts_as_mappable :auto_geocode=>true
  300 + end
  301 +
  302 +It takes two optional params:
  303 +
  304 + class Store < ActiveRecord::Base
  305 + acts_as_mappable :auto_geocode=>{:field=>:address, :error_message=>'Could not geocode address'}
  306 + end
  307 +
  308 +. . . which is equivilent to:
  309 +
  310 + class Store << ActiveRecord::Base
  311 + acts_as_mappable
  312 + before_validation_on_create :geocode_address
  313 +
  314 + private
  315 + def geocode_address
  316 + geo=GeoKit::Geocoders::MultiGeocoder.geocode (address)
  317 + errors.add(:address, "Could not Geocode address") if !geo.success
  318 + self.lat, self.lng = geo.lat,geo.lng if geo.success
  319 + end
  320 + end
  321 +
  322 +If you need any more complicated geocoding behavior for your model, you should roll your own
  323 +before_validate callback.
  324 +
  325 +
  326 +## Distances, headings, endpoints, and midpoints
  327 +
  328 + distance=home.distance_from(work, :units=>:miles)
  329 + heading=home.heading_to(work) # result is in degrees, 0 is north
  330 + endpoint=home.endpoint(90,2) # two miles due east
  331 + midpoing=home.midpoint_to(work)
  332 +
  333 +## Cool stuff you can do with bounds
  334 +
  335 + bounds=Bounds.new(sw_point,ne_point)
  336 + bounds.contains?(home)
  337 + puts bounds.center
  338 +
  339 +
  340 +HOW TO . . .
  341 +=================================================================================
  342 +
  343 +## How to install the GeoKit plugin
  344 + cd [APP_ROOT]
  345 + ruby script/plugin install svn://rubyforge.org/var/svn/geokit/trunk
  346 + or, to install as an external (your project must be version controlled):
  347 + ruby script/plugin install -x svn://rubyforge.org/var/svn/geokit/trunk
  348 +
  349 +## How to find all stores within a 10-mile radius of a given lat/lng
  350 +1. ensure your stores table has lat and lng columns with numeric or float
  351 + datatypes to store your latitude/longitude
  352 +
  353 +3. use acts_as_mappable on your store model:
  354 + class Store < ActiveRecord::Base
  355 + acts_as_mappable
  356 + ...
  357 + end
  358 +3. finders now have extra capabilities:
  359 + Store.find(:all, :origin =>[32.951613,-96.958444], :within=>10)
  360 +
  361 +## How to geocode an address
  362 +
  363 +1. configure your geocoder key(s) in environment.rb
  364 +
  365 +2. also in environment.rb, make sure that PROVIDER_ORDER reflects the
  366 + geocoder(s). If you only want to use one geocoder, there should
  367 + be only one symbol in the array. For example:
  368 + PROVIDER_ORDER=[:google]
  369 +
  370 +3. Test it out in script/console
  371 + include GeoKit::Geocoders
  372 + res = MultiGeocoder.geocode('100 Spear St, San Francisco, CA')
  373 + puts res.lat
  374 + puts res.lng
  375 + puts res.full_address
  376 + ... etc. The return type is GeoLoc, see the API for
  377 + all the methods you can call on it.
  378 +
  379 +## How to find all stores within 10 miles of a given address
  380 +
  381 +1. as above, ensure your table has the lat/lng columns, and you've
  382 + applied acts_as_mappable to the Store model.
  383 +
  384 +2. configure and test out your geocoder, as above
  385 +
  386 +3. pass the address in under the :origin key
  387 + Store.find(:all, :origin=>'100 Spear st, San Francisco, CA',
  388 + :within=>10)
  389 +
  390 +4. you can also use a zipcode, or anything else that's geocodable:
  391 + Store.find(:all, :origin=>'94117',
  392 + :conditions=>'distance<10')
  393 +
  394 +## How to sort a query by distance from an origin
  395 +
  396 +You now have access to a 'distance' column, and you can use it
  397 +as you would any other column. For example:
  398 + Store.find(:all, :origin=>'94117', :order=>'distance')
  399 +
  400 +## How to elements of an array according to distance from a common point
  401 +
  402 +Usually, you can do your sorting in the database as part of your find call.
  403 +If you need to sort things post-query, you can do so:
  404 +
  405 + stores=Store.find :all
  406 + stores.sort_by_distance_from(home)
  407 + puts stores.first.distance
  408 +
  409 +Obviously, each of the items in the array must have a latitude/longitude so
  410 +they can be sorted by distance.
  411 +
  412 +Database Compatability
  413 +=================================================================================
  414 +GeoKit does *not* work with SQLite, as it lacks the necessary geometry functions.
  415 +GeoKit works with MySQL (tested with version 5.0.41) or PostgreSQL (tested with version 8.2.6)
  416 +GeoKit is known to *not* work with Postgres <8.1 -- it uses the least() funciton.
  417 +
  418 +
  419 +HIGH-LEVEL NOTES ON WHAT'S WHERE
  420 +=================================================================================
  421 +
  422 +acts_as_mappable.rb, as you'd expect, contains the ActsAsMappable
  423 +module which gets mixed into your models to provide the
  424 +location-based finder goodness.
  425 +
  426 +mappable.rb contains the Mappable module, which provides basic
  427 +distance calculation methods, i.e., calculating the distance
  428 +between two points.
  429 +
  430 +mappable.rb also contains LatLng, GeoLoc, and Bounds.
  431 +LatLng is a simple container for latitude and longitude, but
  432 +it's made more powerful by mixing in the above-mentioned Mappable
  433 +module -- therefore, you can calculate easily the distance between two
  434 +LatLng ojbects with distance = first.distance_to(other)
  435 +
  436 +GeoLoc (also in mappable.rb) represents an address or location which
  437 +has been geocoded. You can get the city, zipcode, street address, etc.
  438 +from a GeoLoc object. GeoLoc extends LatLng, so you also get lat/lng
  439 +AND the Mappable modeule goodness for free.
  440 +
  441 +geocoders.rb contains the geocoder classes.
  442 +
  443 +ip_geocode_lookup.rb contains the before_filter helper method which
  444 +enables auto lookup of the requesting IP address.
  445 +
  446 +## IMPORTANT NOTE: We have appended to your environment.rb file
  447 +
  448 +Installation of this plugin has appended an API key template
  449 +to your environment.rb file. You *must* add your own keys for the various
  450 +geocoding services if you want to use geocoding. If you need to refer to the original
  451 +template again, see the api_keys_template file in the root of the plugin.
... ...
vendor/plugins/geokit/Rakefile 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +require 'rake'
  2 +require 'rake/testtask'
  3 +require 'rake/rdoctask'
  4 +
  5 +desc 'Default: run unit tests.'
  6 +task :default => :test
  7 +
  8 +desc 'Test the GeoKit plugin.'
  9 +Rake::TestTask.new(:test) do |t|
  10 + t.libs << 'lib'
  11 + t.pattern = 'test/**/*_test.rb'
  12 + t.verbose = true
  13 +end
  14 +
  15 +desc 'Generate documentation for the GeoKit plugin.'
  16 +Rake::RDocTask.new(:rdoc) do |rdoc|
  17 + rdoc.rdoc_dir = 'rdoc'
  18 + rdoc.title = 'GeoKit'
  19 + rdoc.options << '--line-numbers' << '--inline-source'
  20 + rdoc.rdoc_files.include('README')
  21 + rdoc.rdoc_files.include('lib/**/*.rb')
  22 +end
0 23 \ No newline at end of file
... ...
vendor/plugins/geokit/VERSION_HISTORY.txt 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 +01/20/08 Version 1.0.1. Further fix of distance calculation, this time in SQL. Now uses least() function, which is available in MySQL version 3.22.5+ and postgres versions 8.1+
  2 +01/16/08 fixed the "zero-distance" bug (calculating between two points that are the same)
  3 +12/11/07 fixed a small but with queries crossing meridian, and also fixed find(:closest)
  4 +10/11/07 Fixed Rails2/Edge compatability
0 5 \ No newline at end of file
... ...
vendor/plugins/geokit/about.yml 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +author:
  2 + name_1: Bill Eisenhauer
  3 + homepage_1: http://blog.billeisenhauer.com
  4 + name_2: Andre Lewis
  5 + homepage_2: http://www.earthcode.com
  6 +summary: Geo distance calculations, distance calculation query support, geocoding for physical and ip addresses.
  7 +version: 1.0.1
  8 +rails_version: 1.0+
  9 +license: MIT
0 10 \ No newline at end of file
... ...
vendor/plugins/geokit/assets/api_keys_template 0 → 100644
... ... @@ -0,0 +1,50 @@
  1 +# These defaults are used in GeoKit::Mappable.distance_to and in acts_as_mappable
  2 +GeoKit::default_units = :miles
  3 +GeoKit::default_formula = :sphere
  4 +
  5 +# This is the timeout value in seconds to be used for calls to the geocoder web
  6 +# services. For no timeout at all, comment out the setting. The timeout unit
  7 +# is in seconds.
  8 +GeoKit::Geocoders::timeout = 3
  9 +
  10 +# These settings are used if web service calls must be routed through a proxy.
  11 +# These setting can be nil if not needed, otherwise, addr and port must be
  12 +# filled in at a minimum. If the proxy requires authentication, the username
  13 +# and password can be provided as well.
  14 +GeoKit::Geocoders::proxy_addr = nil
  15 +GeoKit::Geocoders::proxy_port = nil
  16 +GeoKit::Geocoders::proxy_user = nil
  17 +GeoKit::Geocoders::proxy_pass = nil
  18 +
  19 +# This is your yahoo application key for the Yahoo Geocoder.
  20 +# See http://developer.yahoo.com/faq/index.html#appid
  21 +# and http://developer.yahoo.com/maps/rest/V1/geocode.html
  22 +GeoKit::Geocoders::yahoo = 'REPLACE_WITH_YOUR_YAHOO_KEY'
  23 +
  24 +# This is your Google Maps geocoder key.
  25 +# See http://www.google.com/apis/maps/signup.html
  26 +# and http://www.google.com/apis/maps/documentation/#Geocoding_Examples
  27 +GeoKit::Geocoders::google = 'REPLACE_WITH_YOUR_GOOGLE_KEY'
  28 +
  29 +# This is your username and password for geocoder.us.
  30 +# To use the free service, the value can be set to nil or false. For
  31 +# usage tied to an account, the value should be set to username:password.
  32 +# See http://geocoder.us
  33 +# and http://geocoder.us/user/signup
  34 +GeoKit::Geocoders::geocoder_us = false
  35 +
  36 +# This is your authorization key for geocoder.ca.
  37 +# To use the free service, the value can be set to nil or false. For
  38 +# usage tied to an account, set the value to the key obtained from
  39 +# Geocoder.ca.
  40 +# See http://geocoder.ca
  41 +# and http://geocoder.ca/?register=1
  42 +GeoKit::Geocoders::geocoder_ca = false
  43 +
  44 +# This is the order in which the geocoders are called in a failover scenario
  45 +# If you only want to use a single geocoder, put a single symbol in the array.
  46 +# Valid symbols are :google, :yahoo, :us, and :ca.
  47 +# Be aware that there are Terms of Use restrictions on how you can use the
  48 +# various geocoders. Make sure you read up on relevant Terms of Use for each
  49 +# geocoder you are going to use.
  50 +GeoKit::Geocoders::provider_order = [:google,:us]
0 51 \ No newline at end of file
... ...
vendor/plugins/geokit/init.rb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +# Load modules and classes needed to automatically mix in ActiveRecord and
  2 +# ActionController helpers. All other functionality must be explicitly
  3 +# required.
  4 +require 'geo_kit/defaults'
  5 +require 'geo_kit/mappable'
  6 +require 'geo_kit/acts_as_mappable'
  7 +require 'geo_kit/ip_geocode_lookup'
  8 +
  9 +# Automatically mix in distance finder support into ActiveRecord classes.
  10 +ActiveRecord::Base.send :include, GeoKit::ActsAsMappable
  11 +
  12 +# Automatically mix in ip geocoding helpers into ActionController classes.
  13 +ActionController::Base.send :include, GeoKit::IpGeocodeLookup
... ...
vendor/plugins/geokit/install.rb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +# Display to the console the contents of the README file.
  2 +puts IO.read(File.join(File.dirname(__FILE__), 'README'))
  3 +
  4 +# Append the contents of api_keys_template to the application's environment.rb file
  5 +environment_rb = File.open(File.expand_path(File.join(File.dirname(__FILE__), '../../../config/environment.rb')), "a")
  6 +environment_rb.puts IO.read(File.join(File.dirname(__FILE__), '/assets/api_keys_template'))
  7 +environment_rb.close
... ...
vendor/plugins/geokit/lib/geo_kit/acts_as_mappable.rb 0 → 100644
... ... @@ -0,0 +1,436 @@
  1 +module GeoKit
  2 + # Contains the class method acts_as_mappable targeted to be mixed into ActiveRecord.
  3 + # When mixed in, augments find services such that they provide distance calculation
  4 + # query services. The find method accepts additional options:
  5 + #
  6 + # * :origin - can be
  7 + # 1. a two-element array of latititude/longitude -- :origin=>[37.792,-122.393]
  8 + # 2. a geocodeable string -- :origin=>'100 Spear st, San Francisco, CA'
  9 + # 3. an object which responds to lat and lng methods, or latitude and longitude methods,
  10 + # or whatever methods you have specified for lng_column_name and lat_column_name
  11 + #
  12 + # Other finder methods are provided for specific queries. These are:
  13 + #
  14 + # * find_within (alias: find_inside)
  15 + # * find_beyond (alias: find_outside)
  16 + # * find_closest (alias: find_nearest)
  17 + # * find_farthest
  18 + #
  19 + # Counter methods are available and work similarly to finders.
  20 + #
  21 + # If raw SQL is desired, the distance_sql method can be used to obtain SQL appropriate
  22 + # to use in a find_by_sql call.
  23 + module ActsAsMappable
  24 + # Mix below class methods into ActiveRecord.
  25 + def self.included(base) # :nodoc:
  26 + base.extend ClassMethods
  27 + end
  28 +
  29 + # Class method to mix into active record.
  30 + module ClassMethods # :nodoc:
  31 + # Class method to bring distance query support into ActiveRecord models. By default
  32 + # uses :miles for distance units and performs calculations based upon the Haversine
  33 + # (sphere) formula. These can be changed by setting GeoKit::default_units and
  34 + # GeoKit::default_formula. Also, by default, uses lat, lng, and distance for respective
  35 + # column names. All of these can be overridden using the :default_units, :default_formula,
  36 + # :lat_column_name, :lng_column_name, and :distance_column_name hash keys.
  37 + #
  38 + # Can also use to auto-geocode a specific column on create. Syntax;
  39 + #
  40 + # acts_as_mappable :auto_geocode=>true
  41 + #
  42 + # By default, it tries to geocode the "address" field. Or, for more customized behavior:
  43 + #
  44 + # acts_as_mappable :auto_geocode=>{:field=>:address,:error_message=>'bad address'}
  45 + #
  46 + # In both cases, it creates a before_validation_on_create callback to geocode the given column.
  47 + # For anything more customized, we recommend you forgo the auto_geocode option
  48 + # and create your own AR callback to handle geocoding.
  49 + def acts_as_mappable(options = {})
  50 + # Mix in the module, but ensure to do so just once.
  51 + return if self.included_modules.include?(GeoKit::ActsAsMappable::InstanceMethods)
  52 + send :include, GeoKit::ActsAsMappable::InstanceMethods
  53 + # include the Mappable module.
  54 + send :include, Mappable
  55 +
  56 + # Handle class variables.
  57 + cattr_accessor :distance_column_name, :default_units, :default_formula, :lat_column_name, :lng_column_name, :qualified_lat_column_name, :qualified_lng_column_name
  58 + self.distance_column_name = options[:distance_column_name] || 'distance'
  59 + self.default_units = options[:default_units] || GeoKit::default_units
  60 + self.default_formula = options[:default_formula] || GeoKit::default_formula
  61 + self.lat_column_name = options[:lat_column_name] || 'lat'
  62 + self.lng_column_name = options[:lng_column_name] || 'lng'
  63 + self.qualified_lat_column_name = "#{table_name}.#{lat_column_name}"
  64 + self.qualified_lng_column_name = "#{table_name}.#{lng_column_name}"
  65 + if options.include?(:auto_geocode) && options[:auto_geocode]
  66 + # if the form auto_geocode=>true is used, let the defaults take over by suppling an empty hash
  67 + options[:auto_geocode] = {} if options[:auto_geocode] == true
  68 + cattr_accessor :auto_geocode_field, :auto_geocode_error_message
  69 + self.auto_geocode_field = options[:auto_geocode][:field] || 'address'
  70 + self.auto_geocode_error_message = options[:auto_geocode][:error_message] || 'could not locate address'
  71 +
  72 + # set the actual callback here
  73 + before_validation_on_create :auto_geocode_address
  74 + end
  75 + end
  76 + end
  77 +
  78 + # this is the callback for auto_geocoding
  79 + def auto_geocode_address
  80 + address=self.send(auto_geocode_field)
  81 + geo=GeoKit::Geocoders::MultiGeocoder.geocode(address)
  82 +
  83 + if geo.success
  84 + self.send("#{lat_column_name}=", geo.lat)
  85 + self.send("#{lng_column_name}=", geo.lng)
  86 + else
  87 + errors.add(auto_geocode_field, auto_geocode_error_message)
  88 + end
  89 +
  90 + geo.success
  91 + end
  92 +
  93 + # Instance methods to mix into ActiveRecord.
  94 + module InstanceMethods #:nodoc:
  95 + # Mix class methods into module.
  96 + def self.included(base) # :nodoc:
  97 + base.extend SingletonMethods
  98 + end
  99 +
  100 + # Class singleton methods to mix into ActiveRecord.
  101 + module SingletonMethods # :nodoc:
  102 + # Extends the existing find method in potentially two ways:
  103 + # - If a mappable instance exists in the options, adds a distance column.
  104 + # - If a mappable instance exists in the options and the distance column exists in the
  105 + # conditions, substitutes the distance sql for the distance column -- this saves
  106 + # having to write the gory SQL.
  107 + def find(*args)
  108 + prepare_for_find_or_count(:find, args)
  109 + super(*args)
  110 + end
  111 +
  112 + # Extends the existing count method by:
  113 + # - If a mappable instance exists in the options and the distance column exists in the
  114 + # conditions, substitutes the distance sql for the distance column -- this saves
  115 + # having to write the gory SQL.
  116 + def count(*args)
  117 + prepare_for_find_or_count(:count, args)
  118 + super(*args)
  119 + end
  120 +
  121 + # Finds within a distance radius.
  122 + def find_within(distance, options={})
  123 + options[:within] = distance
  124 + find(:all, options)
  125 + end
  126 + alias find_inside find_within
  127 +
  128 + # Finds beyond a distance radius.
  129 + def find_beyond(distance, options={})
  130 + options[:beyond] = distance
  131 + find(:all, options)
  132 + end
  133 + alias find_outside find_beyond
  134 +
  135 + # Finds according to a range. Accepts inclusive or exclusive ranges.
  136 + def find_by_range(range, options={})
  137 + options[:range] = range
  138 + find(:all, options)
  139 + end
  140 +
  141 + # Finds the closest to the origin.
  142 + def find_closest(options={})
  143 + find(:nearest, options)
  144 + end
  145 + alias find_nearest find_closest
  146 +
  147 + # Finds the farthest from the origin.
  148 + def find_farthest(options={})
  149 + find(:farthest, options)
  150 + end
  151 +
  152 + # Finds within rectangular bounds (sw,ne).
  153 + def find_within_bounds(bounds, options={})
  154 + options[:bounds] = bounds
  155 + find(:all, options)
  156 + end
  157 +
  158 + # counts within a distance radius.
  159 + def count_within(distance, options={})
  160 + options[:within] = distance
  161 + count(options)
  162 + end
  163 + alias count_inside count_within
  164 +
  165 + # Counts beyond a distance radius.
  166 + def count_beyond(distance, options={})
  167 + options[:beyond] = distance
  168 + count(options)
  169 + end
  170 + alias count_outside count_beyond
  171 +
  172 + # Counts according to a range. Accepts inclusive or exclusive ranges.
  173 + def count_by_range(range, options={})
  174 + options[:range] = range
  175 + count(options)
  176 + end
  177 +
  178 + # Finds within rectangular bounds (sw,ne).
  179 + def count_within_bounds(bounds, options={})
  180 + options[:bounds] = bounds
  181 + count(options)
  182 + end
  183 +
  184 + # Returns the distance calculation to be used as a display column or a condition. This
  185 + # is provide for anyone wanting access to the raw SQL.
  186 + def distance_sql(origin, units=default_units, formula=default_formula)
  187 + case formula
  188 + when :sphere
  189 + sql = sphere_distance_sql(origin, units)
  190 + when :flat
  191 + sql = flat_distance_sql(origin, units)
  192 + end
  193 + sql
  194 + end
  195 +
  196 + private
  197 +
  198 + # Prepares either a find or a count action by parsing through the options and
  199 + # conditionally adding to the select clause for finders.
  200 + def prepare_for_find_or_count(action, args)
  201 + options = defined?(args.extract_options!) ? args.extract_options! : extract_options_from_args!(args)
  202 + # Obtain items affecting distance condition.
  203 + origin = extract_origin_from_options(options)
  204 + units = extract_units_from_options(options)
  205 + formula = extract_formula_from_options(options)
  206 + bounds = extract_bounds_from_options(options)
  207 + # if no explicit bounds were given, try formulating them from the point and distance given
  208 + bounds = formulate_bounds_from_distance(options, origin, units) unless bounds
  209 + # Apply select adjustments based upon action.
  210 + add_distance_to_select(options, origin, units, formula) if origin && action == :find
  211 + # Apply the conditions for a bounding rectangle if applicable
  212 + apply_bounds_conditions(options,bounds) if bounds
  213 + # Apply distance scoping and perform substitutions.
  214 + apply_distance_scope(options)
  215 + substitute_distance_in_conditions(options, origin, units, formula) if origin && options.has_key?(:conditions)
  216 + # Order by scoping for find action.
  217 + apply_find_scope(args, options) if action == :find
  218 + # Unfortunatley, we need to do extra work if you use an :include. See the method for more info.
  219 + handle_order_with_include(options,origin,units,formula) if options.include?(:include) && options.include?(:order) && origin
  220 + # Restore options minus the extra options that we used for the
  221 + # GeoKit API.
  222 + args.push(options)
  223 + end
  224 +
  225 + # If we're here, it means that 1) an origin argument, 2) an :include, 3) an :order clause were supplied.
  226 + # Now we have to sub some SQL into the :order clause. The reason is that when you do an :include,
  227 + # ActiveRecord drops the psuedo-column (specificically, distance) which we supplied for :select.
  228 + # So, the 'distance' column isn't available for the :order clause to reference when we use :include.
  229 + def handle_order_with_include(options, origin, units, formula)
  230 + # replace the distance_column_name with the distance sql in order clause
  231 + options[:order].sub!(distance_column_name, distance_sql(origin, units, formula))
  232 + end
  233 +
  234 + # Looks for mapping-specific tokens and makes appropriate translations so that the
  235 + # original finder has its expected arguments. Resets the the scope argument to
  236 + # :first and ensures the limit is set to one.
  237 + def apply_find_scope(args, options)
  238 + case args.first
  239 + when :nearest, :closest
  240 + args[0] = :first
  241 + options[:limit] = 1
  242 + options[:order] = "#{distance_column_name} ASC"
  243 + when :farthest
  244 + args[0] = :first
  245 + options[:limit] = 1
  246 + options[:order] = "#{distance_column_name} DESC"
  247 + end
  248 + end
  249 +
  250 + # If it's a :within query, add a bounding box to improve performance.
  251 + # This only gets called if a :bounds argument is not otherwise supplied.
  252 + def formulate_bounds_from_distance(options, origin, units)
  253 + distance = options[:within] if options.has_key?(:within)
  254 + distance = options[:range].last-(options[:range].exclude_end?? 1 : 0) if options.has_key?(:range)
  255 + if distance
  256 + res=GeoKit::Bounds.from_point_and_radius(origin,distance,:units=>units)
  257 + else
  258 + nil
  259 + end
  260 + end
  261 +
  262 + # Replace :within, :beyond and :range distance tokens with the appropriate distance
  263 + # where clauses. Removes these tokens from the options hash.
  264 + def apply_distance_scope(options)
  265 + distance_condition = "#{distance_column_name} <= #{options[:within]}" if options.has_key?(:within)
  266 + distance_condition = "#{distance_column_name} > #{options[:beyond]}" if options.has_key?(:beyond)
  267 + distance_condition = "#{distance_column_name} >= #{options[:range].first} AND #{distance_column_name} <#{'=' unless options[:range].exclude_end?} #{options[:range].last}" if options.has_key?(:range)
  268 + [:within, :beyond, :range].each { |option| options.delete(option) } if distance_condition
  269 +
  270 + options[:conditions]=augment_conditions(options[:conditions],distance_condition) if distance_condition
  271 + end
  272 +
  273 + # This method lets you transparently add a new condition to a query without
  274 + # worrying about whether it currently has conditions, or what kind of conditions they are
  275 + # (string or array).
  276 + #
  277 + # Takes the current conditions (which can be an array or a string, or can be nil/false),
  278 + # and a SQL string. It inserts the sql into the existing conditions, and returns new conditions
  279 + # (which can be a string or an array
  280 + def augment_conditions(current_conditions,sql)
  281 + if current_conditions && current_conditions.is_a?(String)
  282 + res="#{current_conditions} AND #{sql}"
  283 + elsif current_conditions && current_conditions.is_a?(Array)
  284 + current_conditions[0]="#{current_conditions[0]} AND #{sql}"
  285 + res=current_conditions
  286 + else
  287 + res=sql
  288 + end
  289 + res
  290 + end
  291 +
  292 + # Alters the conditions to include rectangular bounds conditions.
  293 + def apply_bounds_conditions(options,bounds)
  294 + sw,ne=bounds.sw,bounds.ne
  295 + lng_sql= bounds.crosses_meridian? ? "(#{qualified_lng_column_name}<#{sw.lng} OR #{qualified_lng_column_name}>#{ne.lng})" : "#{qualified_lng_column_name}>#{sw.lng} AND #{qualified_lng_column_name}<#{ne.lng}"
  296 + bounds_sql="#{qualified_lat_column_name}>#{sw.lat} AND #{qualified_lat_column_name}<#{ne.lat} AND #{lng_sql}"
  297 + options[:conditions]=augment_conditions(options[:conditions],bounds_sql)
  298 + end
  299 +
  300 + # Extracts the origin instance out of the options if it exists and returns
  301 + # it. If there is no origin, looks for latitude and longitude values to
  302 + # create an origin. The side-effect of the method is to remove these
  303 + # option keys from the hash.
  304 + def extract_origin_from_options(options)
  305 + origin = options.delete(:origin)
  306 + res = normalize_point_to_lat_lng(origin) if origin
  307 + res
  308 + end
  309 +
  310 + # Extract the units out of the options if it exists and returns it. If
  311 + # there is no :units key, it uses the default. The side effect of the
  312 + # method is to remove the :units key from the options hash.
  313 + def extract_units_from_options(options)
  314 + units = options[:units] || default_units
  315 + options.delete(:units)
  316 + units
  317 + end
  318 +
  319 + # Extract the formula out of the options if it exists and returns it. If
  320 + # there is no :formula key, it uses the default. The side effect of the
  321 + # method is to remove the :formula key from the options hash.
  322 + def extract_formula_from_options(options)
  323 + formula = options[:formula] || default_formula
  324 + options.delete(:formula)
  325 + formula
  326 + end
  327 +
  328 + def extract_bounds_from_options(options)
  329 + bounds = options.delete(:bounds)
  330 + bounds = GeoKit::Bounds.normalize(bounds) if bounds
  331 + end
  332 +
  333 + # Geocode IP address.
  334 + def geocode_ip_address(origin)
  335 + geo_location = GeoKit::Geocoders::IpGeocoder.geocode(origin)
  336 + return geo_location if geo_location.success
  337 + raise GeoKit::Geocoders::GeocodeError
  338 + end
  339 +
  340 +
  341 + # Given a point in a variety of (an address to geocode,
  342 + # an array of [lat,lng], or an object with appropriate lat/lng methods, an IP addres)
  343 + # this method will normalize it into a GeoKit::LatLng instance. The only thing this
  344 + # method adds on top of LatLng#normalize is handling of IP addresses
  345 + def normalize_point_to_lat_lng(point)
  346 + res = geocode_ip_address(point) if point.is_a?(String) && /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(point)
  347 + res = GeoKit::LatLng.normalize(point) unless res
  348 + res
  349 + end
  350 +
  351 + # Augments the select with the distance SQL.
  352 + def add_distance_to_select(options, origin, units=default_units, formula=default_formula)
  353 + if origin
  354 + distance_selector = distance_sql(origin, units, formula) + " AS #{distance_column_name}"
  355 + selector = options.has_key?(:select) && options[:select] ? options[:select] : "*"
  356 + options[:select] = "#{selector}, #{distance_selector}"
  357 + end
  358 + end
  359 +
  360 + # Looks for the distance column and replaces it with the distance sql. If an origin was not
  361 + # passed in and the distance column exists, we leave it to be flagged as bad SQL by the database.
  362 + # Conditions are either a string or an array. In the case of an array, the first entry contains
  363 + # the condition.
  364 + def substitute_distance_in_conditions(options, origin, units=default_units, formula=default_formula)
  365 + original_conditions = options[:conditions]
  366 + condition = original_conditions.is_a?(String) ? original_conditions : original_conditions.first
  367 + pattern = Regexp.new("\s*#{distance_column_name}(\s<>=)*")
  368 + condition = condition.gsub(pattern, distance_sql(origin, units, formula))
  369 + original_conditions = condition if original_conditions.is_a?(String)
  370 + original_conditions[0] = condition if original_conditions.is_a?(Array)
  371 + options[:conditions] = original_conditions
  372 + end
  373 +
  374 + # Returns the distance SQL using the spherical world formula (Haversine). The SQL is tuned
  375 + # to the database in use.
  376 + def sphere_distance_sql(origin, units)
  377 + lat = deg2rad(origin.lat)
  378 + lng = deg2rad(origin.lng)
  379 + multiplier = units_sphere_multiplier(units)
  380 + case connection.adapter_name.downcase
  381 + when "mysql"
  382 + sql=<<-SQL_END
  383 + (ACOS(least(1,COS(#{lat})*COS(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*COS(RADIANS(#{qualified_lng_column_name}))+
  384 + COS(#{lat})*SIN(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*SIN(RADIANS(#{qualified_lng_column_name}))+
  385 + SIN(#{lat})*SIN(RADIANS(#{qualified_lat_column_name}))))*#{multiplier})
  386 + SQL_END
  387 + when "postgresql"
  388 + sql=<<-SQL_END
  389 + (ACOS(least(1,COS(#{lat})*COS(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*COS(RADIANS(#{qualified_lng_column_name}))+
  390 + COS(#{lat})*SIN(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*SIN(RADIANS(#{qualified_lng_column_name}))+
  391 + SIN(#{lat})*SIN(RADIANS(#{qualified_lat_column_name}))))*#{multiplier})
  392 + SQL_END
  393 + else
  394 + sql = "unhandled #{connection.adapter_name.downcase} adapter"
  395 + end
  396 + end
  397 +
  398 + # Returns the distance SQL using the flat-world formula (Phythagorean Theory). The SQL is tuned
  399 + # to the database in use.
  400 + def flat_distance_sql(origin, units)
  401 + lat_degree_units = units_per_latitude_degree(units)
  402 + lng_degree_units = units_per_longitude_degree(origin.lat, units)
  403 + case connection.adapter_name.downcase
  404 + when "mysql"
  405 + sql=<<-SQL_END
  406 + SQRT(POW(#{lat_degree_units}*(#{origin.lat}-#{qualified_lat_column_name}),2)+
  407 + POW(#{lng_degree_units}*(#{origin.lng}-#{qualified_lng_column_name}),2))
  408 + SQL_END
  409 + when "postgresql"
  410 + sql=<<-SQL_END
  411 + SQRT(POW(#{lat_degree_units}*(#{origin.lat}-#{qualified_lat_column_name}),2)+
  412 + POW(#{lng_degree_units}*(#{origin.lng}-#{qualified_lng_column_name}),2))
  413 + SQL_END
  414 + else
  415 + sql = "unhandled #{connection.adapter_name.downcase} adapter"
  416 + end
  417 + end
  418 + end
  419 + end
  420 + end
  421 +end
  422 +
  423 +# Extend Array with a sort_by_distance method.
  424 +# This method creates a "distance" attribute on each object,
  425 +# calculates the distance from the passed origin,
  426 +# and finally sorts the array by the resulting distance.
  427 +class Array
  428 + def sort_by_distance_from(origin, opts={})
  429 + distance_attribute_name = opts.delete(:distance_attribute_name) || 'distance'
  430 + self.each do |e|
  431 + e.class.send(:attr_accessor, distance_attribute_name) if !e.respond_to? "#{distance_attribute_name}="
  432 + e.send("#{distance_attribute_name}=", origin.distance_to(e,opts))
  433 + end
  434 + self.sort!{|a,b|a.send(distance_attribute_name) <=> b.send(distance_attribute_name)}
  435 + end
  436 +end
0 437 \ No newline at end of file
... ...
vendor/plugins/geokit/lib/geo_kit/defaults.rb 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +module GeoKit
  2 + # These defaults are used in GeoKit::Mappable.distance_to and in acts_as_mappable
  3 + @@default_units = :miles
  4 + @@default_formula = :sphere
  5 +
  6 + [:default_units, :default_formula].each do |sym|
  7 + class_eval <<-EOS, __FILE__, __LINE__
  8 + def self.#{sym}
  9 + if defined?(#{sym.to_s.upcase})
  10 + #{sym.to_s.upcase}
  11 + else
  12 + @@#{sym}
  13 + end
  14 + end
  15 +
  16 + def self.#{sym}=(obj)
  17 + @@#{sym} = obj
  18 + end
  19 + EOS
  20 + end
  21 +end
... ...
vendor/plugins/geokit/lib/geo_kit/geocoders.rb 0 → 100644
... ... @@ -0,0 +1,348 @@
  1 +require 'net/http'
  2 +require 'rexml/document'
  3 +require 'yaml'
  4 +require 'timeout'
  5 +
  6 +module GeoKit
  7 + # Contains a set of geocoders which can be used independently if desired. The list contains:
  8 + #
  9 + # * Google Geocoder - requires an API key.
  10 + # * Yahoo Geocoder - requires an API key.
  11 + # * Geocoder.us - may require authentication if performing more than the free request limit.
  12 + # * Geocoder.ca - for Canada; may require authentication as well.
  13 + # * IP Geocoder - geocodes an IP address using hostip.info's web service.
  14 + # * Multi Geocoder - provides failover for the physical location geocoders.
  15 + #
  16 + # Some configuration is required for these geocoders and can be located in the environment
  17 + # configuration files.
  18 + module Geocoders
  19 + @@proxy_addr = nil
  20 + @@proxy_port = nil
  21 + @@proxy_user = nil
  22 + @@proxy_pass = nil
  23 + @@timeout = nil
  24 + @@yahoo = 'REPLACE_WITH_YOUR_YAHOO_KEY'
  25 + @@google = 'REPLACE_WITH_YOUR_GOOGLE_KEY'
  26 + @@geocoder_us = false
  27 + @@geocoder_ca = false
  28 + @@provider_order = [:google,:us]
  29 +
  30 + [:yahoo, :google, :geocoder_us, :geocoder_ca, :provider_order, :timeout,
  31 + :proxy_addr, :proxy_port, :proxy_user, :proxy_pass].each do |sym|
  32 + class_eval <<-EOS, __FILE__, __LINE__
  33 + def self.#{sym}
  34 + if defined?(#{sym.to_s.upcase})
  35 + #{sym.to_s.upcase}
  36 + else
  37 + @@#{sym}
  38 + end
  39 + end
  40 +
  41 + def self.#{sym}=(obj)
  42 + @@#{sym} = obj
  43 + end
  44 + EOS
  45 + end
  46 +
  47 + # Error which is thrown in the event a geocoding error occurs.
  48 + class GeocodeError < StandardError; end
  49 +
  50 + # The Geocoder base class which defines the interface to be used by all
  51 + # other geocoders.
  52 + class Geocoder
  53 + # Main method which calls the do_geocode template method which subclasses
  54 + # are responsible for implementing. Returns a populated GeoLoc or an
  55 + # empty one with a failed success code.
  56 + def self.geocode(address)
  57 + res = do_geocode(address)
  58 + return res.success ? res : GeoLoc.new
  59 + end
  60 +
  61 + # Call the geocoder service using the timeout if configured.
  62 + def self.call_geocoder_service(url)
  63 + timeout(GeoKit::Geocoders::timeout) { return self.do_get(url) } if GeoKit::Geocoders::timeout
  64 + return self.do_get(url)
  65 + rescue TimeoutError
  66 + return nil
  67 + end
  68 +
  69 + protected
  70 +
  71 + def self.logger() RAILS_DEFAULT_LOGGER; end
  72 +
  73 + private
  74 +
  75 + # Wraps the geocoder call around a proxy if necessary.
  76 + def self.do_get(url)
  77 + return Net::HTTP::Proxy(GeoKit::Geocoders::proxy_addr, GeoKit::Geocoders::proxy_port,
  78 + GeoKit::Geocoders::proxy_user, GeoKit::Geocoders::proxy_pass).get_response(URI.parse(url))
  79 + end
  80 +
  81 + # Adds subclass' geocode method making it conveniently available through
  82 + # the base class.
  83 + def self.inherited(clazz)
  84 + class_name = clazz.name.split('::').last
  85 + src = <<-END_SRC
  86 + def self.#{class_name.underscore}(address)
  87 + #{class_name}.geocode(address)
  88 + end
  89 + END_SRC
  90 + class_eval(src)
  91 + end
  92 + end
  93 +
  94 + # Geocoder CA geocoder implementation. Requires the GeoKit::Geocoders::GEOCODER_CA variable to
  95 + # contain true or false based upon whether authentication is to occur. Conforms to the
  96 + # interface set by the Geocoder class.
  97 + #
  98 + # Returns a response like:
  99 + # <?xml version="1.0" encoding="UTF-8" ?>
  100 + # <geodata>
  101 + # <latt>49.243086</latt>
  102 + # <longt>-123.153684</longt>
  103 + # </geodata>
  104 + class CaGeocoder < Geocoder
  105 +
  106 + private
  107 +
  108 + # Template method which does the geocode lookup.
  109 + def self.do_geocode(address)
  110 + raise ArgumentError('Geocoder.ca requires a GeoLoc argument') unless address.is_a?(GeoLoc)
  111 + url = construct_request(address)
  112 + res = self.call_geocoder_service(url)
  113 + return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
  114 + xml = res.body
  115 + logger.debug "Geocoder.ca geocoding. Address: #{address}. Result: #{xml}"
  116 + # Parse the document.
  117 + doc = REXML::Document.new(xml)
  118 + address.lat = doc.elements['//latt'].text
  119 + address.lng = doc.elements['//longt'].text
  120 + address.success = true
  121 + return address
  122 + rescue
  123 + logger.error "Caught an error during Geocoder.ca geocoding call: "+$!
  124 + return GeoLoc.new
  125 + end
  126 +
  127 + # Formats the request in the format acceptable by the CA geocoder.
  128 + def self.construct_request(location)
  129 + url = ""
  130 + url += add_ampersand(url) + "stno=#{location.street_number}" if location.street_address
  131 + url += add_ampersand(url) + "addresst=#{CGI.escape(location.street_name)}" if location.street_address
  132 + url += add_ampersand(url) + "city=#{CGI.escape(location.city)}" if location.city
  133 + url += add_ampersand(url) + "prov=#{location.state}" if location.state
  134 + url += add_ampersand(url) + "postal=#{location.zip}" if location.zip
  135 + url += add_ampersand(url) + "auth=#{GeoKit::Geocoders::geocoder_ca}" if GeoKit::Geocoders::geocoder_ca
  136 + url += add_ampersand(url) + "geoit=xml"
  137 + 'http://geocoder.ca/?' + url
  138 + end
  139 +
  140 + def self.add_ampersand(url)
  141 + url && url.length > 0 ? "&" : ""
  142 + end
  143 + end
  144 +
  145 + # Google geocoder implementation. Requires the GeoKit::Geocoders::GOOGLE variable to
  146 + # contain a Google API key. Conforms to the interface set by the Geocoder class.
  147 + class GoogleGeocoder < Geocoder
  148 +
  149 + private
  150 +
  151 + # Template method which does the geocode lookup.
  152 + def self.do_geocode(address)
  153 + address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
  154 + res = self.call_geocoder_service("http://maps.google.com/maps/geo?q=#{CGI.escape(address_str)}&output=xml&key=#{GeoKit::Geocoders::google}&oe=utf-8")
  155 +# res = Net::HTTP.get_response(URI.parse("http://maps.google.com/maps/geo?q=#{CGI.escape(address_str)}&output=xml&key=#{GeoKit::Geocoders::google}&oe=utf-8"))
  156 + return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
  157 + xml=res.body
  158 + logger.debug "Google geocoding. Address: #{address}. Result: #{xml}"
  159 + doc=REXML::Document.new(xml)
  160 +
  161 + if doc.elements['//kml/Response/Status/code'].text == '200'
  162 + res = GeoLoc.new
  163 + coordinates=doc.elements['//coordinates'].text.to_s.split(',')
  164 +
  165 + #basics
  166 + res.lat=coordinates[1]
  167 + res.lng=coordinates[0]
  168 + res.country_code=doc.elements['//CountryNameCode'].text
  169 + res.provider='google'
  170 +
  171 + #extended -- false if not not available
  172 + res.city = doc.elements['//LocalityName'].text if doc.elements['//LocalityName']
  173 + res.state = doc.elements['//AdministrativeAreaName'].text if doc.elements['//AdministrativeAreaName']
  174 + res.full_address = doc.elements['//address'].text if doc.elements['//address'] # google provides it
  175 + res.zip = doc.elements['//PostalCodeNumber'].text if doc.elements['//PostalCodeNumber']
  176 + res.street_address = doc.elements['//ThoroughfareName'].text if doc.elements['//ThoroughfareName']
  177 + # Translate accuracy into Yahoo-style token address, street, zip, zip+4, city, state, country
  178 + # For Google, 1=low accuracy, 8=high accuracy
  179 + # old way -- address_details=doc.elements['//AddressDetails','urn:oasis:names:tc:ciq:xsdschema:xAL:2.0']
  180 + address_details=doc.elements['//*[local-name() = "AddressDetails"]']
  181 + accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0
  182 + res.precision=%w{unknown country state state city zip zip+4 street address}[accuracy]
  183 + res.success=true
  184 +
  185 + return res
  186 + else
  187 + logger.info "Google was unable to geocode address: "+address
  188 + return GeoLoc.new
  189 + end
  190 +
  191 + rescue
  192 + logger.error "Caught an error during Google geocoding call: "+$!
  193 + return GeoLoc.new
  194 + end
  195 + end
  196 +
  197 + # Provides geocoding based upon an IP address. The underlying web service is a hostip.info
  198 + # which sources their data through a combination of publicly available information as well
  199 + # as community contributions.
  200 + class IpGeocoder < Geocoder
  201 +
  202 + private
  203 +
  204 + # Given an IP address, returns a GeoLoc instance which contains latitude,
  205 + # longitude, city, and country code. Sets the success attribute to false if the ip
  206 + # parameter does not match an ip address.
  207 + def self.do_geocode(ip)
  208 + return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
  209 + url = "http://api.hostip.info/get_html.php?ip=#{ip}&position=true"
  210 + response = self.call_geocoder_service(url)
  211 + response.is_a?(Net::HTTPSuccess) ? parse_body(response.body) : GeoLoc.new
  212 + rescue
  213 + logger.error "Caught an error during HostIp geocoding call: "+$!
  214 + return GeoLoc.new
  215 + end
  216 +
  217 + # Converts the body to YAML since its in the form of:
  218 + #
  219 + # Country: UNITED STATES (US)
  220 + # City: Sugar Grove, IL
  221 + # Latitude: 41.7696
  222 + # Longitude: -88.4588
  223 + #
  224 + # then instantiates a GeoLoc instance to populate with location data.
  225 + def self.parse_body(body) # :nodoc:
  226 + yaml = YAML.load(body)
  227 + res = GeoLoc.new
  228 + res.provider = 'hostip'
  229 + res.city, res.state = yaml['City'].split(', ')
  230 + country, res.country_code = yaml['Country'].split(' (')
  231 + res.lat = yaml['Latitude']
  232 + res.lng = yaml['Longitude']
  233 + res.country_code.chop!
  234 + res.success = res.city != "(Private Address)"
  235 + res
  236 + end
  237 + end
  238 +
  239 + # Geocoder Us geocoder implementation. Requires the GeoKit::Geocoders::GEOCODER_US variable to
  240 + # contain true or false based upon whether authentication is to occur. Conforms to the
  241 + # interface set by the Geocoder class.
  242 + class UsGeocoder < Geocoder
  243 +
  244 + private
  245 +
  246 + # For now, the geocoder_method will only geocode full addresses -- not zips or cities in isolation
  247 + def self.do_geocode(address)
  248 + address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
  249 + url = "http://"+(GeoKit::Geocoders::geocoder_us || '')+"geocoder.us/service/csv/geocode?address=#{CGI.escape(address_str)}"
  250 + res = self.call_geocoder_service(url)
  251 + return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
  252 + data = res.body
  253 + logger.debug "Geocoder.us geocoding. Address: #{address}. Result: #{data}"
  254 + array = data.chomp.split(',')
  255 +
  256 + if array.length == 6
  257 + res=GeoLoc.new
  258 + res.lat,res.lng,res.street_address,res.city,res.state,res.zip=array
  259 + res.country_code='US'
  260 + res.success=true
  261 + return res
  262 + else
  263 + logger.info "geocoder.us was unable to geocode address: "+address
  264 + return GeoLoc.new
  265 + end
  266 + rescue
  267 + logger.error "Caught an error during geocoder.us geocoding call: "+$!
  268 + return GeoLoc.new
  269 + end
  270 + end
  271 +
  272 + # Yahoo geocoder implementation. Requires the GeoKit::Geocoders::YAHOO variable to
  273 + # contain a Yahoo API key. Conforms to the interface set by the Geocoder class.
  274 + class YahooGeocoder < Geocoder
  275 +
  276 + private
  277 +
  278 + # Template method which does the geocode lookup.
  279 + def self.do_geocode(address)
  280 + address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
  281 + url="http://api.local.yahoo.com/MapsService/V1/geocode?appid=#{GeoKit::Geocoders::yahoo}&location=#{CGI.escape(address_str)}"
  282 + res = self.call_geocoder_service(url)
  283 + return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
  284 + xml = res.body
  285 + doc = REXML::Document.new(xml)
  286 + logger.debug "Yahoo geocoding. Address: #{address}. Result: #{xml}"
  287 +
  288 + if doc.elements['//ResultSet']
  289 + res=GeoLoc.new
  290 +
  291 + #basic
  292 + res.lat=doc.elements['//Latitude'].text
  293 + res.lng=doc.elements['//Longitude'].text
  294 + res.country_code=doc.elements['//Country'].text
  295 + res.provider='yahoo'
  296 +
  297 + #extended - false if not available
  298 + res.city=doc.elements['//City'].text if doc.elements['//City'] && doc.elements['//City'].text != nil
  299 + res.state=doc.elements['//State'].text if doc.elements['//State'] && doc.elements['//State'].text != nil
  300 + res.zip=doc.elements['//Zip'].text if doc.elements['//Zip'] && doc.elements['//Zip'].text != nil
  301 + res.street_address=doc.elements['//Address'].text if doc.elements['//Address'] && doc.elements['//Address'].text != nil
  302 + res.precision=doc.elements['//Result'].attributes['precision'] if doc.elements['//Result']
  303 + res.success=true
  304 + return res
  305 + else
  306 + logger.info "Yahoo was unable to geocode address: "+address
  307 + return GeoLoc.new
  308 + end
  309 +
  310 + rescue
  311 + logger.info "Caught an error during Yahoo geocoding call: "+$!
  312 + return GeoLoc.new
  313 + end
  314 + end
  315 +
  316 + # Provides methods to geocode with a variety of geocoding service providers, plus failover
  317 + # among providers in the order you configure.
  318 + #
  319 + # Goal:
  320 + # - homogenize the results of multiple geocoders
  321 + #
  322 + # Limitations:
  323 + # - currently only provides the first result. Sometimes geocoders will return multiple results.
  324 + # - currently discards the "accuracy" component of the geocoding calls
  325 + class MultiGeocoder < Geocoder
  326 + private
  327 +
  328 + # This method will call one or more geocoders in the order specified in the
  329 + # configuration until one of the geocoders work.
  330 + #
  331 + # The failover approach is crucial for production-grade apps, but is rarely used.
  332 + # 98% of your geocoding calls will be successful with the first call
  333 + def self.do_geocode(address)
  334 + GeoKit::Geocoders::provider_order.each do |provider|
  335 + begin
  336 + klass = GeoKit::Geocoders.const_get "#{provider.to_s.capitalize}Geocoder"
  337 + res = klass.send :geocode, address
  338 + return res if res.success
  339 + rescue
  340 + logger.error("Something has gone very wrong during geocoding, OR you have configured an invalid class name in GeoKit::Geocoders::provider_order. Address: #{address}. Provider: #{provider}")
  341 + end
  342 + end
  343 + # If we get here, we failed completely.
  344 + GeoLoc.new
  345 + end
  346 + end
  347 + end
  348 +end
0 349 \ No newline at end of file
... ...
vendor/plugins/geokit/lib/geo_kit/ip_geocode_lookup.rb 0 → 100644
... ... @@ -0,0 +1,46 @@
  1 +require 'yaml'
  2 +
  3 +module GeoKit
  4 + # Contains a class method geocode_ip_address which can be used to enable automatic geocoding
  5 + # for request IP addresses. The geocoded information is stored in a cookie and in the
  6 + # session to minimize web service calls. The point of the helper is to enable location-based
  7 + # websites to have a best-guess for new visitors.
  8 + module IpGeocodeLookup
  9 + # Mix below class methods into ActionController.
  10 + def self.included(base) # :nodoc:
  11 + base.extend ClassMethods
  12 + end
  13 +
  14 + # Class method to mix into active record.
  15 + module ClassMethods # :nodoc:
  16 + def geocode_ip_address(filter_options = {})
  17 + before_filter :store_ip_location, filter_options
  18 + end
  19 + end
  20 +
  21 + private
  22 +
  23 + # Places the IP address' geocode location into the session if it
  24 + # can be found. Otherwise, looks for a geo location cookie and
  25 + # uses that value. The last resort is to call the web service to
  26 + # get the value.
  27 + def store_ip_location
  28 + session[:geo_location] ||= retrieve_location_from_cookie_or_service
  29 + cookies[:geo_location] = { :value => session[:geo_location].to_yaml, :expires => 30.days.from_now } if session[:geo_location]
  30 + end
  31 +
  32 + # Uses the stored location value from the cookie if it exists. If
  33 + # no cookie exists, calls out to the web service to get the location.
  34 + def retrieve_location_from_cookie_or_service
  35 + return YAML.load(cookies[:geo_location]) if cookies[:geo_location]
  36 + location = Geocoders::IpGeocoder.geocode(get_ip_address)
  37 + return location.success ? location : nil
  38 + end
  39 +
  40 + # Returns the real ip address, though this could be the localhost ip
  41 + # address. No special handling here anymore.
  42 + def get_ip_address
  43 + request.remote_ip
  44 + end
  45 + end
  46 +end
0 47 \ No newline at end of file
... ...
vendor/plugins/geokit/lib/geo_kit/mappable.rb 0 → 100644
... ... @@ -0,0 +1,432 @@
  1 +require 'geo_kit/defaults'
  2 +
  3 +module GeoKit
  4 + # Contains class and instance methods providing distance calcuation services. This
  5 + # module is meant to be mixed into classes containing lat and lng attributes where
  6 + # distance calculation is desired.
  7 + #
  8 + # At present, two forms of distance calculations are provided:
  9 + #
  10 + # * Pythagorean Theory (flat Earth) - which assumes the world is flat and loses accuracy over long distances.
  11 + # * Haversine (sphere) - which is fairly accurate, but at a performance cost.
  12 + #
  13 + # Distance units supported are :miles and :kms.
  14 + module Mappable
  15 + PI_DIV_RAD = 0.0174
  16 + KMS_PER_MILE = 1.609
  17 + EARTH_RADIUS_IN_MILES = 3963.19
  18 + EARTH_RADIUS_IN_KMS = EARTH_RADIUS_IN_MILES * KMS_PER_MILE
  19 + MILES_PER_LATITUDE_DEGREE = 69.1
  20 + KMS_PER_LATITUDE_DEGREE = MILES_PER_LATITUDE_DEGREE * KMS_PER_MILE
  21 + LATITUDE_DEGREES = EARTH_RADIUS_IN_MILES / MILES_PER_LATITUDE_DEGREE
  22 +
  23 + # Mix below class methods into the includer.
  24 + def self.included(receiver) # :nodoc:
  25 + receiver.extend ClassMethods
  26 + end
  27 +
  28 + module ClassMethods #:nodoc:
  29 + # Returns the distance between two points. The from and to parameters are
  30 + # required to have lat and lng attributes. Valid options are:
  31 + # :units - valid values are :miles or :kms (GeoKit::default_units is the default)
  32 + # :formula - valid values are :flat or :sphere (GeoKit::default_formula is the default)
  33 + def distance_between(from, to, options={})
  34 + from=GeoKit::LatLng.normalize(from)
  35 + to=GeoKit::LatLng.normalize(to)
  36 + return 0.0 if from == to # fixes a "zero-distance" bug
  37 + units = options[:units] || GeoKit::default_units
  38 + formula = options[:formula] || GeoKit::default_formula
  39 + case formula
  40 + when :sphere
  41 + units_sphere_multiplier(units) *
  42 + Math.acos( Math.sin(deg2rad(from.lat)) * Math.sin(deg2rad(to.lat)) +
  43 + Math.cos(deg2rad(from.lat)) * Math.cos(deg2rad(to.lat)) *
  44 + Math.cos(deg2rad(to.lng) - deg2rad(from.lng)))
  45 + when :flat
  46 + Math.sqrt((units_per_latitude_degree(units)*(from.lat-to.lat))**2 +
  47 + (units_per_longitude_degree(from.lat, units)*(from.lng-to.lng))**2)
  48 + end
  49 + end
  50 +
  51 + # Returns heading in degrees (0 is north, 90 is east, 180 is south, etc)
  52 + # from the first point to the second point. Typicaly, the instance methods will be used
  53 + # instead of this method.
  54 + def heading_between(from,to)
  55 + from=GeoKit::LatLng.normalize(from)
  56 + to=GeoKit::LatLng.normalize(to)
  57 +
  58 + d_lng=deg2rad(to.lng-from.lng)
  59 + from_lat=deg2rad(from.lat)
  60 + to_lat=deg2rad(to.lat)
  61 + y=Math.sin(d_lng) * Math.cos(to_lat)
  62 + x=Math.cos(from_lat)*Math.sin(to_lat)-Math.sin(from_lat)*Math.cos(to_lat)*Math.cos(d_lng)
  63 + heading=to_heading(Math.atan2(y,x))
  64 + end
  65 +
  66 + # Given a start point, distance, and heading (in degrees), provides
  67 + # an endpoint. Returns a LatLng instance. Typically, the instance method
  68 + # will be used instead of this method.
  69 + def endpoint(start,heading, distance, options={})
  70 + units = options[:units] || GeoKit::default_units
  71 + radius = units == :miles ? EARTH_RADIUS_IN_MILES : EARTH_RADIUS_IN_KMS
  72 + start=GeoKit::LatLng.normalize(start)
  73 + lat=deg2rad(start.lat)
  74 + lng=deg2rad(start.lng)
  75 + heading=deg2rad(heading)
  76 + distance=distance.to_f
  77 +
  78 + end_lat=Math.asin(Math.sin(lat)*Math.cos(distance/radius) +
  79 + Math.cos(lat)*Math.sin(distance/radius)*Math.cos(heading))
  80 +
  81 + end_lng=lng+Math.atan2(Math.sin(heading)*Math.sin(distance/radius)*Math.cos(lat),
  82 + Math.cos(distance/radius)-Math.sin(lat)*Math.sin(end_lat))
  83 +
  84 + LatLng.new(rad2deg(end_lat),rad2deg(end_lng))
  85 + end
  86 +
  87 + # Returns the midpoint, given two points. Returns a LatLng.
  88 + # Typically, the instance method will be used instead of this method.
  89 + # Valid option:
  90 + # :units - valid values are :miles or :kms (:miles is the default)
  91 + def midpoint_between(from,to,options={})
  92 + from=GeoKit::LatLng.normalize(from)
  93 +
  94 + units = options[:units] || GeoKit::default_units
  95 +
  96 + heading=from.heading_to(to)
  97 + distance=from.distance_to(to,options)
  98 + midpoint=from.endpoint(heading,distance/2,options)
  99 + end
  100 +
  101 + # Geocodes a location using the multi geocoder.
  102 + def geocode(location)
  103 + res = Geocoders::MultiGeocoder.geocode(location)
  104 + return res if res.success
  105 + raise GeoKit::Geocoders::GeocodeError
  106 + end
  107 +
  108 + protected
  109 +
  110 + def deg2rad(degrees)
  111 + degrees.to_f / 180.0 * Math::PI
  112 + end
  113 +
  114 + def rad2deg(rad)
  115 + rad.to_f * 180.0 / Math::PI
  116 + end
  117 +
  118 + def to_heading(rad)
  119 + (rad2deg(rad)+360)%360
  120 + end
  121 +
  122 + # Returns the multiplier used to obtain the correct distance units.
  123 + def units_sphere_multiplier(units)
  124 + units == :miles ? EARTH_RADIUS_IN_MILES : EARTH_RADIUS_IN_KMS
  125 + end
  126 +
  127 + # Returns the number of units per latitude degree.
  128 + def units_per_latitude_degree(units)
  129 + units == :miles ? MILES_PER_LATITUDE_DEGREE : KMS_PER_LATITUDE_DEGREE
  130 + end
  131 +
  132 + # Returns the number units per longitude degree.
  133 + def units_per_longitude_degree(lat, units)
  134 + miles_per_longitude_degree = (LATITUDE_DEGREES * Math.cos(lat * PI_DIV_RAD)).abs
  135 + units == :miles ? miles_per_longitude_degree : miles_per_longitude_degree * KMS_PER_MILE
  136 + end
  137 + end
  138 +
  139 + # -----------------------------------------------------------------------------------------------
  140 + # Instance methods below here
  141 + # -----------------------------------------------------------------------------------------------
  142 +
  143 + # Extracts a LatLng instance. Use with models that are acts_as_mappable
  144 + def to_lat_lng
  145 + return self if instance_of?(GeoKit::LatLng) || instance_of?(GeoKit::GeoLoc)
  146 + return LatLng.new(send(self.class.lat_column_name),send(self.class.lng_column_name)) if self.class.respond_to?(:acts_as_mappable)
  147 + return nil
  148 + end
  149 +
  150 + # Returns the distance from another point. The other point parameter is
  151 + # required to have lat and lng attributes. Valid options are:
  152 + # :units - valid values are :miles or :kms (:miles is the default)
  153 + # :formula - valid values are :flat or :sphere (:sphere is the default)
  154 + def distance_to(other, options={})
  155 + self.class.distance_between(self, other, options)
  156 + end
  157 + alias distance_from distance_to
  158 +
  159 + # Returns heading in degrees (0 is north, 90 is east, 180 is south, etc)
  160 + # to the given point. The given point can be a LatLng or a string to be Geocoded
  161 + def heading_to(other)
  162 + self.class.heading_between(self,other)
  163 + end
  164 +
  165 + # Returns heading in degrees (0 is north, 90 is east, 180 is south, etc)
  166 + # FROM the given point. The given point can be a LatLng or a string to be Geocoded
  167 + def heading_from(other)
  168 + self.class.heading_between(other,self)
  169 + end
  170 +
  171 + # Returns the endpoint, given a heading (in degrees) and distance.
  172 + # Valid option:
  173 + # :units - valid values are :miles or :kms (:miles is the default)
  174 + def endpoint(heading,distance,options={})
  175 + self.class.endpoint(self,heading,distance,options)
  176 + end
  177 +
  178 + # Returns the midpoint, given another point on the map.
  179 + # Valid option:
  180 + # :units - valid values are :miles or :kms (:miles is the default)
  181 + def midpoint_to(other, options={})
  182 + self.class.midpoint_between(self,other,options)
  183 + end
  184 +
  185 + end
  186 +
  187 + class LatLng
  188 + include Mappable
  189 +
  190 + attr_accessor :lat, :lng
  191 +
  192 + # Accepts latitude and longitude or instantiates an empty instance
  193 + # if lat and lng are not provided. Converted to floats if provided
  194 + def initialize(lat=nil, lng=nil)
  195 + lat = lat.to_f if lat && !lat.is_a?(Numeric)
  196 + lng = lng.to_f if lng && !lng.is_a?(Numeric)
  197 + @lat = lat
  198 + @lng = lng
  199 + end
  200 +
  201 + # Latitude attribute setter; stored as a float.
  202 + def lat=(lat)
  203 + @lat = lat.to_f if lat
  204 + end
  205 +
  206 + # Longitude attribute setter; stored as a float;
  207 + def lng=(lng)
  208 + @lng=lng.to_f if lng
  209 + end
  210 +
  211 + # Returns the lat and lng attributes as a comma-separated string.
  212 + def ll
  213 + "#{lat},#{lng}"
  214 + end
  215 +
  216 + #returns a string with comma-separated lat,lng values
  217 + def to_s
  218 + ll
  219 + end
  220 +
  221 + #returns a two-element array
  222 + def to_a
  223 + [lat,lng]
  224 + end
  225 + # Returns true if the candidate object is logically equal. Logical equivalence
  226 + # is true if the lat and lng attributes are the same for both objects.
  227 + def ==(other)
  228 + other.is_a?(LatLng) ? self.lat == other.lat && self.lng == other.lng : false
  229 + end
  230 +
  231 + # A *class* method to take anything which can be inferred as a point and generate
  232 + # a LatLng from it. You should use this anything you're not sure what the input is,
  233 + # and want to deal with it as a LatLng if at all possible. Can take:
  234 + # 1) two arguments (lat,lng)
  235 + # 2) a string in the format "37.1234,-129.1234" or "37.1234 -129.1234"
  236 + # 3) a string which can be geocoded on the fly
  237 + # 4) an array in the format [37.1234,-129.1234]
  238 + # 5) a LatLng or GeoLoc (which is just passed through as-is)
  239 + # 6) anything which acts_as_mappable -- a LatLng will be extracted from it
  240 + def self.normalize(thing,other=nil)
  241 + # if an 'other' thing is supplied, normalize the input by creating an array of two elements
  242 + thing=[thing,other] if other
  243 +
  244 + if thing.is_a?(String)
  245 + thing.strip!
  246 + if match=thing.match(/(\-?\d+\.?\d*)[, ] ?(\-?\d+\.?\d*)$/)
  247 + return GeoKit::LatLng.new(match[1],match[2])
  248 + else
  249 + res = GeoKit::Geocoders::MultiGeocoder.geocode(thing)
  250 + return res if res.success
  251 + raise GeoKit::Geocoders::GeocodeError
  252 + end
  253 + elsif thing.is_a?(Array) && thing.size==2
  254 + return GeoKit::LatLng.new(thing[0],thing[1])
  255 + elsif thing.is_a?(LatLng) # will also be true for GeoLocs
  256 + return thing
  257 + elsif thing.class.respond_to?(:acts_as_mappable) && thing.class.respond_to?(:distance_column_name)
  258 + return thing.to_lat_lng
  259 + end
  260 +
  261 + throw ArgumentError.new("#{thing} (#{thing.class}) cannot be normalized to a LatLng. We tried interpreting it as an array, string, Mappable, etc., but no dice.")
  262 + end
  263 +
  264 + end
  265 +
  266 + # This class encapsulates the result of a geocoding call
  267 + # It's primary purpose is to homogenize the results of multiple
  268 + # geocoding providers. It also provides some additional functionality, such as
  269 + # the "full address" method for geocoders that do not provide a
  270 + # full address in their results (for example, Yahoo), and the "is_us" method.
  271 + class GeoLoc < LatLng
  272 + # Location attributes. Full address is a concatenation of all values. For example:
  273 + # 100 Spear St, San Francisco, CA, 94101, US
  274 + attr_accessor :street_address, :city, :state, :zip, :country_code, :full_address
  275 + # Attributes set upon return from geocoding. Success will be true for successful
  276 + # geocode lookups. The provider will be set to the name of the providing geocoder.
  277 + # Finally, precision is an indicator of the accuracy of the geocoding.
  278 + attr_accessor :success, :provider, :precision
  279 + # Street number and street name are extracted from the street address attribute.
  280 + attr_reader :street_number, :street_name
  281 +
  282 + # Constructor expects a hash of symbols to correspond with attributes.
  283 + def initialize(h={})
  284 + @street_address=h[:street_address]
  285 + @city=h[:city]
  286 + @state=h[:state]
  287 + @zip=h[:zip]
  288 + @country_code=h[:country_code]
  289 + @success=false
  290 + @precision='unknown'
  291 + super(h[:lat],h[:lng])
  292 + end
  293 +
  294 + # Returns true if geocoded to the United States.
  295 + def is_us?
  296 + country_code == 'US'
  297 + end
  298 +
  299 + # full_address is provided by google but not by yahoo. It is intended that the google
  300 + # geocoding method will provide the full address, whereas for yahoo it will be derived
  301 + # from the parts of the address we do have.
  302 + def full_address
  303 + @full_address ? @full_address : to_geocodeable_s
  304 + end
  305 +
  306 + # Extracts the street number from the street address if the street address
  307 + # has a value.
  308 + def street_number
  309 + street_address[/(\d*)/] if street_address
  310 + end
  311 +
  312 + # Returns the street name portion of the street address.
  313 + def street_name
  314 + street_address[street_number.length, street_address.length].strip if street_address
  315 + end
  316 +
  317 + # gives you all the important fields as key-value pairs
  318 + def hash
  319 + res={}
  320 + [:success,:lat,:lng,:country_code,:city,:state,:zip,:street_address,:provider,:full_address,:is_us?,:ll,:precision].each { |s| res[s] = self.send(s.to_s) }
  321 + res
  322 + end
  323 + alias to_hash hash
  324 +
  325 + # Sets the city after capitalizing each word within the city name.
  326 + def city=(city)
  327 + @city = city.titleize if city
  328 + end
  329 +
  330 + # Sets the street address after capitalizing each word within the street address.
  331 + def street_address=(address)
  332 + @street_address = address.titleize if address
  333 + end
  334 +
  335 + # Returns a comma-delimited string consisting of the street address, city, state,
  336 + # zip, and country code. Only includes those attributes that are non-blank.
  337 + def to_geocodeable_s
  338 + a=[street_address, city, state, zip, country_code].compact
  339 + a.delete_if { |e| !e || e == '' }
  340 + a.join(', ')
  341 + end
  342 +
  343 + # Returns a string representation of the instance.
  344 + def to_s
  345 + "Provider: #{provider}\n Street: #{street_address}\nCity: #{city}\nState: #{state}\nZip: #{zip}\nLatitude: #{lat}\nLongitude: #{lng}\nCountry: #{country_code}\nSuccess: #{success}"
  346 + end
  347 + end
  348 +
  349 + # Bounds represents a rectangular bounds, defined by the SW and NE corners
  350 + class Bounds
  351 + # sw and ne are LatLng objects
  352 + attr_accessor :sw, :ne
  353 +
  354 + # provide sw and ne to instantiate a new Bounds instance
  355 + def initialize(sw,ne)
  356 + raise ArguementError if !(sw.is_a?(GeoKit::LatLng) && ne.is_a?(GeoKit::LatLng))
  357 + @sw,@ne=sw,ne
  358 + end
  359 +
  360 + #returns the a single point which is the center of the rectangular bounds
  361 + def center
  362 + @sw.midpoint_to(@ne)
  363 + end
  364 +
  365 + # a simple string representation:sw,ne
  366 + def to_s
  367 + "#{@sw.to_s},#{@ne.to_s}"
  368 + end
  369 +
  370 + # a two-element array of two-element arrays: sw,ne
  371 + def to_a
  372 + [@sw.to_a, @ne.to_a]
  373 + end
  374 +
  375 + # Returns true if the bounds contain the passed point.
  376 + # allows for bounds which cross the meridian
  377 + def contains?(point)
  378 + point=GeoKit::LatLng.normalize(point)
  379 + res = point.lat > @sw.lat && point.lat < @ne.lat
  380 + if crosses_meridian?
  381 + res &= point.lng < @ne.lng || point.lng > @sw.lng
  382 + else
  383 + res &= point.lng < @ne.lng && point.lng > @sw.lng
  384 + end
  385 + res
  386 + end
  387 +
  388 + # returns true if the bounds crosses the international dateline
  389 + def crosses_meridian?
  390 + @sw.lng > @ne.lng
  391 + end
  392 +
  393 + # Returns true if the candidate object is logically equal. Logical equivalence
  394 + # is true if the lat and lng attributes are the same for both objects.
  395 + def ==(other)
  396 + other.is_a?(Bounds) ? self.sw == other.sw && self.ne == other.ne : false
  397 + end
  398 +
  399 + class <<self
  400 +
  401 + # returns an instance of bounds which completely encompases the given circle
  402 + def from_point_and_radius(point,radius,options={})
  403 + point=LatLng.normalize(point)
  404 + p0=point.endpoint(0,radius,options)
  405 + p90=point.endpoint(90,radius,options)
  406 + p180=point.endpoint(180,radius,options)
  407 + p270=point.endpoint(270,radius,options)
  408 + sw=GeoKit::LatLng.new(p180.lat,p270.lng)
  409 + ne=GeoKit::LatLng.new(p0.lat,p90.lng)
  410 + GeoKit::Bounds.new(sw,ne)
  411 + end
  412 +
  413 + # Takes two main combinations of arguements to create a bounds:
  414 + # point,point (this is the only one which takes two arguments
  415 + # [point,point]
  416 + # . . . where a point is anything LatLng#normalize can handle (which is quite a lot)
  417 + #
  418 + # NOTE: everything combination is assumed to pass points in the order sw, ne
  419 + def normalize (thing,other=nil)
  420 + # maybe this will be simple -- an actual bounds object is passed, and we can all go home
  421 + return thing if thing.is_a? Bounds
  422 +
  423 + # no? OK, if there's no "other," the thing better be a two-element array
  424 + thing,other=thing if !other && thing.is_a?(Array) && thing.size==2
  425 +
  426 + # Now that we're set with a thing and another thing, let LatLng do the heavy lifting.
  427 + # Exceptions may be thrown
  428 + Bounds.new(GeoKit::LatLng.normalize(thing),GeoKit::LatLng.normalize(other))
  429 + end
  430 + end
  431 + end
  432 +end
0 433 \ No newline at end of file
... ...
vendor/plugins/geokit/test/acts_as_mappable_test.rb 0 → 100644
... ... @@ -0,0 +1,481 @@
  1 +require 'rubygems'
  2 +require 'mocha'
  3 +require File.join(File.dirname(__FILE__), 'test_helper')
  4 +
  5 +GeoKit::Geocoders::provider_order=[:google,:us]
  6 +
  7 +# Uses defaults
  8 +class Company < ActiveRecord::Base #:nodoc: all
  9 + has_many :locations
  10 +end
  11 +
  12 +# Configures everything.
  13 +class Location < ActiveRecord::Base #:nodoc: all
  14 + belongs_to :company
  15 + acts_as_mappable
  16 +end
  17 +
  18 +# for auto_geocode
  19 +class Store < ActiveRecord::Base
  20 + acts_as_mappable :auto_geocode=>true
  21 +end
  22 +
  23 +# Uses deviations from conventions.
  24 +class CustomLocation < ActiveRecord::Base #:nodoc: all
  25 + belongs_to :company
  26 + acts_as_mappable :distance_column_name => 'dist',
  27 + :default_units => :kms,
  28 + :default_formula => :flat,
  29 + :lat_column_name => 'latitude',
  30 + :lng_column_name => 'longitude'
  31 +
  32 + def to_s
  33 + "lat: #{latitude} lng: #{longitude} dist: #{dist}"
  34 + end
  35 +end
  36 +
  37 +class ActsAsMappableTest < Test::Unit::TestCase #:nodoc: all
  38 +
  39 + LOCATION_A_IP = "217.10.83.5"
  40 +
  41 + #self.fixture_path = File.dirname(__FILE__) + '/fixtures'
  42 + #self.fixture_path = RAILS_ROOT + '/test/fixtures/'
  43 + #puts "Rails Path #{RAILS_ROOT}"
  44 + #puts "Fixture Path: #{self.fixture_path}"
  45 + #self.fixture_path = ' /Users/bill_eisenhauer/Projects/geokit_test/test/fixtures/'
  46 + fixtures :companies, :locations, :custom_locations, :stores
  47 +
  48 + def setup
  49 + @location_a = GeoKit::GeoLoc.new
  50 + @location_a.lat = 32.918593
  51 + @location_a.lng = -96.958444
  52 + @location_a.city = "Irving"
  53 + @location_a.state = "TX"
  54 + @location_a.country_code = "US"
  55 + @location_a.success = true
  56 +
  57 + @sw = GeoKit::LatLng.new(32.91663,-96.982841)
  58 + @ne = GeoKit::LatLng.new(32.96302,-96.919495)
  59 + @bounds_center=GeoKit::LatLng.new((@sw.lat+@ne.lat)/2,(@sw.lng+@ne.lng)/2)
  60 +
  61 + @starbucks = companies(:starbucks)
  62 + @loc_a = locations(:a)
  63 + @custom_loc_a = custom_locations(:a)
  64 + @loc_e = locations(:e)
  65 + @custom_loc_e = custom_locations(:e)
  66 + end
  67 +
  68 + def test_override_default_units_the_hard_way
  69 + Location.default_units = :kms
  70 + locations = Location.find(:all, :origin => @loc_a, :conditions => "distance < 3.97")
  71 + assert_equal 5, locations.size
  72 + locations = Location.count(:origin => @loc_a, :conditions => "distance < 3.97")
  73 + assert_equal 5, locations
  74 + Location.default_units = :miles
  75 + end
  76 +
  77 + def test_include
  78 + locations = Location.find(:all, :origin => @loc_a, :include => :company, :conditions => "company_id = 1")
  79 + assert !locations.empty?
  80 + assert_equal 1, locations[0].company.id
  81 + assert_equal 'Starbucks', locations[0].company.name
  82 + end
  83 +
  84 + def test_distance_between_geocoded
  85 + GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with("Irving, TX").returns(@location_a)
  86 + GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with("San Francisco, CA").returns(@location_a)
  87 + assert_equal 0, Location.distance_between("Irving, TX", "San Francisco, CA")
  88 + end
  89 +
  90 + def test_distance_to_geocoded
  91 + GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with("Irving, TX").returns(@location_a)
  92 + assert_equal 0, @custom_loc_a.distance_to("Irving, TX")
  93 + end
  94 +
  95 + def test_distance_to_geocoded_error
  96 + GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with("Irving, TX").returns(GeoKit::GeoLoc.new)
  97 + assert_raise(GeoKit::Geocoders::GeocodeError) { @custom_loc_a.distance_to("Irving, TX") }
  98 + end
  99 +
  100 + def test_custom_attributes_distance_calculations
  101 + assert_equal 0, @custom_loc_a.distance_to(@loc_a)
  102 + assert_equal 0, CustomLocation.distance_between(@custom_loc_a, @loc_a)
  103 + end
  104 +
  105 + def test_distance_column_in_select
  106 + locations = Location.find(:all, :origin => @loc_a, :order => "distance ASC")
  107 + assert_equal 6, locations.size
  108 + assert_equal 0, @loc_a.distance_to(locations.first)
  109 + assert_in_delta 3.97, @loc_a.distance_to(locations.last, :units => :miles, :formula => :sphere), 0.01
  110 + end
  111 +
  112 + def test_find_with_distance_condition
  113 + locations = Location.find(:all, :origin => @loc_a, :conditions => "distance < 3.97")
  114 + assert_equal 5, locations.size
  115 + locations = Location.count(:origin => @loc_a, :conditions => "distance < 3.97")
  116 + assert_equal 5, locations
  117 + end
  118 +
  119 + def test_find_with_distance_condition_with_units_override
  120 + locations = Location.find(:all, :origin => @loc_a, :units => :kms, :conditions => "distance < 6.387")
  121 + assert_equal 5, locations.size
  122 + locations = Location.count(:origin => @loc_a, :units => :kms, :conditions => "distance < 6.387")
  123 + assert_equal 5, locations
  124 + end
  125 +
  126 + def test_find_with_distance_condition_with_formula_override
  127 + locations = Location.find(:all, :origin => @loc_a, :formula => :flat, :conditions => "distance < 6.387")
  128 + assert_equal 6, locations.size
  129 + locations = Location.count(:origin => @loc_a, :formula => :flat, :conditions => "distance < 6.387")
  130 + assert_equal 6, locations
  131 + end
  132 +
  133 + def test_find_within
  134 + locations = Location.find_within(3.97, :origin => @loc_a)
  135 + assert_equal 5, locations.size
  136 + locations = Location.count_within(3.97, :origin => @loc_a)
  137 + assert_equal 5, locations
  138 + end
  139 +
  140 + def test_find_within_with_token
  141 + locations = Location.find(:all, :within => 3.97, :origin => @loc_a)
  142 + assert_equal 5, locations.size
  143 + locations = Location.count(:within => 3.97, :origin => @loc_a)
  144 + assert_equal 5, locations
  145 + end
  146 +
  147 + def test_find_within_with_coordinates
  148 + locations = Location.find_within(3.97, :origin =>[@loc_a.lat,@loc_a.lng])
  149 + assert_equal 5, locations.size
  150 + locations = Location.count_within(3.97, :origin =>[@loc_a.lat,@loc_a.lng])
  151 + assert_equal 5, locations
  152 + end
  153 +
  154 + def test_find_with_compound_condition
  155 + locations = Location.find(:all, :origin => @loc_a, :conditions => "distance < 5 and city = 'Coppell'")
  156 + assert_equal 2, locations.size
  157 + locations = Location.count(:origin => @loc_a, :conditions => "distance < 5 and city = 'Coppell'")
  158 + assert_equal 2, locations
  159 + end
  160 +
  161 + def test_find_with_secure_compound_condition
  162 + locations = Location.find(:all, :origin => @loc_a, :conditions => ["distance < ? and city = ?", 5, 'Coppell'])
  163 + assert_equal 2, locations.size
  164 + locations = Location.count(:origin => @loc_a, :conditions => ["distance < ? and city = ?", 5, 'Coppell'])
  165 + assert_equal 2, locations
  166 + end
  167 +
  168 + def test_find_beyond
  169 + locations = Location.find_beyond(3.95, :origin => @loc_a)
  170 + assert_equal 1, locations.size
  171 + locations = Location.count_beyond(3.95, :origin => @loc_a)
  172 + assert_equal 1, locations
  173 + end
  174 +
  175 + def test_find_beyond_with_token
  176 + locations = Location.find(:all, :beyond => 3.95, :origin => @loc_a)
  177 + assert_equal 1, locations.size
  178 + locations = Location.count(:beyond => 3.95, :origin => @loc_a)
  179 + assert_equal 1, locations
  180 + end
  181 +
  182 + def test_find_beyond_with_coordinates
  183 + locations = Location.find_beyond(3.95, :origin =>[@loc_a.lat, @loc_a.lng])
  184 + assert_equal 1, locations.size
  185 + locations = Location.count_beyond(3.95, :origin =>[@loc_a.lat, @loc_a.lng])
  186 + assert_equal 1, locations
  187 + end
  188 +
  189 + def test_find_range_with_token
  190 + locations = Location.find(:all, :range => 0..10, :origin => @loc_a)
  191 + assert_equal 6, locations.size
  192 + locations = Location.count(:range => 0..10, :origin => @loc_a)
  193 + assert_equal 6, locations
  194 + end
  195 +
  196 + def test_find_range_with_token_with_conditions
  197 + locations = Location.find(:all, :origin => @loc_a, :range => 0..10, :conditions => ["city = ?", 'Coppell'])
  198 + assert_equal 2, locations.size
  199 + locations = Location.count(:origin => @loc_a, :range => 0..10, :conditions => ["city = ?", 'Coppell'])
  200 + assert_equal 2, locations
  201 + end
  202 +
  203 + def test_find_range_with_token_excluding_end
  204 + locations = Location.find(:all, :range => 0...10, :origin => @loc_a)
  205 + assert_equal 6, locations.size
  206 + locations = Location.count(:range => 0...10, :origin => @loc_a)
  207 + assert_equal 6, locations
  208 + end
  209 +
  210 + def test_find_nearest
  211 + assert_equal @loc_a, Location.find_nearest(:origin => @loc_a)
  212 + end
  213 +
  214 + def test_find_nearest_through_find
  215 + assert_equal @loc_a, Location.find(:nearest, :origin => @loc_a)
  216 + end
  217 +
  218 + def test_find_nearest_with_coordinates
  219 + assert_equal @loc_a, Location.find_nearest(:origin =>[@loc_a.lat, @loc_a.lng])
  220 + end
  221 +
  222 + def test_find_farthest
  223 + assert_equal @loc_e, Location.find_farthest(:origin => @loc_a)
  224 + end
  225 +
  226 + def test_find_farthest_through_find
  227 + assert_equal @loc_e, Location.find(:farthest, :origin => @loc_a)
  228 + end
  229 +
  230 + def test_find_farthest_with_coordinates
  231 + assert_equal @loc_e, Location.find_farthest(:origin =>[@loc_a.lat, @loc_a.lng])
  232 + end
  233 +
  234 + def test_scoped_distance_column_in_select
  235 + locations = @starbucks.locations.find(:all, :origin => @loc_a, :order => "distance ASC")
  236 + assert_equal 5, locations.size
  237 + assert_equal 0, @loc_a.distance_to(locations.first)
  238 + assert_in_delta 3.97, @loc_a.distance_to(locations.last, :units => :miles, :formula => :sphere), 0.01
  239 + end
  240 +
  241 + def test_scoped_find_with_distance_condition
  242 + locations = @starbucks.locations.find(:all, :origin => @loc_a, :conditions => "distance < 3.97")
  243 + assert_equal 4, locations.size
  244 + locations = @starbucks.locations.count(:origin => @loc_a, :conditions => "distance < 3.97")
  245 + assert_equal 4, locations
  246 + end
  247 +
  248 + def test_scoped_find_within
  249 + locations = @starbucks.locations.find_within(3.97, :origin => @loc_a)
  250 + assert_equal 4, locations.size
  251 + locations = @starbucks.locations.count_within(3.97, :origin => @loc_a)
  252 + assert_equal 4, locations
  253 + end
  254 +
  255 + def test_scoped_find_with_compound_condition
  256 + locations = @starbucks.locations.find(:all, :origin => @loc_a, :conditions => "distance < 5 and city = 'Coppell'")
  257 + assert_equal 2, locations.size
  258 + locations = @starbucks.locations.count( :origin => @loc_a, :conditions => "distance < 5 and city = 'Coppell'")
  259 + assert_equal 2, locations
  260 + end
  261 +
  262 + def test_scoped_find_beyond
  263 + locations = @starbucks.locations.find_beyond(3.95, :origin => @loc_a)
  264 + assert_equal 1, locations.size
  265 + locations = @starbucks.locations.count_beyond(3.95, :origin => @loc_a)
  266 + assert_equal 1, locations
  267 + end
  268 +
  269 + def test_scoped_find_nearest
  270 + assert_equal @loc_a, @starbucks.locations.find_nearest(:origin => @loc_a)
  271 + end
  272 +
  273 + def test_scoped_find_farthest
  274 + assert_equal @loc_e, @starbucks.locations.find_farthest(:origin => @loc_a)
  275 + end
  276 +
  277 + def test_ip_geocoded_distance_column_in_select
  278 + GeoKit::Geocoders::IpGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
  279 + locations = Location.find(:all, :origin => LOCATION_A_IP, :order => "distance ASC")
  280 + assert_equal 6, locations.size
  281 + assert_equal 0, @loc_a.distance_to(locations.first)
  282 + assert_in_delta 3.97, @loc_a.distance_to(locations.last, :units => :miles, :formula => :sphere), 0.01
  283 + end
  284 +
  285 + def test_ip_geocoded_find_with_distance_condition
  286 + GeoKit::Geocoders::IpGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
  287 + locations = Location.find(:all, :origin => LOCATION_A_IP, :conditions => "distance < 3.97")
  288 + assert_equal 5, locations.size
  289 + GeoKit::Geocoders::IpGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
  290 + locations = Location.count(:origin => LOCATION_A_IP, :conditions => "distance < 3.97")
  291 + assert_equal 5, locations
  292 + end
  293 +
  294 + def test_ip_geocoded_find_within
  295 + GeoKit::Geocoders::IpGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
  296 + locations = Location.find_within(3.97, :origin => LOCATION_A_IP)
  297 + assert_equal 5, locations.size
  298 + GeoKit::Geocoders::IpGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
  299 + locations = Location.count_within(3.97, :origin => LOCATION_A_IP)
  300 + assert_equal 5, locations
  301 + end
  302 +
  303 + def test_ip_geocoded_find_with_compound_condition
  304 + GeoKit::Geocoders::IpGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
  305 + locations = Location.find(:all, :origin => LOCATION_A_IP, :conditions => "distance < 5 and city = 'Coppell'")
  306 + assert_equal 2, locations.size
  307 + GeoKit::Geocoders::IpGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
  308 + locations = Location.count(:origin => LOCATION_A_IP, :conditions => "distance < 5 and city = 'Coppell'")
  309 + assert_equal 2, locations
  310 + end
  311 +
  312 + def test_ip_geocoded_find_with_secure_compound_condition
  313 + GeoKit::Geocoders::IpGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
  314 + locations = Location.find(:all, :origin => LOCATION_A_IP, :conditions => ["distance < ? and city = ?", 5, 'Coppell'])
  315 + assert_equal 2, locations.size
  316 + GeoKit::Geocoders::IpGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
  317 + locations = Location.count(:origin => LOCATION_A_IP, :conditions => ["distance < ? and city = ?", 5, 'Coppell'])
  318 + assert_equal 2, locations
  319 + end
  320 +
  321 + def test_ip_geocoded_find_beyond
  322 + GeoKit::Geocoders::IpGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
  323 + locations = Location.find_beyond(3.95, :origin => LOCATION_A_IP)
  324 + assert_equal 1, locations.size
  325 + GeoKit::Geocoders::IpGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
  326 + locations = Location.count_beyond(3.95, :origin => LOCATION_A_IP)
  327 + assert_equal 1, locations
  328 + end
  329 +
  330 + def test_ip_geocoded_find_nearest
  331 + GeoKit::Geocoders::IpGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
  332 + assert_equal @loc_a, Location.find_nearest(:origin => LOCATION_A_IP)
  333 + end
  334 +
  335 + def test_ip_geocoded_find_farthest
  336 + GeoKit::Geocoders::IpGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
  337 + assert_equal @loc_e, Location.find_farthest(:origin => LOCATION_A_IP)
  338 + end
  339 +
  340 + def test_ip_geocoder_exception
  341 + GeoKit::Geocoders::IpGeocoder.expects(:geocode).with('127.0.0.1').returns(GeoKit::GeoLoc.new)
  342 + assert_raises GeoKit::Geocoders::GeocodeError do
  343 + Location.find_farthest(:origin => '127.0.0.1')
  344 + end
  345 + end
  346 +
  347 + def test_address_geocode
  348 + GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with('Irving, TX').returns(@location_a)
  349 + locations = Location.find(:all, :origin => 'Irving, TX', :conditions => ["distance < ? and city = ?", 5, 'Coppell'])
  350 + assert_equal 2, locations.size
  351 + end
  352 +
  353 + def test_find_with_custom_distance_condition
  354 + locations = CustomLocation.find(:all, :origin => @loc_a, :conditions => "dist < 3.97")
  355 + assert_equal 5, locations.size
  356 + locations = CustomLocation.count(:origin => @loc_a, :conditions => "dist < 3.97")
  357 + assert_equal 5, locations
  358 + end
  359 +
  360 + def test_find_with_custom_distance_condition_using_custom_origin
  361 + locations = CustomLocation.find(:all, :origin => @custom_loc_a, :conditions => "dist < 3.97")
  362 + assert_equal 5, locations.size
  363 + locations = CustomLocation.count(:origin => @custom_loc_a, :conditions => "dist < 3.97")
  364 + assert_equal 5, locations
  365 + end
  366 +
  367 + def test_find_within_with_custom
  368 + locations = CustomLocation.find_within(3.97, :origin => @loc_a)
  369 + assert_equal 5, locations.size
  370 + locations = CustomLocation.count_within(3.97, :origin => @loc_a)
  371 + assert_equal 5, locations
  372 + end
  373 +
  374 + def test_find_within_with_coordinates_with_custom
  375 + locations = CustomLocation.find_within(3.97, :origin =>[@loc_a.lat, @loc_a.lng])
  376 + assert_equal 5, locations.size
  377 + locations = CustomLocation.count_within(3.97, :origin =>[@loc_a.lat, @loc_a.lng])
  378 + assert_equal 5, locations
  379 + end
  380 +
  381 + def test_find_with_compound_condition_with_custom
  382 + locations = CustomLocation.find(:all, :origin => @loc_a, :conditions => "dist < 5 and city = 'Coppell'")
  383 + assert_equal 1, locations.size
  384 + locations = CustomLocation.count(:origin => @loc_a, :conditions => "dist < 5 and city = 'Coppell'")
  385 + assert_equal 1, locations
  386 + end
  387 +
  388 + def test_find_with_secure_compound_condition_with_custom
  389 + locations = CustomLocation.find(:all, :origin => @loc_a, :conditions => ["dist < ? and city = ?", 5, 'Coppell'])
  390 + assert_equal 1, locations.size
  391 + locations = CustomLocation.count(:origin => @loc_a, :conditions => ["dist < ? and city = ?", 5, 'Coppell'])
  392 + assert_equal 1, locations
  393 + end
  394 +
  395 + def test_find_beyond_with_custom
  396 + locations = CustomLocation.find_beyond(3.95, :origin => @loc_a)
  397 + assert_equal 1, locations.size
  398 + locations = CustomLocation.count_beyond(3.95, :origin => @loc_a)
  399 + assert_equal 1, locations
  400 + end
  401 +
  402 + def test_find_beyond_with_coordinates_with_custom
  403 + locations = CustomLocation.find_beyond(3.95, :origin =>[@loc_a.lat, @loc_a.lng])
  404 + assert_equal 1, locations.size
  405 + locations = CustomLocation.count_beyond(3.95, :origin =>[@loc_a.lat, @loc_a.lng])
  406 + assert_equal 1, locations
  407 + end
  408 +
  409 + def test_find_nearest_with_custom
  410 + assert_equal @custom_loc_a, CustomLocation.find_nearest(:origin => @loc_a)
  411 + end
  412 +
  413 + def test_find_nearest_with_coordinates_with_custom
  414 + assert_equal @custom_loc_a, CustomLocation.find_nearest(:origin =>[@loc_a.lat, @loc_a.lng])
  415 + end
  416 +
  417 + def test_find_farthest_with_custom
  418 + assert_equal @custom_loc_e, CustomLocation.find_farthest(:origin => @loc_a)
  419 + end
  420 +
  421 + def test_find_farthest_with_coordinates_with_custom
  422 + assert_equal @custom_loc_e, CustomLocation.find_farthest(:origin =>[@loc_a.lat, @loc_a.lng])
  423 + end
  424 +
  425 + def test_find_with_array_origin
  426 + locations = Location.find(:all, :origin =>[@loc_a.lat,@loc_a.lng], :conditions => "distance < 3.97")
  427 + assert_equal 5, locations.size
  428 + locations = Location.count(:origin =>[@loc_a.lat,@loc_a.lng], :conditions => "distance < 3.97")
  429 + assert_equal 5, locations
  430 + end
  431 +
  432 +
  433 + # Bounding box tests
  434 +
  435 + def test_find_within_bounds
  436 + locations = Location.find_within_bounds([@sw,@ne])
  437 + assert_equal 2, locations.size
  438 + locations = Location.count_within_bounds([@sw,@ne])
  439 + assert_equal 2, locations
  440 + end
  441 +
  442 + def test_find_within_bounds_ordered_by_distance
  443 + locations = Location.find_within_bounds([@sw,@ne], :origin=>@bounds_center, :order=>'distance asc')
  444 + assert_equal locations[0], locations(:d)
  445 + assert_equal locations[1], locations(:a)
  446 + end
  447 +
  448 + def test_find_within_bounds_with_token
  449 + locations = Location.find(:all, :bounds=>[@sw,@ne])
  450 + assert_equal 2, locations.size
  451 + locations = Location.count(:bounds=>[@sw,@ne])
  452 + assert_equal 2, locations
  453 + end
  454 +
  455 + def test_find_within_bounds_with_string_conditions
  456 + locations = Location.find(:all, :bounds=>[@sw,@ne], :conditions=>"id !=#{locations(:a).id}")
  457 + assert_equal 1, locations.size
  458 + end
  459 +
  460 + def test_find_within_bounds_with_array_conditions
  461 + locations = Location.find(:all, :bounds=>[@sw,@ne], :conditions=>["id != ?", locations(:a).id])
  462 + assert_equal 1, locations.size
  463 + end
  464 +
  465 + def test_auto_geocode
  466 + GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with("Irving, TX").returns(@location_a)
  467 + store=Store.new(:address=>'Irving, TX')
  468 + store.save
  469 + assert_equal store.lat,@location_a.lat
  470 + assert_equal store.lng,@location_a.lng
  471 + assert_equal 0, store.errors.size
  472 + end
  473 +
  474 + def test_auto_geocode_failure
  475 + GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with("BOGUS").returns(GeoKit::GeoLoc.new)
  476 + store=Store.new(:address=>'BOGUS')
  477 + store.save
  478 + assert store.new_record?
  479 + assert_equal 1, store.errors.size
  480 + end
  481 +end
... ...
vendor/plugins/geokit/test/base_geocoder_test.rb 0 → 100644
... ... @@ -0,0 +1,57 @@
  1 +require 'test/unit'
  2 +require 'net/http'
  3 +require 'rubygems'
  4 +require 'mocha'
  5 +require File.join(File.dirname(__FILE__), '../../../../config/environment')
  6 +
  7 +
  8 +class MockSuccess < Net::HTTPSuccess #:nodoc: all
  9 + def initialize
  10 + end
  11 +end
  12 +
  13 +class MockFailure < Net::HTTPServiceUnavailable #:nodoc: all
  14 + def initialize
  15 + end
  16 +end
  17 +
  18 +# Base class for testing geocoders.
  19 +class BaseGeocoderTest < Test::Unit::TestCase #:nodoc: all
  20 +
  21 + # Defines common test fixtures.
  22 + def setup
  23 + @address = 'San Francisco, CA'
  24 + @full_address = '100 Spear St, San Francisco, CA, 94105-1522, US'
  25 + @full_address_short_zip = '100 Spear St, San Francisco, CA, 94105, US'
  26 +
  27 + @success = GeoKit::GeoLoc.new({:city=>"SAN FRANCISCO", :state=>"CA", :country_code=>"US", :lat=>37.7742, :lng=>-122.417068})
  28 + @success.success = true
  29 + end
  30 +
  31 + def test_timeout_call_web_service
  32 + GeoKit::Geocoders::Geocoder.class_eval do
  33 + def self.do_get(url)
  34 + sleep(2)
  35 + end
  36 + end
  37 + url = "http://www.anything.com"
  38 + GeoKit::Geocoders::timeout = 1
  39 + assert_nil GeoKit::Geocoders::Geocoder.call_geocoder_service(url)
  40 + end
  41 +
  42 + def test_successful_call_web_service
  43 + url = "http://www.anything.com"
  44 + GeoKit::Geocoders::Geocoder.expects(:do_get).with(url).returns("SUCCESS")
  45 + assert_equal "SUCCESS", GeoKit::Geocoders::Geocoder.call_geocoder_service(url)
  46 + end
  47 +
  48 + def test_find_geocoder_methods
  49 + public_methods = GeoKit::Geocoders::Geocoder.public_methods
  50 + assert public_methods.include?("yahoo_geocoder")
  51 + assert public_methods.include?("google_geocoder")
  52 + assert public_methods.include?("ca_geocoder")
  53 + assert public_methods.include?("us_geocoder")
  54 + assert public_methods.include?("multi_geocoder")
  55 + assert public_methods.include?("ip_geocoder")
  56 + end
  57 +end
0 58 \ No newline at end of file
... ...
vendor/plugins/geokit/test/bounds_test.rb 0 → 100644
... ... @@ -0,0 +1,75 @@
  1 +$LOAD_PATH.unshift File.join('..', 'lib')
  2 +require 'geo_kit/mappable'
  3 +require 'test/unit'
  4 +
  5 +class BoundsTest < Test::Unit::TestCase #:nodoc: all
  6 +
  7 + def setup
  8 + # This is the area in Texas
  9 + @sw = GeoKit::LatLng.new(32.91663,-96.982841)
  10 + @ne = GeoKit::LatLng.new(32.96302,-96.919495)
  11 + @bounds=GeoKit::Bounds.new(@sw,@ne)
  12 + @loc_a=GeoKit::LatLng.new(32.918593,-96.958444) # inside bounds
  13 + @loc_b=GeoKit::LatLng.new(32.914144,-96.958444) # outside bouds
  14 +
  15 + # this is a cross-meridan area
  16 + @cross_meridian=GeoKit::Bounds.normalize([30,170],[40,-170])
  17 + @inside_cm=GeoKit::LatLng.new(35,175)
  18 + @inside_cm_2=GeoKit::LatLng.new(35,-175)
  19 + @east_of_cm=GeoKit::LatLng.new(35,-165)
  20 + @west_of_cm=GeoKit::LatLng.new(35,165)
  21 +
  22 + end
  23 +
  24 + def test_equality
  25 + assert_equal GeoKit::Bounds.new(@sw,@ne), GeoKit::Bounds.new(@sw,@ne)
  26 + end
  27 +
  28 + def test_normalize
  29 + res=GeoKit::Bounds.normalize(@sw,@ne)
  30 + assert_equal res,GeoKit::Bounds.new(@sw,@ne)
  31 + res=GeoKit::Bounds.normalize([@sw,@ne])
  32 + assert_equal res,GeoKit::Bounds.new(@sw,@ne)
  33 + res=GeoKit::Bounds.normalize([@sw.lat,@sw.lng],[@ne.lat,@ne.lng])
  34 + assert_equal res,GeoKit::Bounds.new(@sw,@ne)
  35 + res=GeoKit::Bounds.normalize([[@sw.lat,@sw.lng],[@ne.lat,@ne.lng]])
  36 + assert_equal res,GeoKit::Bounds.new(@sw,@ne)
  37 + end
  38 +
  39 + def test_point_inside_bounds
  40 + assert @bounds.contains?(@loc_a)
  41 + end
  42 +
  43 + def test_point_outside_bounds
  44 + assert !@bounds.contains?(@loc_b)
  45 + end
  46 +
  47 + def test_point_inside_bounds_cross_meridian
  48 + assert @cross_meridian.contains?(@inside_cm)
  49 + assert @cross_meridian.contains?(@inside_cm_2)
  50 + end
  51 +
  52 + def test_point_outside_bounds_cross_meridian
  53 + assert !@cross_meridian.contains?(@east_of_cm)
  54 + assert !@cross_meridian.contains?(@west_of_cm)
  55 + end
  56 +
  57 + def test_center
  58 + assert_in_delta 32.939828,@bounds.center.lat,0.00005
  59 + assert_in_delta -96.9511763,@bounds.center.lng,0.00005
  60 + end
  61 +
  62 + def test_center_cross_meridian
  63 + assert_in_delta 35.41160, @cross_meridian.center.lat,0.00005
  64 + assert_in_delta 179.38112, @cross_meridian.center.lng,0.00005
  65 + end
  66 +
  67 + def test_creation_from_circle
  68 + bounds=GeoKit::Bounds.from_point_and_radius([32.939829, -96.951176],2.5)
  69 + inside=GeoKit::LatLng.new 32.9695270000,-96.9901590000
  70 + outside=GeoKit::LatLng.new 32.8951550000,-96.9584440000
  71 + assert bounds.contains?(inside)
  72 + assert !bounds.contains?(outside)
  73 + end
  74 +
  75 +end
0 76 \ No newline at end of file
... ...
vendor/plugins/geokit/test/ca_geocoder_test.rb 0 → 100644
... ... @@ -0,0 +1,41 @@
  1 +require File.join(File.dirname(__FILE__), 'base_geocoder_test')
  2 +
  3 +GeoKit::Geocoders::geocoder_ca = "SOMEKEYVALUE"
  4 +
  5 +class CaGeocoderTest < BaseGeocoderTest #:nodoc: all
  6 +
  7 + CA_SUCCESS=<<-EOF
  8 + <?xml version="1.0" encoding="UTF-8" ?>
  9 + <geodata><latt>49.243086</latt><longt>-123.153684</longt></geodata>
  10 + EOF
  11 +
  12 + def setup
  13 + @ca_full_hash = {:street_address=>"2105 West 32nd Avenue",:city=>"Vancouver", :state=>"BC"}
  14 + @ca_full_loc = GeoKit::GeoLoc.new(@ca_full_hash)
  15 + end
  16 +
  17 + def test_geocoder_with_geo_loc_with_account
  18 + response = MockSuccess.new
  19 + response.expects(:body).returns(CA_SUCCESS)
  20 + url = "http://geocoder.ca/?stno=2105&addresst=West+32nd+Avenue&city=Vancouver&prov=BC&auth=SOMEKEYVALUE&geoit=xml"
  21 + GeoKit::Geocoders::CaGeocoder.expects(:call_geocoder_service).with(url).returns(response)
  22 + verify(GeoKit::Geocoders::CaGeocoder.geocode(@ca_full_loc))
  23 + end
  24 +
  25 + def test_service_unavailable
  26 + response = MockFailure.new
  27 + #Net::HTTP.expects(:get_response).with(URI.parse("http://geocoder.ca/?stno=2105&addresst=West+32nd+Avenue&city=Vancouver&prov=BC&auth=SOMEKEYVALUE&geoit=xml")).returns(response)
  28 + url = "http://geocoder.ca/?stno=2105&addresst=West+32nd+Avenue&city=Vancouver&prov=BC&auth=SOMEKEYVALUE&geoit=xml"
  29 + GeoKit::Geocoders::CaGeocoder.expects(:call_geocoder_service).with(url).returns(response)
  30 + assert !GeoKit::Geocoders::CaGeocoder.geocode(@ca_full_loc).success
  31 + end
  32 +
  33 + private
  34 +
  35 + def verify(location)
  36 + assert_equal "BC", location.state
  37 + assert_equal "Vancouver", location.city
  38 + assert_equal "49.243086,-123.153684", location.ll
  39 + assert !location.is_us?
  40 + end
  41 +end
0 42 \ No newline at end of file
... ...
vendor/plugins/geokit/test/database.yml 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +mysql:
  2 + adapter: mysql
  3 + host: localhost
  4 + username: root
  5 + password:
  6 + database: geokit_plugin_test
  7 +postgresql:
  8 + adapter: postgresql
  9 + host: localhost
  10 + username: root
  11 + password:
  12 + database: geokit_plugin_test
0 13 \ No newline at end of file
... ...
vendor/plugins/geokit/test/fixtures/companies.yml 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +starbucks:
  2 + id: 1
  3 + name: Starbucks
  4 +
  5 +barnes_and_noble:
  6 + id: 2
  7 + name: Barnes & Noble
0 8 \ No newline at end of file
... ...
vendor/plugins/geokit/test/fixtures/custom_locations.yml 0 → 100644
... ... @@ -0,0 +1,54 @@
  1 +a:
  2 + id: 1
  3 + company_id: 1
  4 + street: 7979 N MacArthur Blvd
  5 + city: Irving
  6 + state: TX
  7 + postal_code: 75063
  8 + latitude: 32.918593
  9 + longitude: -96.958444
  10 +b:
  11 + id: 2
  12 + company_id: 1
  13 + street: 7750 N Macarthur Blvd # 160
  14 + city: Irving
  15 + state: TX
  16 + postal_code: 75063
  17 + latitude: 32.914144
  18 + longitude: -96.958444
  19 +c:
  20 + id: 3
  21 + company_id: 1
  22 + street: 5904 N Macarthur Blvd # 160
  23 + city: Irving
  24 + state: TX
  25 + postal_code: 75039
  26 + latitude: 32.895155
  27 + longitude: -96.958444
  28 +d:
  29 + id: 4
  30 + company_id: 1
  31 + street: 817 S Macarthur Blvd # 145
  32 + city: Coppell
  33 + state: TX
  34 + postal_code: 75019
  35 + latitude: 32.951613
  36 + longitude: -96.958444
  37 +e:
  38 + id: 5
  39 + company_id: 1
  40 + street: 106 N Denton Tap Rd # 350
  41 + city: Coppell
  42 + state: TX
  43 + postal_code: 75019
  44 + latitude: 32.969527
  45 + longitude: -96.990159
  46 +f:
  47 + id: 6
  48 + company_id: 2
  49 + street: 5904 N Macarthur Blvd # 160
  50 + city: Irving
  51 + state: TX
  52 + postal_code: 75039
  53 + latitude: 32.895155
  54 + longitude: -96.958444
0 55 \ No newline at end of file
... ...
vendor/plugins/geokit/test/fixtures/locations.yml 0 → 100644
... ... @@ -0,0 +1,54 @@
  1 +a:
  2 + id: 1
  3 + company_id: 1
  4 + street: 7979 N MacArthur Blvd
  5 + city: Irving
  6 + state: TX
  7 + postal_code: 75063
  8 + lat: 32.918593
  9 + lng: -96.958444
  10 +b:
  11 + id: 2
  12 + company_id: 1
  13 + street: 7750 N Macarthur Blvd # 160
  14 + city: Irving
  15 + state: TX
  16 + postal_code: 75063
  17 + lat: 32.914144
  18 + lng: -96.958444
  19 +c:
  20 + id: 3
  21 + company_id: 1
  22 + street: 5904 N Macarthur Blvd # 160
  23 + city: Irving
  24 + state: TX
  25 + postal_code: 75039
  26 + lat: 32.895155
  27 + lng: -96.958444
  28 +d:
  29 + id: 4
  30 + company_id: 1
  31 + street: 817 S Macarthur Blvd # 145
  32 + city: Coppell
  33 + state: TX
  34 + postal_code: 75019
  35 + lat: 32.951613
  36 + lng: -96.958444
  37 +e:
  38 + id: 5
  39 + company_id: 1
  40 + street: 106 N Denton Tap Rd # 350
  41 + city: Coppell
  42 + state: TX
  43 + postal_code: 75019
  44 + lat: 32.969527
  45 + lng: -96.990159
  46 +f:
  47 + id: 6
  48 + company_id: 2
  49 + street: 5904 N Macarthur Blvd # 160
  50 + city: Irving
  51 + state: TX
  52 + postal_code: 75039
  53 + lat: 32.895155
  54 + lng: -96.958444
0 55 \ No newline at end of file
... ...
vendor/plugins/geokit/test/fixtures/stores.yml 0 → 100644
vendor/plugins/geokit/test/geoloc_test.rb 0 → 100644
... ... @@ -0,0 +1,49 @@
  1 +require 'test/unit'
  2 +require File.join(File.dirname(__FILE__), '../../../../config/environment')
  3 +
  4 +class GeoLocTest < Test::Unit::TestCase #:nodoc: all
  5 +
  6 + def setup
  7 + @loc = GeoKit::GeoLoc.new
  8 + end
  9 +
  10 + def test_is_us
  11 + assert !@loc.is_us?
  12 + @loc.country_code = 'US'
  13 + assert @loc.is_us?
  14 + end
  15 +
  16 + def test_street_number
  17 + @loc.street_address = '123 Spear St.'
  18 + assert_equal '123', @loc.street_number
  19 + end
  20 +
  21 + def test_street_name
  22 + @loc.street_address = '123 Spear St.'
  23 + assert_equal 'Spear St.', @loc.street_name
  24 + end
  25 +
  26 + def test_city
  27 + @loc.city = "san francisco"
  28 + assert_equal 'San Francisco', @loc.city
  29 + end
  30 +
  31 + def test_full_address
  32 + @loc.city = 'San Francisco'
  33 + @loc.state = 'CA'
  34 + @loc.zip = '94105'
  35 + @loc.country_code = 'US'
  36 + assert_equal 'San Francisco, CA, 94105, US', @loc.full_address
  37 + @loc.full_address = 'Irving, TX, 75063, US'
  38 + assert_equal 'Irving, TX, 75063, US', @loc.full_address
  39 + end
  40 +
  41 + def test_hash
  42 + @loc.city = 'San Francisco'
  43 + @loc.state = 'CA'
  44 + @loc.zip = '94105'
  45 + @loc.country_code = 'US'
  46 + @another = GeoKit::GeoLoc.new @loc.to_hash
  47 + assert_equal @loc, @another
  48 + end
  49 +end
0 50 \ No newline at end of file
... ...
vendor/plugins/geokit/test/google_geocoder_test.rb 0 → 100644
... ... @@ -0,0 +1,88 @@
  1 +require File.join(File.dirname(__FILE__), 'base_geocoder_test')
  2 +
  3 +GeoKit::Geocoders::google = 'Google'
  4 +
  5 +class GoogleGeocoderTest < BaseGeocoderTest #:nodoc: all
  6 +
  7 + GOOGLE_FULL=<<-EOF.strip
  8 + <?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://earth.google.com/kml/2.0"><Response><name>100 spear st, san francisco, ca</name><Status><code>200</code><request>geocode</request></Status><Placemark><address>100 Spear St, San Francisco, CA 94105, USA</address><AddressDetails Accuracy="8" xmlns="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0"><Country><CountryNameCode>US</CountryNameCode><AdministrativeArea><AdministrativeAreaName>CA</AdministrativeAreaName><SubAdministrativeArea><SubAdministrativeAreaName>San Francisco</SubAdministrativeAreaName><Locality><LocalityName>San Francisco</LocalityName><Thoroughfare><ThoroughfareName>100 Spear St</ThoroughfareName></Thoroughfare><PostalCode><PostalCodeNumber>94105</PostalCodeNumber></PostalCode></Locality></SubAdministrativeArea></AdministrativeArea></Country></AddressDetails><Point><coordinates>-122.393985,37.792501,0</coordinates></Point></Placemark></Response></kml>
  9 + EOF
  10 +
  11 + GOOGLE_CITY=<<-EOF.strip
  12 + <?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://earth.google.com/kml/2.0"><Response><name>San Francisco</name><Status><code>200</code><request>geocode</request></Status><Placemark><address>San Francisco, CA, USA</address><AddressDetails Accuracy="4" xmlns="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0"><Country><CountryNameCode>US</CountryNameCode><AdministrativeArea><AdministrativeAreaName>CA</AdministrativeAreaName><Locality><LocalityName>San Francisco</LocalityName></Locality></AdministrativeArea></Country></AddressDetails><Point><coordinates>-122.418333,37.775000,0</coordinates></Point></Placemark></Response></kml>
  13 + EOF
  14 +
  15 + def setup
  16 + super
  17 + @google_full_hash = {:street_address=>"100 Spear St", :city=>"San Francisco", :state=>"CA", :zip=>"94105", :country_code=>"US"}
  18 + @google_city_hash = {:city=>"San Francisco", :state=>"CA"}
  19 +
  20 + @google_full_loc = GeoKit::GeoLoc.new(@google_full_hash)
  21 + @google_city_loc = GeoKit::GeoLoc.new(@google_city_hash)
  22 + end
  23 +
  24 + def test_google_full_address
  25 + response = MockSuccess.new
  26 + response.expects(:body).returns(GOOGLE_FULL)
  27 + url = "http://maps.google.com/maps/geo?q=#{CGI.escape(@address)}&output=xml&key=Google&oe=utf-8"
  28 + GeoKit::Geocoders::GoogleGeocoder.expects(:call_geocoder_service).with(url).returns(response)
  29 + res=GeoKit::Geocoders::GoogleGeocoder.geocode(@address)
  30 + assert_equal "CA", res.state
  31 + assert_equal "San Francisco", res.city
  32 + assert_equal "37.792501,-122.393985", res.ll # slightly dif from yahoo
  33 + assert res.is_us?
  34 + assert_equal "100 Spear St, San Francisco, CA 94105, USA", res.full_address #slightly different from yahoo
  35 + assert_equal "google", res.provider
  36 + end
  37 +
  38 + def test_google_full_address_with_geo_loc
  39 + response = MockSuccess.new
  40 + response.expects(:body).returns(GOOGLE_FULL)
  41 + url = "http://maps.google.com/maps/geo?q=#{CGI.escape(@full_address_short_zip)}&output=xml&key=Google&oe=utf-8"
  42 + GeoKit::Geocoders::GoogleGeocoder.expects(:call_geocoder_service).with(url).returns(response)
  43 + res=GeoKit::Geocoders::GoogleGeocoder.geocode(@google_full_loc)
  44 + assert_equal "CA", res.state
  45 + assert_equal "San Francisco", res.city
  46 + assert_equal "37.792501,-122.393985", res.ll # slightly dif from yahoo
  47 + assert res.is_us?
  48 + assert_equal "100 Spear St, San Francisco, CA 94105, USA", res.full_address #slightly different from yahoo
  49 + assert_equal "google", res.provider
  50 + end
  51 +
  52 + def test_google_city
  53 + response = MockSuccess.new
  54 + response.expects(:body).returns(GOOGLE_CITY)
  55 + url = "http://maps.google.com/maps/geo?q=#{CGI.escape(@address)}&output=xml&key=Google&oe=utf-8"
  56 + GeoKit::Geocoders::GoogleGeocoder.expects(:call_geocoder_service).with(url).returns(response)
  57 + res=GeoKit::Geocoders::GoogleGeocoder.geocode(@address)
  58 + assert_equal "CA", res.state
  59 + assert_equal "San Francisco", res.city
  60 + assert_equal "37.775,-122.418333", res.ll
  61 + assert res.is_us?
  62 + assert_equal "San Francisco, CA, USA", res.full_address
  63 + assert_nil res.street_address
  64 + assert_equal "google", res.provider
  65 + end
  66 +
  67 + def test_google_city_with_geo_loc
  68 + response = MockSuccess.new
  69 + response.expects(:body).returns(GOOGLE_CITY)
  70 + url = "http://maps.google.com/maps/geo?q=#{CGI.escape(@address)}&output=xml&key=Google&oe=utf-8"
  71 + GeoKit::Geocoders::GoogleGeocoder.expects(:call_geocoder_service).with(url).returns(response)
  72 + res=GeoKit::Geocoders::GoogleGeocoder.geocode(@google_city_loc)
  73 + assert_equal "CA", res.state
  74 + assert_equal "San Francisco", res.city
  75 + assert_equal "37.775,-122.418333", res.ll
  76 + assert res.is_us?
  77 + assert_equal "San Francisco, CA, USA", res.full_address
  78 + assert_nil res.street_address
  79 + assert_equal "google", res.provider
  80 + end
  81 +
  82 + def test_service_unavailable
  83 + response = MockFailure.new
  84 + url = "http://maps.google.com/maps/geo?q=#{CGI.escape(@address)}&output=xml&key=Google&oe=utf-8"
  85 + GeoKit::Geocoders::GoogleGeocoder.expects(:call_geocoder_service).with(url).returns(response)
  86 + assert !GeoKit::Geocoders::GoogleGeocoder.geocode(@google_city_loc).success
  87 + end
  88 +end
0 89 \ No newline at end of file
... ...
vendor/plugins/geokit/test/ip_geocode_lookup_test.rb 0 → 100644
... ... @@ -0,0 +1,84 @@
  1 +require File.join(File.dirname(__FILE__), '../../../../config/environment')
  2 +require 'action_controller/test_process'
  3 +require 'test/unit'
  4 +require 'rubygems'
  5 +require 'mocha'
  6 +
  7 +
  8 +class LocationAwareController < ActionController::Base #:nodoc: all
  9 + geocode_ip_address
  10 +
  11 + def index
  12 + render :nothing => true
  13 + end
  14 +end
  15 +
  16 +class ActionController::TestRequest #:nodoc: all
  17 + attr_accessor :remote_ip
  18 +end
  19 +
  20 +# Re-raise errors caught by the controller.
  21 +class LocationAwareController #:nodoc: all
  22 + def rescue_action(e) raise e end;
  23 +end
  24 +
  25 +class IpGeocodeLookupTest < Test::Unit::TestCase #:nodoc: all
  26 +
  27 + def setup
  28 + @success = GeoKit::GeoLoc.new
  29 + @success.provider = "hostip"
  30 + @success.lat = 41.7696
  31 + @success.lng = -88.4588
  32 + @success.city = "Sugar Grove"
  33 + @success.state = "IL"
  34 + @success.country_code = "US"
  35 + @success.success = true
  36 +
  37 + @failure = GeoKit::GeoLoc.new
  38 + @failure.provider = "hostip"
  39 + @failure.city = "(Private Address)"
  40 + @failure.success = false
  41 +
  42 + @controller = LocationAwareController.new
  43 + @request = ActionController::TestRequest.new
  44 + @response = ActionController::TestResponse.new
  45 + end
  46 +
  47 + def test_no_location_in_cookie_or_session
  48 + GeoKit::Geocoders::IpGeocoder.expects(:geocode).with("good ip").returns(@success)
  49 + @request.remote_ip = "good ip"
  50 + get :index
  51 + verify
  52 + end
  53 +
  54 + def test_location_in_cookie
  55 + @request.remote_ip = "good ip"
  56 + @request.cookies['geo_location'] = CGI::Cookie.new('geo_location', @success.to_yaml)
  57 + get :index
  58 + verify
  59 + end
  60 +
  61 + def test_location_in_session
  62 + @request.remote_ip = "good ip"
  63 + @request.session[:geo_location] = @success
  64 + @request.cookies['geo_location'] = CGI::Cookie.new('geo_location', @success.to_yaml)
  65 + get :index
  66 + verify
  67 + end
  68 +
  69 + def test_ip_not_located
  70 + GeoKit::Geocoders::IpGeocoder.expects(:geocode).with("bad ip").returns(@failure)
  71 + @request.remote_ip = "bad ip"
  72 + get :index
  73 + assert_nil @request.session[:geo_location]
  74 + end
  75 +
  76 + private
  77 +
  78 + def verify
  79 + assert_response :success
  80 + assert_equal @success, @request.session[:geo_location]
  81 + assert_not_nil cookies['geo_location']
  82 + assert_equal @success, YAML.load(cookies['geo_location'].join)
  83 + end
  84 +end
0 85 \ No newline at end of file
... ...
vendor/plugins/geokit/test/ipgeocoder_test.rb 0 → 100644
... ... @@ -0,0 +1,87 @@
  1 +require File.join(File.dirname(__FILE__), 'base_geocoder_test')
  2 +
  3 +class IpGeocoderTest < BaseGeocoderTest #:nodoc: all
  4 +
  5 + IP_FAILURE=<<-EOF
  6 + Country: (Private Address) (XX)
  7 + City: (Private Address)
  8 + Latitude:
  9 + Longitude:
  10 + EOF
  11 +
  12 + IP_SUCCESS=<<-EOF
  13 + Country: UNITED STATES (US)
  14 + City: Sugar Grove, IL
  15 + Latitude: 41.7696
  16 + Longitude: -88.4588
  17 + EOF
  18 +
  19 + IP_UNICODED=<<-EOF
  20 + Country: SWEDEN (SE)
  21 + City: Borås
  22 + Latitude: 57.7167
  23 + Longitude: 12.9167
  24 + EOF
  25 +
  26 + def setup
  27 + super
  28 + @success.provider = "hostip"
  29 + end
  30 +
  31 + def test_successful_lookup
  32 + success = MockSuccess.new
  33 + success.expects(:body).returns(IP_SUCCESS)
  34 + url = 'http://api.hostip.info/get_html.php?ip=12.215.42.19&position=true'
  35 + GeoKit::Geocoders::IpGeocoder.expects(:call_geocoder_service).with(url).returns(success)
  36 + location = GeoKit::Geocoders::IpGeocoder.geocode('12.215.42.19')
  37 + assert_not_nil location
  38 + assert_equal 41.7696, location.lat
  39 + assert_equal -88.4588, location.lng
  40 + assert_equal "Sugar Grove", location.city
  41 + assert_equal "IL", location.state
  42 + assert_equal "US", location.country_code
  43 + assert_equal "hostip", location.provider
  44 + assert location.success
  45 + end
  46 +
  47 + def test_unicoded_lookup
  48 + success = MockSuccess.new
  49 + success.expects(:body).returns(IP_UNICODED)
  50 + url = 'http://api.hostip.info/get_html.php?ip=12.215.42.19&position=true'
  51 + GeoKit::Geocoders::IpGeocoder.expects(:call_geocoder_service).with(url).returns(success)
  52 + location = GeoKit::Geocoders::IpGeocoder.geocode('12.215.42.19')
  53 + assert_not_nil location
  54 + assert_equal 57.7167, location.lat
  55 + assert_equal 12.9167, location.lng
  56 + assert_equal "Borås", location.city
  57 + assert_nil location.state
  58 + assert_equal "SE", location.country_code
  59 + assert_equal "hostip", location.provider
  60 + assert location.success
  61 + end
  62 +
  63 + def test_failed_lookup
  64 + failure = MockSuccess.new
  65 + failure.expects(:body).returns(IP_FAILURE)
  66 + url = 'http://api.hostip.info/get_html.php?ip=0.0.0.0&position=true'
  67 + GeoKit::Geocoders::IpGeocoder.expects(:call_geocoder_service).with(url).returns(failure)
  68 + location = GeoKit::Geocoders::IpGeocoder.geocode("0.0.0.0")
  69 + assert_not_nil location
  70 + assert !location.success
  71 + end
  72 +
  73 + def test_invalid_ip
  74 + location = GeoKit::Geocoders::IpGeocoder.geocode("blah")
  75 + assert_not_nil location
  76 + assert !location.success
  77 + end
  78 +
  79 + def test_service_unavailable
  80 + failure = MockFailure.new
  81 + url = 'http://api.hostip.info/get_html.php?ip=0.0.0.0&position=true'
  82 + GeoKit::Geocoders::IpGeocoder.expects(:call_geocoder_service).with(url).returns(failure)
  83 + location = GeoKit::Geocoders::IpGeocoder.geocode("0.0.0.0")
  84 + assert_not_nil location
  85 + assert !location.success
  86 + end
  87 +end
0 88 \ No newline at end of file
... ...
vendor/plugins/geokit/test/latlng_test.rb 0 → 100644
... ... @@ -0,0 +1,112 @@
  1 +$LOAD_PATH.unshift File.join('..', 'lib')
  2 +require 'geo_kit/mappable'
  3 +require 'test/unit'
  4 +
  5 +class LatLngTest < Test::Unit::TestCase #:nodoc: all
  6 +
  7 + def setup
  8 + @loc_a = GeoKit::LatLng.new(32.918593,-96.958444)
  9 + @loc_e = GeoKit::LatLng.new(32.969527,-96.990159)
  10 + @point = GeoKit::LatLng.new(@loc_a.lat, @loc_a.lng)
  11 + end
  12 +
  13 + def test_distance_between_same_using_defaults
  14 + assert_equal 0, GeoKit::LatLng.distance_between(@loc_a, @loc_a)
  15 + assert_equal 0, @loc_a.distance_to(@loc_a)
  16 + end
  17 +
  18 + def test_distance_between_same_with_miles_and_flat
  19 + assert_equal 0, GeoKit::LatLng.distance_between(@loc_a, @loc_a, :units => :miles, :formula => :flat)
  20 + assert_equal 0, @loc_a.distance_to(@loc_a, :units => :miles, :formula => :flat)
  21 + end
  22 +
  23 + def test_distance_between_same_with_kms_and_flat
  24 + assert_equal 0, GeoKit::LatLng.distance_between(@loc_a, @loc_a, :units => :kms, :formula => :flat)
  25 + assert_equal 0, @loc_a.distance_to(@loc_a, :units => :kms, :formula => :flat)
  26 + end
  27 +
  28 + def test_distance_between_same_with_miles_and_sphere
  29 + assert_equal 0, GeoKit::LatLng.distance_between(@loc_a, @loc_a, :units => :miles, :formula => :sphere)
  30 + assert_equal 0, @loc_a.distance_to(@loc_a, :units => :miles, :formula => :sphere)
  31 + end
  32 +
  33 + def test_distance_between_same_with_kms_and_sphere
  34 + assert_equal 0, GeoKit::LatLng.distance_between(@loc_a, @loc_a, :units => :kms, :formula => :sphere)
  35 + assert_equal 0, @loc_a.distance_to(@loc_a, :units => :kms, :formula => :sphere)
  36 + end
  37 +
  38 + def test_distance_between_diff_using_defaults
  39 + assert_in_delta 3.97, GeoKit::LatLng.distance_between(@loc_a, @loc_e), 0.01
  40 + assert_in_delta 3.97, @loc_a.distance_to(@loc_e), 0.01
  41 + end
  42 +
  43 + def test_distance_between_diff_with_miles_and_flat
  44 + assert_in_delta 3.97, GeoKit::LatLng.distance_between(@loc_a, @loc_e, :units => :miles, :formula => :flat), 0.2
  45 + assert_in_delta 3.97, @loc_a.distance_to(@loc_e, :units => :miles, :formula => :flat), 0.2
  46 + end
  47 +
  48 + def test_distance_between_diff_with_kms_and_flat
  49 + assert_in_delta 6.39, GeoKit::LatLng.distance_between(@loc_a, @loc_e, :units => :kms, :formula => :flat), 0.4
  50 + assert_in_delta 6.39, @loc_a.distance_to(@loc_e, :units => :kms, :formula => :flat), 0.4
  51 + end
  52 +
  53 + def test_distance_between_diff_with_miles_and_sphere
  54 + assert_in_delta 3.97, GeoKit::LatLng.distance_between(@loc_a, @loc_e, :units => :miles, :formula => :sphere), 0.01
  55 + assert_in_delta 3.97, @loc_a.distance_to(@loc_e, :units => :miles, :formula => :sphere), 0.01
  56 + end
  57 +
  58 + def test_distance_between_diff_with_kms_and_sphere
  59 + assert_in_delta 6.39, GeoKit::LatLng.distance_between(@loc_a, @loc_e, :units => :kms, :formula => :sphere), 0.01
  60 + assert_in_delta 6.39, @loc_a.distance_to(@loc_e, :units => :kms, :formula => :sphere), 0.01
  61 + end
  62 +
  63 + def test_manually_mixed_in
  64 + assert_equal 0, GeoKit::LatLng.distance_between(@point, @point)
  65 + assert_equal 0, @point.distance_to(@point)
  66 + assert_equal 0, @point.distance_to(@loc_a)
  67 + assert_in_delta 3.97, @point.distance_to(@loc_e, :units => :miles, :formula => :flat), 0.2
  68 + assert_in_delta 6.39, @point.distance_to(@loc_e, :units => :kms, :formula => :flat), 0.4
  69 + end
  70 +
  71 + def test_heading_between
  72 + assert_in_delta 332, GeoKit::LatLng.heading_between(@loc_a,@loc_e), 0.5
  73 + end
  74 +
  75 + def test_heading_to
  76 + assert_in_delta 332, @loc_a.heading_to(@loc_e), 0.5
  77 + end
  78 +
  79 + def test_class_endpoint
  80 + endpoint=GeoKit::LatLng.endpoint(@loc_a, 332, 3.97)
  81 + assert_in_delta @loc_e.lat, endpoint.lat, 0.0005
  82 + assert_in_delta @loc_e.lng, endpoint.lng, 0.0005
  83 + end
  84 +
  85 + def test_instance_endpoint
  86 + endpoint=@loc_a.endpoint(332, 3.97)
  87 + assert_in_delta @loc_e.lat, endpoint.lat, 0.0005
  88 + assert_in_delta @loc_e.lng, endpoint.lng, 0.0005
  89 + end
  90 +
  91 + def test_midpoint
  92 + midpoint=@loc_a.midpoint_to(@loc_e)
  93 + assert_in_delta 32.944061, midpoint.lat, 0.0005
  94 + assert_in_delta -96.974296, midpoint.lng, 0.0005
  95 + end
  96 +
  97 + def test_normalize
  98 + lat=37.7690
  99 + lng=-122.443
  100 + res=GeoKit::LatLng.normalize(lat,lng)
  101 + assert_equal res,GeoKit::LatLng.new(lat,lng)
  102 + res=GeoKit::LatLng.normalize("#{lat}, #{lng}")
  103 + assert_equal res,GeoKit::LatLng.new(lat,lng)
  104 + res=GeoKit::LatLng.normalize("#{lat} #{lng}")
  105 + assert_equal res,GeoKit::LatLng.new(lat,lng)
  106 + res=GeoKit::LatLng.normalize("#{lat.to_i} #{lng.to_i}")
  107 + assert_equal res,GeoKit::LatLng.new(lat.to_i,lng.to_i)
  108 + res=GeoKit::LatLng.normalize([lat,lng])
  109 + assert_equal res,GeoKit::LatLng.new(lat,lng)
  110 + end
  111 +
  112 +end
0 113 \ No newline at end of file
... ...
vendor/plugins/geokit/test/multi_geocoder_test.rb 0 → 100644
... ... @@ -0,0 +1,44 @@
  1 +require File.join(File.dirname(__FILE__), 'base_geocoder_test')
  2 +
  3 +GeoKit::Geocoders::provider_order=[:google,:yahoo,:us]
  4 +
  5 +class MultiGeocoderTest < BaseGeocoderTest #:nodoc: all
  6 +
  7 + def setup
  8 + super
  9 + @failure = GeoKit::GeoLoc.new
  10 + end
  11 +
  12 + def test_successful_first
  13 + GeoKit::Geocoders::GoogleGeocoder.expects(:geocode).with(@address).returns(@success)
  14 + assert_equal @success, GeoKit::Geocoders::MultiGeocoder.geocode(@address)
  15 + end
  16 +
  17 + def test_failover
  18 + GeoKit::Geocoders::GoogleGeocoder.expects(:geocode).with(@address).returns(@failure)
  19 + GeoKit::Geocoders::YahooGeocoder.expects(:geocode).with(@address).returns(@success)
  20 + assert_equal @success, GeoKit::Geocoders::MultiGeocoder.geocode(@address)
  21 + end
  22 +
  23 + def test_double_failover
  24 + GeoKit::Geocoders::GoogleGeocoder.expects(:geocode).with(@address).returns(@failure)
  25 + GeoKit::Geocoders::YahooGeocoder.expects(:geocode).with(@address).returns(@failure)
  26 + GeoKit::Geocoders::UsGeocoder.expects(:geocode).with(@address).returns(@success)
  27 + assert_equal @success, GeoKit::Geocoders::MultiGeocoder.geocode(@address)
  28 + end
  29 +
  30 + def test_failure
  31 + GeoKit::Geocoders::GoogleGeocoder.expects(:geocode).with(@address).returns(@failure)
  32 + GeoKit::Geocoders::YahooGeocoder.expects(:geocode).with(@address).returns(@failure)
  33 + GeoKit::Geocoders::UsGeocoder.expects(:geocode).with(@address).returns(@failure)
  34 + assert_equal @failure, GeoKit::Geocoders::MultiGeocoder.geocode(@address)
  35 + end
  36 +
  37 + def test_invalid_provider
  38 + temp = GeoKit::Geocoders::provider_order
  39 + GeoKit::Geocoders.provider_order = [:bogus]
  40 + assert_equal @failure, GeoKit::Geocoders::MultiGeocoder.geocode(@address)
  41 + GeoKit::Geocoders.provider_order = temp
  42 + end
  43 +
  44 +end
0 45 \ No newline at end of file
... ...
vendor/plugins/geokit/test/schema.rb 0 → 100644
... ... @@ -0,0 +1,31 @@
  1 +ActiveRecord::Schema.define(:version => 0) do
  2 + create_table :companies, :force => true do |t|
  3 + t.column :name, :string
  4 + end
  5 +
  6 + create_table :locations, :force => true do |t|
  7 + t.column :company_id, :integer, :default => 0, :null => false
  8 + t.column :street, :string, :limit => 60
  9 + t.column :city, :string, :limit => 60
  10 + t.column :state, :string, :limit => 2
  11 + t.column :postal_code, :string, :limit => 16
  12 + t.column :lat, :decimal, :precision => 15, :scale => 10
  13 + t.column :lng, :decimal, :precision => 15, :scale => 10
  14 + end
  15 +
  16 + create_table :custom_locations, :force => true do |t|
  17 + t.column :company_id, :integer, :default => 0, :null => false
  18 + t.column :street, :string, :limit => 60
  19 + t.column :city, :string, :limit => 60
  20 + t.column :state, :string, :limit => 2
  21 + t.column :postal_code, :string, :limit => 16
  22 + t.column :latitude, :decimal, :precision => 15, :scale => 10
  23 + t.column :longitude, :decimal, :precision => 15, :scale => 10
  24 + end
  25 +
  26 + create_table :stores, :force=> true do |t|
  27 + t.column :address, :string
  28 + t.column :lat, :decimal, :precision => 15, :scale => 10
  29 + t.column :lng, :decimal, :precision => 15, :scale => 10
  30 + end
  31 +end
0 32 \ No newline at end of file
... ...
vendor/plugins/geokit/test/test_helper.rb 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +require 'test/unit'
  2 +
  3 +plugin_test_dir = File.dirname(__FILE__)
  4 +
  5 +# Load the Rails environment
  6 +require File.join(plugin_test_dir, '../../../../config/environment')
  7 +require 'active_record/fixtures'
  8 +databases = YAML::load(IO.read(plugin_test_dir + '/database.yml'))
  9 +ActiveRecord::Base.logger = Logger.new(plugin_test_dir + "/debug.log")
  10 +
  11 +# A specific database can be used by setting the DB environment variable
  12 +ActiveRecord::Base.establish_connection(databases[ENV['DB'] || 'mysql'])
  13 +
  14 +# Load the test schema into the database
  15 +load(File.join(plugin_test_dir, 'schema.rb'))
  16 +
  17 +# Load fixtures from the plugin
  18 +Test::Unit::TestCase.fixture_path = File.join(plugin_test_dir, 'fixtures/')
0 19 \ No newline at end of file
... ...
vendor/plugins/geokit/test/us_geocoder_test.rb 0 → 100644
... ... @@ -0,0 +1,47 @@
  1 +require File.join(File.dirname(__FILE__), 'base_geocoder_test')
  2 +
  3 +GeoKit::Geocoders::geocoder_us = nil
  4 +
  5 +class UsGeocoderTest < BaseGeocoderTest #:nodoc: all
  6 +
  7 + GEOCODER_US_FULL='37.792528,-122.393981,100 Spear St,San Francisco,CA,94105'
  8 +
  9 + def setup
  10 + super
  11 + @us_full_hash = {:city=>"San Francisco", :state=>"CA"}
  12 + @us_full_loc = GeoKit::GeoLoc.new(@us_full_hash)
  13 + end
  14 +
  15 + def test_geocoder_us
  16 + response = MockSuccess.new
  17 + response.expects(:body).returns(GEOCODER_US_FULL)
  18 + url = "http://geocoder.us/service/csv/geocode?address=#{CGI.escape(@address)}"
  19 + GeoKit::Geocoders::UsGeocoder.expects(:call_geocoder_service).with(url).returns(response)
  20 + verify(GeoKit::Geocoders::UsGeocoder.geocode(@address))
  21 + end
  22 +
  23 + def test_geocoder_with_geo_loc
  24 + response = MockSuccess.new
  25 + response.expects(:body).returns(GEOCODER_US_FULL)
  26 + url = "http://geocoder.us/service/csv/geocode?address=#{CGI.escape(@address)}"
  27 + GeoKit::Geocoders::UsGeocoder.expects(:call_geocoder_service).with(url).returns(response)
  28 + verify(GeoKit::Geocoders::UsGeocoder.geocode(@us_full_loc))
  29 + end
  30 +
  31 + def test_service_unavailable
  32 + response = MockFailure.new
  33 + url = "http://geocoder.us/service/csv/geocode?address=#{CGI.escape(@address)}"
  34 + GeoKit::Geocoders::UsGeocoder.expects(:call_geocoder_service).with(url).returns(response)
  35 + assert !GeoKit::Geocoders::UsGeocoder.geocode(@us_full_loc).success
  36 + end
  37 +
  38 + private
  39 +
  40 + def verify(location)
  41 + assert_equal "CA", location.state
  42 + assert_equal "San Francisco", location.city
  43 + assert_equal "37.792528,-122.393981", location.ll
  44 + assert location.is_us?
  45 + assert_equal "100 Spear St, San Francisco, CA, 94105, US", location.full_address #slightly different from yahoo
  46 + end
  47 +end
0 48 \ No newline at end of file
... ...
vendor/plugins/geokit/test/yahoo_geocoder_test.rb 0 → 100644
... ... @@ -0,0 +1,87 @@
  1 +require File.join(File.dirname(__FILE__), 'base_geocoder_test')
  2 +
  3 +GeoKit::Geocoders::yahoo = 'Yahoo'
  4 +
  5 +class YahooGeocoderTest < BaseGeocoderTest #:nodoc: all
  6 + YAHOO_FULL=<<-EOF.strip
  7 + <?xml version="1.0"?>
  8 + <ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:yahoo:maps" xsi:schemaLocation="urn:yahoo:maps http://api.local.yahoo.com/MapsService/V1/GeocodeResponse.xsd"><Result precision="address"><Latitude>37.792406</Latitude><Longitude>-122.39411</Longitude><Address>100 SPEAR ST</Address><City>SAN FRANCISCO</City><State>CA</State><Zip>94105-1522</Zip><Country>US</Country></Result></ResultSet>
  9 + <!-- ws01.search.scd.yahoo.com uncompressed/chunked Mon Jan 29 16:23:43 PST 2007 -->
  10 + EOF
  11 +
  12 + YAHOO_CITY=<<-EOF.strip
  13 + <?xml version="1.0"?>
  14 + <ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:yahoo:maps" xsi:schemaLocation="urn:yahoo:maps http://api.local.yahoo.com/MapsService/V1/GeocodeResponse.xsd"><Result precision="city"><Latitude>37.7742</Latitude><Longitude>-122.417068</Longitude><Address></Address><City>SAN FRANCISCO</City><State>CA</State><Zip></Zip><Country>US</Country></Result></ResultSet>
  15 + <!-- ws02.search.scd.yahoo.com uncompressed/chunked Mon Jan 29 18:00:28 PST 2007 -->
  16 + EOF
  17 +
  18 + def setup
  19 + super
  20 + @yahoo_full_hash = {:street_address=>"100 Spear St", :city=>"San Francisco", :state=>"CA", :zip=>"94105-1522", :country_code=>"US"}
  21 + @yahoo_city_hash = {:city=>"San Francisco", :state=>"CA"}
  22 + @yahoo_full_loc = GeoKit::GeoLoc.new(@yahoo_full_hash)
  23 + @yahoo_city_loc = GeoKit::GeoLoc.new(@yahoo_city_hash)
  24 + end
  25 +
  26 + # the testing methods themselves
  27 + def test_yahoo_full_address
  28 + response = MockSuccess.new
  29 + response.expects(:body).returns(YAHOO_FULL)
  30 + url = "http://api.local.yahoo.com/MapsService/V1/geocode?appid=Yahoo&location=#{CGI.escape(@address)}"
  31 + GeoKit::Geocoders::YahooGeocoder.expects(:call_geocoder_service).with(url).returns(response)
  32 + do_full_address_assertions(GeoKit::Geocoders::YahooGeocoder.geocode(@address))
  33 + end
  34 +
  35 + def test_yahoo_full_address_with_geo_loc
  36 + response = MockSuccess.new
  37 + response.expects(:body).returns(YAHOO_FULL)
  38 + url = "http://api.local.yahoo.com/MapsService/V1/geocode?appid=Yahoo&location=#{CGI.escape(@full_address)}"
  39 + GeoKit::Geocoders::YahooGeocoder.expects(:call_geocoder_service).with(url).returns(response)
  40 + do_full_address_assertions(GeoKit::Geocoders::YahooGeocoder.geocode(@yahoo_full_loc))
  41 + end
  42 +
  43 + def test_yahoo_city
  44 + response = MockSuccess.new
  45 + response.expects(:body).returns(YAHOO_CITY)
  46 + url = "http://api.local.yahoo.com/MapsService/V1/geocode?appid=Yahoo&location=#{CGI.escape(@address)}"
  47 + GeoKit::Geocoders::YahooGeocoder.expects(:call_geocoder_service).with(url).returns(response)
  48 + do_city_assertions(GeoKit::Geocoders::YahooGeocoder.geocode(@address))
  49 + end
  50 +
  51 + def test_yahoo_city_with_geo_loc
  52 + response = MockSuccess.new
  53 + response.expects(:body).returns(YAHOO_CITY)
  54 + url = "http://api.local.yahoo.com/MapsService/V1/geocode?appid=Yahoo&location=#{CGI.escape(@address)}"
  55 + GeoKit::Geocoders::YahooGeocoder.expects(:call_geocoder_service).with(url).returns(response)
  56 + do_city_assertions(GeoKit::Geocoders::YahooGeocoder.geocode(@yahoo_city_loc))
  57 + end
  58 +
  59 + def test_service_unavailable
  60 + response = MockFailure.new
  61 + url = "http://api.local.yahoo.com/MapsService/V1/geocode?appid=Yahoo&location=#{CGI.escape(@address)}"
  62 + GeoKit::Geocoders::YahooGeocoder.expects(:call_geocoder_service).with(url).returns(response)
  63 + assert !GeoKit::Geocoders::YahooGeocoder.geocode(@yahoo_city_loc).success
  64 + end
  65 +
  66 + private
  67 +
  68 + # next two methods do the assertions for both address-level and city-level lookups
  69 + def do_full_address_assertions(res)
  70 + assert_equal "CA", res.state
  71 + assert_equal "San Francisco", res.city
  72 + assert_equal "37.792406,-122.39411", res.ll
  73 + assert res.is_us?
  74 + assert_equal "100 Spear St, San Francisco, CA, 94105-1522, US", res.full_address
  75 + assert_equal "yahoo", res.provider
  76 + end
  77 +
  78 + def do_city_assertions(res)
  79 + assert_equal "CA", res.state
  80 + assert_equal "San Francisco", res.city
  81 + assert_equal "37.7742,-122.417068", res.ll
  82 + assert res.is_us?
  83 + assert_equal "San Francisco, CA, US", res.full_address
  84 + assert_nil res.street_address
  85 + assert_equal "yahoo", res.provider
  86 + end
  87 +end
0 88 \ No newline at end of file
... ...