diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2014, Ben Gamari
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Ben Gamari nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/kd-tree.cabal b/kd-tree.cabal
new file mode 100644
--- /dev/null
+++ b/kd-tree.cabal
@@ -0,0 +1,28 @@
+name:                kd-tree
+version:             0.1.0
+synopsis:            A simple k-d tree implementation
+description:         A simple k-d tree implementation
+homepage:            http://github.com/bgamari/kd-tree
+license:             BSD3
+license-file:        LICENSE
+author:              Ben Gamari
+maintainer:          bgamari@gmail.com
+copyright:           (c) 2014 Ben Gamari <bgamari@gmail.com>
+category:            Data
+build-type:          Simple
+cabal-version:       >=1.10
+
+source-repository head
+  type:                git
+  location:            git@github.com:bgamari/kd-tree
+
+library
+  exposed-modules:     Data.KdTree
+  other-extensions:    ScopedTypeVariables
+  build-depends:       base >=4.7 && <4.8,
+                       linear >=1.10 && <1.11,
+                       lens >=4.2 && <4.3,
+                       vector >=0.10 && <0.11,
+                       vector-algorithms >=0.6 && <0.7
+  hs-source-dirs:      src
+  default-language:    Haskell2010
diff --git a/src/Data/KdTree.hs b/src/Data/KdTree.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/KdTree.hs
@@ -0,0 +1,113 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Data.KdTree
+    ( KdTree
+      -- * Construction
+    , fromVector
+      -- * Queries
+    , nearest
+    , toList
+      -- * Diagnostics
+    , isValid
+    , showKdTree
+    ) where
+
+import Prelude hiding (sort)
+import Data.List (minimumBy)
+import Data.Maybe (maybeToList)
+import Data.Ord (comparing)
+
+import Linear hiding (point)
+import Control.Lens
+import qualified Data.Vector.Generic as V
+import Data.Vector.Algorithms.Intro (sortBy)
+
+-- | The k-d tree is a data structure capable of efficiently answering
+-- nearest neighbor search queries in low-dimensional spaces. As a rule
+-- of thumb, for efficient lookups the number of points in @k@ dimensions
+-- should greatly exceed @2^k@
+data KdTree f a = KdNode { point :: !(f a)
+                         , axis  :: E f
+                         , left  :: KdTree f a
+                         , right :: KdTree f a
+                         }
+                | KdEmpty
+
+-- | Construct a @KdTree@ from a vector of points
+fromVector :: (Ord a, V.Vector v (f a)) => [E f] -> v (f a) -> KdTree f a
+fromVector basis pts = go (cycle basis) pts
+  where
+    go _ pts | V.null pts = KdEmpty
+    go (axis:rest) pts =
+      let pts' = V.modify (sortBy $ comparing (^. el axis)) pts
+          pivotIdx = V.length pts' `div` 2
+      in KdNode { point = pts' V.! pivotIdx
+                , axis  = axis
+                , left  = go rest $ V.take pivotIdx pts'
+                , right = go rest $ V.drop (pivotIdx+1) pts'
+                }
+
+quadranceTo :: (Num a, Metric f) => f a -> f a -> a
+quadranceTo a b = quadrance (a ^-^ b)
+
+-- | Find the point nearest to the given point
+nearest :: forall f a. (Ord a, Num a, Metric f)
+        => f a -> KdTree f a -> Maybe (f a)
+nearest pt tree = go tree
+  where
+    go :: KdTree f a -> Maybe (f a)
+    go KdEmpty = Nothing
+    go (KdNode nodePt axis l r)
+      | (pt ^. el axis) <= (nodePt ^. el axis) = go' nodePt axis l r
+      | otherwise                              = go' nodePt axis r l
+
+    go' :: f a   -- ^ The point of the node we are sitting at
+        -> E f   -- ^ The splitting axis of the node
+        -> KdTree f a -- ^ The subnode the query point sits in
+        -> KdTree f a -- ^ The other subnode
+        -> Maybe (f a)
+    go' nodePt axis side other =
+      let best = case go side of
+                   Nothing    -> [nodePt]
+                   Just best' -> [best', nodePt]
+          tryAdj = (pt^.el axis - nodePt^.el axis)^2 <= quadrance (pt ^-^ nodePt)
+          bestAdj = if tryAdj
+                      then maybeToList $ go other
+                      else []
+      in Just $ minimumBy (comparing $ quadranceTo pt) (best ++ bestAdj)
+
+-- | List all points in a tree
+toList :: KdTree f a -> [f a]
+toList KdEmpty = []
+toList (KdNode point _ l r) = point : (toList l ++ toList r)
+
+-- | Verify that the node is well-formed
+nodeIsValid :: Ord a => KdTree f a -> Bool
+nodeIsValid KdEmpty = True
+nodeIsValid (KdNode point axis l r) =
+       all (\p->p^.el axis <= point^.el axis) (toList l)
+    && all (\p->p^.el axis >  point^.el axis) (toList r)
+
+-- | Verify that the tree is well-formed (recursively)
+isValid :: Ord a => KdTree f a -> Bool
+isValid KdEmpty = True
+isValid node@(KdNode _ _ l r) =
+    nodeIsValid node && isValid l && isValid r
+
+onAxis :: E f -> (a -> a -> b) -> f a -> f a -> b
+onAxis (E l) f a b = f (a ^. l) (b ^. l)
+
+-- | Given names for the axes show the tree
+showKdTree :: Show (f a) => f String -> KdTree f a -> String
+showKdTree axisNames tree = unlines $ fmt 0 tree
+  where
+    --fmt :: Int -> Kdtree f a -> [String]
+    fmt depth node =
+      case node of
+        KdEmpty -> [indent "KdEmpty"]
+        (KdNode point axis l r) ->
+          [ indent $ "KdNode ("++show point++") "++show (axisNames ^. el axis) ]
+          ++ fmt (depth+2) l
+          ++ [""]
+          ++ fmt (depth+2) r
+      where indent = (replicate depth ' ' ++)
