diff --git a/lib/geocoder/lookup.rb b/lib/geocoder/lookup.rb
index d6728b51f6c913d590bd83c1ac506022887d4987..1002f40c0e1eb8e1e3b37cfb0e0e4024022c2593 100644
--- a/lib/geocoder/lookup.rb
+++ b/lib/geocoder/lookup.rb
@@ -39,6 +39,7 @@ module Geocoder
         :baidu,
         :geocodio,
         :smarty_streets,
+        :okf,
         :test
       ]
     end
diff --git a/lib/geocoder/lookups/okf.rb b/lib/geocoder/lookups/okf.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ae45e06a4680c8e61aaf05eeae547495aff5e03a
--- /dev/null
+++ b/lib/geocoder/lookups/okf.rb
@@ -0,0 +1,43 @@
+require 'geocoder/lookups/base'
+require "geocoder/results/okf"
+
+module Geocoder::Lookup
+  class Okf < Base
+
+    def name
+      "Okf"
+    end
+
+    def query_url(query)
+      "#{protocol}://data.okf.fi/gis/1/geocode/json?" + url_query_string(query)
+    end
+
+    private # ---------------------------------------------------------------
+
+    def valid_response?(response)
+      status = parse_json(response.body)["status"]
+      super(response) and ['OK', 'ZERO_RESULTS'].include?(status)
+    end
+
+    def results(query)
+      return [] unless doc = fetch_data(query)
+      case doc['status']; when "OK" # OK status implies >0 results
+        return doc['results']
+      end
+      return []
+    end
+
+    def query_url_okf_params(query)
+      params = {
+        (query.reverse_geocode? ? :latlng : :address) => query.sanitized_text,
+        :sensor => "false",
+        :language => (query.language || configuration.language)
+      }
+      params
+    end
+
+    def query_url_params(query)
+      query_url_okf_params(query).merge(super)
+    end
+  end
+end
diff --git a/lib/geocoder/results/okf.rb b/lib/geocoder/results/okf.rb
new file mode 100644
index 0000000000000000000000000000000000000000..973922591474c75897a963e4d0b1aef46cd028ed
--- /dev/null
+++ b/lib/geocoder/results/okf.rb
@@ -0,0 +1,106 @@
+require 'geocoder/results/base'
+
+module Geocoder::Result
+  class Okf < Base
+
+    def coordinates
+      ['lat', 'lng'].map{ |i| geometry['location'][i] }
+    end
+
+    def address(format = :full)
+      formatted_address
+    end
+
+    def city
+      fields = [:locality, :sublocality,
+        :administrative_area_level_3,
+        :administrative_area_level_2]
+      fields.each do |f|
+        if entity = address_components_of_type(f).first
+          return entity['long_name']
+        end
+      end
+      return nil # no appropriate components found
+    end
+
+  def state
+    ""
+  end
+
+  def sub_state
+    ""
+  end
+
+  def state_code
+    ""
+  end
+
+    def country
+      if country = address_components_of_type(:country).first
+        country['long_name']
+      end
+    end
+
+    def country_code
+      if country = address_components_of_type(:country).first
+        country['short_name']
+      end
+    end
+
+    def postal_code
+      if postal = address_components_of_type(:postal_code).first
+        postal['long_name']
+      end
+    end
+
+    def route
+      if route = address_components_of_type(:route).first
+        route['long_name']
+      end
+    end
+
+    def street_number
+      if street_number = address_components_of_type(:street_number).first
+        street_number['long_name']
+      end
+    end
+
+    def street_address
+      [route, street_number].compact.join(' ')
+    end
+
+    def types
+      @data['types']
+    end
+
+    def formatted_address
+      @data['formatted_address']
+    end
+
+    def address_components
+      @data['address_components']
+    end
+
+    ##
+    # Get address components of a given type. Valid types are defined in
+    # Google's Geocoding API documentation and include (among others):
+    #
+    #   :street_number
+    #   :locality
+    #   :neighborhood
+    #   :route
+    #   :postal_code
+    #
+    def address_components_of_type(type)
+      address_components.select{ |c| c['types'].include?(type.to_s) }
+    end
+
+    def geometry
+      @data['geometry']
+    end
+
+    def precision
+      geometry['location_type'] if geometry
+    end
+  end
+end
diff --git a/test/fixtures/okf_kirstinmaki b/test/fixtures/okf_kirstinmaki
new file mode 100644
index 0000000000000000000000000000000000000000..79d50b9636c554fb7af243820cfe29ae3d3b6cc7
--- /dev/null
+++ b/test/fixtures/okf_kirstinmaki
@@ -0,0 +1,67 @@
+{
+   "status" : "OK",
+   "results" : [
+      {
+         "formatted_address" : "Kirstinmäki 1, 02760 Espoo, Suomi",
+         "sources" : [
+            {
+               "name" : "National Land Survey of Finland - Topographic Dataset (2013-03-08)",
+               "terms_of_use" : "National Land Survey open data licence - version 1.0 - 1 May 2012\nhttp://www.maanmittauslaitos.fi/en/NLS_open_data_licence_version1_20120501"
+            },
+            {
+               "name" : "Itella perusosoitteisto (2014-02-01)",
+               "terms_of_use" : "http://www.itella.fi/liitteet/palvelutjatuotteet/yhteystietopalvelut/Postinumeropalvelut-Palvelukuvausjakayttoehdot.pdf"
+            }
+         ],
+         "types" : [
+            "street_address"
+         ],
+         "address_components" : [
+            {
+               "types" : [
+                  "street_number"
+               ],
+               "short_name" : "1",
+               "long_name" : "1"
+            },
+            {
+               "types" : [
+                  "route"
+               ],
+               "short_name" : "Kirstinmäki",
+               "long_name" : "Kirstinmäki"
+            },
+            {
+               "types" : [
+                  "administrative_area_level_3",
+                  "political"
+               ],
+               "short_name" : "Espoo",
+               "long_name" : "Espoo"
+            },
+            {
+               "types" : [
+                  "postal_code"
+               ],
+               "short_name" : "02760",
+               "long_name" : "02760"
+            },
+            {
+               "types" : [
+                  "country",
+                  "political"
+               ],
+               "short_name" : "FI",
+               "long_name" : "Suomi"
+            }
+         ],
+         "geometry" : {
+            "location" : {
+               "lat" : 60.2015185792087,
+               "lng" : 24.6667520050026
+            },
+            "location_type" : "RANGE_INTERPOLATED"
+         }
+      }
+   ]
+}
diff --git a/test/fixtures/okf_no_results b/test/fixtures/okf_no_results
new file mode 100644
index 0000000000000000000000000000000000000000..184b56deadf96dcb3f8a3138cbe72fe4e610c7a6
--- /dev/null
+++ b/test/fixtures/okf_no_results
@@ -0,0 +1,4 @@
+{
+   "status" : "ZERO_RESULTS",
+   "results" : []
+}
diff --git a/test/test_helper.rb b/test/test_helper.rb
index ce5ad195739a93e83f74c6b9f42bece6e6d46eed..e57c779ac614dc5c1d818838eea215a38acad638 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -207,6 +207,13 @@ module Geocoder
         "geocodio_1101_pennsylvania_ave"
       end
     end
+
+    class Okf
+      private
+      def default_fixture_filename
+        "okf_kirstinmaki"
+      end
+    end
   end
 end
 
diff --git a/test/unit/lookups/okf_test.rb b/test/unit/lookups/okf_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7882ff247cfe024e4d6349065db34952c766b04c
--- /dev/null
+++ b/test/unit/lookups/okf_test.rb
@@ -0,0 +1,38 @@
+# encoding: utf-8
+$: << File.join(File.dirname(__FILE__), "..", "..")
+require 'test_helper'
+
+class OkgTest < GeocoderTestCase
+
+  def setup
+    Geocoder.configure(lookup: :okf)
+  end
+
+  def test_okf_result_components
+    result = Geocoder.search("Kirstinmäki 11b28").first
+    assert_equal "Espoo",
+      result.address_components_of_type(:administrative_area_level_3).first['long_name']
+  end
+
+  def test_okf_result_components_contains_route
+    result = Geocoder.search("Kirstinmäki 11b28").first
+    assert_equal "Kirstinmäki",
+      result.address_components_of_type(:route).first['long_name']
+  end
+
+  def test_okf_result_components_contains_street_number
+    result = Geocoder.search("Kirstinmäki 11b28").first
+    assert_equal "1",
+      result.address_components_of_type(:street_number).first['long_name']
+  end
+
+  def test_okf_street_address_returns_formatted_street_address
+    result = Geocoder.search("Kirstinmäki 11b28").first
+    assert_equal "Kirstinmäki 1", result.street_address
+  end
+
+  def test_okf_precision
+    result = Geocoder.search("Kirstinmäki 11b28").first
+    assert_equal "RANGE_INTERPOLATED", result.precision
+  end
+end