diff --git a/geocoder.install b/geocoder.install
new file mode 100644
index 0000000..245eb97
--- /dev/null
+++ b/geocoder.install
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the Geocoder module.
+ */
+
+/**
+ * Implements hook_schema().
+ */
+function geocoder_schema() {
+  // See, e.g., block_schema() for this trick. Seems to be the best way to get a
+  // cache table definition.
+  $schema['cache_geocoder'] = drupal_get_schema_unprocessed('system', 'cache');
+  $schema['cache_geocoder']['description'] = 'Cache table for the geocoder module to store geocoded locations.';
+  return $schema;
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function geocoder_uninstall() {
+  variable_del('geocoder_settings');
+}
+
+/**
+ * Add a caching table for geocoded locations.
+ */
+function geocoder_update_7101() {
+  $schema = drupal_get_schema_unprocessed('system', 'cache');
+  $schema['description'] = 'Cache table for the geocoder module to store geocoded locations.';
+  db_create_table('cache_geocoder', $schema);
+}
diff --git a/geocoder.module b/geocoder.module
index a4ff05c..0a9f1e7 100644
--- a/geocoder.module
+++ b/geocoder.module
@@ -55,7 +55,19 @@ function geocoder($handler, $data, $options = array(), $cache_type = 2, $cache_r
   }
   
   $processor = ctools_get_plugins('geocoder', 'geocoder_handler', $handler);
-  $geometry = call_user_func($processor['callback'], $data, $options);
+  if (!$processor) {
+    return NULL;
+  }
+
+  if (!$cache_reset && $cache_type > 1) {
+    $geometry = geocoder_cache_get($cache_id, $processor);
+  }
+  if (!isset($geometry)) {
+    $geometry = call_user_func($processor['callback'], $data, $options);
+    if ($cache_type > 1) {
+      geocoder_cache_set($cache_id, $processor, $geometry);
+    }
+  }
   
   if ($cache_type) {
     $static_cache[$cache_id] = $geometry;
@@ -287,3 +299,68 @@ function geocoder_service_check_request($handler, $format, $check_ac = TRUE) {
     exit();
   }
 }
+
+// These function implement a caching layer for this module.
+
+/**
+ * Retrieve a cached geocoded location, if it exists.
+ *
+ * This uses the cache_geocoder bin by default, but this can be overridden in
+ * the processor definition. Specifying a function that circumvents this process
+ * wholly is also possible.
+ *
+ * @param $cache_id
+ *   A string hash of the location data passed to geocoder().
+ * @param $processor
+ *   The processor used to handle the caching request.
+ *
+ * @return Geometry
+ *   A Geometry object if it was found in the cache; FALSE, if the cached result
+ *   of the last geocoding request was FALSE; or NULL, if there was no result
+ *   found.
+ */
+function geocoder_cache_get($cache_id, $processor) {
+  if (isset($processor['cache_function']) && is_callable($processor['cache_function'])) {
+    return call_user_func($processor['cache_function'], 'get', $cache_id);
+  }
+  $bin = isset($processor['cache_bin']) ? $processor['cache_bin'] : 'cache_geocoder';
+  if (!$bin) {
+    return NULL;
+  }
+  $cache = cache_get($cache_id, $bin);
+  geophp_load();
+  return $cache ? unserialize($cache->data) : NULL;
+}
+
+/**
+ * Set a cached geocoded location.
+ *
+ * This uses the cache_geocoder bin by default, but this can be overridden in
+ * the processor definition. Specifying a function that circumvents this process
+ * wholly is also possible, as well as specifying the expiry time.
+ *
+ * @param $cache_id
+ *   A string hash of the location data passed to geocoder().
+ * @param $processor
+ *   The processor used to handle the caching request.
+ * @param $geometry
+ *   The result to cache.
+ */
+function geocoder_cache_set($cache_id, $processor, $geometry) {
+  if (isset($processor['cache_function']) && is_callable($processor['cache_function'])) {
+    return call_user_func($processor['cache_function'], 'set', $cache_id, $geometry);
+  }
+  $bin = isset($processor['cache_bin']) ? $processor['cache_bin'] : 'cache_geocoder';
+  if (!$bin) {
+    return FALSE;
+  }
+  $expire = isset($processor['cache_expire']) ? $processor['cache_expire'] : CACHE_PERMANENT;
+  cache_set($cache_id, $geometry ? serialize($geometry) : NULL, $bin, $expire);
+}
+
+/**
+ * Implements hook_flush_caches().
+ */
+function geocoder_flush_caches() {
+  return array('cache_geocoder');
+}
