darrell-geokit 1.4.1.1 → 1.5.0.1
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.
- data/README.markdown +1 -1
- data/lib/geokit.rb +1 -1
- data/lib/geokit/geocoders.rb +204 -11
- data/lib/geokit/mappable.rb +20 -14
- data/test/test_base_geocoder.rb +1 -1
- data/test/test_geoloc.rb +1 -1
- data/test/test_geoplugin_geocoder.rb +2 -2
- data/test/test_google_geocoder.rb +44 -0
- data/test/test_google_geocoder3.rb +536 -0
- data/test/test_ipgeocoder.rb +32 -10
- metadata +30 -8
    
        data/README.markdown
    CHANGED
    
    | @@ -54,7 +54,7 @@ If you're using this gem by itself, here are the configuration options: | |
| 54 54 | 
             
            		# This is the timeout value in seconds to be used for calls to the geocoder web
         | 
| 55 55 | 
             
            		# services.  For no timeout at all, comment out the setting.  The timeout unit
         | 
| 56 56 | 
             
            		# is in seconds. 
         | 
| 57 | 
            -
            		Geokit::Geocoders:: | 
| 57 | 
            +
            		Geokit::Geocoders::request_timeout = 3
         | 
| 58 58 |  | 
| 59 59 | 
             
            		# These settings are used if web service calls must be routed through a proxy.
         | 
| 60 60 | 
             
            		# These setting can be nil if not needed, otherwise, addr and port must be 
         | 
    
        data/lib/geokit.rb
    CHANGED
    
    
    
        data/lib/geokit/geocoders.rb
    CHANGED
    
    | @@ -1,10 +1,15 @@ | |
| 1 1 | 
             
            require 'net/http'
         | 
| 2 | 
            +
            require 'ipaddr'
         | 
| 2 3 | 
             
            require 'rexml/document'
         | 
| 3 4 | 
             
            require 'yaml'
         | 
| 4 5 | 
             
            require 'timeout'
         | 
| 5 6 | 
             
            require 'logger'
         | 
| 7 | 
            +
            require 'json'
         | 
| 6 8 |  | 
| 7 9 | 
             
            module Geokit
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              class TooManyQueriesError < StandardError; end
         | 
| 12 | 
            +
             | 
| 8 13 | 
             
              module Inflector
         | 
| 9 14 |  | 
| 10 15 | 
             
                extend self
         | 
| @@ -67,7 +72,7 @@ module Geokit | |
| 67 72 | 
             
                @@proxy_port = nil
         | 
| 68 73 | 
             
                @@proxy_user = nil
         | 
| 69 74 | 
             
                @@proxy_pass = nil
         | 
| 70 | 
            -
                @@ | 
| 75 | 
            +
                @@request_timeout = nil    
         | 
| 71 76 | 
             
                @@yahoo = 'REPLACE_WITH_YOUR_YAHOO_KEY'
         | 
| 72 77 | 
             
                @@google = 'REPLACE_WITH_YOUR_GOOGLE_KEY'
         | 
| 73 78 | 
             
                @@geocoder_us = false
         | 
| @@ -133,7 +138,7 @@ module Geokit | |
| 133 138 |  | 
| 134 139 | 
             
                  # Call the geocoder service using the timeout if configured.
         | 
| 135 140 | 
             
                  def self.call_geocoder_service(url)
         | 
| 136 | 
            -
                    timeout(Geokit::Geocoders:: | 
| 141 | 
            +
                    Timeout::timeout(Geokit::Geocoders::request_timeout) { return self.do_get(url) } if Geokit::Geocoders::request_timeout        
         | 
| 137 142 | 
             
                    return self.do_get(url)
         | 
| 138 143 | 
             
                  rescue TimeoutError
         | 
| 139 144 | 
             
                    return nil  
         | 
| @@ -162,8 +167,7 @@ module Geokit | |
| 162 167 | 
             
                    res = Net::HTTP::Proxy(GeoKit::Geocoders::proxy_addr,
         | 
| 163 168 | 
             
                            GeoKit::Geocoders::proxy_port,
         | 
| 164 169 | 
             
                            GeoKit::Geocoders::proxy_user,
         | 
| 165 | 
            -
                            GeoKit::Geocoders::proxy_pass).start(uri.host, uri.port) { |http| http. | 
| 166 | 
            -
             | 
| 170 | 
            +
                            GeoKit::Geocoders::proxy_pass).start(uri.host, uri.port) { |http| http.get(uri.path + "?" + uri.query) }
         | 
| 167 171 | 
             
                    return res
         | 
| 168 172 | 
             
                  end
         | 
| 169 173 |  | 
| @@ -463,14 +467,19 @@ module Geokit | |
| 463 467 | 
             
                        end  
         | 
| 464 468 | 
             
                      end
         | 
| 465 469 | 
             
                      return geoloc
         | 
| 466 | 
            -
                     | 
| 470 | 
            +
                    elsif doc.elements['//kml/Response/Status/code'].text == '620'
         | 
| 471 | 
            +
                       raise Geokit::TooManyQueriesError
         | 
| 472 | 
            +
                    else
         | 
| 467 473 | 
             
                      logger.info "Google was unable to geocode address: "+address
         | 
| 468 474 | 
             
                      return GeoLoc.new
         | 
| 469 475 | 
             
                    end
         | 
| 470 476 |  | 
| 471 | 
            -
             | 
| 472 | 
            -
             | 
| 473 | 
            -
             | 
| 477 | 
            +
                  rescue Geokit::TooManyQueriesError
         | 
| 478 | 
            +
                    # re-raise because of other rescue
         | 
| 479 | 
            +
                    raise Geokit::TooManyQueriesError, "Google returned a 620 status, too many queries. The given key has gone over the requests limit in the 24 hour period or has submitted too many requests in too short a period of time. If you're sending multiple requests in parallel or in a tight loop, use a timer or pause in your code to make sure you don't send the requests too quickly."
         | 
| 480 | 
            +
                  rescue
         | 
| 481 | 
            +
                    logger.error "Caught an error during Google geocoding call: "+$!
         | 
| 482 | 
            +
                    return GeoLoc.new
         | 
| 474 483 | 
             
                  end  
         | 
| 475 484 |  | 
| 476 485 | 
             
                  # extracts a single geoloc from a //placemark element in the google results xml
         | 
| @@ -487,9 +496,12 @@ module Geokit | |
| 487 496 | 
             
                    #extended -- false if not not available
         | 
| 488 497 | 
             
                    res.city = doc.elements['.//LocalityName'].text if doc.elements['.//LocalityName']
         | 
| 489 498 | 
             
                    res.state = doc.elements['.//AdministrativeAreaName'].text if doc.elements['.//AdministrativeAreaName']
         | 
| 499 | 
            +
                    res.province = doc.elements['.//SubAdministrativeAreaName'].text if doc.elements['.//SubAdministrativeAreaName']
         | 
| 490 500 | 
             
                    res.full_address = doc.elements['.//address'].text if doc.elements['.//address'] # google provides it
         | 
| 491 501 | 
             
                    res.zip = doc.elements['.//PostalCodeNumber'].text if doc.elements['.//PostalCodeNumber']
         | 
| 492 502 | 
             
                    res.street_address = doc.elements['.//ThoroughfareName'].text if doc.elements['.//ThoroughfareName']
         | 
| 503 | 
            +
                    res.country = doc.elements['.//CountryName'].text if doc.elements['.//CountryName']
         | 
| 504 | 
            +
                    res.district = doc.elements['.//DependentLocalityName'].text if doc.elements['.//DependentLocalityName']
         | 
| 493 505 | 
             
                    # Translate accuracy into Yahoo-style token address, street, zip, zip+4, city, state, country
         | 
| 494 506 | 
             
                    # For Google, 1=low accuracy, 8=high accuracy
         | 
| 495 507 | 
             
                    address_details=doc.elements['.//*[local-name() = "AddressDetails"]']
         | 
| @@ -504,12 +516,163 @@ module Geokit | |
| 504 516 | 
             
                    end
         | 
| 505 517 |  | 
| 506 518 | 
             
                    res.success=true
         | 
| 507 | 
            -
             | 
| 508 | 
            -
                    return res | 
| 519 | 
            +
             | 
| 520 | 
            +
                    return res
         | 
| 509 521 | 
             
                  end
         | 
| 510 522 | 
             
                end
         | 
| 511 523 |  | 
| 524 | 
            +
                class GoogleGeocoder3 < Geocoder
         | 
| 525 | 
            +
             | 
| 526 | 
            +
                  private 
         | 
| 527 | 
            +
                  # Template method which does the reverse-geocode lookup.
         | 
| 528 | 
            +
                  def self.do_reverse_geocode(latlng) 
         | 
| 529 | 
            +
                    latlng=LatLng.normalize(latlng)
         | 
| 530 | 
            +
                    res = self.call_geocoder_service("http://maps.google.com/maps/api/geocode/json?sensor=false&latlng=#{Geokit::Inflector::url_escape(latlng.ll)}")
         | 
| 531 | 
            +
                    return GeoLoc.new unless (res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPOK))
         | 
| 532 | 
            +
                    json = res.body
         | 
| 533 | 
            +
                    logger.debug "Google reverse-geocoding. LL: #{latlng}. Result: #{json}"
         | 
| 534 | 
            +
                    return self.json2GeoLoc(json)        
         | 
| 535 | 
            +
                  end  
         | 
| 512 536 |  | 
| 537 | 
            +
                  # Template method which does the geocode lookup.
         | 
| 538 | 
            +
                  #
         | 
| 539 | 
            +
                  # Supports viewport/country code biasing
         | 
| 540 | 
            +
                  #
         | 
| 541 | 
            +
                  # ==== OPTIONS
         | 
| 542 | 
            +
                  # * :bias - This option makes the Google Geocoder return results biased to a particular
         | 
| 543 | 
            +
                  #           country or viewport. Country code biasing is achieved by passing the ccTLD
         | 
| 544 | 
            +
                  #           ('uk' for .co.uk, for example) as a :bias value. For a list of ccTLD's, 
         | 
| 545 | 
            +
                  #           look here: http://en.wikipedia.org/wiki/CcTLD. By default, the geocoder
         | 
| 546 | 
            +
                  #           will be biased to results within the US (ccTLD .com).
         | 
| 547 | 
            +
                  #
         | 
| 548 | 
            +
                  #           If you'd like the Google Geocoder to prefer results within a given viewport,
         | 
| 549 | 
            +
                  #           you can pass a Geokit::Bounds object as the :bias value.
         | 
| 550 | 
            +
                  #
         | 
| 551 | 
            +
                  # ==== EXAMPLES
         | 
| 552 | 
            +
                  # # By default, the geocoder will return Syracuse, NY
         | 
| 553 | 
            +
                  # Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse').country_code # => 'US'
         | 
| 554 | 
            +
                  # # With country code biasing, it returns Syracuse in Sicily, Italy
         | 
| 555 | 
            +
                  # Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse', :bias => :it).country_code # => 'IT'
         | 
| 556 | 
            +
                  #
         | 
| 557 | 
            +
                  # # By default, the geocoder will return Winnetka, IL
         | 
| 558 | 
            +
                  # Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka').state # => 'IL'
         | 
| 559 | 
            +
                  # # When biased to an bounding box around California, it will now return the Winnetka neighbourhood, CA
         | 
| 560 | 
            +
                  # bounds = Geokit::Bounds.normalize([34.074081, -118.694401], [34.321129, -118.399487])
         | 
| 561 | 
            +
                  # Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka', :bias => bounds).state # => 'CA'
         | 
| 562 | 
            +
                  def self.do_geocode(address, options = {})
         | 
| 563 | 
            +
                    bias_str = options[:bias] ? construct_bias_string_from_options(options[:bias]) : ''
         | 
| 564 | 
            +
                    address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
         | 
| 565 | 
            +
                    res = self.call_geocoder_service("http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(address_str)}#{bias_str}")
         | 
| 566 | 
            +
                    return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
         | 
| 567 | 
            +
                    json = res.body
         | 
| 568 | 
            +
                    logger.debug "Google geocoding. Address: #{address}. Result: #{json}"
         | 
| 569 | 
            +
                    return self.json2GeoLoc(json, address)        
         | 
| 570 | 
            +
                  end
         | 
| 571 | 
            +
                  
         | 
| 572 | 
            +
                  def self.construct_bias_string_from_options(bias)
         | 
| 573 | 
            +
                    if bias.is_a?(String) or bias.is_a?(Symbol)
         | 
| 574 | 
            +
                      # country code biasing
         | 
| 575 | 
            +
                      "®ion=#{bias.to_s.downcase}"
         | 
| 576 | 
            +
                    elsif bias.is_a?(Bounds)
         | 
| 577 | 
            +
                      # viewport biasing
         | 
| 578 | 
            +
                      "&bounds=#{bias.nw.to_s}|#{bias.se.to_s}"
         | 
| 579 | 
            +
                    end
         | 
| 580 | 
            +
                  end
         | 
| 581 | 
            +
                  
         | 
| 582 | 
            +
                  def self.json2GeoLoc(json, address="")
         | 
| 583 | 
            +
                    ret=nil
         | 
| 584 | 
            +
                    results=JSON.parse(json, :symbolize_names => true)
         | 
| 585 | 
            +
                    
         | 
| 586 | 
            +
                    if results[:status] == 'OVER_QUERY_LIMIT'
         | 
| 587 | 
            +
                      raise Geokit::TooManyQueriesError
         | 
| 588 | 
            +
                    end
         | 
| 589 | 
            +
                    if results[:status] == 'ZERO_RESULTS'
         | 
| 590 | 
            +
                      return GeoLoc.new
         | 
| 591 | 
            +
                    end
         | 
| 592 | 
            +
                    # this should probably be smarter.
         | 
| 593 | 
            +
                    if !results[:status] == 'OK'
         | 
| 594 | 
            +
                      raise Geokit::Geocoders::GeocodeError
         | 
| 595 | 
            +
                    end
         | 
| 596 | 
            +
                    # location_type stores additional data about the specified location.
         | 
| 597 | 
            +
                    # The following values are currently supported:
         | 
| 598 | 
            +
                    # "ROOFTOP" indicates that the returned result is a precise geocode
         | 
| 599 | 
            +
                    # for which we have location information accurate down to street
         | 
| 600 | 
            +
                    # address precision.
         | 
| 601 | 
            +
                    # "RANGE_INTERPOLATED" indicates that the returned result reflects an
         | 
| 602 | 
            +
                    # approximation (usually on a road) interpolated between two precise
         | 
| 603 | 
            +
                    # points (such as intersections). Interpolated results are generally
         | 
| 604 | 
            +
                    # returned when rooftop geocodes are unavailable for a street address.
         | 
| 605 | 
            +
                    # "GEOMETRIC_CENTER" indicates that the returned result is the
         | 
| 606 | 
            +
                    # geometric center of a result such as a polyline (for example, a
         | 
| 607 | 
            +
                    # street) or polygon (region).
         | 
| 608 | 
            +
                    # "APPROXIMATE" indicates that the returned result is approximate
         | 
| 609 | 
            +
             | 
| 610 | 
            +
                    # these do not map well. Perhaps we should guess better based on size
         | 
| 611 | 
            +
                    # of bounding box where it exists? Does it really matter?
         | 
| 612 | 
            +
                    accuracy = {
         | 
| 613 | 
            +
                      "ROOFTOP" => 9,
         | 
| 614 | 
            +
                      "RANGE_INTERPOLATED" => 8,
         | 
| 615 | 
            +
                      "GEOMETRIC_CENTER" => 5,
         | 
| 616 | 
            +
                      "APPROXIMATE" => 4
         | 
| 617 | 
            +
                    }
         | 
| 618 | 
            +
                    results[:results].sort_by{|a|accuracy[a[:geometry][:location_type]]}.reverse.each do |addr|
         | 
| 619 | 
            +
                      res=GeoLoc.new
         | 
| 620 | 
            +
                      res.provider = 'google3'
         | 
| 621 | 
            +
                      res.success = true
         | 
| 622 | 
            +
                      res.full_address = addr[:formatted_address]
         | 
| 623 | 
            +
                      addr[:address_components].each do |comp|
         | 
| 624 | 
            +
                        case
         | 
| 625 | 
            +
                        when comp[:types].include?("street_number")
         | 
| 626 | 
            +
                          res.street_number = comp[:short_name]
         | 
| 627 | 
            +
                        when comp[:types].include?("route")
         | 
| 628 | 
            +
                          res.street_name = comp[:long_name]
         | 
| 629 | 
            +
                        when comp[:types].include?("locality")
         | 
| 630 | 
            +
                          res.city = comp[:long_name]
         | 
| 631 | 
            +
                        when comp[:types].include?("administrative_area_level_1")
         | 
| 632 | 
            +
                          res.state = comp[:short_name]
         | 
| 633 | 
            +
                          res.province = comp[:short_name]
         | 
| 634 | 
            +
                        when comp[:types].include?("postal_code")
         | 
| 635 | 
            +
                          res.zip = comp[:long_name]
         | 
| 636 | 
            +
                        when comp[:types].include?("country")
         | 
| 637 | 
            +
                          res.country_code = comp[:short_name]
         | 
| 638 | 
            +
                          res.country = comp[:long_name]
         | 
| 639 | 
            +
                        when comp[:types].include?("administrative_area_level_2")
         | 
| 640 | 
            +
                          res.district = comp[:long_name]
         | 
| 641 | 
            +
                        end
         | 
| 642 | 
            +
                      end
         | 
| 643 | 
            +
                      if res.street_name
         | 
| 644 | 
            +
                        res.street_address=[res.street_number,res.street_name].join(' ').strip
         | 
| 645 | 
            +
                      end
         | 
| 646 | 
            +
                      res.accuracy = accuracy[addr[:geometry][:location_type]]
         | 
| 647 | 
            +
                      res.precision=%w{unknown country state state city zip zip+4 street address building}[res.accuracy]
         | 
| 648 | 
            +
                      # try a few overrides where we can
         | 
| 649 | 
            +
                      if res.street_name && res.precision=='city'
         | 
| 650 | 
            +
                        res.precision = 'street'
         | 
| 651 | 
            +
                        res.accuracy = 7
         | 
| 652 | 
            +
                      end
         | 
| 653 | 
            +
                        
         | 
| 654 | 
            +
                      res.lat=addr[:geometry][:location][:lat].to_f
         | 
| 655 | 
            +
                      res.lng=addr[:geometry][:location][:lng].to_f
         | 
| 656 | 
            +
             | 
| 657 | 
            +
                      ne=Geokit::LatLng.new(
         | 
| 658 | 
            +
                        addr[:geometry][:viewport][:northeast][:lat].to_f, 
         | 
| 659 | 
            +
                        addr[:geometry][:viewport][:northeast][:lng].to_f
         | 
| 660 | 
            +
                        )
         | 
| 661 | 
            +
                      sw=Geokit::LatLng.new(
         | 
| 662 | 
            +
                        addr[:geometry][:viewport][:southwest][:lat].to_f,
         | 
| 663 | 
            +
                        addr[:geometry][:viewport][:southwest][:lng].to_f
         | 
| 664 | 
            +
                      )
         | 
| 665 | 
            +
                      res.suggested_bounds = Geokit::Bounds.new(sw,ne)
         | 
| 666 | 
            +
             | 
| 667 | 
            +
                      if ret
         | 
| 668 | 
            +
                        ret.all.push(res)
         | 
| 669 | 
            +
                      else
         | 
| 670 | 
            +
                        ret=res
         | 
| 671 | 
            +
                      end
         | 
| 672 | 
            +
                    end
         | 
| 673 | 
            +
                    return ret
         | 
| 674 | 
            +
                  end
         | 
| 675 | 
            +
                end
         | 
| 513 676 | 
             
                # -------------------------------------------------------------------------------------------
         | 
| 514 677 | 
             
                # IP Geocoders
         | 
| 515 678 | 
             
                # -------------------------------------------------------------------------------------------
         | 
| @@ -546,14 +709,35 @@ module Geokit | |
| 546 709 | 
             
                # as community contributions.
         | 
| 547 710 | 
             
                class IpGeocoder < Geocoder 
         | 
| 548 711 |  | 
| 712 | 
            +
                  # A number of non-routable IP ranges.
         | 
| 713 | 
            +
                  #
         | 
| 714 | 
            +
                  # --
         | 
| 715 | 
            +
                  # Sources for these:
         | 
| 716 | 
            +
                  #   RFC 3330: Special-Use IPv4 Addresses
         | 
| 717 | 
            +
                  #   The bogon list: http://www.cymru.com/Documents/bogon-list.html
         | 
| 718 | 
            +
             | 
| 719 | 
            +
                  NON_ROUTABLE_IP_RANGES = [
         | 
| 720 | 
            +
            	IPAddr.new('0.0.0.0/8'),      # "This" Network
         | 
| 721 | 
            +
            	IPAddr.new('10.0.0.0/8'),     # Private-Use Networks
         | 
| 722 | 
            +
            	IPAddr.new('14.0.0.0/8'),     # Public-Data Networks
         | 
| 723 | 
            +
            	IPAddr.new('127.0.0.0/8'),    # Loopback
         | 
| 724 | 
            +
            	IPAddr.new('169.254.0.0/16'), # Link local
         | 
| 725 | 
            +
            	IPAddr.new('172.16.0.0/12'),  # Private-Use Networks
         | 
| 726 | 
            +
            	IPAddr.new('192.0.2.0/24'),   # Test-Net
         | 
| 727 | 
            +
            	IPAddr.new('192.168.0.0/16'), # Private-Use Networks
         | 
| 728 | 
            +
            	IPAddr.new('198.18.0.0/15'),  # Network Interconnect Device Benchmark Testing
         | 
| 729 | 
            +
            	IPAddr.new('224.0.0.0/4'),    # Multicast
         | 
| 730 | 
            +
            	IPAddr.new('240.0.0.0/4')     # Reserved for future use
         | 
| 731 | 
            +
                  ].freeze
         | 
| 732 | 
            +
             | 
| 549 733 | 
             
                  private 
         | 
| 550 734 |  | 
| 551 735 | 
             
                  # Given an IP address, returns a GeoLoc instance which contains latitude,
         | 
| 552 736 | 
             
                  # longitude, city, and country code.  Sets the success attribute to false if the ip 
         | 
| 553 737 | 
             
                  # parameter does not match an ip address.  
         | 
| 554 738 | 
             
                  def self.do_geocode(ip, options = {})
         | 
| 555 | 
            -
                    return GeoLoc.new if '0.0.0.0' == ip
         | 
| 556 739 | 
             
                    return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
         | 
| 740 | 
            +
                    return GeoLoc.new if self.private_ip_address?(ip)
         | 
| 557 741 | 
             
                    url = "http://api.hostip.info/get_html.php?ip=#{ip}&position=true"
         | 
| 558 742 | 
             
                    response = self.call_geocoder_service(url)
         | 
| 559 743 | 
             
                    response.is_a?(Net::HTTPSuccess) ? parse_body(response.body) : GeoLoc.new
         | 
| @@ -582,6 +766,15 @@ module Geokit | |
| 582 766 | 
             
                    res.success = !(res.city =~ /\(.+\)/)
         | 
| 583 767 | 
             
                    res
         | 
| 584 768 | 
             
                  end
         | 
| 769 | 
            +
             | 
| 770 | 
            +
                  # Checks whether the IP address belongs to a private address range.
         | 
| 771 | 
            +
                  #
         | 
| 772 | 
            +
                  # This function is used to reduce the number of useless queries made to
         | 
| 773 | 
            +
                  # the geocoding service. Such queries can occur frequently during
         | 
| 774 | 
            +
                  # integration tests.
         | 
| 775 | 
            +
                  def self.private_ip_address?(ip)
         | 
| 776 | 
            +
            	return NON_ROUTABLE_IP_RANGES.any? { |range| range.include?(ip) }
         | 
| 777 | 
            +
                  end
         | 
| 585 778 | 
             
                end
         | 
| 586 779 |  | 
| 587 780 | 
             
                # -------------------------------------------------------------------------------------------
         | 
    
        data/lib/geokit/mappable.rb
    CHANGED
    
    | @@ -336,19 +336,18 @@ module Geokit | |
| 336 336 | 
             
              #
         | 
| 337 337 | 
             
              #     puts geo.full_address     # just like usual
         | 
| 338 338 | 
             
              #     puts geo.all.size  => 3   # there's three results total
         | 
| 339 | 
            -
              #     puts geo.all.first        # all is just an array or additional geolocs, | 
| 339 | 
            +
              #     puts geo.all.first        # all is just an array or additional geolocs,
         | 
| 340 340 | 
             
              #                                 so do what you want with it
         | 
| 341 341 | 
             
              class GeoLoc < LatLng
         | 
| 342 342 |  | 
| 343 343 | 
             
                # Location attributes.  Full address is a concatenation of all values.  For example:
         | 
| 344 344 | 
             
                # 100 Spear St, San Francisco, CA, 94101, US
         | 
| 345 | 
            -
                 | 
| 345 | 
            +
                # Street number and street name are extracted from the street address attribute if they don't exist
         | 
| 346 | 
            +
                attr_accessor :street_number,:street_name,:street_address, :city, :state, :zip, :country_code, :country, :full_address, :all, :district, :province
         | 
| 346 347 | 
             
                # Attributes set upon return from geocoding.  Success will be true for successful
         | 
| 347 348 | 
             
                # geocode lookups.  The provider will be set to the name of the providing geocoder.
         | 
| 348 349 | 
             
                # Finally, precision is an indicator of the accuracy of the geocoding.
         | 
| 349 350 | 
             
                attr_accessor :success, :provider, :precision, :suggested_bounds
         | 
| 350 | 
            -
                # Street number and street name are extracted from the street address attribute.
         | 
| 351 | 
            -
                attr_reader :street_number, :street_name
         | 
| 352 351 | 
             
                # accuracy is set for Yahoo and Google geocoders, it is a numeric value of the 
         | 
| 353 352 | 
             
                # precision. see http://code.google.com/apis/maps/documentation/geocoding/#GeocodingAccuracy
         | 
| 354 353 | 
             
                attr_accessor :accuracy
         | 
| @@ -358,10 +357,13 @@ module Geokit | |
| 358 357 | 
             
                  @all = [self]
         | 
| 359 358 |  | 
| 360 359 | 
             
                  @street_address=h[:street_address] 
         | 
| 360 | 
            +
                  @street_number=nil
         | 
| 361 | 
            +
                  @street_name=nil
         | 
| 361 362 | 
             
                  @city=h[:city] 
         | 
| 362 363 | 
             
                  @state=h[:state] 
         | 
| 363 364 | 
             
                  @zip=h[:zip] 
         | 
| 364 | 
            -
                  @country_code=h[:country_code] | 
| 365 | 
            +
                  @country_code=h[:country_code]
         | 
| 366 | 
            +
                  @province = h[:province]
         | 
| 365 367 | 
             
                  @success=false
         | 
| 366 368 | 
             
                  @precision='unknown'
         | 
| 367 369 | 
             
                  @full_address=nil
         | 
| @@ -384,21 +386,22 @@ module Geokit | |
| 384 386 | 
             
                  @full_address ? @full_address : to_geocodeable_s
         | 
| 385 387 | 
             
                end
         | 
| 386 388 |  | 
| 387 | 
            -
                # Extracts the street number from the street address  | 
| 388 | 
            -
                # has a value.
         | 
| 389 | 
            +
                # Extracts the street number from the street address where possible.
         | 
| 389 390 | 
             
                def street_number
         | 
| 390 | 
            -
                  street_address[/(\d*)/] if street_address
         | 
| 391 | 
            +
                  @street_number ||= street_address[/(\d*)/] if street_address
         | 
| 392 | 
            +
                  @street_number
         | 
| 391 393 | 
             
                end
         | 
| 392 394 |  | 
| 393 | 
            -
                # Returns the street name portion of the street address | 
| 395 | 
            +
                # Returns the street name portion of the street address where possible
         | 
| 394 396 | 
             
                def street_name
         | 
| 395 | 
            -
             | 
| 397 | 
            +
                  @street_name||=street_address[street_number.length, street_address.length].strip if street_address
         | 
| 398 | 
            +
                  @street_name
         | 
| 396 399 | 
             
                end
         | 
| 397 400 |  | 
| 398 401 | 
             
                # gives you all the important fields as key-value pairs
         | 
| 399 402 | 
             
                def hash
         | 
| 400 403 | 
             
                  res={}
         | 
| 401 | 
            -
                  [: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) }
         | 
| 404 | 
            +
                  [:success,:lat,:lng,:country_code,:city,:state,:zip,:street_address,:province,:district,:provider,:full_address,:is_us?,:ll,:precision].each { |s| res[s] = self.send(s.to_s) }
         | 
| 402 405 | 
             
                  res
         | 
| 403 406 | 
             
                end
         | 
| 404 407 | 
             
                alias to_hash hash
         | 
| @@ -410,14 +413,17 @@ module Geokit | |
| 410 413 |  | 
| 411 414 | 
             
                # Sets the street address after capitalizing each word within the street address.
         | 
| 412 415 | 
             
                def street_address=(address)
         | 
| 413 | 
            -
                   | 
| 414 | 
            -
             | 
| 416 | 
            +
                  if address and not ['google','google3'].include?(self.provider)
         | 
| 417 | 
            +
                    @street_address = Geokit::Inflector::titleize(address) 
         | 
| 418 | 
            +
                  else
         | 
| 419 | 
            +
                    @street_address = address
         | 
| 420 | 
            +
                  end
         | 
| 415 421 | 
             
                end  
         | 
| 416 422 |  | 
| 417 423 | 
             
                # Returns a comma-delimited string consisting of the street address, city, state,
         | 
| 418 424 | 
             
                # zip, and country code.  Only includes those attributes that are non-blank.
         | 
| 419 425 | 
             
                def to_geocodeable_s
         | 
| 420 | 
            -
                  a=[street_address, city, state, zip, country_code].compact
         | 
| 426 | 
            +
                  a=[street_address, district, city, province, state, zip, country_code].compact
         | 
| 421 427 | 
             
                  a.delete_if { |e| !e || e == '' }
         | 
| 422 428 | 
             
                  a.join(', ')      
         | 
| 423 429 | 
             
                end
         | 
    
        data/test/test_base_geocoder.rb
    CHANGED
    
    | @@ -36,7 +36,7 @@ class BaseGeocoderTest < Test::Unit::TestCase #:nodoc: all | |
| 36 36 |  | 
| 37 37 | 
             
              def test_timeout_call_web_service
         | 
| 38 38 | 
             
                url = "http://www.anything.com"
         | 
| 39 | 
            -
                Geokit::Geocoders:: | 
| 39 | 
            +
                Geokit::Geocoders::request_timeout = 1
         | 
| 40 40 | 
             
                assert_nil Geokit::Geocoders::TestGeocoder.call_geocoder_service(url)    
         | 
| 41 41 | 
             
              end
         | 
| 42 42 |  | 
    
        data/test/test_geoloc.rb
    CHANGED
    
    | @@ -65,7 +65,7 @@ class GeoLocTest < Test::Unit::TestCase #:nodoc: all | |
| 65 65 | 
             
                @loc.zip = '94105'
         | 
| 66 66 | 
             
                @loc.country_code = 'US'
         | 
| 67 67 | 
             
                assert_equal( 
         | 
| 68 | 
            -
                  "--- !ruby/object:Geokit::GeoLoc \ncity: San Francisco\ncountry_code: US\nfull_address: \nlat: \nlng: \nprecision: unknown\nstate: CA\nstreet_address: \nsuccess: false\nzip: \"94105\"\n", 
         | 
| 68 | 
            +
                  "--- !ruby/object:Geokit::GeoLoc \ncity: San Francisco\ncountry_code: US\nfull_address: \nlat: \nlng: \nprecision: unknown\nprovince: \nstate: CA\nstreet_address: \nstreet_name: \nstreet_number: \nsuccess: false\nzip: \"94105\"\n", 
         | 
| 69 69 | 
             
                  @loc.to_yaml)
         | 
| 70 70 | 
             
              end
         | 
| 71 71 |  | 
| @@ -33,8 +33,8 @@ class IpGeocoderTest < BaseGeocoderTest #:nodoc: all | |
| 33 33 | 
             
                GeoKit::Geocoders::GeoPluginGeocoder.expects(:call_geocoder_service).with(url).returns(success)
         | 
| 34 34 | 
             
                location = GeoKit::Geocoders::GeoPluginGeocoder.geocode('200.150.38.66')
         | 
| 35 35 | 
             
                assert_not_nil location
         | 
| 36 | 
            -
                assert_equal | 
| 37 | 
            -
                assert_equal | 
| 36 | 
            +
                assert_equal(-19.916700, location.lat)
         | 
| 37 | 
            +
                assert_equal(-43.933300, location.lng)
         | 
| 38 38 | 
             
                assert_equal "Belo Horizonte", location.city
         | 
| 39 39 | 
             
                assert_equal "Minas Gerais", location.state
         | 
| 40 40 | 
             
                assert_equal "BR", location.country_code
         | 
| @@ -18,6 +18,8 @@ class GoogleGeocoderTest < BaseGeocoderTest #:nodoc: all | |
| 18 18 |  | 
| 19 19 | 
             
              GOOGLE_MULTI="<?xml version='1.0' encoding='UTF-8'?>\n<kml xmlns='http://earth.google.com/kml/2.0'><Response>\n  <name>via Sandro Pertini 8, Ossona, MI</name>\n  <Status>\n    <code>200</code>\n    <request>geocode</request>\n  </Status>\n  <Placemark id='p1'>\n    <address>Via Sandro Pertini, 8, 20010 Mesero MI, Italy</address>\n    <AddressDetails Accuracy='8' xmlns='urn:oasis:names:tc:ciq:xsdschema:xAL:2.0'><Country><CountryNameCode>IT</CountryNameCode><CountryName>Italy</CountryName><AdministrativeArea><AdministrativeAreaName>Lombardy</AdministrativeAreaName><SubAdministrativeArea><SubAdministrativeAreaName>Milan</SubAdministrativeAreaName><Locality><LocalityName>Mesero</LocalityName><Thoroughfare><ThoroughfareName>8 Via Sandro Pertini</ThoroughfareName></Thoroughfare><PostalCode><PostalCodeNumber>20010</PostalCodeNumber></PostalCode></Locality></SubAdministrativeArea></AdministrativeArea></Country></AddressDetails>\n    <Point><coordinates>8.8527131,45.4966243,0</coordinates></Point>\n  </Placemark>\n  <Placemark id='p2'>\n    <address>Via Sandro Pertini, 20010 Ossona MI, Italy</address>\n    <AddressDetails Accuracy='6' xmlns='urn:oasis:names:tc:ciq:xsdschema:xAL:2.0'><Country><CountryNameCode>IT</CountryNameCode><CountryName>Italy</CountryName><AdministrativeArea><AdministrativeAreaName>Lombardy</AdministrativeAreaName><SubAdministrativeArea><SubAdministrativeAreaName>Milan</SubAdministrativeAreaName><Locality><LocalityName>Ossona</LocalityName><Thoroughfare><ThoroughfareName>Via Sandro Pertini</ThoroughfareName></Thoroughfare><PostalCode><PostalCodeNumber>20010</PostalCodeNumber></PostalCode></Locality></SubAdministrativeArea></AdministrativeArea></Country></AddressDetails>\n    <Point><coordinates>8.9023200,45.5074444,0</coordinates></Point>\n  </Placemark>\n</Response></kml>\n"
         | 
| 20 20 |  | 
| 21 | 
            +
              GOOGLE_REVERSE_MADRID="<?xml version='1.0' encoding='UTF-8' ?><kml xmlns='http://earth.google.com/kml/2.0'><Response><name>40.416741,-3.703250</name><Status><code>200</code><request>geocode</request></Status><Placemark id='p1'><address>Plaza de la Puerta del Sol, 28013, Madrid, Spain</address><AddressDetails Accuracy='6' xmlns='urn:oasis:names:tc:ciq:xsdschema:xAL:2.0'><Country><CountryNameCode>ES</CountryNameCode><CountryName>Spain</CountryName><AdministrativeArea><AdministrativeAreaName>Madrid</AdministrativeAreaName><SubAdministrativeArea><SubAdministrativeAreaName>Madrid</SubAdministrativeAreaName><Locality><LocalityName>Madrid</LocalityName><Thoroughfare><ThoroughfareName>Plaza de la Puerta del Sol</ThoroughfareName></Thoroughfare><PostalCode><PostalCodeNumber>28013</PostalCodeNumber></PostalCode></Locality></SubAdministrativeArea></AdministrativeArea></Country></AddressDetails><ExtendedData><LatLonBox north='40.4199522' south='40.4136570' east='-3.7001138' west='-3.7064091' /></ExtendedData><Point><coordinates>-3.7032537,40.4168023,0</coordinates></Point></Placemark><Placemark id='p2'><address>28013, Madrid, Spain</address><AddressDetails Accuracy='5' xmlns='urn:oasis:names:tc:ciq:xsdschema:xAL:2.0'><Country><CountryNameCode>ES</CountryNameCode><CountryName>Spain</CountryName><AdministrativeArea><AdministrativeAreaName>Madrid</AdministrativeAreaName><SubAdministrativeArea><SubAdministrativeAreaName>Madrid</SubAdministrativeAreaName><Locality><LocalityName>Madrid</LocalityName><PostalCode><PostalCodeNumber>28013</PostalCodeNumber></PostalCode></Locality></SubAdministrativeArea></AdministrativeArea></Country></AddressDetails><ExtendedData><LatLonBox north='40.4244113' south='40.4142840' east='-3.6969862' west='-3.7224820' /></ExtendedData><Point><coordinates>-3.7117806,40.4189645,0</coordinates></Point></Placemark><Placemark id='p3'><address>Madrid, Spain</address><AddressDetails Accuracy='4' xmlns='urn:oasis:names:tc:ciq:xsdschema:xAL:2.0'><Country><CountryNameCode>ES</CountryNameCode><CountryName>Spain</CountryName><AdministrativeArea><AdministrativeAreaName>Madrid</AdministrativeAreaName><SubAdministrativeArea><SubAdministrativeAreaName>Madrid</SubAdministrativeAreaName><Locality><LocalityName>Madrid</LocalityName></Locality></SubAdministrativeArea></AdministrativeArea></Country></AddressDetails><ExtendedData><LatLonBox north='40.6435181' south='40.3120713' east='-3.5180102' west='-3.8890049' /></ExtendedData><Point><coordinates>-3.7032498,40.4167413,0</coordinates></Point></Placemark><Placemark id='p4'><address>Madrid, Spain</address><AddressDetails Accuracy='2' xmlns='urn:oasis:names:tc:ciq:xsdschema:xAL:2.0'><Country><CountryNameCode>ES</CountryNameCode><CountryName>Spain</CountryName><AdministrativeArea><AdministrativeAreaName>Madrid</AdministrativeAreaName></AdministrativeArea></Country></AddressDetails><ExtendedData><LatLonBox north='41.1649106' south='39.8845366' east='-3.0531322' west='-4.5791745' /></ExtendedData><Point><coordinates>-3.5812692,40.4167088,0</coordinates></Point></Placemark><Placemark id='p5'><address>Madrid, Spain</address><AddressDetails Accuracy='3' xmlns='urn:oasis:names:tc:ciq:xsdschema:xAL:2.0'><Country><CountryNameCode>ES</CountryNameCode><CountryName>Spain</CountryName><AdministrativeArea><AdministrativeAreaName>Madrid</AdministrativeAreaName><SubAdministrativeArea><SubAdministrativeAreaName>Madrid</SubAdministrativeAreaName></SubAdministrativeArea></AdministrativeArea></Country></AddressDetails><ExtendedData><LatLonBox north='41.1649106' south='39.8845366' east='-3.0531322' west='-4.5791745' /></ExtendedData><Point><coordinates>-3.5812692,40.4167088,0</coordinates></Point></Placemark><Placemark id='p6'><address>Spain</address><AddressDetails Accuracy='1' xmlns='urn:oasis:names:tc:ciq:xsdschema:xAL:2.0'><Country><CountryNameCode>ES</CountryNameCode><CountryName>Spain</CountryName></Country></AddressDetails><ExtendedData><LatLonBox north='43.7903881' south='27.6377504' east='4.3279851' west='-18.1606948' /></ExtendedData><Point><coordinates>-3.7492200,40.4636670,0</coordinates></Point></Placemark></Response></kml>"
         | 
| 22 | 
            +
               
         | 
| 21 23 | 
             
              GOOGLE_COUNTRY_CODE_BIASED_RESULT = <<-EOF.strip
         | 
| 22 24 | 
             
              <?xml version="1.0" encoding="UTF-8" ?><kml xmlns="http://earth.google.com/kml/2.0"><Response><name>Syracuse</name><Status><code>200</code><request>geocode</request></Status><Placemark id="p1"><address>Syracuse, Italy</address><AddressDetails Accuracy="3" xmlns="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0"><Country><CountryNameCode>IT</CountryNameCode><CountryName>Italy</CountryName><AdministrativeArea><AdministrativeAreaName>Sicily</AdministrativeAreaName><SubAdministrativeArea><SubAdministrativeAreaName>Syracuse</SubAdministrativeAreaName></SubAdministrativeArea></AdministrativeArea></Country></AddressDetails><ExtendedData><LatLonBox north="37.4125978" south="36.6441736" east="15.3367367" west="14.7724913" /></ExtendedData><Point><coordinates>14.9856176,37.0630218,0</coordinates></Point></Placemark></Response></kml>
         | 
| 23 25 | 
             
              EOF
         | 
| @@ -25,6 +27,10 @@ class GoogleGeocoderTest < BaseGeocoderTest #:nodoc: all | |
| 25 27 | 
             
              GOOGLE_BOUNDS_BIASED_RESULT = <<-EOF.strip
         | 
| 26 28 | 
             
              <?xml version="1.0" encoding="UTF-8" ?><kml xmlns="http://earth.google.com/kml/2.0"><Response><name>Winnetka</name><Status><code>200</code><request>geocode</request></Status><Placemark id="p1"><address>Winnetka, California, USA</address><AddressDetails Accuracy="4" xmlns="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0"><Country><CountryNameCode>US</CountryNameCode><CountryName>USA</CountryName><AdministrativeArea><AdministrativeAreaName>CA</AdministrativeAreaName><AddressLine>Winnetka</AddressLine></AdministrativeArea></Country></AddressDetails><ExtendedData><LatLonBox north="34.2353090" south="34.1791050" east="-118.5534191" west="-118.5883200" /></ExtendedData><Point><coordinates>-118.5710220,34.2131710,0</coordinates></Point></Placemark></Response></kml>
         | 
| 27 29 | 
             
              EOF
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              GOOGLE_TOO_MANY=<<-EOF.strip
         | 
| 32 | 
            +
              <?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>620</code><request>geocode</request></Status></Response></kml>
         | 
| 33 | 
            +
              EOF
         | 
| 28 34 |  | 
| 29 35 | 
             
              def setup
         | 
| 30 36 | 
             
                super
         | 
| @@ -154,6 +160,34 @@ class GoogleGeocoderTest < BaseGeocoderTest #:nodoc: all | |
| 154 160 | 
             
                assert_equal "Via Sandro Pertini", res.street_address
         | 
| 155 161 | 
             
                assert_equal "google", res.provider
         | 
| 156 162 | 
             
              end
         | 
| 163 | 
            +
             | 
| 164 | 
            +
              def test_reverse_geocode
         | 
| 165 | 
            +
                #Geokit::Geocoders::GoogleGeocoder.do_reverse_geocode("40.4167413, -3.7032498")
         | 
| 166 | 
            +
                madrid = Geokit::GeoLoc.new
         | 
| 167 | 
            +
                madrid.lat, madrid.lng = "40.4167413", "-3.7032498"
         | 
| 168 | 
            +
                response = MockSuccess.new
         | 
| 169 | 
            +
                response.expects(:body).returns(GOOGLE_REVERSE_MADRID)
         | 
| 170 | 
            +
                url = "http://maps.google.com/maps/geo?ll=#{Geokit::Inflector::url_escape(madrid.ll)}&output=xml&key=#{Geokit::Geocoders::google}&oe=utf-8"
         | 
| 171 | 
            +
                Geokit::Geocoders::GoogleGeocoder.expects(:call_geocoder_service).with(url).
         | 
| 172 | 
            +
                  returns(response)
         | 
| 173 | 
            +
                res=Geokit::Geocoders::GoogleGeocoder.do_reverse_geocode(madrid.ll)
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                assert_equal madrid.lat.to_s.slice(1..5), res.lat.to_s.slice(1..5)
         | 
| 176 | 
            +
                assert_equal madrid.lng.to_s.slice(1..5), res.lng.to_s.slice(1..5)
         | 
| 177 | 
            +
                assert_equal "ES", res.country_code
         | 
| 178 | 
            +
                assert_equal "google", res.provider
         | 
| 179 | 
            +
             | 
| 180 | 
            +
                assert_equal "Madrid", res.city
         | 
| 181 | 
            +
                assert_equal "Madrid", res.state
         | 
| 182 | 
            +
             | 
| 183 | 
            +
                assert_equal "Spain", res.country
         | 
| 184 | 
            +
                assert_equal "zip+4", res.precision
         | 
| 185 | 
            +
                assert_equal true, res.success
         | 
| 186 | 
            +
             | 
| 187 | 
            +
                assert_equal "Plaza de la Puerta del Sol, 28013, Madrid, Spain", res.full_address
         | 
| 188 | 
            +
                assert_equal "28013", res.zip
         | 
| 189 | 
            +
                assert_equal "Plaza de la Puerta del Sol", res.street_address
         | 
| 190 | 
            +
              end  
         | 
| 157 191 |  | 
| 158 192 | 
             
              def test_country_code_biasing
         | 
| 159 193 | 
             
                response = MockSuccess.new
         | 
| @@ -180,4 +214,14 @@ class GoogleGeocoderTest < BaseGeocoderTest #:nodoc: all | |
| 180 214 | 
             
                assert_equal 'US', biased_result.country_code
         | 
| 181 215 | 
             
                assert_equal 'CA', biased_result.state
         | 
| 182 216 | 
             
              end
         | 
| 217 | 
            +
             | 
| 218 | 
            +
              def test_too_many_queries
         | 
| 219 | 
            +
                response = MockSuccess.new
         | 
| 220 | 
            +
                response.expects(:body).returns(GOOGLE_TOO_MANY)
         | 
| 221 | 
            +
                url = "http://maps.google.com/maps/geo?q=#{Geokit::Inflector.url_escape(@address)}&output=xml&key=Google&oe=utf-8"
         | 
| 222 | 
            +
                Geokit::Geocoders::GoogleGeocoder.expects(:call_geocoder_service).with(url).returns(response)
         | 
| 223 | 
            +
                assert_raise Geokit::TooManyQueriesError do
         | 
| 224 | 
            +
                  res=Geokit::Geocoders::GoogleGeocoder.geocode(@address)
         | 
| 225 | 
            +
                end
         | 
| 226 | 
            +
              end
         | 
| 183 227 | 
             
            end
         | 
| @@ -0,0 +1,536 @@ | |
| 1 | 
            +
            require File.join(File.dirname(__FILE__), 'test_base_geocoder')
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Geokit::Geocoders::google = 'Google'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class GoogleGeocoder3Test < BaseGeocoderTest #:nodoc: all
         | 
| 6 | 
            +
              
         | 
| 7 | 
            +
              GOOGLE3_FULL=%q/
         | 
| 8 | 
            +
              {
         | 
| 9 | 
            +
                "status": "OK",
         | 
| 10 | 
            +
                "results": [ {
         | 
| 11 | 
            +
                  "types": [ "street_address" ],
         | 
| 12 | 
            +
                  "formatted_address": "100 Spear St, San Francisco, CA 94105, USA",
         | 
| 13 | 
            +
                  "address_components": [ {
         | 
| 14 | 
            +
                    "long_name": "100",
         | 
| 15 | 
            +
                    "short_name": "100",
         | 
| 16 | 
            +
                    "types": [ "street_number" ]
         | 
| 17 | 
            +
                  }, {
         | 
| 18 | 
            +
                    "long_name": "Spear St",
         | 
| 19 | 
            +
                    "short_name": "Spear St",
         | 
| 20 | 
            +
                    "types": [ "route" ]
         | 
| 21 | 
            +
                  }, {
         | 
| 22 | 
            +
                    "long_name": "San Francisco",
         | 
| 23 | 
            +
                    "short_name": "San Francisco",
         | 
| 24 | 
            +
                    "types": [ "locality", "political" ]
         | 
| 25 | 
            +
                  }, {
         | 
| 26 | 
            +
                    "long_name": "San Francisco",
         | 
| 27 | 
            +
                    "short_name": "San Francisco",
         | 
| 28 | 
            +
                    "types": [ "administrative_area_level_3", "political" ]
         | 
| 29 | 
            +
                  }, {
         | 
| 30 | 
            +
                    "long_name": "San Francisco",
         | 
| 31 | 
            +
                    "short_name": "San Francisco",
         | 
| 32 | 
            +
                    "types": [ "administrative_area_level_2", "political" ]
         | 
| 33 | 
            +
                  }, {
         | 
| 34 | 
            +
                    "long_name": "California",
         | 
| 35 | 
            +
                    "short_name": "CA",
         | 
| 36 | 
            +
                    "types": [ "administrative_area_level_1", "political" ]
         | 
| 37 | 
            +
                  }, {
         | 
| 38 | 
            +
                    "long_name": "United States",
         | 
| 39 | 
            +
                    "short_name": "US",
         | 
| 40 | 
            +
                    "types": [ "country", "political" ]
         | 
| 41 | 
            +
                  }, {
         | 
| 42 | 
            +
                    "long_name": "94105",
         | 
| 43 | 
            +
                    "short_name": "94105",
         | 
| 44 | 
            +
                    "types": [ "postal_code" ]
         | 
| 45 | 
            +
                  } ],
         | 
| 46 | 
            +
                  "geometry": {
         | 
| 47 | 
            +
                    "location": {
         | 
| 48 | 
            +
                      "lat": 37.7921509,
         | 
| 49 | 
            +
                      "lng": -122.3940000
         | 
| 50 | 
            +
                    },
         | 
| 51 | 
            +
                    "location_type": "ROOFTOP",
         | 
| 52 | 
            +
                    "viewport": {
         | 
| 53 | 
            +
                      "southwest": {
         | 
| 54 | 
            +
                        "lat": 37.7890033,
         | 
| 55 | 
            +
                        "lng": -122.3971476
         | 
| 56 | 
            +
                      },
         | 
| 57 | 
            +
                      "northeast": {
         | 
| 58 | 
            +
                        "lat": 37.7952985,
         | 
| 59 | 
            +
                        "lng": -122.3908524
         | 
| 60 | 
            +
                      }
         | 
| 61 | 
            +
                    }
         | 
| 62 | 
            +
                  }
         | 
| 63 | 
            +
                } ]
         | 
| 64 | 
            +
              }
         | 
| 65 | 
            +
              /.strip
         | 
| 66 | 
            +
             | 
| 67 | 
            +
              GOOGLE3_CITY=%q/
         | 
| 68 | 
            +
              {
         | 
| 69 | 
            +
                "status": "OK",
         | 
| 70 | 
            +
                "results": [ {
         | 
| 71 | 
            +
                  "types": [ "locality", "political" ],
         | 
| 72 | 
            +
                  "formatted_address": "San Francisco, CA, USA",
         | 
| 73 | 
            +
                  "address_components": [ {
         | 
| 74 | 
            +
                    "long_name": "San Francisco",
         | 
| 75 | 
            +
                    "short_name": "San Francisco",
         | 
| 76 | 
            +
                    "types": [ "locality", "political" ]
         | 
| 77 | 
            +
                  }, {
         | 
| 78 | 
            +
                    "long_name": "San Francisco",
         | 
| 79 | 
            +
                    "short_name": "San Francisco",
         | 
| 80 | 
            +
                    "types": [ "administrative_area_level_2", "political" ]
         | 
| 81 | 
            +
                  }, {
         | 
| 82 | 
            +
                    "long_name": "California",
         | 
| 83 | 
            +
                    "short_name": "CA",
         | 
| 84 | 
            +
                    "types": [ "administrative_area_level_1", "political" ]
         | 
| 85 | 
            +
                  }, {
         | 
| 86 | 
            +
                    "long_name": "United States",
         | 
| 87 | 
            +
                    "short_name": "US",
         | 
| 88 | 
            +
                    "types": [ "country", "political" ]
         | 
| 89 | 
            +
                  } ],
         | 
| 90 | 
            +
                  "geometry": {
         | 
| 91 | 
            +
                    "location": {
         | 
| 92 | 
            +
                      "lat": 37.7749295,
         | 
| 93 | 
            +
                      "lng": -122.4194155
         | 
| 94 | 
            +
                    },
         | 
| 95 | 
            +
                    "location_type": "APPROXIMATE",
         | 
| 96 | 
            +
                    "viewport": {
         | 
| 97 | 
            +
                      "southwest": {
         | 
| 98 | 
            +
                        "lat": 37.7043396,
         | 
| 99 | 
            +
                        "lng": -122.5474749
         | 
| 100 | 
            +
                      },
         | 
| 101 | 
            +
                      "northeast": {
         | 
| 102 | 
            +
                        "lat": 37.8454521,
         | 
| 103 | 
            +
                        "lng": -122.2913561
         | 
| 104 | 
            +
                      }
         | 
| 105 | 
            +
                    },
         | 
| 106 | 
            +
                    "bounds": {
         | 
| 107 | 
            +
                      "southwest": {
         | 
| 108 | 
            +
                        "lat": 37.7034000,
         | 
| 109 | 
            +
                        "lng": -122.5270000
         | 
| 110 | 
            +
                      },
         | 
| 111 | 
            +
                      "northeast": {
         | 
| 112 | 
            +
                        "lat": 37.8120000,
         | 
| 113 | 
            +
                        "lng": -122.3482000
         | 
| 114 | 
            +
                      }
         | 
| 115 | 
            +
                    }
         | 
| 116 | 
            +
                  }
         | 
| 117 | 
            +
                } ]
         | 
| 118 | 
            +
              }
         | 
| 119 | 
            +
              /.strip
         | 
| 120 | 
            +
              GOOGLE3_MULTI=%q/
         | 
| 121 | 
            +
                {
         | 
| 122 | 
            +
                  "status": "OK",
         | 
| 123 | 
            +
                  "results": [ {
         | 
| 124 | 
            +
                    "types": [ "street_address" ],
         | 
| 125 | 
            +
                    "formatted_address": "Via Sandro Pertini, 8, 20010 Mesero MI, Italy",
         | 
| 126 | 
            +
                    "address_components": [ {
         | 
| 127 | 
            +
                      "long_name": "8",
         | 
| 128 | 
            +
                      "short_name": "8",
         | 
| 129 | 
            +
                      "types": [ "street_number" ]
         | 
| 130 | 
            +
                    }, {
         | 
| 131 | 
            +
                      "long_name": "Via Sandro Pertini",
         | 
| 132 | 
            +
                      "short_name": "Via Sandro Pertini",
         | 
| 133 | 
            +
                      "types": [ "route" ]
         | 
| 134 | 
            +
                    }, {
         | 
| 135 | 
            +
                      "long_name": "Mesero",
         | 
| 136 | 
            +
                      "short_name": "Mesero",
         | 
| 137 | 
            +
                      "types": [ "locality", "political" ]
         | 
| 138 | 
            +
                    }, {
         | 
| 139 | 
            +
                      "long_name": "Milan",
         | 
| 140 | 
            +
                      "short_name": "MI",
         | 
| 141 | 
            +
                      "types": [ "administrative_area_level_2", "political" ]
         | 
| 142 | 
            +
                    }, {
         | 
| 143 | 
            +
                      "long_name": "Lombardy",
         | 
| 144 | 
            +
                      "short_name": "Lombardy",
         | 
| 145 | 
            +
                      "types": [ "administrative_area_level_1", "political" ]
         | 
| 146 | 
            +
                    }, {
         | 
| 147 | 
            +
                      "long_name": "Italy",
         | 
| 148 | 
            +
                      "short_name": "IT",
         | 
| 149 | 
            +
                      "types": [ "country", "political" ]
         | 
| 150 | 
            +
                    }, {
         | 
| 151 | 
            +
                      "long_name": "20010",
         | 
| 152 | 
            +
                      "short_name": "20010",
         | 
| 153 | 
            +
                      "types": [ "postal_code" ]
         | 
| 154 | 
            +
                    } ],
         | 
| 155 | 
            +
                    "geometry": {
         | 
| 156 | 
            +
                      "location": {
         | 
| 157 | 
            +
                        "lat": 45.4966218,
         | 
| 158 | 
            +
                        "lng": 8.8526940
         | 
| 159 | 
            +
                      },
         | 
| 160 | 
            +
                      "location_type": "RANGE_INTERPOLATED",
         | 
| 161 | 
            +
                      "viewport": {
         | 
| 162 | 
            +
                        "southwest": {
         | 
| 163 | 
            +
                          "lat": 45.4934754,
         | 
| 164 | 
            +
                          "lng": 8.8495559
         | 
| 165 | 
            +
                        },
         | 
| 166 | 
            +
                        "northeast": {
         | 
| 167 | 
            +
                          "lat": 45.4997707,
         | 
| 168 | 
            +
                          "lng": 8.8558512
         | 
| 169 | 
            +
                        }
         | 
| 170 | 
            +
                      },
         | 
| 171 | 
            +
                      "bounds": {
         | 
| 172 | 
            +
                        "southwest": {
         | 
| 173 | 
            +
                          "lat": 45.4966218,
         | 
| 174 | 
            +
                          "lng": 8.8526940
         | 
| 175 | 
            +
                        },
         | 
| 176 | 
            +
                        "northeast": {
         | 
| 177 | 
            +
                          "lat": 45.4966243,
         | 
| 178 | 
            +
                          "lng": 8.8527131
         | 
| 179 | 
            +
                        }
         | 
| 180 | 
            +
                      }
         | 
| 181 | 
            +
                    },
         | 
| 182 | 
            +
                    "partial_match": true
         | 
| 183 | 
            +
                  },
         | 
| 184 | 
            +
                  {
         | 
| 185 | 
            +
                     "types": [ "route" ],
         | 
| 186 | 
            +
                     "formatted_address": "Via Sandro Pertini, 20010 Ossona MI, Italy",
         | 
| 187 | 
            +
                     "address_components": [ {
         | 
| 188 | 
            +
                       "long_name": "Via Sandro Pertini",
         | 
| 189 | 
            +
                       "short_name": "Via Sandro Pertini",
         | 
| 190 | 
            +
                       "types": [ "route" ]
         | 
| 191 | 
            +
                     }, {
         | 
| 192 | 
            +
                       "long_name": "Ossona",
         | 
| 193 | 
            +
                       "short_name": "Ossona",
         | 
| 194 | 
            +
                       "types": [ "locality", "political" ]
         | 
| 195 | 
            +
                     }, {
         | 
| 196 | 
            +
                       "long_name": "Milan",
         | 
| 197 | 
            +
                       "short_name": "MI",
         | 
| 198 | 
            +
                       "types": [ "administrative_area_level_2", "political" ]
         | 
| 199 | 
            +
                     }, {
         | 
| 200 | 
            +
                       "long_name": "Lombardy",
         | 
| 201 | 
            +
                       "short_name": "Lombardy",
         | 
| 202 | 
            +
                       "types": [ "administrative_area_level_1", "political" ]
         | 
| 203 | 
            +
                     }, {
         | 
| 204 | 
            +
                       "long_name": "Italy",
         | 
| 205 | 
            +
                       "short_name": "IT",
         | 
| 206 | 
            +
                       "types": [ "country", "political" ]
         | 
| 207 | 
            +
                     }, {
         | 
| 208 | 
            +
                       "long_name": "20010",
         | 
| 209 | 
            +
                       "short_name": "20010",
         | 
| 210 | 
            +
                       "types": [ "postal_code" ]
         | 
| 211 | 
            +
                     } ],
         | 
| 212 | 
            +
                     "geometry": {
         | 
| 213 | 
            +
                       "location": {
         | 
| 214 | 
            +
                         "lat": 45.5074444,
         | 
| 215 | 
            +
                         "lng": 8.9023200
         | 
| 216 | 
            +
                       },
         | 
| 217 | 
            +
                       "location_type": "GEOMETRIC_CENTER",
         | 
| 218 | 
            +
                       "viewport": {
         | 
| 219 | 
            +
                         "southwest": {
         | 
| 220 | 
            +
                           "lat": 45.5043320,
         | 
| 221 | 
            +
                           "lng": 8.8990670
         | 
| 222 | 
            +
                         },
         | 
| 223 | 
            +
                         "northeast": {
         | 
| 224 | 
            +
                           "lat": 45.5106273,
         | 
| 225 | 
            +
                           "lng": 8.9053622
         | 
| 226 | 
            +
                         }
         | 
| 227 | 
            +
                       },
         | 
| 228 | 
            +
                       "bounds": {
         | 
| 229 | 
            +
                         "southwest": {
         | 
| 230 | 
            +
                           "lat": 45.5064427,
         | 
| 231 | 
            +
                           "lng": 8.9020024
         | 
| 232 | 
            +
                         },
         | 
| 233 | 
            +
                         "northeast": {
         | 
| 234 | 
            +
                           "lat": 45.5085166,
         | 
| 235 | 
            +
                           "lng": 8.9024268
         | 
| 236 | 
            +
                         }
         | 
| 237 | 
            +
                       }
         | 
| 238 | 
            +
                     }
         | 
| 239 | 
            +
                   }
         | 
| 240 | 
            +
                  ]
         | 
| 241 | 
            +
                }
         | 
| 242 | 
            +
              /.strip
         | 
| 243 | 
            +
              GOOGLE3_REVERSE_MADRID=%q/
         | 
| 244 | 
            +
              {
         | 
| 245 | 
            +
                "status": "OK",
         | 
| 246 | 
            +
                "results": [ {
         | 
| 247 | 
            +
                  "types": [ ],
         | 
| 248 | 
            +
                  "formatted_address": "Calle de las Carretas, 28013 Madrid, Spain",
         | 
| 249 | 
            +
                  "address_components": [ {
         | 
| 250 | 
            +
                    "long_name": "Calle de las Carretas",
         | 
| 251 | 
            +
                    "short_name": "Calle de las Carretas",
         | 
| 252 | 
            +
                    "types": [ "route" ]
         | 
| 253 | 
            +
                  }, {
         | 
| 254 | 
            +
                    "long_name": "Madrid",
         | 
| 255 | 
            +
                    "short_name": "Madrid",
         | 
| 256 | 
            +
                    "types": [ "locality", "political" ]
         | 
| 257 | 
            +
                  }, {
         | 
| 258 | 
            +
                    "long_name": "Madrid",
         | 
| 259 | 
            +
                    "short_name": "M",
         | 
| 260 | 
            +
                    "types": [ "administrative_area_level_2", "political" ]
         | 
| 261 | 
            +
                  }, {
         | 
| 262 | 
            +
                    "long_name": "Madrid",
         | 
| 263 | 
            +
                    "short_name": "Madrid",
         | 
| 264 | 
            +
                    "types": [ "administrative_area_level_1", "political" ]
         | 
| 265 | 
            +
                  }, {
         | 
| 266 | 
            +
                    "long_name": "Spain",
         | 
| 267 | 
            +
                    "short_name": "ES",
         | 
| 268 | 
            +
                    "types": [ "country", "political" ]
         | 
| 269 | 
            +
                  }, {
         | 
| 270 | 
            +
                    "long_name": "28013",
         | 
| 271 | 
            +
                    "short_name": "28013",
         | 
| 272 | 
            +
                    "types": [ "postal_code" ]
         | 
| 273 | 
            +
                  } ],
         | 
| 274 | 
            +
                  "geometry": {
         | 
| 275 | 
            +
                    "location": {
         | 
| 276 | 
            +
                      "lat": 40.4166824,
         | 
| 277 | 
            +
                      "lng": -3.7033411
         | 
| 278 | 
            +
                    },
         | 
| 279 | 
            +
                    "location_type": "APPROXIMATE",
         | 
| 280 | 
            +
                    "viewport": {
         | 
| 281 | 
            +
                      "southwest": {
         | 
| 282 | 
            +
                        "lat": 40.4135351,
         | 
| 283 | 
            +
                        "lng": -3.7064880
         | 
| 284 | 
            +
                      },
         | 
| 285 | 
            +
                      "northeast": {
         | 
| 286 | 
            +
                        "lat": 40.4198303,
         | 
| 287 | 
            +
                        "lng": -3.7001927
         | 
| 288 | 
            +
                      }
         | 
| 289 | 
            +
                    },
         | 
| 290 | 
            +
                    "bounds": {
         | 
| 291 | 
            +
                      "southwest": {
         | 
| 292 | 
            +
                        "lat": 40.4166419,
         | 
| 293 | 
            +
                        "lng": -3.7033685
         | 
| 294 | 
            +
                      },
         | 
| 295 | 
            +
                      "northeast": {
         | 
| 296 | 
            +
                        "lat": 40.4167235,
         | 
| 297 | 
            +
                        "lng": -3.7033122
         | 
| 298 | 
            +
                      }
         | 
| 299 | 
            +
                    }
         | 
| 300 | 
            +
                  }
         | 
| 301 | 
            +
                } ]
         | 
| 302 | 
            +
              }
         | 
| 303 | 
            +
              /
         | 
| 304 | 
            +
              GOOGLE3_COUNTRY_CODE_BIASED_RESULT=%q/
         | 
| 305 | 
            +
              {
         | 
| 306 | 
            +
                "status": "OK",
         | 
| 307 | 
            +
                "results": [ {
         | 
| 308 | 
            +
                  "types": [ "administrative_area_level_2", "political" ],
         | 
| 309 | 
            +
                  "formatted_address": "Syracuse, Italy",
         | 
| 310 | 
            +
                  "address_components": [ {
         | 
| 311 | 
            +
                    "long_name": "Syracuse",
         | 
| 312 | 
            +
                    "short_name": "SR",
         | 
| 313 | 
            +
                    "types": [ "administrative_area_level_2", "political" ]
         | 
| 314 | 
            +
                  }, {
         | 
| 315 | 
            +
                    "long_name": "Sicily",
         | 
| 316 | 
            +
                    "short_name": "Sicily",
         | 
| 317 | 
            +
                    "types": [ "administrative_area_level_1", "political" ]
         | 
| 318 | 
            +
                  }, {
         | 
| 319 | 
            +
                    "long_name": "Italy",
         | 
| 320 | 
            +
                    "short_name": "IT",
         | 
| 321 | 
            +
                    "types": [ "country", "political" ]
         | 
| 322 | 
            +
                  } ],
         | 
| 323 | 
            +
                  "geometry": {
         | 
| 324 | 
            +
                    "location": {
         | 
| 325 | 
            +
                      "lat": 37.0630218,
         | 
| 326 | 
            +
                      "lng": 14.9856176
         | 
| 327 | 
            +
                    },
         | 
| 328 | 
            +
                    "location_type": "APPROXIMATE",
         | 
| 329 | 
            +
                    "viewport": {
         | 
| 330 | 
            +
                      "southwest": {
         | 
| 331 | 
            +
                        "lat": 36.7775664,
         | 
| 332 | 
            +
                        "lng": 14.4733800
         | 
| 333 | 
            +
                      },
         | 
| 334 | 
            +
                      "northeast": {
         | 
| 335 | 
            +
                        "lat": 37.3474070,
         | 
| 336 | 
            +
                        "lng": 15.4978552
         | 
| 337 | 
            +
                      }
         | 
| 338 | 
            +
                    },
         | 
| 339 | 
            +
                    "bounds": {
         | 
| 340 | 
            +
                      "southwest": {
         | 
| 341 | 
            +
                        "lat": 36.6441736,
         | 
| 342 | 
            +
                        "lng": 14.7724913
         | 
| 343 | 
            +
                      },
         | 
| 344 | 
            +
                      "northeast": {
         | 
| 345 | 
            +
                        "lat": 37.4125978,
         | 
| 346 | 
            +
                        "lng": 15.3367367
         | 
| 347 | 
            +
                      }
         | 
| 348 | 
            +
                    }
         | 
| 349 | 
            +
                  }
         | 
| 350 | 
            +
                } ]
         | 
| 351 | 
            +
              }
         | 
| 352 | 
            +
              /
         | 
| 353 | 
            +
              GOOGLE3_TOO_MANY=%q/
         | 
| 354 | 
            +
                {
         | 
| 355 | 
            +
                   "status": "OVER_QUERY_LIMIT"
         | 
| 356 | 
            +
                }
         | 
| 357 | 
            +
              /
         | 
| 358 | 
            +
              def setup
         | 
| 359 | 
            +
                super
         | 
| 360 | 
            +
                @google_full_hash = {:street_address=>"100 Spear St", :city=>"San Francisco", :state=>"CA", :zip=>"94105", :country_code=>"US"}
         | 
| 361 | 
            +
                @google_city_hash = {:city=>"San Francisco", :state=>"CA"}
         | 
| 362 | 
            +
             | 
| 363 | 
            +
                @google_full_loc = Geokit::GeoLoc.new(@google_full_hash)
         | 
| 364 | 
            +
                @google_city_loc = Geokit::GeoLoc.new(@google_city_hash)
         | 
| 365 | 
            +
              end  
         | 
| 366 | 
            +
             | 
| 367 | 
            +
              def test_google3_full_address
         | 
| 368 | 
            +
                response = MockSuccess.new
         | 
| 369 | 
            +
                response.expects(:body).returns(GOOGLE3_FULL)
         | 
| 370 | 
            +
                url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(@address)}"
         | 
| 371 | 
            +
                Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
         | 
| 372 | 
            +
                res=Geokit::Geocoders::GoogleGeocoder3.geocode(@address)
         | 
| 373 | 
            +
                assert_equal "CA", res.state
         | 
| 374 | 
            +
                assert_equal "San Francisco", res.city 
         | 
| 375 | 
            +
                assert_equal "37.7921509,-122.394", res.ll # slightly dif from yahoo
         | 
| 376 | 
            +
                assert res.is_us?
         | 
| 377 | 
            +
                assert_equal "100 Spear St, San Francisco, CA 94105, USA", res.full_address #slightly different from yahoo
         | 
| 378 | 
            +
                assert_equal "google3", res.provider
         | 
| 379 | 
            +
              end
         | 
| 380 | 
            +
              
         | 
| 381 | 
            +
              def test_google3_full_address_with_geo_loc
         | 
| 382 | 
            +
                 response = MockSuccess.new
         | 
| 383 | 
            +
                 response.expects(:body).returns(GOOGLE3_FULL)
         | 
| 384 | 
            +
                 url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(@full_address_short_zip)}"
         | 
| 385 | 
            +
                 Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
         | 
| 386 | 
            +
                 res=Geokit::Geocoders::GoogleGeocoder3.geocode(@google_full_loc)
         | 
| 387 | 
            +
                 assert_equal "CA", res.state
         | 
| 388 | 
            +
                 assert_equal "San Francisco", res.city 
         | 
| 389 | 
            +
                 assert_equal "37.7921509,-122.394", res.ll # slightly dif from yahoo
         | 
| 390 | 
            +
                 assert res.is_us?
         | 
| 391 | 
            +
                 assert_equal "100 Spear St, San Francisco, CA 94105, USA", res.full_address #slightly different from yahoo
         | 
| 392 | 
            +
                 assert_equal "google3", res.provider
         | 
| 393 | 
            +
               end  
         | 
| 394 | 
            +
               
         | 
| 395 | 
            +
               def test_google3_full_address_accuracy
         | 
| 396 | 
            +
                 response = MockSuccess.new
         | 
| 397 | 
            +
                 response.expects(:body).returns(GOOGLE3_FULL)
         | 
| 398 | 
            +
                 url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(@full_address_short_zip)}"
         | 
| 399 | 
            +
                 Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
         | 
| 400 | 
            +
                 res=Geokit::Geocoders::GoogleGeocoder3.geocode(@google_full_loc)
         | 
| 401 | 
            +
                 assert_equal 9, res.accuracy
         | 
| 402 | 
            +
               end
         | 
| 403 | 
            +
              
         | 
| 404 | 
            +
               def test_google3_city
         | 
| 405 | 
            +
                 response = MockSuccess.new
         | 
| 406 | 
            +
                 response.expects(:body).returns(GOOGLE3_CITY)
         | 
| 407 | 
            +
                 url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(@address)}"
         | 
| 408 | 
            +
                 Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
         | 
| 409 | 
            +
                 res=Geokit::Geocoders::GoogleGeocoder3.do_geocode(@address)
         | 
| 410 | 
            +
                 assert_nil res.street_address
         | 
| 411 | 
            +
                 assert_equal "CA", res.state
         | 
| 412 | 
            +
                 assert_equal "San Francisco", res.city
         | 
| 413 | 
            +
                 assert_equal "37.7749295,-122.4194155", res.ll
         | 
| 414 | 
            +
                 assert res.is_us?
         | 
| 415 | 
            +
                 assert_equal "San Francisco, CA, USA", res.full_address
         | 
| 416 | 
            +
                 assert_equal "google3", res.provider
         | 
| 417 | 
            +
               end  
         | 
| 418 | 
            +
               
         | 
| 419 | 
            +
               def test_google3_city_accuracy
         | 
| 420 | 
            +
                 response = MockSuccess.new
         | 
| 421 | 
            +
                 response.expects(:body).returns(GOOGLE3_CITY)
         | 
| 422 | 
            +
                 url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(@address)}"
         | 
| 423 | 
            +
                 Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
         | 
| 424 | 
            +
                 res=Geokit::Geocoders::GoogleGeocoder3.geocode(@address)
         | 
| 425 | 
            +
                 assert_equal 4, res.accuracy
         | 
| 426 | 
            +
               end
         | 
| 427 | 
            +
               
         | 
| 428 | 
            +
               def test_google3_city_with_geo_loc
         | 
| 429 | 
            +
                 response = MockSuccess.new
         | 
| 430 | 
            +
                 response.expects(:body).returns(GOOGLE3_CITY)
         | 
| 431 | 
            +
                 url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(@address)}"
         | 
| 432 | 
            +
                 Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
         | 
| 433 | 
            +
                 res=Geokit::Geocoders::GoogleGeocoder3.geocode(@google_city_loc)
         | 
| 434 | 
            +
                 assert_equal "CA", res.state
         | 
| 435 | 
            +
                 assert_equal "San Francisco", res.city
         | 
| 436 | 
            +
                 assert_equal "37.7749295,-122.4194155", res.ll
         | 
| 437 | 
            +
                 assert res.is_us?
         | 
| 438 | 
            +
                 assert_equal "San Francisco, CA, USA", res.full_address
         | 
| 439 | 
            +
                 assert_nil res.street_address
         | 
| 440 | 
            +
                 assert_equal "google3", res.provider
         | 
| 441 | 
            +
               end  
         | 
| 442 | 
            +
             | 
| 443 | 
            +
               def test_google3_suggested_bounds
         | 
| 444 | 
            +
                 response = MockSuccess.new
         | 
| 445 | 
            +
                 response.expects(:body).returns(GOOGLE3_FULL)
         | 
| 446 | 
            +
                 url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(@full_address_short_zip)}"
         | 
| 447 | 
            +
                 Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
         | 
| 448 | 
            +
                 res = Geokit::Geocoders::GoogleGeocoder3.geocode(@google_full_loc)
         | 
| 449 | 
            +
                 
         | 
| 450 | 
            +
                 assert_instance_of Geokit::Bounds, res.suggested_bounds
         | 
| 451 | 
            +
                 assert_equal Geokit::Bounds.new(Geokit::LatLng.new(37.7890033, -122.3971476), Geokit::LatLng.new(37.7952985, -122.3908524)), res.suggested_bounds
         | 
| 452 | 
            +
               end
         | 
| 453 | 
            +
               
         | 
| 454 | 
            +
               def test_service_unavailable
         | 
| 455 | 
            +
                 response = MockFailure.new
         | 
| 456 | 
            +
                 url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(@address)}"
         | 
| 457 | 
            +
                 Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
         | 
| 458 | 
            +
                 assert !Geokit::Geocoders::GoogleGeocoder3.geocode(@google_city_loc).success
         | 
| 459 | 
            +
               end 
         | 
| 460 | 
            +
               
         | 
| 461 | 
            +
               def test_multiple_results
         | 
| 462 | 
            +
                 #Geokit::Geocoders::GoogleGeocoder3.do_geocode('via Sandro Pertini 8, Ossona, MI')
         | 
| 463 | 
            +
                 response = MockSuccess.new
         | 
| 464 | 
            +
                 response.expects(:body).returns(GOOGLE3_MULTI)
         | 
| 465 | 
            +
                 url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector.url_escape('via Sandro Pertini 8, Ossona, MI')}"
         | 
| 466 | 
            +
                 Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
         | 
| 467 | 
            +
                 res=Geokit::Geocoders::GoogleGeocoder3.geocode('via Sandro Pertini 8, Ossona, MI')
         | 
| 468 | 
            +
                 assert_equal "Lombardy", res.state
         | 
| 469 | 
            +
                 assert_equal "Mesero", res.city
         | 
| 470 | 
            +
                 assert_equal "45.4966218,8.852694", res.ll
         | 
| 471 | 
            +
                 assert !res.is_us?
         | 
| 472 | 
            +
                 assert_equal "Via Sandro Pertini, 8, 20010 Mesero MI, Italy", res.full_address
         | 
| 473 | 
            +
                 assert_equal "8 Via Sandro Pertini", res.street_address
         | 
| 474 | 
            +
                 assert_equal "google3", res.provider
         | 
| 475 | 
            +
              
         | 
| 476 | 
            +
                 assert_equal 2, res.all.size
         | 
| 477 | 
            +
                 res = res.all[1]
         | 
| 478 | 
            +
                 assert_equal "Lombardy", res.state
         | 
| 479 | 
            +
                 assert_equal "Ossona", res.city
         | 
| 480 | 
            +
                 assert_equal "45.5074444,8.90232", res.ll
         | 
| 481 | 
            +
                 assert !res.is_us?
         | 
| 482 | 
            +
                 assert_equal "Via Sandro Pertini, 20010 Ossona MI, Italy", res.full_address
         | 
| 483 | 
            +
                 assert_equal "Via Sandro Pertini", res.street_address
         | 
| 484 | 
            +
                 assert_equal "google3", res.provider
         | 
| 485 | 
            +
               end
         | 
| 486 | 
            +
              # 
         | 
| 487 | 
            +
               def test_reverse_geocode
         | 
| 488 | 
            +
                 #Geokit::Geocoders::GoogleGeocoder3.do_reverse_geocode("40.4167413, -3.7032498")
         | 
| 489 | 
            +
                 madrid = Geokit::GeoLoc.new
         | 
| 490 | 
            +
                 madrid.lat, madrid.lng = "40.4167413", "-3.7032498"
         | 
| 491 | 
            +
                 response = MockSuccess.new
         | 
| 492 | 
            +
                 response.expects(:body).returns(GOOGLE3_REVERSE_MADRID)
         | 
| 493 | 
            +
                 url = "http://maps.google.com/maps/api/geocode/json?sensor=false&latlng=#{Geokit::Inflector::url_escape(madrid.ll)}"
         | 
| 494 | 
            +
                 Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).
         | 
| 495 | 
            +
                   returns(response)
         | 
| 496 | 
            +
                 res=Geokit::Geocoders::GoogleGeocoder3.do_reverse_geocode(madrid.ll)
         | 
| 497 | 
            +
              
         | 
| 498 | 
            +
                 assert_equal madrid.lat.to_s.slice(1..5), res.lat.to_s.slice(1..5)
         | 
| 499 | 
            +
                 assert_equal madrid.lng.to_s.slice(1..5), res.lng.to_s.slice(1..5)
         | 
| 500 | 
            +
                 assert_equal "ES", res.country_code
         | 
| 501 | 
            +
                 assert_equal "google3", res.provider
         | 
| 502 | 
            +
              
         | 
| 503 | 
            +
                 assert_equal "Madrid", res.city
         | 
| 504 | 
            +
                 assert_equal "Madrid", res.state
         | 
| 505 | 
            +
              
         | 
| 506 | 
            +
                 assert_equal "Spain", res.country
         | 
| 507 | 
            +
                 assert_equal "street", res.precision
         | 
| 508 | 
            +
                 assert_equal true, res.success
         | 
| 509 | 
            +
              
         | 
| 510 | 
            +
                 assert_equal "Calle de las Carretas, 28013 Madrid, Spain", res.full_address
         | 
| 511 | 
            +
                 assert_equal "28013", res.zip
         | 
| 512 | 
            +
                 assert_equal "Calle de las Carretas", res.street_address
         | 
| 513 | 
            +
               end  
         | 
| 514 | 
            +
             | 
| 515 | 
            +
               def test_country_code_biasing
         | 
| 516 | 
            +
                 response = MockSuccess.new
         | 
| 517 | 
            +
                 response.expects(:body).returns(GOOGLE3_COUNTRY_CODE_BIASED_RESULT)
         | 
| 518 | 
            +
                 
         | 
| 519 | 
            +
                 url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=Syracuse®ion=it"
         | 
| 520 | 
            +
                 Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
         | 
| 521 | 
            +
                 biased_result = Geokit::Geocoders::GoogleGeocoder3.geocode('Syracuse', :bias => 'it')
         | 
| 522 | 
            +
                 
         | 
| 523 | 
            +
                 assert_equal 'IT', biased_result.country_code
         | 
| 524 | 
            +
                 assert_equal 'Sicily', biased_result.state
         | 
| 525 | 
            +
               end
         | 
| 526 | 
            +
              
         | 
| 527 | 
            +
               def test_too_many_queries
         | 
| 528 | 
            +
                 response = MockSuccess.new
         | 
| 529 | 
            +
                 response.expects(:body).returns(GOOGLE3_TOO_MANY)
         | 
| 530 | 
            +
                 url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector.url_escape(@address)}"
         | 
| 531 | 
            +
                 Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
         | 
| 532 | 
            +
                 assert_raise Geokit::TooManyQueriesError do
         | 
| 533 | 
            +
                   res=Geokit::Geocoders::GoogleGeocoder3.geocode(@address)
         | 
| 534 | 
            +
                 end
         | 
| 535 | 
            +
               end
         | 
| 536 | 
            +
            end
         | 
    
        data/test/test_ipgeocoder.rb
    CHANGED
    
    | @@ -4,8 +4,8 @@ require File.join(File.dirname(__FILE__), 'test_base_geocoder') | |
| 4 4 | 
             
            class IpGeocoderTest < BaseGeocoderTest #:nodoc: all
         | 
| 5 5 |  | 
| 6 6 | 
             
              IP_FAILURE=<<-EOF
         | 
| 7 | 
            -
                Country:  | 
| 8 | 
            -
                City: ( | 
| 7 | 
            +
                Country: SWITZERLAND (CH)
         | 
| 8 | 
            +
                City: (Unknown City)
         | 
| 9 9 | 
             
                Latitude: 
         | 
| 10 10 | 
             
                Longitude:
         | 
| 11 11 | 
             
                EOF
         | 
| @@ -24,10 +24,22 @@ class IpGeocoderTest < BaseGeocoderTest #:nodoc: all | |
| 24 24 | 
             
                Longitude: 12.9167
         | 
| 25 25 | 
             
                EOF
         | 
| 26 26 |  | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
                 | 
| 27 | 
            +
              PRIVATE_IPS_TO_TEST = [
         | 
| 28 | 
            +
                '10.10.10.10',
         | 
| 29 | 
            +
                '172.16.1.3',
         | 
| 30 | 
            +
                '172.22.3.42',
         | 
| 31 | 
            +
                '172.30.254.164',
         | 
| 32 | 
            +
                '192.168.1.1',
         | 
| 33 | 
            +
                '0.0.0.0',
         | 
| 34 | 
            +
                '127.0.0.1',
         | 
| 35 | 
            +
                '240.3.4.5',
         | 
| 36 | 
            +
                '225.1.6.55'
         | 
| 37 | 
            +
              ].freeze
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              def setup
         | 
| 40 | 
            +
                super
         | 
| 41 | 
            +
                @success.provider = "hostip"
         | 
| 42 | 
            +
              end    
         | 
| 31 43 |  | 
| 32 44 | 
             
              def test_successful_lookup
         | 
| 33 45 | 
             
                success = MockSuccess.new
         | 
| @@ -64,14 +76,24 @@ class IpGeocoderTest < BaseGeocoderTest #:nodoc: all | |
| 64 76 | 
             
              def test_failed_lookup
         | 
| 65 77 | 
             
                failure = MockSuccess.new
         | 
| 66 78 | 
             
                failure.expects(:body).returns(IP_FAILURE)
         | 
| 67 | 
            -
                url = 'http://api.hostip.info/get_html.php?ip= | 
| 79 | 
            +
                url = 'http://api.hostip.info/get_html.php?ip=128.178.0.0&position=true'
         | 
| 68 80 | 
             
                GeoKit::Geocoders::IpGeocoder.expects(:call_geocoder_service).with(url).returns(failure)
         | 
| 69 | 
            -
                location = GeoKit::Geocoders::IpGeocoder.geocode(" | 
| 81 | 
            +
                location = GeoKit::Geocoders::IpGeocoder.geocode("128.178.0.0")
         | 
| 70 82 | 
             
                assert_not_nil location
         | 
| 71 83 | 
             
                assert !location.success?
         | 
| 72 84 | 
             
              end
         | 
| 73 85 |  | 
| 86 | 
            +
              def test_private_ips
         | 
| 87 | 
            +
                GeoKit::Geocoders::IpGeocoder.expects(:call_geocoder_service).never
         | 
| 88 | 
            +
                PRIVATE_IPS_TO_TEST.each do |ip|
         | 
| 89 | 
            +
                  location = GeoKit::Geocoders::IpGeocoder.geocode(ip)
         | 
| 90 | 
            +
                  assert_not_nil location
         | 
| 91 | 
            +
                  assert !location.success?
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
              end
         | 
| 94 | 
            +
              
         | 
| 74 95 | 
             
              def test_invalid_ip
         | 
| 96 | 
            +
                GeoKit::Geocoders::IpGeocoder.expects(:call_geocoder_service).never
         | 
| 75 97 | 
             
                location = GeoKit::Geocoders::IpGeocoder.geocode("blah")
         | 
| 76 98 | 
             
                assert_not_nil location
         | 
| 77 99 | 
             
                assert !location.success?
         | 
| @@ -79,9 +101,9 @@ class IpGeocoderTest < BaseGeocoderTest #:nodoc: all | |
| 79 101 |  | 
| 80 102 | 
             
              def test_service_unavailable
         | 
| 81 103 | 
             
                failure = MockFailure.new
         | 
| 82 | 
            -
                url = 'http://api.hostip.info/get_html.php?ip= | 
| 104 | 
            +
                url = 'http://api.hostip.info/get_html.php?ip=12.215.42.19&position=true'
         | 
| 83 105 | 
             
                GeoKit::Geocoders::IpGeocoder.expects(:call_geocoder_service).with(url).returns(failure)
         | 
| 84 | 
            -
                location = GeoKit::Geocoders::IpGeocoder.geocode(" | 
| 106 | 
            +
                location = GeoKit::Geocoders::IpGeocoder.geocode("12.215.42.19")
         | 
| 85 107 | 
             
                assert_not_nil location
         | 
| 86 108 | 
             
                assert !location.success?
         | 
| 87 109 | 
             
              end  
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,13 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification 
         | 
| 2 2 | 
             
            name: darrell-geokit
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version 
         | 
| 4 | 
            -
               | 
| 4 | 
            +
              prerelease: false
         | 
| 5 | 
            +
              segments: 
         | 
| 6 | 
            +
              - 1
         | 
| 7 | 
            +
              - 5
         | 
| 8 | 
            +
              - 0
         | 
| 9 | 
            +
              - 1
         | 
| 10 | 
            +
              version: 1.5.0.1
         | 
| 5 11 | 
             
            platform: ruby
         | 
| 6 12 | 
             
            authors: 
         | 
| 7 13 | 
             
            - Andre Lewis and Bill Eisenhauer
         | 
| @@ -9,10 +15,21 @@ autorequire: | |
| 9 15 | 
             
            bindir: bin
         | 
| 10 16 | 
             
            cert_chain: []
         | 
| 11 17 |  | 
| 12 | 
            -
            date: 2009- | 
| 18 | 
            +
            date: 2009-08-02 00:00:00 -07:00
         | 
| 13 19 | 
             
            default_executable: 
         | 
| 14 | 
            -
            dependencies:  | 
| 15 | 
            -
             | 
| 20 | 
            +
            dependencies: 
         | 
| 21 | 
            +
            - !ruby/object:Gem::Dependency 
         | 
| 22 | 
            +
              name: json_pure
         | 
| 23 | 
            +
              prerelease: false
         | 
| 24 | 
            +
              requirement: &id001 !ruby/object:Gem::Requirement 
         | 
| 25 | 
            +
                requirements: 
         | 
| 26 | 
            +
                - - ">="
         | 
| 27 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 28 | 
            +
                    segments: 
         | 
| 29 | 
            +
                    - 0
         | 
| 30 | 
            +
                    version: "0"
         | 
| 31 | 
            +
              type: :runtime
         | 
| 32 | 
            +
              version_requirements: *id001
         | 
| 16 33 | 
             
            description: Geokit Gem
         | 
| 17 34 | 
             
            email: 
         | 
| 18 35 | 
             
            - andre@earthcode.com / bill_eisenhauer@yahoo.com
         | 
| @@ -34,6 +51,7 @@ files: | |
| 34 51 | 
             
            - test/test_bounds.rb
         | 
| 35 52 | 
             
            - test/test_ca_geocoder.rb
         | 
| 36 53 | 
             
            - test/test_geoloc.rb
         | 
| 54 | 
            +
            - test/test_google_geocoder3.rb
         | 
| 37 55 | 
             
            - test/test_google_geocoder.rb
         | 
| 38 56 | 
             
            - test/test_latlng.rb
         | 
| 39 57 | 
             
            - test/test_multi_geocoder.rb
         | 
| @@ -41,7 +59,8 @@ files: | |
| 41 59 | 
             
            - test/test_yahoo_geocoder.rb
         | 
| 42 60 | 
             
            has_rdoc: true
         | 
| 43 61 | 
             
            homepage: http://geokit.rubyforge.org
         | 
| 44 | 
            -
            licenses: 
         | 
| 62 | 
            +
            licenses: []
         | 
| 63 | 
            +
             | 
| 45 64 | 
             
            post_install_message: 
         | 
| 46 65 | 
             
            rdoc_options: 
         | 
| 47 66 | 
             
            - --main
         | 
| @@ -52,18 +71,20 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 52 71 | 
             
              requirements: 
         | 
| 53 72 | 
             
              - - ">="
         | 
| 54 73 | 
             
                - !ruby/object:Gem::Version 
         | 
| 74 | 
            +
                  segments: 
         | 
| 75 | 
            +
                  - 0
         | 
| 55 76 | 
             
                  version: "0"
         | 
| 56 | 
            -
              version: 
         | 
| 57 77 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement 
         | 
| 58 78 | 
             
              requirements: 
         | 
| 59 79 | 
             
              - - ">="
         | 
| 60 80 | 
             
                - !ruby/object:Gem::Version 
         | 
| 81 | 
            +
                  segments: 
         | 
| 82 | 
            +
                  - 0
         | 
| 61 83 | 
             
                  version: "0"
         | 
| 62 | 
            -
              version: 
         | 
| 63 84 | 
             
            requirements: []
         | 
| 64 85 |  | 
| 65 86 | 
             
            rubyforge_project: geokit
         | 
| 66 | 
            -
            rubygems_version: 1.3. | 
| 87 | 
            +
            rubygems_version: 1.3.6
         | 
| 67 88 | 
             
            signing_key: 
         | 
| 68 89 | 
             
            specification_version: 2
         | 
| 69 90 | 
             
            summary: none
         | 
| @@ -73,6 +94,7 @@ test_files: | |
| 73 94 | 
             
            - test/test_ca_geocoder.rb
         | 
| 74 95 | 
             
            - test/test_geoloc.rb
         | 
| 75 96 | 
             
            - test/test_geoplugin_geocoder.rb
         | 
| 97 | 
            +
            - test/test_google_geocoder3.rb
         | 
| 76 98 | 
             
            - test/test_google_geocoder.rb
         | 
| 77 99 | 
             
            - test/test_google_reverse_geocoder.rb
         | 
| 78 100 | 
             
            - test/test_inflector.rb
         |