diff --git a/app-src/Benchmarks/KDTBenchmark.hs b/app-src/Benchmarks/KDTBenchmark.hs
--- a/app-src/Benchmarks/KDTBenchmark.hs
+++ b/app-src/Benchmarks/KDTBenchmark.hs
@@ -25,9 +25,9 @@
            (DKDT.KdTree Double Point2d, [Point2d])
       f (kdt, accList) (treePt, queryPt) =
         let newKdt = DKDT.insert kdt treePt
-            nearest = DKDT.nearestNeighbor newKdt queryPt
-        in  (newKdt, nearest : accList)
-      start = (DKDT.emptyKdTreeWithDistFn pointAsList2d distSqr2d, [])
+            near = DKDT.nearest newKdt queryPt
+        in  (newKdt, near : accList)
+      start = (DKDT.emptyWithDist pointAsList2d distSqr2d, [])
   in  snd . foldl' f start
 
 -- nn implemented with optimized linear scan
@@ -76,33 +76,33 @@
 rangeOfPointKdt :: KDT.KdTree Double Point2d -> Double -> Point2d -> [Point2d]
 rangeOfPointKdt kdt w q =
   let (lowers, uppers) = pointToBounds q w
-  in  KDT.pointsInRange kdt lowers uppers
+  in  KDT.inRange kdt lowers uppers
 
 linearInterleaveBuildQuery :: [(Point2d, Point2d)] -> [Point2d]
 linearInterleaveBuildQuery =
   let f :: ([Point2d], [Point2d]) -> (Point2d, Point2d) -> ([Point2d], [Point2d])
       f (ps, accList) (structPt, queryPt) =
         let ps' = structPt : ps
-            nearest = nearestLinear ps' queryPt
-        in  (ps', nearest : accList)
+            near = nearestLinear ps' queryPt
+        in  (ps', near : accList)
   in  snd . foldl' f ([], [])
 
 main :: IO ()
 main =
   let seed = 1
       treePoints = CMR.evalRand (sequence $ repeat zeroOnePointSampler) $ pureMT seed
-      kdtN n = KDT.buildKdTreeWithDistFn pointAsList2d distSqr2d $ take n treePoints
+      kdtN n = KDT.buildWithDist pointAsList2d distSqr2d $ take n treePoints
       queryPoints = CMR.evalRand (sequence $ repeat zeroOnePointSampler) $ pureMT (seed + 1)
       buildKdtBench n = bench (show n) $ nf kdtN n
       nnKdtBench nq np =
         bench ("np-" ++ show np ++ "-nq-" ++ show nq) $
-          nf (map (KDT.nearestNeighbor (kdtN np))) (take nq queryPoints)
+          nf (map (KDT.nearest (kdtN np))) (take nq queryPoints)
       inRadKdtBench nq r np =
         bench ("np-" ++ show np ++ "-nq-" ++ show nq ++ "-r-" ++ show r) $
-          nf (map (KDT.pointsInRadius (kdtN np) r)) (take nq queryPoints)
+          nf (map (KDT.inRadius (kdtN np) r)) (take nq queryPoints)
       knnKdtBench nq k np =
         bench ("np-" ++ show np ++ "-nq-" ++ show nq ++ "-k-" ++ show k) $
-          nf (map (KDT.kNearestNeighbors (kdtN np) k)) (take nq queryPoints)
+          nf (map (KDT.kNearest (kdtN np) k)) (take nq queryPoints)
       rangeKdtBench nq w np =
         bench ("np-" ++ show np ++ "-nq-" ++ show nq ++ "-w-" ++ show w) $
           nf (map $ rangeOfPointKdt (kdtN np) w) (take nq queryPoints)
diff --git a/app-src/Tests/DynamicTest.hs b/app-src/Tests/DynamicTest.hs
new file mode 100644
--- /dev/null
+++ b/app-src/Tests/DynamicTest.hs
@@ -0,0 +1,113 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+import qualified Data.KdMap.Static as KDM
+import Data.KdMap.Dynamic
+
+import Control.Monad
+import Data.Bits
+import Data.List
+import Data.Point2d
+import System.Exit
+import Test.QuickCheck
+
+testElements :: [p] -> [(p, Int)]
+testElements ps = zip ps [1 ..]
+
+checkLogNTrees :: Real a => PointAsListFn a p -> SquaredDistanceFn a p -> [p] -> Bool
+checkLogNTrees p2l d2 ps =
+  let lengthIsLogN kdm = length (subtreeSizes kdm) == popCount (size kdm)
+  in  all lengthIsLogN $ scanl insertPair (emptyWithDist p2l d2) $ testElements ps
+
+prop_logNTrees :: [Point2d] -> Bool
+prop_logNTrees = checkLogNTrees pointAsList2d distSqr2d
+
+checkTreeSizesPowerOf2 :: Real a => PointAsListFn a p ->
+                                    SquaredDistanceFn a p ->
+                                    [p] ->
+                                    Bool
+checkTreeSizesPowerOf2 p2l d2 ps =
+  let sizesPowerOf2 = all ((== 1) . popCount) . subtreeSizes
+  in  all sizesPowerOf2 $ scanl insertPair (emptyWithDist p2l d2) $ testElements ps
+
+prop_treeSizesPowerOf2 :: [Point2d] -> Bool
+prop_treeSizesPowerOf2 = checkTreeSizesPowerOf2 pointAsList2d distSqr2d
+
+checkNumElements :: Real a => PointAsListFn a p -> SquaredDistanceFn a p -> [p] -> Bool
+checkNumElements p2l d2 ps =
+  let numsMatch (num, kdm) = size kdm == num && num == sum (subtreeSizes kdm)
+  in  all numsMatch $ zip [0..] $ scanl insertPair (emptyWithDist p2l d2) $ testElements ps
+
+prop_validNumElements :: [Point2d] -> Bool
+prop_validNumElements = checkNumElements pointAsList2d distSqr2d
+
+checkNearestEqualToBatch :: (Eq p, Real a) => PointAsListFn a p ->
+                                              SquaredDistanceFn a p ->
+                                              ([p], p) ->
+                                              Bool
+checkNearestEqualToBatch p2l d2 (ps, query) =
+  let kdt = KDM.buildWithDist p2l d2 $ testElements ps
+      kdtAnswer = KDM.nearest kdt query
+      dkdt = batchInsert (emptyWithDist p2l d2) $ testElements ps
+      dkdtAnswer = nearest dkdt query
+  in  dkdtAnswer == kdtAnswer
+
+prop_nearestEqualToBatch :: Point2d -> Property
+prop_nearestEqualToBatch query =
+  forAll (listOf1 arbitrary) $ \xs ->
+    checkNearestEqualToBatch pointAsList2d distSqr2d (xs, query)
+
+checkKNearestEqualToBatch :: (Eq p, Real a) => PointAsListFn a p ->
+                                               SquaredDistanceFn a p ->
+                                               ([p], Int, p) ->
+                                               Bool
+checkKNearestEqualToBatch p2l d2 (ps, k, query) =
+  let kdt = KDM.buildWithDist p2l d2 $ testElements ps
+      kdtAnswer = KDM.kNearest kdt k query
+      dkdt = batchInsert (emptyWithDist p2l d2) $ testElements ps
+      dkdtAnswer = kNearest dkdt k query
+  in  dkdtAnswer == kdtAnswer
+
+prop_kNearestEqualToBatch :: Point2d -> Property
+prop_kNearestEqualToBatch query =
+  forAll (listOf1 arbitrary) $ \xs ->
+    forAll (choose (1, length xs)) $ \k ->
+      checkKNearestEqualToBatch pointAsList2d distSqr2d (xs, k, query)
+
+checkInRadiusEqualToBatch :: (Ord p, Real a) => PointAsListFn a p ->
+                                            SquaredDistanceFn a p ->
+                                            ([p], a, p) ->
+                                            Bool
+checkInRadiusEqualToBatch p2l d2 (ps, radius, query) =
+  let kdt = KDM.buildWithDist p2l d2 $ testElements ps
+      kdtAnswer = KDM.inRadius kdt radius query
+      dkdt = batchInsert (emptyWithDist p2l d2) $ testElements ps
+      dkdtAnswer = inRadius dkdt radius query
+  in  sort dkdtAnswer == sort kdtAnswer
+
+prop_checkInRadiusEqualToBatch :: Point2d -> Property
+prop_checkInRadiusEqualToBatch query =
+  forAll (listOf1 arbitrary) $ \xs ->
+    forAll (choose (0.0, 1000.0)) $ \radius ->
+      checkInRadiusEqualToBatch pointAsList2d distSqr2d (xs, radius, query)
+
+prop_checkInRangeEqualToBatch :: ([Point2d], Point2d, Point2d) -> Bool
+prop_checkInRangeEqualToBatch ([], _, _) = True
+prop_checkInRangeEqualToBatch (xs, lowers, uppers)
+  | and $ zipWith (<) (pointAsList2d lowers) (pointAsList2d uppers) =
+      let kdt = KDM.buildWithDist pointAsList2d distSqr2d $ testElements xs
+          kdtAnswer = KDM.inRange kdt lowers uppers
+          dkdt = batchInsert (emptyWithDist pointAsList2d distSqr2d) $ testElements xs
+          dkdtAnswer = inRange dkdt lowers uppers
+      in  sort dkdtAnswer == sort kdtAnswer
+  | otherwise = True
+
+
+-- Run all tests
+return []
+runTests :: IO Bool
+runTests =  $quickCheckAll
+
+main :: IO ()
+main = do
+  success <- runTests
+  unless success exitFailure
diff --git a/app-src/Tests/KdTreeTest.hs b/app-src/Tests/KdTreeTest.hs
deleted file mode 100644
--- a/app-src/Tests/KdTreeTest.hs
+++ /dev/null
@@ -1,10 +0,0 @@
-import Data.KdMap.Static as KDM
-import Data.KdMap.Dynamic as DKDM
-
-import Control.Monad
-import System.Exit
-
-main :: IO ()
-main = do
-  success <- liftM2 (&&) KDM.runTests DKDM.runTests
-  unless success exitFailure
diff --git a/app-src/Tests/StaticTest.hs b/app-src/Tests/StaticTest.hs
new file mode 100644
--- /dev/null
+++ b/app-src/Tests/StaticTest.hs
@@ -0,0 +1,144 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+import Data.KdMap.Static as KDM
+
+import Control.Monad
+import Data.List
+import Data.Ord
+import Data.Point2d
+import System.Exit
+import Test.QuickCheck
+
+testElements :: [p] -> [(p, Int)]
+testElements ps = zip ps [0 ..]
+
+prop_validTree :: Property
+prop_validTree =
+  forAll (listOf1 arbitrary) $ isValid . build pointAsList2d . testElements
+
+checkElements :: (Ord p, Real a) => PointAsListFn a p -> [p] -> Bool
+checkElements pointAsList ps =
+  let kdt = build pointAsList $ testElements ps
+  in  sort (assocs kdt) == sort (testElements ps)
+
+prop_sameElements :: Property
+prop_sameElements = forAll (listOf1 arbitrary) $ checkElements pointAsList2d
+
+checkNumElements :: Real a => PointAsListFn a p -> [p] -> Bool
+checkNumElements pointAsList ps =
+  let kdm = build pointAsList $ testElements ps
+  in  size kdm == length ps
+
+prop_validNumElements :: Property
+prop_validNumElements = forAll (listOf1 arbitrary) $ checkNumElements pointAsList2d
+
+nearestLinear :: Real a => KDM.PointAsListFn a p -> [(p, v)] -> p -> (p, v)
+nearestLinear pointAsList xs query =
+  minimumBy (comparing (KDM.defaultSqrDist pointAsList query . fst)) xs
+
+checkNearestEqualToLinear :: (Eq p, Real a) => KDM.PointAsListFn a p -> ([p], p) -> Bool
+checkNearestEqualToLinear pointAsList (ps, query) =
+  let kdt = build pointAsList $ testElements ps
+  in  nearest kdt query == nearestLinear pointAsList (testElements ps) query
+
+prop_nearestEqualToLinear :: Point2d -> Property
+prop_nearestEqualToLinear query =
+  forAll (listOf1 arbitrary) $ \xs ->
+    checkNearestEqualToLinear pointAsList2d (xs, query)
+
+inRadiusLinear :: Real a => KDM.PointAsListFn a p -> [(p, v)] -> p -> a -> [(p, v)]
+inRadiusLinear pointAsList xs query radius =
+  filter ((<= radius * radius) . defaultSqrDist pointAsList query . fst) xs
+
+checkInRadiusEqualToLinear :: (Ord p, Real a) => KDM.PointAsListFn a p -> a -> ([p], p) -> Bool
+checkInRadiusEqualToLinear pointAsList radius (ps, query) =
+  let kdt = build pointAsList $ testElements ps
+      kdtNear = inRadius kdt radius query
+      linearNear = inRadiusLinear pointAsList (testElements ps) query radius
+  in  sort kdtNear == sort linearNear
+
+prop_inRadiusEqualToLinear :: Point2d -> Property
+prop_inRadiusEqualToLinear query =
+  forAll (listOf1 arbitrary) $ \xs ->
+    forAll (choose (0.0, 1000.0)) $ \radius ->
+    checkInRadiusEqualToLinear pointAsList2d radius (xs, query)
+
+kNearestLinear :: Real a => KDM.PointAsListFn a p -> [(p, v)] -> p -> Int -> [(p, v)]
+kNearestLinear pointAsList xs query k =
+  take k $ sortBy (comparing (defaultSqrDist pointAsList query . fst)) xs
+
+checkKNearestEqualToLinear :: (Ord p, Real a) => KDM.PointAsListFn a p -> Int -> ([p], p) -> Bool
+checkKNearestEqualToLinear pointAsList k (xs, query) =
+  let kdt = build pointAsList $ testElements xs
+      kdtKNear = kNearest kdt k query
+      linearKNear = kNearestLinear pointAsList (testElements xs) query k
+  in  kdtKNear == linearKNear
+
+prop_kNearestEqualToLinear :: Point2d -> Property
+prop_kNearestEqualToLinear query =
+  forAll (listOf1 arbitrary) $ \xs ->
+    forAll (choose (1, length xs)) $ \k ->
+      checkKNearestEqualToLinear pointAsList2d k (xs, query)
+
+checkKNearestSorted :: (Eq p, Real a) => KDM.PointAsListFn a p -> ([p], p) -> Bool
+checkKNearestSorted _ ([], _) = True
+checkKNearestSorted pointAsList (ps, query) =
+  let kdt = build pointAsList $ testElements ps
+      kNearestDists =
+        map (defaultSqrDist pointAsList query . fst) $ kNearest kdt (length ps) query
+  in  kNearestDists == sort kNearestDists
+
+prop_kNearestSorted :: Point2d -> Property
+prop_kNearestSorted query =
+  forAll (listOf1 arbitrary) $ \xs ->
+    checkKNearestSorted pointAsList2d (xs, query)
+
+rangeLinear :: Real a => KDM.PointAsListFn a p -> [(p, v)] -> p -> p -> [(p, v)]
+rangeLinear pointAsList xs lowers uppers =
+  let valInRange a lower upper = lower <= a && a <= upper
+      lowersAsList = pointAsList lowers
+      uppersAsList = pointAsList uppers
+      pointInRange (p, _) =
+        and $ zipWith3 valInRange (pointAsList p) lowersAsList uppersAsList
+  in  filter pointInRange xs
+
+prop_rangeEqualToLinear :: ([Point2d], Point2d, Point2d) -> Bool
+prop_rangeEqualToLinear (xs, lowers, uppers)
+  | Data.List.null xs = True
+  | and $ zipWith (<) (pointAsList2d lowers) (pointAsList2d uppers) =
+      let linear = rangeLinear pointAsList2d (testElements xs) lowers uppers
+          kdt    = build pointAsList2d $ testElements xs
+          kdtPoints = inRange kdt lowers uppers
+      in  sort linear == sort kdtPoints
+  | otherwise = True
+
+prop_equalAxisValueSameElems :: Property
+prop_equalAxisValueSameElems =
+  forAll (listOf1 arbitrary) $ \xs@(Point2d x y : _) ->
+    checkElements pointAsList2d $ Point2d x (y + 1) : xs
+
+prop_equalAxisValueEqualToLinear :: Point2d -> Property
+prop_equalAxisValueEqualToLinear query =
+  forAll (listOf1 arbitrary) $ \xs@(Point2d x y : _) ->
+    checkNearestEqualToLinear pointAsList2d (Point2d x (y + 1) : xs, query)
+
+prop_unbalancedInsertValid :: Property
+prop_unbalancedInsertValid =
+  forAll (listOf1 arbitrary) $
+    isValid . batchInsertUnbalanced (empty pointAsList2d) . testElements
+
+prop_unbalancedInsertNNEqualToLinear :: Point2d -> Property
+prop_unbalancedInsertNNEqualToLinear query =
+  forAll (listOf1 arbitrary) $ \xs ->
+    let kdm = batchInsertUnbalanced (empty pointAsList2d) $ testElements xs
+    in  nearest kdm query == nearestLinear pointAsList2d (testElements xs) query
+
+-- Run all tests
+return []
+runTests :: IO Bool
+runTests = $quickCheckAll
+
+main :: IO ()
+main = do
+  success <- runTests
+  unless success exitFailure
diff --git a/changelog.md b/changelog.md
new file mode 100644
--- /dev/null
+++ b/changelog.md
@@ -0,0 +1,6 @@
+# 0.2.0
+* Lots and lots of renaming all throughout to more closely match terminology used in `containers`.
+* Removed kdt library dependency on QuickCheck (if not building testing code).
+* Removed testing module Point2d from public API
+* All structures now have Show instance
+* Static variants now have functions for dynamically inserting new points into existing structure, with caveat that these functions do not maintain balanced tree structure.
diff --git a/kdt.cabal b/kdt.cabal
--- a/kdt.cabal
+++ b/kdt.cabal
@@ -2,7 +2,7 @@
 -- see http://haskell.org/cabal/users-guide/
 
 name:                kdt
-version:             0.1.0
+version:             0.2.0
 synopsis:            Fast and flexible k-d trees for various types of point queries.
 description:         This package includes static and dynamic versions of k-d trees,
                      as well as \"Map\" variants that store data at each point in the
@@ -19,7 +19,7 @@
 copyright:           Luis G. Torres, 2014
 category:            Data
 build-type:          Simple
--- extra-source-files:
+extra-source-files:  changelog.md
 cabal-version:       >=1.10
 source-repository head
   type: git
@@ -30,29 +30,42 @@
   exposed-modules:     Data.KdMap.Static,
                        Data.KdTree.Static,
                        Data.KdMap.Dynamic,
-                       Data.KdTree.Dynamic,
-                       Data.Point2d
+                       Data.KdTree.Dynamic
   -- other-modules:
   other-extensions:    DeriveGeneric, TemplateHaskell
   ghc-options:         -Wall -O3
   ghc-prof-options:    -Wall -O3 -fprof-auto
   build-depends:       base >=4.6 && <4.8,
                        deepseq >=1.3 && <1.4,
-                       QuickCheck >=2.7 && <2.8,
                        pqueue >=1.2.1 && <1.3,
                        deepseq-generics >=0.1.1.1
   hs-source-dirs:      lib-src
   default-language:    Haskell2010
 
-Test-Suite KdTreeTest
+Test-Suite StaticTest
   type:                 exitcode-stdio-1.0
-  main-is:              Tests/KdTreeTest.hs
+  main-is:              Tests/StaticTest.hs
   hs-source-dirs:       app-src
   ghc-options:          -Wall -O3
   build-depends:        base >=4.6 && <4.8,
-                        kdt -any
+                        kdt -any,
+                        QuickCheck >=2.7 && <2.8,
+                        deepseq >=1.3 && <1.4,
+                        deepseq-generics >=0.1.1.1
   default-language:     Haskell2010
 
+Test-Suite DynamicTest
+  type:                 exitcode-stdio-1.0
+  main-is:              Tests/DynamicTest.hs
+  hs-source-dirs:       app-src
+  ghc-options:          -Wall -O3
+  build-depends:        base >=4.6 && <4.8,
+                        kdt -any,
+                        QuickCheck >=2.7 && <2.8,
+                        deepseq >=1.3 && <1.4,
+                        deepseq-generics >=0.1.1.1
+  default-language:     Haskell2010
+
 benchmark KDTBenchmark
   type:                 exitcode-stdio-1.0
   main-is:              Benchmarks/KDTBenchmark.hs
@@ -65,5 +78,8 @@
                         MonadRandom >= 0.1.12 && <0.2,
                         mersenne-random-pure64 >=0.2.0.4 && <0.3,
                         criterion >= 1.0.0.0 && <1.1,
-                        pqueue >=1.2.1 && <1.3
+                        pqueue >=1.2.1 && <1.3,
+                        QuickCheck >=2.7 && <2.8,
+                        deepseq >=1.3 && <1.4,
+                        deepseq-generics >=0.1.1.1
   default-language:     Haskell2010
diff --git a/lib-src/Data/KdMap/Dynamic.hs b/lib-src/Data/KdMap/Dynamic.hs
--- a/lib-src/Data/KdMap/Dynamic.hs
+++ b/lib-src/Data/KdMap/Dynamic.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE DeriveGeneric, TemplateHaskell #-}
+{-# LANGUAGE DeriveGeneric #-}
 
 module Data.KdMap.Dynamic
        ( -- * Usage
@@ -12,33 +12,35 @@
        , SquaredDistanceFn
        , KdMap
          -- ** Dynamic /k/-d map construction
-       , emptyKdMap
+       , empty
        , singleton
-       , emptyKdMapWithDistFn
-       , singletonWithDistFn
+       , emptyWithDist
+       , singletonWithDist
          -- ** Insertion
        , insert
+       , insertPair
        , batchInsert
          -- ** Query
-       , nearestNeighbor
-       , pointsInRadius
-       , kNearestNeighbors
-       , pointsInRange
+       , nearest
+       , inRadius
+       , kNearest
+       , inRange
        , assocs
-       , points
-       , values
+       , keys
+       , elems
        , null
        , size
          -- ** Folds
-       , foldrKdMap
+       , foldrWithKey
          -- ** Utilities
-       , defaultDistSqrFn
-       , runTests
+       , defaultSqrDist
+         -- ** Internal (for testing)
+       , subtreeSizes
        ) where
 
 import Prelude hiding (null)
 
-import Control.Applicative
+import Control.Applicative hiding (empty)
 import Data.Bits
 import Data.Foldable
 import Data.Function
@@ -49,11 +51,9 @@
 import Control.DeepSeq
 import Control.DeepSeq.Generics (genericRnf)
 import GHC.Generics
-import Test.QuickCheck hiding ((.&.))
 
-import Data.Point2d
 import qualified Data.KdMap.Static as KDM
-import Data.KdMap.Static (PointAsListFn, SquaredDistanceFn, defaultDistSqrFn)
+import Data.KdMap.Static (PointAsListFn, SquaredDistanceFn, defaultSqrDist)
 
 -- $usage
 --
@@ -67,16 +67,16 @@
 -- 'String's:
 --
 -- @
--- >>> let dkdm = singleton point3dAsList ((Point3D 0.0 0.0 0.0), \"First\")
+-- >>> let dkdm = 'singleton' point3dAsList ((Point3D 0.0 0.0 0.0), \"First\")
 --
--- >>> let dkdm' = insert dkdm ((Point3D 1.0 1.0 1.0), \"Second\")
+-- >>> let dkdm' = 'insert' dkdm ((Point3D 1.0 1.0 1.0), \"Second\")
 --
--- >>> nearestNeighbor dkdm' (Point3D 0.4 0.4 0.4)
+-- >>> 'nearest' dkdm' (Point3D 0.4 0.4 0.4)
 -- (Point3D {x = 0.0, y = 0.0, z = 0.0}, \"First\")
 --
--- >>> let dkdm'' = insert dkdm' ((Point3D 0.5 0.5 0.5), \"Third\")
+-- >>> let dkdm'' = 'insert' dkdm' ((Point3D 0.5 0.5 0.5), \"Third\")
 --
--- >>> nearestNeighbor dkdm'' (Point3D 0.4 0.4 0.4)
+-- >>> 'nearest' dkdm'' (Point3D 0.4 0.4 0.4)
 -- (Point3D {x = 0.5, y = 0.5, z = 0.5}, \"Third\")
 -- @
 
@@ -91,23 +91,26 @@
                    } deriving Generic
 instance (NFData a, NFData p, NFData v) => NFData (KdMap a p v) where rnf = genericRnf
 
+instance (Show a, Show p, Show v) => Show (KdMap a p v) where
+  show kdm = "KdMap " ++ show (_trees kdm)
+
 instance Functor (KdMap a p) where
   fmap f dkdMap = dkdMap { _trees = map (fmap f) $ _trees dkdMap }
 
 -- | Performs a foldr over each point-value pair in the 'KdMap'.
-foldrKdMap :: ((p, v) -> b -> b) -> b -> KdMap a p v -> b
-foldrKdMap f z dkdMap = L.foldr (flip $ KDM.foldrKdMap f) z $ _trees dkdMap
+foldrWithKey :: ((p, v) -> b -> b) -> b -> KdMap a p v -> b
+foldrWithKey f z dkdMap = L.foldr (flip $ KDM.foldrWithKey f) z $ _trees dkdMap
 
 instance Foldable (KdMap a p) where
-  foldr f = foldrKdMap (f . snd)
+  foldr f = foldrWithKey (f . snd)
 
 instance Traversable (KdMap a p) where
   traverse f (KdMap t p d n) =
     KdMap <$> traverse (traverse f) t <*> pure p <*> pure d <*> pure n
 
 -- | Generates an empty 'KdMap' with a user-specified distance function.
-emptyKdMapWithDistFn :: PointAsListFn a p -> SquaredDistanceFn a p -> KdMap a p v
-emptyKdMapWithDistFn p2l d2 = KdMap [] p2l d2 0
+emptyWithDist :: PointAsListFn a p -> SquaredDistanceFn a p -> KdMap a p v
+emptyWithDist p2l d2 = KdMap [] p2l d2 0
 
 -- | Returns whether the 'KdMap' is empty.
 null :: KdMap a p v -> Bool
@@ -116,18 +119,21 @@
 
 -- | Generates a 'KdMap' with a single point-value pair using a
 -- user-specified distance function.
-singletonWithDistFn :: Real a => PointAsListFn a p -> SquaredDistanceFn a p -> (p, v) -> KdMap a p v
-singletonWithDistFn p2l d2 (k, v) =
-  KdMap [KDM.buildKdMapWithDistFn p2l d2 [(k, v)]] p2l d2 1
+singletonWithDist :: Real a => PointAsListFn a p
+                            -> SquaredDistanceFn a p
+                            -> (p, v)
+                            -> KdMap a p v
+singletonWithDist p2l d2 (k, v) =
+  KdMap [KDM.buildWithDist p2l d2 [(k, v)]] p2l d2 1
 
 -- | Generates an empty 'KdMap' with the default distance function.
-emptyKdMap :: Real a => PointAsListFn a p -> KdMap a p v
-emptyKdMap p2l = emptyKdMapWithDistFn p2l $ defaultDistSqrFn p2l
+empty :: Real a => PointAsListFn a p -> KdMap a p v
+empty p2l = emptyWithDist p2l $ defaultSqrDist p2l
 
 -- | Generates a 'KdMap' with a single point-value pair using the
 -- default distance function.
 singleton :: Real a => PointAsListFn a p -> (p, v) -> KdMap a p v
-singleton p2l = singletonWithDistFn p2l $ defaultDistSqrFn p2l
+singleton p2l = singletonWithDist p2l $ defaultSqrDist p2l
 
 -- | Adds a given point-value pair to a 'KdMap'.
 --
@@ -137,23 +143,24 @@
   let bitList = map ((1 .&.) . (n `shiftR`)) [0..]
       (onesPairs, theRestPairs) = span ((== 1) . fst) $ zip bitList trees
       ((_, ones), (_, theRest)) = (unzip onesPairs, unzip theRestPairs)
-      newTree = KDM.buildKdMapWithDistFn p2l d2  $ (k, v) : L.concatMap KDM.assocs ones
+      newTree = KDM.buildWithDist p2l d2  $ (k, v) : L.concatMap KDM.assocs ones
   in  KdMap (newTree : theRest) p2l d2 $ n + 1
 
+-- | Same as 'insert', but takes point and value as a pair.
+insertPair :: Real a => KdMap a p v -> (p, v) -> KdMap a p v
+insertPair t = uncurry (insert t)
+
 -- | Given a 'KdMap' and a query point, returns the point-value pair in
 -- the 'KdMap' with the point nearest to the query.
 --
 -- Average time complexity: /O(log^2(n))/.
-nearestNeighbor :: Real a => KdMap a p v -> p -> (p, v)
-nearestNeighbor (KdMap ts _ d2 _) query =
-  let nearests = map (`KDM.nearestNeighbor` query) ts
+nearest :: Real a => KdMap a p v -> p -> (p, v)
+nearest (KdMap ts _ d2 _) query =
+  let nearests = map (`KDM.nearest` query) ts
   in  if   Data.List.null nearests
-      then error "Called nearestNeighbor on empty KdMap."
+      then error "Called nearest on empty KdMap."
       else L.minimumBy (compare `on` (d2 query . fst)) nearests
 
-insertPair :: Real a => KdMap a p v -> (p, v) -> KdMap a p v
-insertPair t = uncurry (insert t)
-
 -- | Given a 'KdMap', a query point, and a number @k@, returns the
 -- @k@ point-value pairs with the nearest points to the query.
 --
@@ -165,9 +172,9 @@
 --
 -- Worst case time complexity: /n * log(k)/ for /k/ nearest neighbors
 -- on a structure with /n/ data points.
-kNearestNeighbors :: Real a => KdMap a p v -> Int -> p -> [(p, v)]
-kNearestNeighbors (KdMap trees _ d2 _) k query =
-  let neighborSets = map (\t -> KDM.kNearestNeighbors t k query) trees
+kNearest :: Real a => KdMap a p v -> Int -> p -> [(p, v)]
+kNearest (KdMap trees _ d2 _) k query =
+  let neighborSets = map (\t -> KDM.kNearest t k query) trees
   in  take k $ L.foldr merge [] neighborSets
  where merge [] ys = ys
        merge xs [] = xs
@@ -184,9 +191,9 @@
 -- Points are not returned in any particular order.
 --
 -- Worst case time complexity: /O(n)/ for /n/ data points.
-pointsInRadius :: Real a => KdMap a p v -> a -> p -> [(p, v)]
-pointsInRadius (KdMap trees _ _ _) radius query =
-  L.concatMap (\t -> KDM.pointsInRadius t radius query) trees
+inRadius :: Real a => KdMap a p v -> a -> p -> [(p, v)]
+inRadius (KdMap trees _ _ _) radius query =
+  L.concatMap (\t -> KDM.inRadius t radius query) trees
 
 -- | Finds all point-value pairs in a 'KdMap' with points within a
 -- given range, where the range is specified as a set of lower and
@@ -196,13 +203,13 @@
 --
 -- Worst case time complexity: /O(n)/ for n data points and a range
 -- that spans all the points.
-pointsInRange :: Real a => KdMap a p v
-                           -> p -- ^ lower bounds of range
-                           -> p -- ^ upper bounds of range
-                           -> [(p, v)] -- ^ point-value pairs within
-                                       -- given range
-pointsInRange (KdMap trees _ _ _) lowers uppers =
-  L.concatMap (\t -> KDM.pointsInRange t lowers uppers) trees
+inRange :: Real a => KdMap a p v
+                  -> p -- ^ lower bounds of range
+                  -> p -- ^ upper bounds of range
+                  -> [(p, v)] -- ^ point-value pairs within given
+                              -- range
+inRange (KdMap trees _ _ _) lowers uppers =
+  L.concatMap (\t -> KDM.inRange t lowers uppers) trees
 
 -- | Returns the number of elements in the 'KdMap'.
 --
@@ -219,118 +226,23 @@
 -- | Returns all points in the 'KdMap'.
 --
 -- Time complexity: /O(n)/ for /n/ data points.
-points :: KdMap a p v -> [p]
-points = map fst . assocs
+keys :: KdMap a p v -> [p]
+keys = map fst . assocs
 
 -- | Returns all values in the 'KdMap'.
 --
 -- Time complexity: /O(n)/ for /n/ data points.
-values :: KdMap a p v -> [v]
-values = map snd . assocs
+elems :: KdMap a p v -> [v]
+elems = map snd . assocs
 
 -- | Inserts a list of point-value pairs into the 'KdMap'.
+--
+-- TODO: This will be made far more efficient than simply repeatedly
+-- inserting.
 batchInsert :: Real a => KdMap a p v -> [(p, v)] -> KdMap a p v
--- TODO: This can be made far more efficient by batch-creating the
--- individual KdMaps before placing them into the KdMap
 batchInsert =  L.foldl' insertPair
 
---------------------------------------------------------------------------------
--- Tests
---------------------------------------------------------------------------------
-
-testElements :: [p] -> [(p, Int)]
-testElements ps = zip ps [1 ..]
-
-checkLogNTrees :: Real a => PointAsListFn a p -> SquaredDistanceFn a p -> [p] -> Bool
-checkLogNTrees p2l d2 ps =
-  let lengthIsLogN (KdMap ts _ _ n) = length ts == popCount n
-  in  L.all lengthIsLogN $ scanl insertPair (emptyKdMapWithDistFn p2l d2) $ testElements ps
-
-prop_logNTrees :: [Point2d] -> Bool
-prop_logNTrees = checkLogNTrees pointAsList2d distSqr2d
-
-checkTreeSizesPowerOf2 :: Real a => PointAsListFn a p ->
-                                    SquaredDistanceFn a p ->
-                                    [p] ->
-                                    Bool
-checkTreeSizesPowerOf2 p2l d2 ps =
-  let sizesPowerOf2 (KdMap ts _ _ _) = L.all (== 1) $ map (popCount . length . KDM.assocs) ts
-  in  L.all sizesPowerOf2 $ scanl insertPair (emptyKdMapWithDistFn p2l d2) $ testElements ps
-
-prop_treeSizesPowerOf2 :: [Point2d] -> Bool
-prop_treeSizesPowerOf2 = checkTreeSizesPowerOf2 pointAsList2d distSqr2d
-
-checkNumElements :: Real a => PointAsListFn a p -> SquaredDistanceFn a p -> [p] -> Bool
-checkNumElements p2l d2 ps =
-  let numsMatch (num, KdMap ts _ _ n) = n == num && n == L.sum (map (length . KDM.assocs) ts)
-  in  L.all numsMatch $ zip [0..] $ scanl insertPair (emptyKdMapWithDistFn p2l d2) $ testElements ps
-
-prop_validNumElements :: [Point2d] -> Bool
-prop_validNumElements = checkNumElements pointAsList2d distSqr2d
-
-checkNearestEqualToBatch :: (Eq p, Real a) => PointAsListFn a p ->
-                                              SquaredDistanceFn a p ->
-                                              ([p], p) ->
-                                              Bool
-checkNearestEqualToBatch p2l d2 (ps, query) =
-  let kdt = KDM.buildKdMapWithDistFn p2l d2 $ testElements ps
-      kdtAnswer = KDM.nearestNeighbor kdt query
-      dkdt = batchInsert (emptyKdMapWithDistFn p2l d2) $ testElements ps
-      dkdtAnswer = nearestNeighbor dkdt query
-  in  dkdtAnswer == kdtAnswer
-
-prop_nearestEqualToBatch :: Point2d -> Property
-prop_nearestEqualToBatch query =
-  forAll (listOf1 arbitrary) $ \xs ->
-    checkNearestEqualToBatch pointAsList2d distSqr2d (xs, query)
-
-checkKNearestEqualToBatch :: (Eq p, Real a) => PointAsListFn a p ->
-                                               SquaredDistanceFn a p ->
-                                               ([p], Int, p) ->
-                                               Bool
-checkKNearestEqualToBatch p2l d2 (ps, k, query) =
-  let kdt = KDM.buildKdMapWithDistFn p2l d2 $ testElements ps
-      kdtAnswer = KDM.kNearestNeighbors kdt k query
-      dkdt = batchInsert (emptyKdMapWithDistFn p2l d2) $ testElements ps
-      dkdtAnswer = kNearestNeighbors dkdt k query
-  in  dkdtAnswer == kdtAnswer
-
-prop_kNearestEqualToBatch :: Point2d -> Property
-prop_kNearestEqualToBatch query =
-  forAll (listOf1 arbitrary) $ \xs ->
-    forAll (choose (1, length xs)) $ \k ->
-      checkKNearestEqualToBatch pointAsList2d distSqr2d (xs, k, query)
-
-checkInRadiusEqualToBatch :: (Ord p, Real a) => PointAsListFn a p ->
-                                            SquaredDistanceFn a p ->
-                                            ([p], a, p) ->
-                                            Bool
-checkInRadiusEqualToBatch p2l d2 (ps, radius, query) =
-  let kdt = KDM.buildKdMapWithDistFn p2l d2 $ testElements ps
-      kdtAnswer = KDM.pointsInRadius kdt radius query
-      dkdt = batchInsert (emptyKdMapWithDistFn p2l d2) $ testElements ps
-      dkdtAnswer = pointsInRadius dkdt radius query
-  in  sort dkdtAnswer == sort kdtAnswer
-
-prop_checkInRadiusEqualToBatch :: Point2d -> Property
-prop_checkInRadiusEqualToBatch query =
-  forAll (listOf1 arbitrary) $ \xs ->
-    forAll (choose (0.0, 1000.0)) $ \radius ->
-      checkInRadiusEqualToBatch pointAsList2d distSqr2d (xs, radius, query)
-
-prop_checkInRangeEqualToBatch :: ([Point2d], Point2d, Point2d) -> Bool
-prop_checkInRangeEqualToBatch ([], _, _) = True
-prop_checkInRangeEqualToBatch (xs, lowers, uppers)
-  | L.and $ zipWith (<) (pointAsList2d lowers) (pointAsList2d uppers) =
-      let kdt = KDM.buildKdMapWithDistFn pointAsList2d distSqr2d $ testElements xs
-          kdtAnswer = KDM.pointsInRange kdt lowers uppers
-          dkdt = batchInsert (emptyKdMapWithDistFn pointAsList2d distSqr2d) $ testElements xs
-          dkdtAnswer = pointsInRange dkdt lowers uppers
-      in  sort dkdtAnswer == sort kdtAnswer
-  | otherwise = True
-
-
--- Run all tests
-return []
-runTests :: IO Bool
-runTests =  $quickCheckAll
+-- | Returns size of each internal /k/-d tree that makes up the
+-- dynamic structure. For internal testing use.
+subtreeSizes :: KdMap a p v -> [Int]
+subtreeSizes (KdMap trees _ _ _) = map KDM.size trees
diff --git a/lib-src/Data/KdMap/Static.hs b/lib-src/Data/KdMap/Static.hs
--- a/lib-src/Data/KdMap/Static.hs
+++ b/lib-src/Data/KdMap/Static.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE DeriveGeneric, TemplateHaskell #-}
+{-# LANGUAGE DeriveGeneric #-}
 
 module Data.KdMap.Static
        ( -- * Usage
@@ -12,40 +12,45 @@
        , SquaredDistanceFn
        , KdMap
          -- ** /k/-d map construction
-       , buildKdMap
-       , buildKdMapWithDistFn
+       , empty
+       , emptyWithDist
+       , singleton
+       , singletonWithDist
+       , build
+       , buildWithDist
+       , insertUnbalanced
+       , batchInsertUnbalanced
          -- ** Query
-       , nearestNeighbor
-       , pointsInRadius
-       , kNearestNeighbors
-       , pointsInRange
+       , nearest
+       , inRadius
+       , kNearest
+       , inRange
        , assocs
-       , points
-       , values
+       , keys
+       , elems
+       , null
        , size
          -- ** Folds
-       , foldrKdMap
+       , foldrWithKey
          -- ** Utilities
-       , defaultDistSqrFn
-       , runTests
+       , defaultSqrDist
+         -- ** Internal (for testing)
+       , isValid
        ) where
 
 import Control.DeepSeq
 import Control.DeepSeq.Generics (genericRnf)
 import GHC.Generics
 
-import Control.Applicative
+import Control.Applicative hiding (empty)
 import Data.Foldable
-import Data.Function
+import Prelude hiding (null)
 import qualified Data.List as L
 import Data.Maybe
 import Data.Ord
 import qualified Data.PQueue.Prio.Max as Q
 import Data.Traversable
-import Test.QuickCheck
 
-import Data.Point2d
-
 -- $usage
 --
 -- The 'KdMap' is a variant of 'Data.KdTree.Static.KdTree' where each point in
@@ -53,7 +58,7 @@
 -- we'll refer to the points and their associated data as the /points/
 -- and /values/ of the 'KdMap', respectively. It might help to think
 -- of 'Data.KdTree.Static.KdTree' and 'KdMap' as being analogous to
--- 'Data.Set' and 'Data.Map'.
+-- 'Set' and 'Map'.
 --
 -- Suppose you wanted to perform point queries on a set of 3D points,
 -- where each point is associated with a 'String'. Here's how to build
@@ -66,11 +71,11 @@
 --
 -- >>> let valueStrings = [\"First\", \"Second\"]
 --
--- >>> let pointValuePairs = zip points valueStrings
+-- >>> let pointValuePairs = 'zip' points valueStrings
 --
--- >>> let kdm = buildKdMap point3dAsList pointValuePairs
+-- >>> let kdm = 'build' point3dAsList pointValuePairs
 --
--- >>> nearestNeighbor kdm (Point3d 0.1 0.1 0.1)
+-- >>> 'nearest' kdm (Point3d 0.1 0.1 0.1)
 -- [Point3d {x = 0.0, y = 0.0, z = 0.0}, \"First\"]
 -- @
 
@@ -80,7 +85,7 @@
                                , _treeRight :: TreeNode a p v
                                } |
                       Empty
-  deriving Generic
+  deriving (Generic, Show, Read)
 instance (NFData a, NFData p, NFData v) => NFData (TreeNode a p v) where rnf = genericRnf
 
 mapTreeNode :: (v1 -> v2) -> TreeNode a p v1 -> TreeNode a p v2
@@ -106,6 +111,9 @@
                          } deriving Generic
 instance (NFData a, NFData p, NFData v) => NFData (KdMap a p v) where rnf = genericRnf
 
+instance (Show a, Show p, Show v) => Show (KdMap a p v) where
+  show (KdMap _ _ rootNode _) = "KdMap " ++ show rootNode
+
 instance Functor (KdMap a p) where
   fmap f kdMap = kdMap { _rootNode = mapTreeNode f (_rootNode kdMap) }
 
@@ -115,11 +123,11 @@
   foldrTreeNode f (f p (foldrTreeNode f z right)) left
 
 -- | Performs a foldr over each point-value pair in the 'KdMap'.
-foldrKdMap :: ((p, v) -> b -> b) -> b -> KdMap a p v -> b
-foldrKdMap f z (KdMap _ _ r _) = foldrTreeNode f z r
+foldrWithKey :: ((p, v) -> b -> b) -> b -> KdMap a p v -> b
+foldrWithKey f z (KdMap _ _ r _) = foldrTreeNode f z r
 
 instance Foldable (KdMap a p) where
-  foldr f = foldrKdMap (f . snd)
+  foldr f = foldrWithKey (f . snd)
 
 traverseTreeNode :: Applicative f => (b -> f c) -> TreeNode a p b -> f (TreeNode a p c)
 traverseTreeNode _ Empty = pure Empty
@@ -136,6 +144,35 @@
   traverse f (KdMap p d r n) =
     KdMap <$> pure p <*> pure d <*> traverseTreeNode f r <*> pure n
 
+-- | Builds an empty 'KdMap'.
+empty :: Real a => PointAsListFn a p -> KdMap a p v
+empty p2l = emptyWithDist p2l (defaultSqrDist p2l)
+
+-- | Builds an empty 'KdMap' using a user-specified squared distance
+-- function.
+emptyWithDist :: Real a => PointAsListFn a p
+                        -> SquaredDistanceFn a p
+                        -> KdMap a p v
+emptyWithDist p2l d2 = KdMap p2l d2 Empty 0
+
+-- | Returns 'True' if the given 'KdMap' is empty.
+null :: KdMap a p v -> Bool
+null kdm = _size kdm == 0
+
+-- | Builds a 'KdMap' with a single point-value pair and a
+-- user-specified squared distance function.
+singletonWithDist :: Real a => PointAsListFn a p
+                            -> SquaredDistanceFn a p
+                            -> (p, v)
+                            -> KdMap a p v
+singletonWithDist p2l d2 (p, v) =
+  let singletonTreeNode = TreeNode Empty (p, v) (head $ p2l p) Empty
+  in  KdMap p2l d2 singletonTreeNode 1
+
+-- | Builds a 'KdMap' with a single point-value pair.
+singleton :: Real a => PointAsListFn a p -> (p, v) -> KdMap a p v
+singleton p2l (p, v) = singletonWithDist p2l (defaultSqrDist p2l) (p, v)
+
 quickselect :: (b -> b -> Ordering) -> Int -> [b] -> b
 quickselect cmp = go
   where go _ [] = error "quickselect must be called on a non-empty list."
@@ -154,14 +191,12 @@
 -- Worst case time complexity: /O(n^2)/ for /n/ data points.
 --
 -- Worst case space complexity: /O(n)/ for /n/ data points.
---
--- Throws an error if given an empty list of data points.
-buildKdMapWithDistFn :: Real a => PointAsListFn a p ->
-                                  SquaredDistanceFn a p ->
-                                  [(p, v)] ->
-                                  KdMap a p v
-buildKdMapWithDistFn _ _ [] = error "KdMap must be built with a non-empty list."
-buildKdMapWithDistFn pointAsList distSqr dataPoints =
+buildWithDist :: Real a => PointAsListFn a p
+                        -> SquaredDistanceFn a p
+                        -> [(p, v)]
+                        -> KdMap a p v
+buildWithDist p2l d2 [] = emptyWithDist p2l d2
+buildWithDist pointAsList distSqr dataPoints =
   let axisValsPointsPairs = zip (map (cycle . pointAsList . fst) dataPoints) dataPoints
   in  KdMap { _pointAsList = pointAsList
             , _distSqr     = distSqr
@@ -191,25 +226,53 @@
 
 -- | A default implementation of squared distance given two points and
 -- a 'PointAsListFn'.
-defaultDistSqrFn :: Num a => PointAsListFn a p -> SquaredDistanceFn a p
-defaultDistSqrFn pointAsList k1 k2 =
+defaultSqrDist :: Num a => PointAsListFn a p -> SquaredDistanceFn a p
+defaultSqrDist pointAsList k1 k2 =
   L.sum $ map (^ (2 :: Int)) $ zipWith (-) (pointAsList k1) (pointAsList k2)
 
 -- | Builds a 'KdTree' from a list of pairs of points (of type p) and
 -- values (of type v) using a default squared distance function
--- 'defaultDistSqrFn'.
+-- 'defaultSqrDist'.
 --
 -- Average complexity: /O(n * log(n))/ for /n/ data points.
 --
 -- Worst case time complexity: /O(n^2)/ for /n/ data points.
 --
 -- Worst case space complexity: /O(n)/ for /n/ data points.
+build :: Real a => PointAsListFn a p -> [(p, v)] -> KdMap a p v
+build pointAsList =
+  buildWithDist pointAsList $ defaultSqrDist pointAsList
+
+-- | Inserts a point-value pair into a 'KdMap'. This can potentially
+-- cause the internal tree structure to become unbalanced. If the tree
+-- becomes too unbalanced, point queries will be very inefficient. If
+-- you need to perform lots of point insertions on an already existing
+-- /k/-d map, check out
+-- @Data.KdMap.Dynamic.@'Data.KdMap.Dynamic.KdMap'.
 --
--- Throws an error if given an empty list of data points.
-buildKdMap :: Real a => PointAsListFn a p -> [(p, v)] -> KdMap a p v
-buildKdMap pointAsList =
-  buildKdMapWithDistFn pointAsList $ defaultDistSqrFn pointAsList
+-- Average complexity: /O(log(n))/ for /n/ data points.
+--
+-- Worst case time complexity: /O(n)/ for /n/ data points.
+insertUnbalanced :: Real a => KdMap a p v -> p -> v -> KdMap a p v
+insertUnbalanced kdm@(KdMap pointAsList _ rootNode n) p' v' =
+  kdm { _rootNode = go rootNode (cycle $ pointAsList p'), _size = n + 1 }
+  where
+    go _ [] = error "insertUnbalanced.go: no empty lists allowed!"
+    go Empty (axisValue' : _) = TreeNode Empty (p', v') axisValue' Empty
+    go t@(TreeNode left _ nodeAxisValue right) (axisValue' : nextAxisValues)
+      | axisValue' <= nodeAxisValue = t { _treeLeft = go left nextAxisValues }
+      | otherwise = t { _treeRight = go right nextAxisValues }
 
+-- | Inserts a list of point-value pairs into a 'KdMap'. This can
+-- potentially cause the internal tree structure to become unbalanced,
+-- which leads to inefficient point queries.
+--
+-- Average complexity: /O(n * log(n))/ for /n/ data points.
+--
+-- Worst case time complexity: /O(n^2)/ for /n/ data points.
+batchInsertUnbalanced :: Real a => KdMap a p v -> [(p, v)] -> KdMap a p v
+batchInsertUnbalanced = foldl' $ \kdm (p, v) -> insertUnbalanced kdm p v
+
 assocsInternal :: TreeNode a p v -> [(p, v)]
 assocsInternal t = go t []
   where go Empty = id
@@ -224,14 +287,14 @@
 -- | Returns all points in the 'KdMap'.
 --
 -- Time complexity: /O(n)/ for /n/ data points.
-points :: KdMap a p v -> [p]
-points = map fst . assocs
+keys :: KdMap a p v -> [p]
+keys = map fst . assocs
 
 -- | Returns all values in the 'KdMap'.
 --
 -- Time complexity: /O(n)/ for /n/ data points.
-values :: KdMap a p v -> [v]
-values = map snd . assocs
+elems :: KdMap a p v -> [v]
+elems = map snd . assocs
 
 -- | Given a 'KdMap' and a query point, returns the point-value pair
 -- in the 'KdMap' with the point nearest to the query.
@@ -239,15 +302,17 @@
 -- Average time complexity: /O(log(n))/ for /n/ data points.
 --
 -- Worst case time complexity: /O(n)/ for /n/ data points.
-nearestNeighbor :: Real a => KdMap a p v -> p -> (p, v)
-nearestNeighbor (KdMap _ _ Empty _) _ =
-  error "nearestNeighbor: why is there an empty KdMap?"
-nearestNeighbor (KdMap pointAsList distSqr t@(TreeNode _ root _ _) _) query =
+--
+-- Throws error if called on an empty 'KdMap'.
+nearest :: Real a => KdMap a p v -> p -> (p, v)
+nearest (KdMap _ _ Empty _) _ =
+  error "Attempted to call nearest on an empty KdMap."
+nearest (KdMap pointAsList distSqr t@(TreeNode _ root _ _) _) query =
   -- This is an ugly way to kickstart the function but it's faster
   -- than using a Maybe.
   fst $ go (root, distSqr (fst root) query) (cycle $ pointAsList query) t
   where
-    go _ [] _ = error "nearestNeighbor.go: no empty lists allowed!"
+    go _ [] _ = error "nearest.go: no empty lists allowed!"
     go bestSoFar _ Empty = bestSoFar
     go bestSoFar
        (queryAxisValue : qvs)
@@ -276,16 +341,15 @@
 --
 -- Worst case time complexity: /O(n)/ for /n/ data points and a radius
 -- that spans all points in the structure.
-pointsInRadius :: Real a => KdMap a p v
-                            -> a -- ^ radius
-                            -> p -- ^ query point
-                            -> [(p, v)] -- ^ list of point-value pairs
-                                        -- with points within given
-                                        -- radius of query
-pointsInRadius (KdMap pointAsList distSqr t _) radius query =
+inRadius :: Real a => KdMap a p v
+                   -> a -- ^ radius
+                   -> p -- ^ query point
+                   -> [(p, v)] -- ^ list of point-value pairs with
+                               -- points within given radius of query
+inRadius (KdMap pointAsList distSqr t _) radius query =
   go (cycle $ pointAsList query) t []
   where
-    go [] _ _ = error "pointsInRadius.go: no empty lists allowed!"
+    go [] _ _ = error "inRadius.go: no empty lists allowed!"
     go _ Empty acc = acc
     go (queryAxisValue : qvs) (TreeNode left (k, v) nodeAxisVal right) acc =
       let onTheLeft = queryAxisValue <= nodeAxisVal
@@ -313,12 +377,12 @@
 --
 -- Worst case time complexity: /n * log(k)/ for /k/ nearest
 -- neighbors on a structure with /n/ data points.
-kNearestNeighbors :: Real a => KdMap a p v -> Int -> p -> [(p, v)]
-kNearestNeighbors (KdMap pointAsList distSqr t _) numNeighbors query =
+kNearest :: Real a => KdMap a p v -> Int -> p -> [(p, v)]
+kNearest (KdMap pointAsList distSqr t _) numNeighbors query =
   reverse $ map snd $ Q.toList $ go (cycle $ pointAsList query) Q.empty t
   where
     -- go :: [Double] -> Q.MaxPQueue Double (p, d) -> TreeNode p d -> KQueue p d
-    go [] _ _ = error "kNearestNeighbors.go: no empty lists allowed!"
+    go [] _ _ = error "kNearest.go: no empty lists allowed!"
     go _ q Empty = q
     go (queryAxisValue : qvs) q (TreeNode left (k, v) nodeAxisVal right) =
       let insertBounded queue dist x
@@ -327,7 +391,7 @@
                           then Q.insert dist x $ Q.deleteMax queue
                           else queue
           q' = insertBounded q (distSqr k query) (k, v)
-          kNearest queue onsideSubtree offsideSubtree =
+          kNear queue onsideSubtree offsideSubtree =
             let queue' = go qvs queue onsideSubtree
                 checkOffsideTree =
                   Q.size queue' < numNeighbors ||
@@ -336,8 +400,8 @@
                 then go qvs queue' offsideSubtree
                 else queue'
       in  if queryAxisValue <= nodeAxisVal
-          then kNearest q' left right
-          else kNearest q' right left
+          then kNear q' left right
+          else kNear q' right left
 
 -- | Finds all point-value pairs in a 'KdMap' with points within a
 -- given range, where the range is specified as a set of lower and
@@ -351,15 +415,15 @@
 -- TODO: Maybe use known bounds on entire tree structure to be able to
 -- automatically count whole portions of tree as being within given
 -- range.
-pointsInRange :: Real a => KdMap a p v
-                           -> p -- ^ lower bounds of range
-                           -> p -- ^ upper bounds of range
-                           -> [(p, v)] -- ^ point-value pairs within
-                                       -- given range
-pointsInRange (KdMap pointAsList _ t _) lowers uppers =
+inRange :: Real a => KdMap a p v
+                  -> p -- ^ lower bounds of range
+                  -> p -- ^ upper bounds of range
+                  -> [(p, v)] -- ^ point-value pairs within given
+                              -- range
+inRange (KdMap pointAsList _ t _) lowers uppers =
   go (cycle (pointAsList lowers) `zip` cycle (pointAsList uppers)) t []
   where
-    go [] _ _ = error "neighborsInRange.go: no empty lists allowed!"
+    go [] _ _ = error "inRange.go: no empty lists allowed!"
     go _ Empty acc = acc
     go ((lower, upper) : nextBounds) (TreeNode left p nodeAxisVal right) acc =
       let accAfterLeft = if lower <= nodeAxisVal
@@ -386,138 +450,17 @@
 size :: KdMap a p v -> Int
 size (KdMap _ _ _ n) = n
 
---------------------------------------------------------------------------------
--- Tests
---------------------------------------------------------------------------------
-
-testElements :: [p] -> [(p, Int)]
-testElements ps = zip ps [0 ..]
-
-isTreeValid :: Real a => PointAsListFn a p -> Int -> TreeNode a p v -> Bool
-isTreeValid _ _ Empty = True
-isTreeValid pointAsList axis (TreeNode l (k, _) nodeAxisVal r) =
+isTreeNodeValid :: Real a => PointAsListFn a p -> Int -> TreeNode a p v -> Bool
+isTreeNodeValid _ _ Empty = True
+isTreeNodeValid pointAsList axis (TreeNode l (k, _) nodeAxisVal r) =
   let childrenAxisValues = map ((!! axis) . pointAsList . fst) . assocsInternal
       leftSubtreeLess = L.all (<= nodeAxisVal) $ childrenAxisValues l
       rightSubtreeGreater = L.all (> nodeAxisVal) $ childrenAxisValues r
       nextAxis = (axis + 1) `mod` length (pointAsList k)
   in  leftSubtreeLess && rightSubtreeGreater &&
-      isTreeValid pointAsList nextAxis l && isTreeValid pointAsList nextAxis r
-
-checkValidTree :: Real a => PointAsListFn a p -> [p] -> Bool
-checkValidTree pointAsList ps =
-  let (KdMap _ _ r _) = buildKdMap pointAsList $ testElements ps
-  in  isTreeValid pointAsList 0 r
-
-prop_validTree :: Property
-prop_validTree = forAll (listOf1 arbitrary) $ checkValidTree pointAsList2d
-
-checkElements :: (Ord p, Real a) => PointAsListFn a p -> [p] -> Bool
-checkElements pointAsList ps =
-  let kdt = buildKdMap pointAsList $ testElements ps
-  in  L.sort (assocs kdt) == L.sort (testElements ps)
-
-prop_sameElements :: Property
-prop_sameElements = forAll (listOf1 arbitrary) $ checkElements pointAsList2d
-
-checkNumElements :: Real a => PointAsListFn a p -> [p] -> Bool
-checkNumElements pointAsList ps =
-  let (KdMap _ _ _ n) = buildKdMap pointAsList $ testElements ps
-  in  n == length ps
-
-prop_validNumElements :: Property
-prop_validNumElements = forAll (listOf1 arbitrary) $ checkNumElements pointAsList2d
-
-nearestNeighborLinear :: Real a => PointAsListFn a p -> [(p, v)] -> p -> (p, v)
-nearestNeighborLinear pointAsList xs query =
-  L.minimumBy (compare `on` (defaultDistSqrFn pointAsList query . fst)) xs
-
-checkNearestEqualToLinear :: (Eq p, Real a) => PointAsListFn a p -> ([p], p) -> Bool
-checkNearestEqualToLinear pointAsList (ps, query) =
-  let kdt = buildKdMap pointAsList $ testElements ps
-  in  nearestNeighbor kdt query == nearestNeighborLinear pointAsList (testElements ps) query
-
-prop_nearestEqualToLinear :: Point2d -> Property
-prop_nearestEqualToLinear query =
-  forAll (listOf1 arbitrary) $ \xs ->
-    checkNearestEqualToLinear pointAsList2d (xs, query)
-
-pointsInRadiusLinear :: Real a => PointAsListFn a p -> [(p, v)] -> p -> a -> [(p, v)]
-pointsInRadiusLinear pointAsList xs query radius =
-  filter ((<= radius * radius) . defaultDistSqrFn pointAsList query . fst) xs
-
-checkInRadiusEqualToLinear :: (Ord p, Real a) => PointAsListFn a p -> a -> ([p], p) -> Bool
-checkInRadiusEqualToLinear pointAsList radius (ps, query) =
-  let kdt = buildKdMap pointAsList $ testElements ps
-      kdtNear = pointsInRadius kdt radius query
-      linearNear = pointsInRadiusLinear pointAsList (testElements ps) query radius
-  in  L.sort kdtNear == L.sort linearNear
-
-prop_inRadiusEqualToLinear :: Point2d -> Property
-prop_inRadiusEqualToLinear query =
-  forAll (listOf1 arbitrary) $ \xs ->
-    forAll (choose (0.0, 1000.0)) $ \radius ->
-    checkInRadiusEqualToLinear pointAsList2d radius (xs, query)
-
-kNearestNeighborsLinear :: Real a => PointAsListFn a p -> [(p, v)] -> p -> Int -> [(p, v)]
-kNearestNeighborsLinear pointAsList xs query k =
-  take k $ L.sortBy (compare `on` (defaultDistSqrFn pointAsList query . fst)) xs
-
-checkKNearestEqualToLinear :: (Ord p, Real a) => PointAsListFn a p -> Int -> ([p], p) -> Bool
-checkKNearestEqualToLinear pointAsList k (xs, query) =
-  let kdt = buildKdMap pointAsList $ testElements xs
-      kdtKNear = kNearestNeighbors kdt k query
-      linearKNear = kNearestNeighborsLinear pointAsList (testElements xs) query k
-  in  kdtKNear == linearKNear
-
-prop_kNearestEqualToLinear :: Point2d -> Property
-prop_kNearestEqualToLinear query =
-  forAll (listOf1 arbitrary) $ \xs ->
-    forAll (choose (1, length xs)) $ \k ->
-      checkKNearestEqualToLinear pointAsList2d k (xs, query)
-
-checkKNearestSorted :: (Eq p, Real a) => PointAsListFn a p -> ([p], p) -> Bool
-checkKNearestSorted _ ([], _) = True
-checkKNearestSorted pointAsList (ps, query) =
-  let kdt = buildKdMap pointAsList $ testElements ps
-      kNearestDists =
-        map (defaultDistSqrFn pointAsList query . fst) $ kNearestNeighbors kdt (length ps) query
-  in  kNearestDists == L.sort kNearestDists
-
-prop_kNearestSorted :: Point2d -> Property
-prop_kNearestSorted query =
-  forAll (listOf1 arbitrary) $ \xs ->
-    checkKNearestSorted pointAsList2d (xs, query)
-
-rangeLinear :: Real a => PointAsListFn a p -> [(p, v)] -> p -> p -> [(p, v)]
-rangeLinear pointAsList xs lowers uppers =
-  let valInRange a lower upper = lower <= a && a <= upper
-      lowersAsList = pointAsList lowers
-      uppersAsList = pointAsList uppers
-      pointInRange (p, _) =
-        L.and $ zipWith3 valInRange (pointAsList p) lowersAsList uppersAsList
-  in  filter pointInRange xs
-
-prop_rangeEqualToLinear :: ([Point2d], Point2d, Point2d) -> Bool
-prop_rangeEqualToLinear (xs, lowers, uppers)
-  | null xs = True
-  | L.and $ zipWith (<) (pointAsList2d lowers) (pointAsList2d uppers) =
-      let linear = rangeLinear pointAsList2d (testElements xs) lowers uppers
-          kdt    = buildKdMap pointAsList2d $ testElements xs
-          kdtPoints = pointsInRange kdt lowers uppers
-      in  L.sort linear == L.sort kdtPoints
-  | otherwise = True
-
-prop_equalAxisValueSameElems :: Property
-prop_equalAxisValueSameElems =
-  forAll (listOf1 arbitrary) $ \xs@(Point2d x y : _) ->
-    checkElements pointAsList2d $ Point2d x (y + 1) : xs
-
-prop_equalAxisValueEqualToLinear :: Point2d -> Property
-prop_equalAxisValueEqualToLinear query =
-  forAll (listOf1 arbitrary) $ \xs@(Point2d x y : _) ->
-    checkNearestEqualToLinear pointAsList2d (Point2d x (y + 1) : xs, query)
+      isTreeNodeValid pointAsList nextAxis l && isTreeNodeValid pointAsList nextAxis r
 
--- Run all tests
-return []
-runTests :: IO Bool
-runTests = $quickCheckAll
+-- | Returns 'True' if tree structure adheres to k-d tree
+-- properties. For internal testing use.
+isValid :: Real a => KdMap a p v -> Bool
+isValid (KdMap pointAsList _ r _) = isTreeNodeValid pointAsList 0 r
diff --git a/lib-src/Data/KdTree/Dynamic.hs b/lib-src/Data/KdTree/Dynamic.hs
--- a/lib-src/Data/KdTree/Dynamic.hs
+++ b/lib-src/Data/KdTree/Dynamic.hs
@@ -10,30 +10,30 @@
        , SquaredDistanceFn
        , KdTree
          -- ** Dynamic /k/-d tree construction
-       , emptyKdTree
+       , empty
        , singleton
-       , emptyKdTreeWithDistFn
-       , singletonWithDistFn
+       , emptyWithDist
+       , singletonWithDist
          -- ** Insertion
        , insert
          -- ** Query
-       , nearestNeighbor
-       , pointsInRadius
-       , kNearestNeighbors
-       , pointsInRange
-       , points
+       , nearest
+       , inRadius
+       , kNearest
+       , inRange
+       , toList
        , null
        , size
          -- ** Utilities
-       , defaultDistSqrFn
+       , defaultSqrDist
        ) where
 
 import Prelude hiding (null)
 
-import Data.Foldable
+import qualified Data.Foldable as F
 
 import qualified Data.KdMap.Dynamic as DKDM
-import Data.KdMap.Dynamic (PointAsListFn, SquaredDistanceFn, defaultDistSqrFn)
+import Data.KdMap.Dynamic (PointAsListFn, SquaredDistanceFn, defaultSqrDist)
 
 -- $usage
 --
@@ -47,36 +47,39 @@
 -- queries using 'KdTree':
 --
 -- @
--- >>> let dkdt = singleton point3dAsList (Point3D 0.0 0.0 0.0)
+-- >>> let dkdt = 'singleton' point3dAsList (Point3D 0.0 0.0 0.0)
 --
--- >>> let dkdt' = insert dkdt (Point3D 1.0 1.0 1.0)
+-- >>> let dkdt' = 'insert' dkdt (Point3D 1.0 1.0 1.0)
 --
--- >>> nearestNeighbor dkdt' (Point3D 0.4 0.4 0.4)
+-- >>> 'nearest' dkdt' (Point3D 0.4 0.4 0.4)
 -- Point3D {x = 0.0, y = 0.0, z = 0.0}
 --
--- >>> let dkdt'' = insert dkdt' (Point3D 0.5 0.5 0.5)
+-- >>> let dkdt'' = 'insert' dkdt' (Point3D 0.5 0.5 0.5)
 --
--- >>> nearestNeighbor dkdt'' (Point3D 0.4 0.4 0.4)
+-- >>> 'nearest' dkdt'' (Point3D 0.4 0.4 0.4)
 -- Point3D {x = 0.5, y = 0.5, z = 0.5}
 -- @
 --
--- Check out @Data.KdMap.Dynamic.@'Data.KdMap.Dynamic.KdMap' if you want to associate a value
--- with each point in your tree structure.
+-- Check out @Data.KdMap.Dynamic.@'Data.KdMap.Dynamic.KdMap' if you
+-- want to associate a value with each point in your tree structure.
 
 -- | A dynamic /k/-d tree structure that stores points of type @p@
 -- with axis values of type @a@.
 newtype KdTree a p = KdTree (DKDM.KdMap a p ())
 
-instance Foldable (KdTree a) where
-  foldr f z (KdTree dkdMap) = DKDM.foldrKdMap (f . fst) z dkdMap
+instance F.Foldable (KdTree a) where
+  foldr f z (KdTree dkdMap) = DKDM.foldrWithKey (f . fst) z dkdMap
 
+instance (Show a, Show p) => Show (KdTree a p) where
+  show (KdTree kdm) = "KdTree " ++ show kdm
+
 -- | Generates an empty 'KdTree' with a user-specified distance function.
-emptyKdTreeWithDistFn :: Real a => PointAsListFn a p -> SquaredDistanceFn a p -> KdTree a p
-emptyKdTreeWithDistFn p2l d2 = KdTree $ DKDM.emptyKdMapWithDistFn p2l d2
+emptyWithDist :: Real a => PointAsListFn a p -> SquaredDistanceFn a p -> KdTree a p
+emptyWithDist p2l d2 = KdTree $ DKDM.emptyWithDist p2l d2
 
 -- | Generates an empty 'KdTree' with the default distance function.
-emptyKdTree :: Real a => PointAsListFn a p -> KdTree a p
-emptyKdTree p2l = emptyKdTreeWithDistFn p2l $ defaultDistSqrFn p2l
+empty :: Real a => PointAsListFn a p -> KdTree a p
+empty p2l = emptyWithDist p2l $ defaultSqrDist p2l
 
 -- | Returns whether the 'KdTree' is empty.
 null :: KdTree a p -> Bool
@@ -84,13 +87,16 @@
 
 -- | Generates a 'KdTree' with a single point using a
 -- user-specified distance function.
-singletonWithDistFn :: Real a => PointAsListFn a p -> SquaredDistanceFn a p -> p -> KdTree a p
-singletonWithDistFn p2l d2 p = KdTree $ DKDM.singletonWithDistFn p2l d2 (p, ())
+singletonWithDist :: Real a => PointAsListFn a p
+                            -> SquaredDistanceFn a p
+                            -> p
+                            -> KdTree a p
+singletonWithDist p2l d2 p = KdTree $ DKDM.singletonWithDist p2l d2 (p, ())
 
 -- | Generates a 'KdTree' with a single point using the default
 -- distance function.
 singleton :: Real a => PointAsListFn a p -> p -> KdTree a p
-singleton p2l = singletonWithDistFn p2l $ defaultDistSqrFn p2l
+singleton p2l = singletonWithDist p2l $ defaultSqrDist p2l
 
 -- | Adds a given point to a 'KdTree'.
 --
@@ -102,8 +108,8 @@
 -- in the 'KdTree' to the query point.
 --
 -- Average time complexity: /O(log^2(n))/.
-nearestNeighbor :: Real a => KdTree a p -> p -> p
-nearestNeighbor (KdTree dkdMap) = fst . DKDM.nearestNeighbor dkdMap
+nearest :: Real a => KdTree a p -> p -> p
+nearest (KdTree dkdMap) = fst . DKDM.nearest dkdMap
 
 -- | Given a 'KdTree', a query point, and a number @k@, returns the
 -- @k@ nearest points in the 'KdTree' to the query point.
@@ -116,9 +122,9 @@
 --
 -- Worst case time complexity: /n * log(k)/ for /k/ nearest neighbors
 -- on a structure with /n/ data points.
-kNearestNeighbors :: Real a => KdTree a p -> Int -> p -> [p]
-kNearestNeighbors (KdTree dkdMap) k query =
-  map fst $ DKDM.kNearestNeighbors dkdMap k query
+kNearest :: Real a => KdTree a p -> Int -> p -> [p]
+kNearest (KdTree dkdMap) k query =
+  map fst $ DKDM.kNearest dkdMap k query
 
 -- | Given a 'KdTree', a query point, and a radius, returns all
 -- points in the 'KdTree' that are within the given radius of the
@@ -127,9 +133,9 @@
 -- Points are not returned in any particular order.
 --
 -- Worst case time complexity: /O(n)/ for /n/ data points.
-pointsInRadius :: Real a => KdTree a p -> a -> p -> [p]
-pointsInRadius (KdTree dkdMap) radius query =
-  map fst $ DKDM.pointsInRadius dkdMap radius query
+inRadius :: Real a => KdTree a p -> a -> p -> [p]
+inRadius (KdTree dkdMap) radius query =
+  map fst $ DKDM.inRadius dkdMap radius query
 
 -- | Finds all points in a 'KdTree' with points within a given range,
 -- where the range is specified as a set of lower and upper bounds.
@@ -138,12 +144,12 @@
 --
 -- Worst case time complexity: /O(n)/ for n data points and a range
 -- that spans all the points.
-pointsInRange :: Real a => KdTree a p
-                           -> p -- ^ lower bounds of range
-                           -> p -- ^ upper bounds of range
-                           -> [p] -- ^ all points within given range
-pointsInRange (KdTree dkdMap) lowers uppers =
-  map fst $ DKDM.pointsInRange dkdMap lowers uppers
+inRange :: Real a => KdTree a p
+                     -> p -- ^ lower bounds of range
+                     -> p -- ^ upper bounds of range
+                     -> [p] -- ^ all points within given range
+inRange (KdTree dkdMap) lowers uppers =
+  map fst $ DKDM.inRange dkdMap lowers uppers
 
 -- | Returns the number of elements in the 'KdTree'.
 --
@@ -154,5 +160,5 @@
 -- | Returns a list of all the points in the 'KdTree'.
 --
 -- Time complexity: /O(n)/
-points :: KdTree a p -> [p]
-points (KdTree dkdMap) = DKDM.points dkdMap
+toList :: KdTree a p -> [p]
+toList (KdTree dkdMap) = DKDM.keys dkdMap
diff --git a/lib-src/Data/KdTree/Static.hs b/lib-src/Data/KdTree/Static.hs
--- a/lib-src/Data/KdTree/Static.hs
+++ b/lib-src/Data/KdTree/Static.hs
@@ -36,27 +36,35 @@
        , SquaredDistanceFn
        , KdTree
          -- ** /k/-d tree construction
-       , buildKdTree
-       , buildKdTreeWithDistFn
+       , empty
+       , emptyWithDist
+       , singleton
+       , singletonWithDist
+       , build
+       , buildWithDist
+       , insertUnbalanced
+       , batchInsertUnbalanced
          -- ** Query
-       , nearestNeighbor
-       , pointsInRadius
-       , kNearestNeighbors
-       , points
-       , pointsInRange
+       , nearest
+       , inRadius
+       , kNearest
+       , inRange
+       , toList
+       , null
        , size
          -- ** Utilities
-       , defaultDistSqrFn
+       , defaultSqrDist
        ) where
 
 import Control.DeepSeq
 import Control.DeepSeq.Generics (genericRnf)
 import GHC.Generics
 
-import Data.Foldable
+import qualified Data.Foldable as F
+import Prelude hiding (null)
 
 import qualified Data.KdMap.Static as KDM
-import Data.KdMap.Static (PointAsListFn, SquaredDistanceFn, defaultDistSqrFn)
+import Data.KdMap.Static (PointAsListFn, SquaredDistanceFn, defaultSqrDist)
 
 -- $intro
 --
@@ -100,11 +108,11 @@
 -- @
 -- >>> let dataPoints = [(Point3d 0.0 0.0 0.0), (Point3d 1.0 1.0 1.0)]
 --
--- >>> let kdt = 'buildKdTree' point3dAsList dataPoints
+-- >>> let kdt = 'build' point3dAsList dataPoints
 --
 -- >>> let queryPoint = Point3d 0.1 0.1 0.1
 --
--- >>> 'nearestNeighbor' kdt queryPoint
+-- >>> 'nearest' kdt queryPoint
 -- Point3d {x = 0.0, y = 0.0, z = 0.0}
 -- @
 
@@ -128,7 +136,7 @@
 --
 -- You may have noticed in the previous use case that we never
 -- specified what "nearest" means for our points. By default,
--- 'buildKdTree' uses a Euclidean distance function that is sufficient
+-- 'build' uses a Euclidean distance function that is sufficient
 -- in most cases. However, point queries are typically faster on a
 -- 'KdTree' built with a user-specified custom distance
 -- function. Let's generate a 'KdTree' using a custom distance
@@ -156,7 +164,7 @@
 -- We can build a 'KdTree' using our custom distance function as follows:
 --
 -- @
--- >>> let kdt = 'buildKdTreeWithDistFn' point3dAsList point3dSquaredDistance points
+-- >>> let kdt = 'buildWithDist' point3dAsList point3dSquaredDistance points
 -- @
 
 -- $axisvaluetypes
@@ -173,7 +181,7 @@
 -- point2iAsList (Point2i x y) = [x, y]
 --
 -- kdt :: [Point2i] -> KdTree Int Point2i
--- kdt dataPoints = buildKdTree point2iAsList dataPoints
+-- kdt dataPoints = 'build' point2iAsList dataPoints
 -- @
 
 -- | A /k/-d tree structure that stores points of type @p@ with axis
@@ -181,25 +189,51 @@
 newtype KdTree a p = KdTree (KDM.KdMap a p ()) deriving Generic
 instance (NFData a, NFData p) => NFData (KdTree a p) where rnf = genericRnf
 
-instance Foldable (KdTree a) where
-  foldr f z (KdTree kdMap) = KDM.foldrKdMap (f . fst) z kdMap
+instance (Show a, Show p) => Show (KdTree a p) where
+  show (KdTree kdm) = "KdTree " ++ show kdm
 
+instance F.Foldable (KdTree a) where
+  foldr f z (KdTree kdMap) = KDM.foldrWithKey (f . fst) z kdMap
+
+-- | Builds an empty 'KdTree'.
+empty :: Real a => PointAsListFn a p -> KdTree a p
+empty = KdTree . KDM.empty
+
+-- | Builds an empty 'KdTree' using a user-specified squared distance
+-- function.
+emptyWithDist :: Real a => PointAsListFn a p
+                        -> SquaredDistanceFn a p
+                        -> KdTree a p
+emptyWithDist p2l d2 = KdTree $ KDM.emptyWithDist p2l d2
+
+-- | Builds a 'KdTree' with a single point.
+singleton :: Real a => PointAsListFn a p -> p -> KdTree a p
+singleton p2l p = KdTree $ KDM.singleton p2l (p, ())
+
+-- | Builds a 'KdTree' with a single point using a user-specified
+-- squared distance function.
+singletonWithDist :: Real a => PointAsListFn a p
+                            -> SquaredDistanceFn a p
+                            -> p
+                            -> KdTree a p
+singletonWithDist p2l d2 p = KdTree $ KDM.singletonWithDist p2l d2 (p, ())
+
+null :: KdTree a p -> Bool
+null (KdTree kdm) = KDM.null kdm
+
 -- | Builds a 'KdTree' from a list of data points using a default
--- squared distance function 'defaultDistSqrFn'.
+-- squared distance function 'defaultSqrDist'.
 --
 -- Average complexity: /O(n * log(n))/ for /n/ data points.
 --
 -- Worst case time complexity: /O(n^2)/ for /n/ data points.
 --
 -- Worst case space complexity: /O(n)/ for /n/ data points.
---
--- Throws an error if given an empty list of data points.
-buildKdTree :: Real a => PointAsListFn a p
-                         -> [p] -- ^ non-empty list of data points to be stored in the /k/-d tree
-                         -> KdTree a p
-buildKdTree _ [] = error "KdTree must be built with a non-empty list."
-buildKdTree pointAsList ps =
-  KdTree $ KDM.buildKdMap pointAsList $ zip ps $ repeat ()
+build :: Real a => PointAsListFn a p
+                   -> [p] -- ^ non-empty list of data points to be stored in the /k/-d tree
+                   -> KdTree a p
+build pointAsList ps =
+  KdTree $ KDM.build pointAsList $ zip ps $ repeat ()
 
 -- | Builds a 'KdTree' from a list of data points using a
 -- user-specified squared distance function.
@@ -209,24 +243,49 @@
 -- Worst case time complexity: /O(n^2)/ for /n/ data points.
 --
 -- Worst case space complexity: /O(n)/ for /n/ data points.
+buildWithDist :: Real a => PointAsListFn a p
+                        -> SquaredDistanceFn a p
+                        -> [p]
+                        -> KdTree a p
+buildWithDist pointAsList distSqr ps =
+  KdTree $ KDM.buildWithDist pointAsList distSqr $ zip ps $ repeat ()
+
+-- | Inserts a point into a 'KdTree'. This can potentially
+-- cause the internal tree structure to become unbalanced. If the tree
+-- becomes too unbalanced, point queries will be very inefficient. If
+-- you need to perform lots of point insertions on an already existing
+-- /k/-d tree, check out
+-- @Data.KdTree.Dynamic.@'Data.KdTree.Dynamic.KdTree'.
 --
--- Throws an error if given an empty list of data points.
-buildKdTreeWithDistFn :: Real a => PointAsListFn a p
-                                   -> SquaredDistanceFn a p
-                                   -> [p]
-                                   -> KdTree a p
-buildKdTreeWithDistFn _ _ [] = error "KdTree must be built with a non-empty list."
-buildKdTreeWithDistFn pointAsList distSqr ps =
-  KdTree $ KDM.buildKdMapWithDistFn pointAsList distSqr $ zip ps $ repeat ()
+-- Average complexity: /O(log(n))/ for /n/ data points.
+--
+-- Worse case time complexity: /O(n)/ for /n/ data points.
+insertUnbalanced :: Real a => KdTree a p -> p -> KdTree a p
+insertUnbalanced (KdTree kdm) p = KdTree $ KDM.insertUnbalanced kdm p ()
 
+-- | Inserts a list of points into a 'KdTree'. This can potentially
+-- cause the internal tree structure to become unbalanced, which leads
+-- to inefficient point queries.
+--
+-- Average complexity: /O(n * log(n))/ for /n/ data points.
+--
+-- Worst case time complexity: /O(n^2)/ for /n/ data points.
+batchInsertUnbalanced :: Real a => KdTree a p -> [p] -> KdTree a p
+batchInsertUnbalanced (KdTree kdm) ps =
+  KdTree $ KDM.batchInsertUnbalanced kdm $ zip ps $ repeat ()
+
 -- | Given a 'KdTree' and a query point, returns the nearest point
 -- in the 'KdTree' to the query point.
 --
 -- Average time complexity: /O(log(n))/ for /n/ data points.
 --
 -- Worst case time complexity: /O(n)/ for /n/ data points.
-nearestNeighbor :: Real a => KdTree a p -> p -> p
-nearestNeighbor (KdTree t) query = fst $ KDM.nearestNeighbor t query
+--
+-- Throws an error if called on an empty 'KdTree'.
+nearest :: Real a => KdTree a p -> p -> p
+nearest (KdTree t) query
+  | KDM.null t = error "Attempted to call nearest on an empty KdTree."
+  | otherwise = fst $ KDM.nearest t query
 
 -- | Given a 'KdTree', a query point, and a radius, returns all
 -- points in the 'KdTree' that are within the given radius of the
@@ -236,12 +295,12 @@
 --
 -- Worst case time complexity: /O(n)/ for /n/ data points and
 -- a radius that subsumes all points in the structure.
-pointsInRadius :: Real a => KdTree a p
-                            -> a -- ^ radius
-                            -> p -- ^ query point
-                            -> [p] -- ^ list of points in tree with
-                                   -- given radius of query point
-pointsInRadius (KdTree t) radius query = map fst $ KDM.pointsInRadius t radius query
+inRadius :: Real a => KdTree a p
+                   -> a -- ^ radius
+                   -> p -- ^ query point
+                   -> [p] -- ^ list of points in tree with given
+                          -- radius of query point
+inRadius (KdTree t) radius query = map fst $ KDM.inRadius t radius query
 
 -- | Given a 'KdTree', a query point, and a number @k@, returns the
 -- @k@ nearest points in the 'KdTree' to the query point.
@@ -254,8 +313,8 @@
 --
 -- Worst case time complexity: /n * log(k)/ for /k/ nearest
 -- neighbors on a structure with /n/ data points.
-kNearestNeighbors :: Real a => KdTree a p -> Int -> p -> [p]
-kNearestNeighbors (KdTree t) k query = map fst $ KDM.kNearestNeighbors t k query
+kNearest :: Real a => KdTree a p -> Int -> p -> [p]
+kNearest (KdTree t) k query = map fst $ KDM.kNearest t k query
 
 -- | Finds all points in a 'KdTree' with points within a given range,
 -- where the range is specified as a set of lower and upper bounds.
@@ -264,17 +323,17 @@
 --
 -- Worst case time complexity: /O(n)/ for n data points and a range
 -- that spans all the points.
-pointsInRange :: Real a => KdTree a p
-                           -> p -- ^ lower bounds of range
-                           -> p -- ^ upper bounds of range
-                           -> [p] -- ^ all points within given range
-pointsInRange (KdTree t) lower upper = map fst $ KDM.pointsInRange t lower upper
+inRange :: Real a => KdTree a p
+                  -> p -- ^ lower bounds of range
+                  -> p -- ^ upper bounds of range
+                  -> [p] -- ^ all points within given range
+inRange (KdTree t) lower upper = map fst $ KDM.inRange t lower upper
 
 -- | Returns a list of all the points in the 'KdTree'.
 --
 -- Time complexity: /O(n)/ for /n/ data points.
-points :: KdTree a p -> [p]
-points (KdTree t) = KDM.points t
+toList :: KdTree a p -> [p]
+toList (KdTree t) = KDM.keys t
 
 -- | Returns the number of elements in the 'KdTree'.
 --
diff --git a/lib-src/Data/Point2d.hs b/lib-src/Data/Point2d.hs
deleted file mode 100644
--- a/lib-src/Data/Point2d.hs
+++ /dev/null
@@ -1,27 +0,0 @@
-{-# OPTIONS_HADDOCK hide #-}
-
-{-# LANGUAGE DeriveGeneric #-}
-
-module Data.Point2d where
-
-import Control.DeepSeq
-import Control.DeepSeq.Generics (genericRnf)
-import GHC.Generics
-import Test.QuickCheck
-
-data Point2d = Point2d Double Double deriving (Show, Eq, Ord, Generic)
-instance NFData Point2d where rnf = genericRnf
-
-pointAsList2d :: Point2d -> [Double]
-pointAsList2d (Point2d x y) = [x, y]
-
-distSqr2d :: Point2d -> Point2d -> Double
-distSqr2d (Point2d x1 y1) (Point2d x2 y2) = let dx = x2 - x1
-                                                dy = y2 - y1
-                                            in  dx*dx + dy*dy
-
-instance Arbitrary Point2d where
-    arbitrary = do
-        x <- arbitrary
-        y <- arbitrary
-        return (Point2d x y)
