geokit 1.7.1 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +6 -14
- data/.travis.yml +1 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +2 -1
- data/MIT-LICENSE +20 -0
- data/README.markdown +44 -39
- data/Rakefile +15 -0
- data/fixtures/vcr_cassettes/bing_full.yml +102 -0
- data/fixtures/vcr_cassettes/bing_full_au.yml +91 -0
- data/fixtures/vcr_cassettes/bing_full_de.yml +91 -0
- data/fixtures/vcr_cassettes/fcc_reverse_geocode.yml +37 -0
- data/fixtures/vcr_cassettes/free_geo_ip_geocode.yml +36 -0
- data/fixtures/vcr_cassettes/geo_plugin_geocode.yml +38 -0
- data/fixtures/vcr_cassettes/geonames_geocode.yml +304 -0
- data/fixtures/vcr_cassettes/{google3_city.yml → google_city.yml} +0 -0
- data/fixtures/vcr_cassettes/{google3_country_code_biased_result.yml → google_country_code_biased_result.yml} +0 -0
- data/fixtures/vcr_cassettes/{google3_full.yml → google_full.yml} +0 -0
- data/fixtures/vcr_cassettes/{google3_full_short.yml → google_full_short.yml} +0 -0
- data/fixtures/vcr_cassettes/{google3_language_response_fr.yml → google_language_response_fr.yml} +0 -0
- data/fixtures/vcr_cassettes/{google3_multi.yml → google_multi.yml} +0 -0
- data/fixtures/vcr_cassettes/{google3_reverse_madrid.yml → google_reverse_madrid.yml} +0 -0
- data/fixtures/vcr_cassettes/ripe_geocode.yml +66 -0
- data/fixtures/vcr_cassettes/ripe_geocode_au.yml +66 -0
- data/geokit.gemspec +1 -1
- data/lib/geokit.rb +5 -0
- data/lib/geokit/bounds.rb +96 -0
- data/lib/geokit/core_ext.rb +17 -0
- data/lib/geokit/geo_loc.rb +134 -0
- data/lib/geokit/geocoders.rb +48 -35
- data/lib/geokit/geocoders/base_ip.rb +43 -0
- data/lib/geokit/geocoders/bing.rb +101 -0
- data/lib/geokit/geocoders/ca_geocoder.rb +50 -0
- data/lib/geokit/{services → geocoders}/fcc.rb +17 -20
- data/lib/geokit/geocoders/free_geo_ip.rb +34 -0
- data/lib/geokit/geocoders/geo_plugin.rb +33 -0
- data/lib/geokit/geocoders/geonames.rb +53 -0
- data/lib/geokit/{services/google3.rb → geocoders/google.rb} +59 -57
- data/lib/geokit/geocoders/ip.rb +69 -0
- data/lib/geokit/geocoders/mapquest.rb +72 -0
- data/lib/geokit/geocoders/maxmind.rb +29 -0
- data/lib/geokit/geocoders/openstreetmap.rb +119 -0
- data/lib/geokit/geocoders/ripe.rb +41 -0
- data/lib/geokit/{services → geocoders}/us_geocoder.rb +15 -20
- data/lib/geokit/{services → geocoders}/yahoo.rb +52 -55
- data/lib/geokit/geocoders/yandex.rb +61 -0
- data/lib/geokit/inflectors.rb +1 -2
- data/lib/geokit/lat_lng.rb +129 -0
- data/lib/geokit/mappable.rb +41 -424
- data/lib/geokit/multi_geocoder.rb +6 -2
- data/lib/geokit/polygon.rb +46 -0
- data/lib/geokit/version.rb +1 -1
- data/test/helper.rb +2 -12
- data/test/test_base_geocoder.rb +0 -10
- data/test/test_bing_geocoder.rb +60 -0
- data/test/test_fcc_geocoder.rb +23 -0
- data/test/test_free_geo_ip_geocoder.rb +23 -0
- data/test/test_geo_plugin_geocoder.rb +23 -0
- data/test/test_geonames_geocoder.rb +23 -0
- data/test/test_google_geocoder.rb +208 -235
- data/test/test_maxmind_geocoder.rb +35 -4
- data/test/test_multi_geocoder.rb +3 -1
- data/test/test_ripe_geocoder.rb +35 -0
- data/test/test_yahoo_geocoder.rb +0 -12
- metadata +78 -52
- data/LICENSE +0 -25
- data/Manifest.txt +0 -21
- data/data/GeoLiteCity.dat +0 -0
- data/lib/geokit/services/ca_geocoder.rb +0 -55
- data/lib/geokit/services/geo_plugin.rb +0 -31
- data/lib/geokit/services/geonames.rb +0 -53
- data/lib/geokit/services/google.rb +0 -158
- data/lib/geokit/services/ip.rb +0 -103
- data/lib/geokit/services/maxmind.rb +0 -39
- data/lib/geokit/services/openstreetmap.rb +0 -119
- data/lib/geokit/services/ripe.rb +0 -32
- data/lib/geokit/services/yandex.rb +0 -51
- data/test/test_google_geocoder3.rb +0 -238
- data/test/test_google_reverse_geocoder.rb +0 -49
| @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            module Geokit
         | 
| 2 | 
            +
              module Geocoders
         | 
| 3 | 
            +
                class BaseIpGeocoder < Geocoder
         | 
| 4 | 
            +
                  # A number of non-routable IP ranges.
         | 
| 5 | 
            +
                  #
         | 
| 6 | 
            +
                  # --
         | 
| 7 | 
            +
                  # Sources for these:
         | 
| 8 | 
            +
                  #   RFC 3330: Special-Use IPv4 Addresses
         | 
| 9 | 
            +
                  #   The bogon list: http://www.cymru.com/Documents/bogon-list.html
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  NON_ROUTABLE_IP_RANGES = [
         | 
| 12 | 
            +
                    IPAddr.new('0.0.0.0/8'),      # "This" Network
         | 
| 13 | 
            +
                    IPAddr.new('10.0.0.0/8'),     # Private-Use Networks
         | 
| 14 | 
            +
                    IPAddr.new('14.0.0.0/8'),     # Public-Data Networks
         | 
| 15 | 
            +
                    IPAddr.new('127.0.0.0/8'),    # Loopback
         | 
| 16 | 
            +
                    IPAddr.new('169.254.0.0/16'), # Link local
         | 
| 17 | 
            +
                    IPAddr.new('172.16.0.0/12'),  # Private-Use Networks
         | 
| 18 | 
            +
                    IPAddr.new('192.0.2.0/24'),   # Test-Net
         | 
| 19 | 
            +
                    IPAddr.new('192.168.0.0/16'), # Private-Use Networks
         | 
| 20 | 
            +
                    IPAddr.new('198.18.0.0/15'),  # Network Interconnect Device Benchmark Testing
         | 
| 21 | 
            +
                    IPAddr.new('224.0.0.0/4'),    # Multicast
         | 
| 22 | 
            +
                    IPAddr.new('240.0.0.0/4')     # Reserved for future use
         | 
| 23 | 
            +
                  ].freeze
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def self.valid_ip?(ip)
         | 
| 26 | 
            +
                    ip?(ip) && !private_ip_address?(ip)
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def self.ip?(ip)
         | 
| 30 | 
            +
                    /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  # Checks whether the IP address belongs to a private address range.
         | 
| 34 | 
            +
                  #
         | 
| 35 | 
            +
                  # This function is used to reduce the number of useless queries made to
         | 
| 36 | 
            +
                  # the geocoding service. Such queries can occur frequently during
         | 
| 37 | 
            +
                  # integration tests.
         | 
| 38 | 
            +
                  def self.private_ip_address?(ip)
         | 
| 39 | 
            +
                    NON_ROUTABLE_IP_RANGES.any? { |range| range.include?(ip) }
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
            end
         | 
| @@ -0,0 +1,101 @@ | |
| 1 | 
            +
            module Geokit
         | 
| 2 | 
            +
              module Geocoders
         | 
| 3 | 
            +
                # Bing geocoder implementation.  Requires the Geokit::Geocoders::bing variable to
         | 
| 4 | 
            +
                # contain a Bing Maps API key.  Conforms to the interface set by the Geocoder class.
         | 
| 5 | 
            +
                class BingGeocoder < Geocoder
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  private
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  # Template method which does the geocode lookup.
         | 
| 10 | 
            +
                  def self.do_geocode(address)
         | 
| 11 | 
            +
                    url = submit_url(address)
         | 
| 12 | 
            +
                    res = call_geocoder_service(url)
         | 
| 13 | 
            +
                    return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
         | 
| 14 | 
            +
                    xml = transcode_to_utf8(res.body)
         | 
| 15 | 
            +
                    logger.debug "Bing geocoding. Address: #{address}. Result: #{xml}"
         | 
| 16 | 
            +
                    parse :xml, xml
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def self.submit_url(address)
         | 
| 20 | 
            +
                    options = Geokit::Geocoders::bing_options
         | 
| 21 | 
            +
                    culture = options && options[:culture]
         | 
| 22 | 
            +
                    culture_string = culture ? "&c=#{culture}" : ''
         | 
| 23 | 
            +
                    address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
         | 
| 24 | 
            +
                    "http://dev.virtualearth.net/REST/v1/Locations/#{URI.escape(address_str)}?key=#{Geokit::Geocoders::bing}#{culture_string}&o=xml"
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def self.parse_xml(xml)
         | 
| 28 | 
            +
                    return GeoLoc.new if xml.elements['//Response/StatusCode'].try(:text) != '200'
         | 
| 29 | 
            +
                    loc = nil
         | 
| 30 | 
            +
                    # Bing can return multiple results as //Location elements.
         | 
| 31 | 
            +
                    # iterate through each and extract each location as a geoloc
         | 
| 32 | 
            +
                    xml.each_element('//Location') do |l|
         | 
| 33 | 
            +
                      extracted_geoloc = extract_location(l)
         | 
| 34 | 
            +
                      loc.nil? ? loc = extracted_geoloc : loc.all.push(extracted_geoloc)
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
                    loc
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  # extracts a single geoloc from a //Location element in the bing results xml
         | 
| 40 | 
            +
                  def self.extract_location(xml)
         | 
| 41 | 
            +
                    loc                 = GeoLoc.new
         | 
| 42 | 
            +
                    loc.provider        = 'bing'
         | 
| 43 | 
            +
                    set_address_components(loc, xml)
         | 
| 44 | 
            +
                    set_precision(loc, xml)
         | 
| 45 | 
            +
                    set_bounds(loc, xml)
         | 
| 46 | 
            +
                    loc.success         = true
         | 
| 47 | 
            +
                    loc
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  XML_MAPPINGS = {
         | 
| 51 | 
            +
                    :street_address => 'Address/AddressLine',
         | 
| 52 | 
            +
                    :full_address   => 'Address/FormattedAddress',
         | 
| 53 | 
            +
                    :city           => 'Address/Locality',
         | 
| 54 | 
            +
                    :state          => 'Address/AdminDistrict',
         | 
| 55 | 
            +
                    :province       => 'Address/AdminDistrict2',
         | 
| 56 | 
            +
                    :zip            => 'Address/PostalCode',
         | 
| 57 | 
            +
                    :country        => 'Address/CountryRegion',
         | 
| 58 | 
            +
                    :lat            => 'Point/Latitude',
         | 
| 59 | 
            +
                    :lng            => 'Point/Longitude'
         | 
| 60 | 
            +
                  }
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                  def self.set_address_components(loc, xml)
         | 
| 63 | 
            +
                    set_mappings(loc, xml, XML_MAPPINGS)
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  def self.set_precision(loc, xml)
         | 
| 67 | 
            +
                    if xml.elements['.//Confidence']
         | 
| 68 | 
            +
                      loc.accuracy      = case xml.elements['.//Confidence'].text
         | 
| 69 | 
            +
                      when 'High'    then 8
         | 
| 70 | 
            +
                      when 'Medium'  then 5
         | 
| 71 | 
            +
                      when 'Low'     then 2
         | 
| 72 | 
            +
                      else             0
         | 
| 73 | 
            +
                      end
         | 
| 74 | 
            +
                    end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                    if xml.elements['.//EntityType']
         | 
| 77 | 
            +
                      loc.precision     = case xml.elements['.//EntityType'].text
         | 
| 78 | 
            +
                      when 'Sovereign'      then 'country'
         | 
| 79 | 
            +
                      when 'AdminDivision1' then 'state'
         | 
| 80 | 
            +
                      when 'AdminDivision2' then 'state'
         | 
| 81 | 
            +
                      when 'PopulatedPlace' then 'city'
         | 
| 82 | 
            +
                      when 'Postcode1'      then 'zip'
         | 
| 83 | 
            +
                      when 'Postcode2'      then 'zip'
         | 
| 84 | 
            +
                      when 'RoadBlock'      then 'street'
         | 
| 85 | 
            +
                      when 'Address'        then 'address'
         | 
| 86 | 
            +
                      else                    'unkown'
         | 
| 87 | 
            +
                      end
         | 
| 88 | 
            +
                    end
         | 
| 89 | 
            +
                  end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  def self.set_bounds(loc, xml)
         | 
| 92 | 
            +
                    if suggested_bounds = xml.elements['.//BoundingBox']
         | 
| 93 | 
            +
                      bounds = suggested_bounds.elements
         | 
| 94 | 
            +
                      loc.suggested_bounds = Bounds.normalize(
         | 
| 95 | 
            +
                        [bounds['.//SouthLatitude'].text, bounds['.//WestLongitude'].text],
         | 
| 96 | 
            +
                        [bounds['.//NorthLatitude'].text, bounds['.//EastLongitude'].text])
         | 
| 97 | 
            +
                    end
         | 
| 98 | 
            +
                  end
         | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
              end
         | 
| 101 | 
            +
            end
         | 
| @@ -0,0 +1,50 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            # Geocoder CA geocoder implementation.  Requires the Geokit::Geocoders::GEOCODER_CA variable to
         | 
| 3 | 
            +
            # contain true or false based upon whether authentication is to occur.  Conforms to the
         | 
| 4 | 
            +
            # interface set by the Geocoder class.
         | 
| 5 | 
            +
            #
         | 
| 6 | 
            +
            # Returns a response like:
         | 
| 7 | 
            +
            # <?xml version="1.0" encoding="UTF-8" ?>
         | 
| 8 | 
            +
            # <geodata>
         | 
| 9 | 
            +
            #   <latt>49.243086</latt>
         | 
| 10 | 
            +
            #   <longt>-123.153684</longt>
         | 
| 11 | 
            +
            # </geodata>
         | 
| 12 | 
            +
            module Geokit
         | 
| 13 | 
            +
             module Geocoders
         | 
| 14 | 
            +
               class CaGeocoder < Geocoder
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                 private
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                 # Template method which does the geocode lookup.
         | 
| 19 | 
            +
                 def self.do_geocode(loc)
         | 
| 20 | 
            +
                   raise ArgumentError('Geocoder.ca requires a GeoLoc argument') unless loc.is_a?(GeoLoc)
         | 
| 21 | 
            +
                   url = submit_url(loc)
         | 
| 22 | 
            +
                   res = call_geocoder_service(url)
         | 
| 23 | 
            +
                   return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
         | 
| 24 | 
            +
                   xml = res.body
         | 
| 25 | 
            +
                   logger.debug "Geocoder.ca geocoding. Address: #{loc}. Result: #{xml}"
         | 
| 26 | 
            +
                   parse :xml, xml, loc
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def self.parse_xml(xml, loc)
         | 
| 30 | 
            +
                   loc.lat = xml.elements['//latt'].text
         | 
| 31 | 
            +
                   loc.lng = xml.elements['//longt'].text
         | 
| 32 | 
            +
                   loc.success = true
         | 
| 33 | 
            +
                   loc
         | 
| 34 | 
            +
                 end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                 # Formats the request in the format acceptable by the CA geocoder.
         | 
| 37 | 
            +
                 def self.submit_url(loc)
         | 
| 38 | 
            +
                   args = []
         | 
| 39 | 
            +
                   args << "stno=#{loc.street_number}" if loc.street_address
         | 
| 40 | 
            +
                   args << "addresst=#{Geokit::Inflector::url_escape(loc.street_name)}" if loc.street_address
         | 
| 41 | 
            +
                   args << "city=#{Geokit::Inflector::url_escape(loc.city)}" if loc.city
         | 
| 42 | 
            +
                   args << "prov=#{loc.state}" if loc.state
         | 
| 43 | 
            +
                   args << "postal=#{loc.zip}" if loc.zip
         | 
| 44 | 
            +
                   args << "auth=#{Geokit::Geocoders::geocoder_ca}" if Geokit::Geocoders::geocoder_ca
         | 
| 45 | 
            +
                   args << "geoit=xml"
         | 
| 46 | 
            +
                   'http://geocoder.ca/?' + args.join('&')
         | 
| 47 | 
            +
                 end
         | 
| 48 | 
            +
               end
         | 
| 49 | 
            +
             end
         | 
| 50 | 
            +
            end
         | 
| @@ -6,11 +6,12 @@ module Geokit | |
| 6 6 | 
             
                  # Template method which does the reverse-geocode lookup.
         | 
| 7 7 | 
             
                  def self.do_reverse_geocode(latlng)
         | 
| 8 8 | 
             
                    latlng=LatLng.normalize(latlng)
         | 
| 9 | 
            -
                     | 
| 10 | 
            -
                     | 
| 9 | 
            +
                    url = "http://data.fcc.gov/api/block/find?format=json&latitude=#{Geokit::Inflector::url_escape(latlng.lat.to_s)}&longitude=#{Geokit::Inflector::url_escape(latlng.lng.to_s)}"
         | 
| 10 | 
            +
                    res = call_geocoder_service(url)
         | 
| 11 | 
            +
                    return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
         | 
| 11 12 | 
             
                    json = res.body
         | 
| 12 13 | 
             
                    logger.debug "FCC reverse-geocoding. LL: #{latlng}. Result: #{json}"
         | 
| 13 | 
            -
                     | 
| 14 | 
            +
                    parse :json, json
         | 
| 14 15 | 
             
                  end
         | 
| 15 16 |  | 
| 16 17 | 
             
                  # Template method which does the geocode lookup.
         | 
| @@ -26,11 +27,8 @@ module Geokit | |
| 26 27 | 
             
                  # "State"=>{"name"=>"Indiana", "code"=>"IN", "FIPS"=>"18"},
         | 
| 27 28 | 
             
                  # "status"=>"OK"}
         | 
| 28 29 |  | 
| 29 | 
            -
                  def self. | 
| 30 | 
            -
                     | 
| 31 | 
            -
                    results = MultiJson.load(json)
         | 
| 32 | 
            -
             | 
| 33 | 
            -
                    if results.has_key?('Err') and results['Err']["msg"] == 'There are no results for this location'
         | 
| 30 | 
            +
                  def self.parse_json(results)
         | 
| 31 | 
            +
                    if results.has_key?('Err') && results['Err']["msg"] == 'There are no results for this location'
         | 
| 34 32 | 
             
                      return GeoLoc.new
         | 
| 35 33 | 
             
                    end
         | 
| 36 34 | 
             
                    # this should probably be smarter.
         | 
| @@ -38,18 +36,17 @@ module Geokit | |
| 38 36 | 
             
                      raise Geokit::Geocoders::GeocodeError
         | 
| 39 37 | 
             
                    end
         | 
| 40 38 |  | 
| 41 | 
            -
                     | 
| 42 | 
            -
                     | 
| 43 | 
            -
                     | 
| 44 | 
            -
                     | 
| 45 | 
            -
                     | 
| 46 | 
            -
                     | 
| 47 | 
            -
                     | 
| 48 | 
            -
                     | 
| 49 | 
            -
                     | 
| 50 | 
            -
                     | 
| 51 | 
            -
             | 
| 52 | 
            -
                    res
         | 
| 39 | 
            +
                    loc = GeoLoc.new
         | 
| 40 | 
            +
                    loc.provider      = 'fcc'
         | 
| 41 | 
            +
                    loc.success       = true
         | 
| 42 | 
            +
                    loc.precision     = 'block'
         | 
| 43 | 
            +
                    loc.country_code  = 'US'
         | 
| 44 | 
            +
                    loc.district      = results['County']['name']
         | 
| 45 | 
            +
                    loc.district_fips = results['County']['FIPS']
         | 
| 46 | 
            +
                    loc.state         = results['State']['code']
         | 
| 47 | 
            +
                    loc.state_fips    = results['State']['FIPS']
         | 
| 48 | 
            +
                    loc.block_fips    = results['Block']['FIPS']
         | 
| 49 | 
            +
                    loc
         | 
| 53 50 | 
             
                  end
         | 
| 54 51 | 
             
                end
         | 
| 55 52 |  | 
| @@ -0,0 +1,34 @@ | |
| 1 | 
            +
            module Geokit
         | 
| 2 | 
            +
              module Geocoders
         | 
| 3 | 
            +
                # Provides geocoding based upon an IP address.  The underlying web service is freegeoip.net
         | 
| 4 | 
            +
                class FreeGeoIpGeocoder < BaseIpGeocoder
         | 
| 5 | 
            +
                  private
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  def self.do_geocode(ip)
         | 
| 8 | 
            +
                    return GeoLoc.new unless valid_ip?(ip)
         | 
| 9 | 
            +
                    url = "http://freegeoip.net/xml/#{ip}"
         | 
| 10 | 
            +
                    res = call_geocoder_service(url)
         | 
| 11 | 
            +
                    return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
         | 
| 12 | 
            +
                    parse :xml, res.body
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  XML_MAPPINGS = {
         | 
| 16 | 
            +
                    :city         => 'City',
         | 
| 17 | 
            +
                    :state        => 'RegionCode',
         | 
| 18 | 
            +
                    :zip          => 'ZipCode',
         | 
| 19 | 
            +
                    :country_code => 'CountryCode',
         | 
| 20 | 
            +
                    :lat          => 'Latitude',
         | 
| 21 | 
            +
                    :lng          => 'Longitude'
         | 
| 22 | 
            +
                  }
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def self.parse_xml(xml)
         | 
| 25 | 
            +
                    loc = GeoLoc.new
         | 
| 26 | 
            +
                    loc.provider = 'freegeoip'
         | 
| 27 | 
            +
                    set_mappings(loc, xml.elements['Response'], XML_MAPPINGS)
         | 
| 28 | 
            +
                    loc.success = !!loc.city && !loc.city.empty?
         | 
| 29 | 
            +
                    loc
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
            end
         | 
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            module Geokit
         | 
| 2 | 
            +
              module Geocoders
         | 
| 3 | 
            +
                # Provides geocoding based upon an IP address.  The underlying web service is geoplugin.net
         | 
| 4 | 
            +
                class GeoPluginGeocoder < BaseIpGeocoder
         | 
| 5 | 
            +
                  private
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  def self.do_geocode(ip)
         | 
| 8 | 
            +
                    return GeoLoc.new unless valid_ip?(ip)
         | 
| 9 | 
            +
                    url = "http://www.geoplugin.net/xml.gp?ip=#{ip}"
         | 
| 10 | 
            +
                    res = call_geocoder_service(url)
         | 
| 11 | 
            +
                    return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
         | 
| 12 | 
            +
                    parse :xml, res.body
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  XML_MAPPINGS = {
         | 
| 16 | 
            +
                    :city         => 'geoplugin_city',
         | 
| 17 | 
            +
                    :state        => 'geoplugin_region',
         | 
| 18 | 
            +
                    :country_code => 'geoplugin_countryCode',
         | 
| 19 | 
            +
                    :lat          => 'geoplugin_latitude',
         | 
| 20 | 
            +
                    :lng          => 'geoplugin_longitude'
         | 
| 21 | 
            +
                  }
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def self.parse_xml(xml)
         | 
| 24 | 
            +
                    loc = GeoLoc.new
         | 
| 25 | 
            +
                    loc.provider = 'geoPlugin'
         | 
| 26 | 
            +
                    set_mappings(loc, xml.elements['geoPlugin'], XML_MAPPINGS)
         | 
| 27 | 
            +
                    loc.success = !!loc.city && !loc.city.empty?
         | 
| 28 | 
            +
                    loc
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
| @@ -0,0 +1,53 @@ | |
| 1 | 
            +
            module Geokit
         | 
| 2 | 
            +
              module Geocoders
         | 
| 3 | 
            +
                # Another geocoding web service
         | 
| 4 | 
            +
                # http://www.geonames.org
         | 
| 5 | 
            +
                class GeonamesGeocoder < Geocoder
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  private
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  # Template method which does the geocode lookup.
         | 
| 10 | 
            +
                  def self.do_geocode(address)
         | 
| 11 | 
            +
                    url = submit_url(address)
         | 
| 12 | 
            +
                    res = call_geocoder_service(url)
         | 
| 13 | 
            +
                    return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    xml = res.body
         | 
| 16 | 
            +
                    logger.debug "Geonames geocoding. Address: #{address}. Result: #{xml}"
         | 
| 17 | 
            +
                    parse :xml, xml
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def self.submit_url(address)
         | 
| 21 | 
            +
                    address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
         | 
| 22 | 
            +
                    # geonames need a space seperated search string
         | 
| 23 | 
            +
                    address_str.gsub!(/,/, " ")
         | 
| 24 | 
            +
                    params = "/postalCodeSearch?placename=#{Geokit::Inflector::url_escape(address_str)}&maxRows=10"
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    if Geokit::Geocoders::geonames
         | 
| 27 | 
            +
                      "http://ws.geonames.net#{params}&username=#{Geokit::Geocoders::geonames}"
         | 
| 28 | 
            +
                    else
         | 
| 29 | 
            +
                      "http://ws.geonames.org#{params}"
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  XML_MAPPINGS = {
         | 
| 34 | 
            +
                    :city         => 'name',
         | 
| 35 | 
            +
                    :state        => 'adminName1',
         | 
| 36 | 
            +
                    :zip          => 'postalcode',
         | 
| 37 | 
            +
                    :country_code => 'countryCode',
         | 
| 38 | 
            +
                    :lat          => 'lat',
         | 
| 39 | 
            +
                    :lng          => 'lng'
         | 
| 40 | 
            +
                  }
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  def self.parse_xml(xml)
         | 
| 43 | 
            +
                    return GeoLoc.new unless xml.elements['geonames/totalResultsCount'].text.to_i > 0
         | 
| 44 | 
            +
                    loc = GeoLoc.new
         | 
| 45 | 
            +
                    loc.provider = 'genomes'
         | 
| 46 | 
            +
                    # only take the first result
         | 
| 47 | 
            +
                    set_mappings(loc, xml.elements['geonames/code'], XML_MAPPINGS)
         | 
| 48 | 
            +
                    loc.success = true
         | 
| 49 | 
            +
                    loc
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
            end
         | 
| @@ -1,17 +1,17 @@ | |
| 1 1 | 
             
            module Geokit
         | 
| 2 2 | 
             
              module Geocoders
         | 
| 3 | 
            -
                class  | 
| 3 | 
            +
                class GoogleGeocoder < Geocoder
         | 
| 4 4 |  | 
| 5 5 | 
             
                  private
         | 
| 6 6 | 
             
                  # Template method which does the reverse-geocode lookup.
         | 
| 7 7 | 
             
                  def self.do_reverse_geocode(latlng)
         | 
| 8 8 | 
             
                    latlng=LatLng.normalize(latlng)
         | 
| 9 | 
            -
                     | 
| 10 | 
            -
                    res =  | 
| 11 | 
            -
                    return GeoLoc.new  | 
| 9 | 
            +
                    url = submit_url("/maps/api/geocode/json?sensor=false&latlng=#{Geokit::Inflector::url_escape(latlng.ll)}")
         | 
| 10 | 
            +
                    res = call_geocoder_service(url)
         | 
| 11 | 
            +
                    return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
         | 
| 12 12 | 
             
                    json = res.body
         | 
| 13 13 | 
             
                    logger.debug "Google reverse-geocoding. LL: #{latlng}. Result: #{CGI.escape(json)}"
         | 
| 14 | 
            -
                     | 
| 14 | 
            +
                    parse :json, json
         | 
| 15 15 | 
             
                  end
         | 
| 16 16 |  | 
| 17 17 | 
             
                  # Template method which does the geocode lookup.
         | 
| @@ -43,15 +43,15 @@ module Geokit | |
| 43 43 | 
             
                    bias_str = options[:bias] ? construct_bias_string_from_options(options[:bias]) : ''
         | 
| 44 44 | 
             
                    language_str = options[:language] ? "&language=#{options[:language]}" : ''
         | 
| 45 45 | 
             
                    address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
         | 
| 46 | 
            -
                     | 
| 46 | 
            +
                    url = submit_url("/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(address_str)}#{bias_str}#{language_str}")
         | 
| 47 47 |  | 
| 48 | 
            -
                    res =  | 
| 48 | 
            +
                    res = call_geocoder_service(url)
         | 
| 49 49 | 
             
                    return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
         | 
| 50 50 |  | 
| 51 51 | 
             
                    json = res.body
         | 
| 52 52 | 
             
                    logger.debug "Google geocoding. Address: #{address}. Result: #{CGI.escape(json)}"
         | 
| 53 53 |  | 
| 54 | 
            -
                     | 
| 54 | 
            +
                    parse :json, json
         | 
| 55 55 | 
             
                  end
         | 
| 56 56 |  | 
| 57 57 | 
             
                  # This code comes from Googles Examples
         | 
| @@ -64,13 +64,14 @@ module Geokit | |
| 64 64 | 
             
                    # create a signature using the private key and the URL
         | 
| 65 65 | 
             
                    rawSignature = OpenSSL::HMAC.digest('sha1', rawKey, urlToSign)
         | 
| 66 66 | 
             
                    # encode the signature into base64 for url use form.
         | 
| 67 | 
            -
                     | 
| 67 | 
            +
                    Base64.encode64(rawSignature).tr('+/','-_').gsub(/\n/, '')
         | 
| 68 68 | 
             
                  end
         | 
| 69 69 |  | 
| 70 70 |  | 
| 71 71 | 
             
                  def self.submit_url(query_string)
         | 
| 72 | 
            -
                    if  | 
| 73 | 
            -
                       | 
| 72 | 
            +
                    if Geokit::Geocoders::google_client_id && Geokit::Geocoders::google_cryptographic_key
         | 
| 73 | 
            +
                      channel = Geokit::Geocoders::google_channel ? "&channel=#{Geokit::Geocoders::google_channel}" : ''
         | 
| 74 | 
            +
                      urlToSign = query_string + "&client=#{Geokit::Geocoders::google_client_id}" + channel
         | 
| 74 75 | 
             
                      signature = sign_gmap_bus_api_url(urlToSign, Geokit::Geocoders::google_cryptographic_key)
         | 
| 75 76 | 
             
                      "http://maps.googleapis.com" + urlToSign + "&signature=#{signature}"
         | 
| 76 77 | 
             
                    else
         | 
| @@ -91,11 +92,9 @@ module Geokit | |
| 91 92 | 
             
                    end
         | 
| 92 93 | 
             
                  end
         | 
| 93 94 |  | 
| 94 | 
            -
                  def self. | 
| 95 | 
            -
                    results = MultiJson.load(json)
         | 
| 96 | 
            -
             | 
| 95 | 
            +
                  def self.parse_json(results)
         | 
| 97 96 | 
             
                    case results['status']
         | 
| 98 | 
            -
                    when 'OVER_QUERY_LIMIT' then raise Geokit::TooManyQueriesError
         | 
| 97 | 
            +
                    when 'OVER_QUERY_LIMIT' then raise Geokit::Geocoders::TooManyQueriesError
         | 
| 99 98 | 
             
                    when 'ZERO_RESULTS' then return GeoLoc.new
         | 
| 100 99 | 
             
                    end
         | 
| 101 100 | 
             
                    # this should probably be smarter.
         | 
| @@ -138,66 +137,69 @@ module Geokit | |
| 138 137 | 
             
                  }
         | 
| 139 138 |  | 
| 140 139 | 
             
                  def self.single_json_to_geoloc(addr)
         | 
| 141 | 
            -
                     | 
| 142 | 
            -
                     | 
| 143 | 
            -
                     | 
| 144 | 
            -
                     | 
| 140 | 
            +
                    loc = GeoLoc.new
         | 
| 141 | 
            +
                    loc.provider = 'google'
         | 
| 142 | 
            +
                    loc.success = true
         | 
| 143 | 
            +
                    loc.full_address = addr['formatted_address']
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                    set_address_components(loc, addr)
         | 
| 146 | 
            +
                    set_precision(loc, addr)
         | 
| 147 | 
            +
                    if loc.street_name
         | 
| 148 | 
            +
                      loc.street_address=[loc.street_number, loc.street_name].join(' ').strip
         | 
| 149 | 
            +
                    end
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                    ll = addr['geometry']['location']
         | 
| 152 | 
            +
                    loc.lat = ll['lat'].to_f
         | 
| 153 | 
            +
                    loc.lng = ll['lng'].to_f
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                    viewport = addr['geometry']['viewport']
         | 
| 156 | 
            +
                    ne = Geokit::LatLng.from_json(viewport['northeast'])
         | 
| 157 | 
            +
                    sw = Geokit::LatLng.from_json(viewport['southwest'])
         | 
| 158 | 
            +
                    loc.suggested_bounds = Geokit::Bounds.new(sw, ne)
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                    loc
         | 
| 161 | 
            +
                  end
         | 
| 145 162 |  | 
| 163 | 
            +
                  def self.set_address_components(loc, addr)
         | 
| 146 164 | 
             
                    addr['address_components'].each do |comp|
         | 
| 147 165 | 
             
                      case
         | 
| 148 166 | 
             
                      when comp['types'].include?("subpremise")
         | 
| 149 | 
            -
                         | 
| 167 | 
            +
                        loc.sub_premise = comp['short_name']
         | 
| 150 168 | 
             
                      when comp['types'].include?("street_number")
         | 
| 151 | 
            -
                         | 
| 169 | 
            +
                        loc.street_number = comp['short_name']
         | 
| 152 170 | 
             
                      when comp['types'].include?("route")
         | 
| 153 | 
            -
                         | 
| 171 | 
            +
                        loc.street_name = comp['long_name']
         | 
| 154 172 | 
             
                      when comp['types'].include?("locality")
         | 
| 155 | 
            -
                         | 
| 173 | 
            +
                        loc.city = comp['long_name']
         | 
| 156 174 | 
             
                      when comp['types'].include?("administrative_area_level_1")
         | 
| 157 | 
            -
                         | 
| 158 | 
            -
                         | 
| 175 | 
            +
                        loc.state = comp['short_name']
         | 
| 176 | 
            +
                        loc.province = comp['short_name']
         | 
| 159 177 | 
             
                      when comp['types'].include?("postal_code")
         | 
| 160 | 
            -
                         | 
| 178 | 
            +
                        loc.zip = comp['long_name']
         | 
| 161 179 | 
             
                      when comp['types'].include?("country")
         | 
| 162 | 
            -
                         | 
| 163 | 
            -
                         | 
| 180 | 
            +
                        loc.country_code = comp['short_name']
         | 
| 181 | 
            +
                        loc.country = comp['long_name']
         | 
| 164 182 | 
             
                      when comp['types'].include?("administrative_area_level_2")
         | 
| 165 | 
            -
                         | 
| 183 | 
            +
                        loc.district = comp['long_name']
         | 
| 166 184 | 
             
                      when comp['types'].include?('neighborhood')
         | 
| 167 | 
            -
                         | 
| 185 | 
            +
                        loc.neighborhood = comp['short_name']
         | 
| 168 186 | 
             
                      end
         | 
| 169 187 | 
             
                    end
         | 
| 170 | 
            -
             | 
| 171 | 
            -
             | 
| 172 | 
            -
             | 
| 173 | 
            -
                     | 
| 174 | 
            -
                     | 
| 188 | 
            +
                  end
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                  def self.set_precision(loc, addr)
         | 
| 191 | 
            +
                    loc.accuracy = ACCURACY[addr['geometry']['location_type']]
         | 
| 192 | 
            +
                    loc.precision=%w{unknown country state state city zip zip+4 street address building}[loc.accuracy]
         | 
| 175 193 | 
             
                    # try a few overrides where we can
         | 
| 176 | 
            -
                    if  | 
| 177 | 
            -
                       | 
| 178 | 
            -
                       | 
| 194 | 
            +
                    if loc.sub_premise
         | 
| 195 | 
            +
                      loc.accuracy = 9
         | 
| 196 | 
            +
                      loc.precision = 'building'
         | 
| 179 197 | 
             
                    end
         | 
| 180 | 
            -
                    if  | 
| 181 | 
            -
                       | 
| 182 | 
            -
                       | 
| 198 | 
            +
                    if loc.street_name && loc.precision=='city'
         | 
| 199 | 
            +
                      loc.precision = 'street'
         | 
| 200 | 
            +
                      loc.accuracy = 7
         | 
| 183 201 | 
             
                    end
         | 
| 184 | 
            -
             | 
| 185 | 
            -
                    res.lat=addr['geometry']['location']['lat'].to_f
         | 
| 186 | 
            -
                    res.lng=addr['geometry']['location']['lng'].to_f
         | 
| 187 | 
            -
             | 
| 188 | 
            -
                    ne=Geokit::LatLng.new(
         | 
| 189 | 
            -
                      addr['geometry']['viewport']['northeast']['lat'].to_f,
         | 
| 190 | 
            -
                      addr['geometry']['viewport']['northeast']['lng'].to_f
         | 
| 191 | 
            -
                    )
         | 
| 192 | 
            -
                    sw=Geokit::LatLng.new(
         | 
| 193 | 
            -
                      addr['geometry']['viewport']['southwest']['lat'].to_f,
         | 
| 194 | 
            -
                      addr['geometry']['viewport']['southwest']['lng'].to_f
         | 
| 195 | 
            -
                    )
         | 
| 196 | 
            -
                    res.suggested_bounds = Geokit::Bounds.new(sw,ne)
         | 
| 197 | 
            -
             | 
| 198 | 
            -
                    res
         | 
| 199 202 | 
             
                  end
         | 
| 200 203 | 
             
                end
         | 
| 201 | 
            -
                Google3Geocoder = GoogleGeocoder3
         | 
| 202 204 | 
             
              end
         | 
| 203 205 | 
             
            end
         |