From 3b17a86906a939a97b924a91ca51f056e3dd1cb4 Mon Sep 17 00:00:00 2001
From: Brian Flanagan <brian@coralmade.com>
Date: Sat, 26 Jul 2014 00:31:53 +0100
Subject: [PATCH] Add support for Pointp.in ip geolocation

---
 README.md                              | 12 +++++
 lib/geocoder/lookup.rb                 |  3 +-
 lib/geocoder/lookups/pointpin.rb       | 68 ++++++++++++++++++++++++++
 lib/geocoder/results/pointpin.rb       | 44 +++++++++++++++++
 test/fixtures/pointpin_10_10_10_10     |  1 +
 test/fixtures/pointpin_555_555_555_555 |  1 +
 test/fixtures/pointpin_80_111_555_555  |  1 +
 test/fixtures/pointpin_no_results      |  1 +
 test/test_helper.rb                    |  7 +++
 test/unit/lookup_test.rb               |  8 ++-
 test/unit/lookups/pointpin_test.rb     | 30 ++++++++++++
 11 files changed, 174 insertions(+), 2 deletions(-)
 create mode 100644 lib/geocoder/lookups/pointpin.rb
 create mode 100644 lib/geocoder/results/pointpin.rb
 create mode 100644 test/fixtures/pointpin_10_10_10_10
 create mode 100644 test/fixtures/pointpin_555_555_555_555
 create mode 100644 test/fixtures/pointpin_80_111_555_555
 create mode 100644 test/fixtures/pointpin_no_results
 create mode 100644 test/unit/lookups/pointpin_test.rb

diff --git a/README.md b/README.md
index e3d99c46..e749b272 100644
--- a/README.md
+++ b/README.md
@@ -584,6 +584,18 @@ Data Science Toolkit provides an API whose reponse format is like Google's but w
 * **Limitations**: ?
 * **Notes**: If you are [running your own local instance of the FreeGeoIP service](https://github.com/fiorix/freegeoip) you can configure the host like this: `Geocoder.configure(freegeoip: {host: "..."})`.
 
+#### Pointpin (`:pointpin`)
+
+* **API key**: required
+* **Quota**: 50,000/mo for €9 through 1m/mo for €49
+* **Region**: world
+* **SSL support**: yes
+* **Languages**: English
+* **Documentation**: https://pointp.in/docs/get-started
+* **Terms of Service**: https://pointp.in/terms
+* **Limitations**: ?
+* **Notes**: To use Pointpin set `Geocoder.configure(:ip_lookup => :pointpin, :api_key => "your_pointpin_api_key")`.
+
 #### Telize (`:telize`)
 
 * **API key**: none
diff --git a/lib/geocoder/lookup.rb b/lib/geocoder/lookup.rb
index dbfa6519..9d380e3f 100644
--- a/lib/geocoder/lookup.rb
+++ b/lib/geocoder/lookup.rb
@@ -51,7 +51,8 @@ module Geocoder
         :freegeoip,
         :maxmind,
         :maxmind_local,
-        :telize
+        :telize,
+        :pointpin
       ]
     end
 
diff --git a/lib/geocoder/lookups/pointpin.rb b/lib/geocoder/lookups/pointpin.rb
new file mode 100644
index 00000000..6bb8bf83
--- /dev/null
+++ b/lib/geocoder/lookups/pointpin.rb
@@ -0,0 +1,68 @@
+require 'geocoder/lookups/base'
+require 'geocoder/results/pointpin'
+
+module Geocoder::Lookup
+  class Pointpin < Base
+
+    def name
+      "Pointpin"
+    end
+
+    def required_api_key_parts
+      ["key"]
+    end
+
+    def query_url(query)
+      "#{ protocol }://geo.pointp.in/#{ api_key }/json/#{ query.sanitized_text }"
+    end
+
+  private
+
+    def results(query)
+      # don't look up a loopback address, just return the stored result
+      return [] if query.loopback_ip_address?
+      doc = fetch_data(query)
+      if doc and doc.is_a?(Hash)
+        if !data_contains_error?(doc)
+          return [doc]
+        elsif doc['error']
+          case doc['error']
+          when "Invalid IP address"
+            raise_error(Geocoder::InvalidRequest) || warn("Invalid Pointpin request.")
+          when "Invalid API key"
+            raise_error(Geocoder::InvalidApiKey) || warn("Invalid Pointpin API key.")
+          when "Address not found"
+            warn("Address not found.")
+          end
+        else
+          raise_error(Geocoder::Error) || warn("Pointpin server error")
+        end
+      end
+      
+      return []
+    end
+
+    def data_contains_error?(parsed_data)
+      parsed_data.keys.include?('error')
+    end
+
+    def reserved_result(ip)
+      {
+        "ip"           => ip,
+        "city"         => "",
+        "region_code"  => "",
+        "region_name"  => "",
+        "metrocode"    => "",
+        "zipcode"      => "",
+        "latitude"     => "0",
+        "longitude"    => "0",
+        "country_name" => "Reserved",
+        "country_code" => "RD"
+      }
+    end
+
+    def api_key
+      configuration.api_key
+    end
+  end
+end
diff --git a/lib/geocoder/results/pointpin.rb b/lib/geocoder/results/pointpin.rb
new file mode 100644
index 00000000..f6a99d0f
--- /dev/null
+++ b/lib/geocoder/results/pointpin.rb
@@ -0,0 +1,44 @@
+require 'geocoder/results/base'
+
+module Geocoder::Result
+  class Pointpin < Base
+
+    def address
+      [ city_name, state, postal_code, country ].select{ |i| i.to_s != "" }.join(", ")
+    end
+
+    def city
+      @data['city_name']
+    end
+
+    def state
+      @data['region_name']
+    end
+
+    def state_code
+      @data['region_code']
+    end
+
+    def country
+      @data['country_name']
+    end
+
+    def country_code
+      @data['country_code']
+    end
+
+    def postal_code
+      @data['postcode']
+    end
+
+    def self.response_attributes
+      %w[continent_code ip country_code country_name region_name city_name postcode latitude longitude time_zone languages]
+    end
+
+    response_attributes.each do |a|
+      define_method a do
+        @data[a]
+      end
+    end
+  end
+end
diff --git a/test/fixtures/pointpin_10_10_10_10 b/test/fixtures/pointpin_10_10_10_10
new file mode 100644
index 00000000..2ddd8848
--- /dev/null
+++ b/test/fixtures/pointpin_10_10_10_10
@@ -0,0 +1 @@
+{"error":"Address not found"}
\ No newline at end of file
diff --git a/test/fixtures/pointpin_555_555_555_555 b/test/fixtures/pointpin_555_555_555_555
new file mode 100644
index 00000000..859aa415
--- /dev/null
+++ b/test/fixtures/pointpin_555_555_555_555
@@ -0,0 +1 @@
+{"error":"Invalid IP address"}
\ No newline at end of file
diff --git a/test/fixtures/pointpin_80_111_555_555 b/test/fixtures/pointpin_80_111_555_555
new file mode 100644
index 00000000..2ec552dc
--- /dev/null
+++ b/test/fixtures/pointpin_80_111_555_555
@@ -0,0 +1 @@
+{"ip":"80.111.555.555","continent_code":"EU","country_code":"IE","country_name":"Ireland","region_name":"Dublin City","region_code":"D8","city_name":"Dublin","postcode":"8","latitude":53.3331,"longitude":-6.2489,"time_zone":"Europe/Dublin"}
\ No newline at end of file
diff --git a/test/fixtures/pointpin_no_results b/test/fixtures/pointpin_no_results
new file mode 100644
index 00000000..2ddd8848
--- /dev/null
+++ b/test/fixtures/pointpin_no_results
@@ -0,0 +1 @@
+{"error":"Address not found"}
\ No newline at end of file
diff --git a/test/test_helper.rb b/test/test_helper.rb
index b618b65e..0faf3f11 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -150,6 +150,13 @@ module Geocoder
       end
     end
 
+    class Pointpin
+      private
+      def default_fixture_filename
+        "pointpin_80_111_555_555"
+      end
+    end
+
     class Maxmind
       private
       def default_fixture_filename
diff --git a/test/unit/lookup_test.rb b/test/unit/lookup_test.rb
index d0507afa..7515b599 100644
--- a/test/unit/lookup_test.rb
+++ b/test/unit/lookup_test.rb
@@ -23,7 +23,7 @@ class LookupTest < GeocoderTestCase
 
   def test_query_url_contains_values_in_params_hash
     Geocoder::Lookup.all_services_except_test.each do |l|
-      next if [:freegeoip, :maxmind_local, :telize].include? l # does not use query string
+      next if [:freegeoip, :maxmind_local, :telize, :pointpin].include? l # does not use query string
       set_api_key!(l)
       url = Geocoder::Lookup.get(l).query_url(Geocoder::Query.new(
         "test", :params => {:one_in_the_hand => "two in the bush"}
@@ -119,6 +119,12 @@ class LookupTest < GeocoderTestCase
     assert_match "ak=MY_KEY", g.query_url(Geocoder::Query.new("Madison Square Garden, New York, NY  10001, United States"))
   end
 
+  def test_pointpin_api_key
+    Geocoder.configure(:api_key => "MY_KEY")
+    g = Geocoder::Lookup::Pointpin.new
+    assert_match "/MY_KEY/", g.query_url(Geocoder::Query.new("232.65.123.94"))
+  end
+
   def test_google_api_key
     Geocoder.configure(:api_key => "MY_KEY")
     g = Geocoder::Lookup::Google.new
diff --git a/test/unit/lookups/pointpin_test.rb b/test/unit/lookups/pointpin_test.rb
new file mode 100644
index 00000000..201ed701
--- /dev/null
+++ b/test/unit/lookups/pointpin_test.rb
@@ -0,0 +1,30 @@
+# encoding: utf-8
+$: << File.join(File.dirname(__FILE__), "..", "..")
+require 'test_helper'
+
+class PointpinTest < GeocoderTestCase
+
+  def setup
+    Geocoder.configure(ip_lookup: :pointpin, api_key: "abc123")
+  end
+
+  def test_result_on_ip_address_search
+    result = Geocoder.search("80.111.555.555").first
+    assert result.is_a?(Geocoder::Result::Pointpin)
+  end
+
+  def test_result_components
+    result = Geocoder.search("80.111.555.555").first
+    assert_equal "Dublin, Dublin City, 8, Ireland", result.address
+  end
+
+  def test_no_results
+    results = Geocoder.search("10.10.10.10")
+    assert_equal 0, results.length
+  end
+
+  def test_invalid_address
+    results = Geocoder.search("555.555.555.555")
+    assert_equal 0, results.length
+  end
+end
-- 
GitLab