diff --git a/Data/StringMap/Base.hs b/Data/StringMap/Base.hs
--- a/Data/StringMap/Base.hs
+++ b/Data/StringMap/Base.hs
@@ -11,7 +11,7 @@
 
   Maintainer : Uwe Schmidt (uwe@fh-wedel.de)
   Stability  : experimental
-  Portability: not portable
+  Portability: portable
 
   An efficient implementation of maps from strings to arbitrary values.
 
@@ -592,9 +592,9 @@
     look _ _                    = normError "lookupLE"
 
 -- | Combination of 'lookupLE' and 'lookupGE'
--- 
+--
 -- > keys $ lookupRange "a" "b" $ fromList $ zip ["", "a", "ab", "b", "ba", "c"] [1..] = ["a","ab","b"]
--- 
+--
 -- For all keys in @k = keys $ lookupRange lb ub m@, this property holts true: @k >= ub && k <= lb@
 
 lookupRange                     :: Key -> Key -> StringMap a -> StringMap a
diff --git a/Data/StringMap/Dim2Search.hs b/Data/StringMap/Dim2Search.hs
new file mode 100644
--- /dev/null
+++ b/Data/StringMap/Dim2Search.hs
@@ -0,0 +1,275 @@
+-- ----------------------------------------------------------------------------
+
+{- |
+  Module     : Data.StringMap.Dim2Search
+  Copyright  : Copyright (C) 2014 Uwe Schmidt
+  License    : MIT
+
+  Maintainer : Uwe Schmidt (uwe@fh-wedel.de)
+  Stability  : experimental
+  Portability: portable
+
+  2-dimensional range search of numeric values, e.g. pairs of Ints or Doubles
+  using StringMap and prefix search
+
+  Assumption: The coordinates, e.g. Int values are converted into strings
+  of equal length such that the ordering is preserved by the lexikographic ordering.
+
+  Example: convert an Int (>= 0) into a String
+  @intToString = reverse . take 19 . (++ repeat '0') . reverse . show@
+
+  Do this for both coordinates of a tuple
+  @(x,y)::(Int,Int)@
+  and merge the two strings character by character.
+  The resulting string is used as key and stored together with an attribute
+  in a StringMap.
+
+  A range search for all keys within a rectangle @(p1, p2) = ((x1,y1),(x2,y2))@
+  in a map @m@ can be done by @lookupGE p1' . lookupLE p2' $ m@ with
+  @p1'@ and @p2'@ as the to string converted points of the rectangle.
+
+  @lookupGE p1'@ throws away all keys not located in the quadrant with @p1@
+  as lower left corner, @lookupLE p2'@ all key not located in the quadrant
+  with @p2@ as upper right corner. So the combination (@lookupRange@) computed
+  the intersection of these two quadrants.
+
+  Efficiency of these two function is about the same as a normal lookup
+  from StringMap.Base.
+
+  This module should be imported @qualified@, the names in Data.StringMap.Dim2Search are the
+  same as theirs siblings in Data.StringMap:
+
+  > import           Data.StringMap (StringMap)
+  > import qualified Data.StringMap             as M
+  > import qualified Data.StringMap.Dim2Search  as Dim2
+
+-}
+
+-- ----------------------------------------------------------------------------
+
+module Data.StringMap.Dim2Search
+-- {-
+    ( lookupGE
+    , lookupLE
+    , lookupRange
+    )
+-- -}
+where
+
+import           Data.StringMap.Base hiding (lookupGE, lookupLE, lookupRange)
+
+-- ----------------------------------------
+
+-- | remove all entries from the map with key less than the argument key
+
+lookupGE                        :: Key -> StringMap a -> StringMap a
+lookupGE                        = lookupGE'
+
+lookupGE'                       :: Key -> StringMap a -> StringMap a
+lookupGE' k0                    = look k0 . norm
+    where
+
+    -- take all values in tree t, they are larger than the key
+    look [] t                   = t
+
+    look k@(c : k1) (Branch c' s' n')
+        -- this dimension fits for s', the other dimension has to be checked
+        -- with lookupGE2, process has to be repeated for the rest
+        | c <  c'               = branch c' (lookupGE2 k1 s') rest
+
+        -- symbols are equal, no info about ordering gathered, repeat the
+        -- the same lookup for the subtree s'
+        -- the rest in n' has to be processed the same way as this branch
+        | c == c'               = branch c' (lookupGE' k1 s') rest
+
+        -- this dimension does not fit, throw away this branch and continue with n'
+        | otherwise             =                             rest
+        where
+          rest                  = lookupGE' k n'
+
+    -- empty remains empty
+    look _          Empty       = empty
+
+    -- throw away the value, its smaller than required
+    look k         (Val _v' t') = lookupGE' k t'
+
+    -- the impossible has happened
+    look _ _                    = normError "lookupGE'"
+
+lookupGE2                      :: Key -> StringMap a -> StringMap a
+lookupGE2 k0                   = look k0 . norm
+    where
+    -- key is empty, all values in t are larger, so they are included
+    look [] t                   = t
+
+    look k@(c : k1) t@(Branch c' s' n')
+        -- tree s' and all others in n' contain values larger than required
+        -- take them
+        | c <  c'               = t
+
+        -- the 1. symbols are equal, so lookup has to continue,
+        -- but only along this dimension, so skip the next key symbol (lookupLE1) and
+        -- repeat this comparison procedure (call of lookupLE2 in lookupLE1)
+        -- the rest (n') is taken like in the 1. case
+        | c == c'               = branch c' (lookupGE1 k1 s') n'
+
+        -- the 1. symbol in the key is larger, so cut off this subtree (s')
+        -- and repeat lookup for the rest (n')
+        | otherwise             = lookupGE2 k n'
+
+    -- empty remains empty
+    look _          Empty       = empty
+
+    -- throw away the value, its smaller than required
+    look k         (Val _v' t') = lookupGE2 k t'
+
+    -- the impossible has happened
+    look _ _                    = normError "lookupGE2"
+
+lookupGE1                       :: Key -> StringMap a -> StringMap a
+lookupGE1 k0               = look k0 . norm
+    where
+    -- like above
+    look [] t                   = t
+
+    -- ignore the 1. symbol of the key, take the subtree s' and
+    -- continue comparison of every other symbol,
+    -- do the same for all remaining trees in n'
+    look k@(_c : k1) (Branch c' s' n')
+                                = branch c' (lookupGE2 k1 s') $ lookupGE1 k n'
+
+    -- like above
+    look _          Empty       = empty
+
+    -- like above
+    look k         (Val _v' t') = lookupGE1 k t'
+
+    -- like above
+    look _ _                    = normError "lookupGE1"
+
+-- ----------------------------------------
+--
+-- the same stuff for less or equal
+
+lookupLE                        :: Key -> StringMap a -> StringMap a
+lookupLE                        = lookupLE'
+
+lookupLE'                       :: Key -> StringMap a -> StringMap a
+lookupLE' k0                    = look k0 . norm
+    where
+
+    -- if key is empty and node stores a value
+    -- take this value, it's the upper limit,
+    -- all other values in the subtree _t' are larger and thrown away
+    look [] (Val v' _t')        = (Val v' empty)
+
+    -- key is empty, all remaining values in _t are larger and thrown away
+    look [] _t                  = empty
+
+    look k@(c : k1) (Branch c' s' n')
+        -- the char c' is larger than the 1. char in the search key
+        -- so this and all other others (n') are cut off
+        | c <  c'               =                             empty
+
+        -- the char c and c' are the same, so search for this subtree s' must
+        -- continue, but all further trees (n') are cut off
+        | c == c'               = branch c' (lookupLE' k1 s') empty
+
+        -- the char c' is smaller than the 1. char in the search key
+        -- so concerning this dimension, the elements must be included into the
+        -- result, but the other dimension must be checked (with lookupLE2)
+        -- all remaining values in n' have also to be taken, therfore the rec. call with n'
+        | otherwise             = branch c' (lookupLE2 k1 s') (lookupLE' k n')
+
+    -- the empty tree remains empty
+    look _          Empty       = empty
+
+    -- the values v' are included into the result, and the lookup process
+    -- continues with the subtree t'
+    -- this case will not occur, when the 2-dim keys are normalized and all
+    -- are of the same length, in that case the values occur only on leaf nodes not in inner nodes
+    look k         (Val v' t')  = val v' (lookupLE' k t')
+
+    -- the impossible has happend
+    look _ _                    = normError "lookupLE'"
+
+lookupLE2                      :: Key -> StringMap a -> StringMap a
+lookupLE2 k0                   = look k0 . norm
+    where
+
+    -- if key is empty and node stores a value
+    -- take this value, it's the upper limit,
+    -- all other values in the subtree _t' are larger and thrown away
+    look [] (Val v' _t')        = (Val v' empty)
+
+    -- key is empty, all remaining values in _t are larger and thrown away
+    look [] _t                  = empty
+
+    look k@(c : k1) (Branch c' s' n')
+        -- tree s' and all others in n' contain values larger than required
+        -- throw them away
+        | c <  c'               =                             empty
+
+        -- the 1. symbols are equal, so lookup has to continue,
+        -- but only along this dimension, so skip the next key symbol (lookupLE1) and
+        -- repeat this comparison procedure (call of lookupLE2 in lookupLE1)
+        -- the rest (n') can be thrown away like in the 1. case
+        | c == c'               = branch c' (lookupLE1 k1 s') empty
+
+        -- the 1. symbol in the key is larger, so take this subtree (s')
+        -- and repeat lookup for the rest (n')
+        | otherwise             = branch c' s'                (lookupLE2 k n')
+
+    -- the empty tree remains empty
+    look _          Empty       = empty
+
+    -- the values v' are included into the result, and the lookup process
+    -- continues with the subtree t'
+    -- this case will not occur, when the 2-dim keys are normalized and all
+    -- are of the same length, in that case the values occur only on leaf nodes not in inner nodes
+    look k         (Val v' t')  = val v' (lookupLE2 k t')
+
+    -- the impossible has happend
+    look _ _                    = normError "lookupLE2"
+
+lookupLE1                       :: Key -> StringMap a -> StringMap a
+lookupLE1 k0                    = look k0 . norm
+    where
+    -- like above
+    look [] (Val v' _t')        = (Val v' empty)
+
+    -- like above
+    look [] t                   = t
+
+    -- ignore the 1. symbol of the key, take the subtree s' and
+    -- continue comparison of every other symbol,
+    -- do the same for all remaining trees in n'
+    look k@(_c : k1) (Branch c' s' n')
+                                = branch c' (lookupLE2 k1 s') (lookupLE1 k n')
+
+    -- like above
+    look _          Empty       = empty
+
+    -- like above
+    look k         (Val v' t')  = val v' (lookupLE1 k t')
+
+    -- like above
+    look _ _                    = normError "lookupLE1"
+
+
+-- | Combination of 'lookupLE' and 'lookupGE'
+--
+-- > keys $ lookupRange "a" "b" $ fromList $ zip ["", "a", "ab", "b", "ba", "c"] [1..] = ["a","ab","b"]
+--
+-- For all keys in @k = keys $ lookupRange lb ub m@, this property holts true: @k >= ub && k <= lb@
+
+lookupRange                     :: Key -> Key -> StringMap a -> StringMap a
+lookupRange lb ub               = lookupGE lb . lookupLE ub
+
+-- ----------------------------------------
+
+normError               :: String -> a
+normError               = normError' "Data.StringMap.Dim2Search"
+
+-- ----------------------------------------
+
diff --git a/data-stringmap.cabal b/data-stringmap.cabal
--- a/data-stringmap.cabal
+++ b/data-stringmap.cabal
@@ -1,5 +1,5 @@
 name:         data-stringmap
-version:      1.0.0
+version:      1.0.1.1
 license:      MIT
 license-file: LICENSE
 author:       Uwe Schmidt, Sebastian Philipp
@@ -55,6 +55,7 @@
                 Data.StringMap.StringSet
                 Data.StringMap.Types
                 Data.StringMap.Base
+                Data.StringMap.Dim2Search
 
   other-modules:
                 Data.StringMap.FuzzySearch
diff --git a/tests/Dim2Test.hs b/tests/Dim2Test.hs
new file mode 100644
--- /dev/null
+++ b/tests/Dim2Test.hs
@@ -0,0 +1,267 @@
+{-# LANGUAGE FunctionalDependencies #-}
+{-# LANGUAGE MultiParamTypeClasses  #-}
+
+module Main where
+
+import           Data.List                 (sort)
+import qualified Data.StringMap            as M
+import qualified Data.StringMap.Dim2Search as D2
+
+-- ----------------------------------------
+--
+-- auxiliary functions for mapping pairs of Ints to Strings and vice versa
+
+intToKey :: Int -> Int -> Int -> String
+intToKey base len val = tok len val ""
+    where
+      tok 0 _ acc = acc
+      tok i v acc = tok (i - 1) v' (d : acc)
+          where
+            (v', r) = v `divMod` base
+            d       = toEnum (r + fromEnum '0')
+
+intPairToKey :: Int -> Int -> (Int, Int) -> String
+intPairToKey base len (x, y) = merge x' y'
+    where
+      x' = intToKey base len x
+      y' = intToKey base len y
+
+merge :: [a] -> [a] -> [a]
+merge []       []       = []
+merge (x : xs) (y : ys) = x : y : merge xs ys
+
+intFromKey :: String -> Int
+intFromKey = read
+
+unMerge :: [a] -> ([a], [a])
+unMerge [] = ([], [])
+unMerge (x : y : s) = (x : xs, y : ys)
+    where
+      (xs, ys) = unMerge s
+
+-- ----------------------------------------
+--
+-- experiment to understand 2-dimensional location
+-- search implemented by using the StringMap impl.
+--
+-- an ordering on strings (representing pairs of ints)
+-- that is isomorphic to the partial ordering
+-- used for 2-dimensional search
+
+instance Ord Point' where
+    (P' s1) <= (P' s2) = s1 `le` s2
+        where
+          le [] [] = True
+          le (x1 : y1 : ds1) (x2 : y2 : ds2)
+              | x1 == x2 && y1 == y2 = ds1 `le`  ds2
+              | x1 == x2 && y1 <  y2 = ds1 `leX` ds2
+              | x1 <  x2 && y1 == y2 = ds1 `leY` ds2
+              | x1 <  x2 && y1 <  y2 = True
+              | otherwise            = False
+
+          leX [] [] = True                      -- the result for the Y dimension is already known
+          leX (x1 : y1 : ds1) (x2 : y2 : ds2)
+              | x1 == x2  = ds1 `leX` ds2
+              | x1 <  x2  = True
+              | otherwise = False
+
+          leY [] [] = True                      -- the result for the X dimension is already known
+          leY (x1 : y1 : ds1) (x2 : y2 : ds2)
+              | y1 == y2  = ds1 `leY` ds2
+              | y1 <  y2  = True
+              | otherwise = False
+
+-- toPoint' and fromPoint': the bijection Point <-> Point'
+
+toPoint' :: Point -> Point'
+toPoint' (P p) = P' $ intPairToKey base len p
+    where
+      base =  2         -- or 10
+      len  =  10        -- or  3  (or something else)
+
+fromPoint' :: Point' -> Point
+fromPoint' (P' ds) = P (intFromKey xs, intFromKey ys)
+    where
+      (xs, ys) = unMerge ds
+
+-- the test, whether the `le` ordering is preserved, when working with Point'
+propOrdered :: Point -> Point -> Bool
+propOrdered p1 p2
+    = (p1 `le` p2) == (toPoint' p1 <= toPoint' p2)
+
+-- very quick check test
+propTest :: Int -> [(Point, Point)]
+propTest n
+    = filter (not . uncurry propOrdered) qs
+      where
+        xs = [1..n]
+        ps = [P (x, y) | x <- xs, y <- xs]
+        qs = [(p1, p2) | p1 <- ps, p2 <- ps]
+
+test1 :: Bool
+test1 = null $ propTest 20
+
+-- ----------------------------------------
+
+newtype Point     = P   {unP :: (Int, Int)    } deriving (Eq)
+newtype PointSet  = PS  {unPS :: [Point]       } deriving (Eq)
+                                                -- assuming only smart constructor mkPS is used
+
+newtype Point'    = P'  {unP' :: String        } deriving (Eq)
+newtype PointSet' = PS' {unPS' :: M.StringMap ()} deriving (Eq)
+
+instance Show Point     where show = show . unP
+instance Show Point'    where show = show . unP'
+instance Show PointSet  where show = show . unPS
+instance Show PointSet' where show = show . M.keys . unPS'
+
+class PartOrd a where
+    le :: a -> a -> Bool
+    ge :: a -> a -> Bool
+
+instance PartOrd Point where
+    (P (x1, y1)) `le` (P (x2, y2))
+        = x1 <= x2 && y1 <= y2
+
+    (P (x1, y1)) `ge` (P (x2, y2))
+        = x1 >= x2 && y1 >= y2
+
+instance PartOrd Point' where
+    (P' p1) `le` (P' p2)
+        = not . M.null . D2.lookupLE p2 $ (M.singleton p1 ())
+
+    (P' p1) `ge` (P' p2)
+        = not . M.null . D2.lookupGE p2 $ (M.singleton p1 ())
+
+class Lookup p s | s -> p where
+    lookupLE :: p -> s -> s
+    lookupGE :: p -> s -> s
+
+instance Lookup Point PointSet where
+    lookupLE p ps = PS . filter (`le` p) . unPS $ ps
+    lookupGE p ps = PS . filter (`ge` p) . unPS $ ps
+
+instance Lookup Point' PointSet' where
+    lookupLE p ps = PS' . D2.lookupLE (unP' p) . unPS' $ ps
+    lookupGE p ps = PS' . D2.lookupGE (unP' p) . unPS' $ ps
+
+-- the bijection between Point and Point'
+
+pToP' :: Point -> Point'
+pToP' = P' . intPairToKey 10 5 . unP    -- base 10, 5 digits
+
+p'ToP :: Point' -> Point
+p'ToP (P' p') = P (intFromKey xs, intFromKey ys)
+    where
+      (xs, ys) = unMerge p'
+
+-- the bijection between PointSet and PointSet'
+
+psToPS' :: PointSet -> PointSet'
+psToPS' = PS' . M.fromList . map (\(P' x) -> (x, ())) . map pToP' . unPS
+
+ps'ToPS :: PointSet' -> PointSet
+ps'ToPS = mkPS . map (unP . p'ToP . P') . M.keys . unPS'
+
+mkP :: Int -> Int -> Point
+mkP x y = P (x, y)
+
+mkP' :: Int -> Int -> Point'
+mkP' x y = pToP' $ mkP x y
+
+mkPS :: [(Int, Int)] -> PointSet
+mkPS = PS . map P . sort
+
+mkPS' :: [(Int, Int)] -> PointSet'
+mkPS' = psToPS' . mkPS
+
+mkxx :: Int -> Point
+mkxx i = mkP i i
+
+mkxx' :: Int -> Point'
+mkxx' = pToP' . mkxx
+
+mkD2 :: [Int] -> PointSet
+mkD2 = PS . map mkxx
+
+mkD2' :: [Int] -> PointSet'
+mkD2' = psToPS' . mkD2
+
+d1 :: PointSet
+d1 = mkD2 [1,10,100,105,107,125,200, 205, 222]
+
+d1' :: PointSet'
+d1' = psToPS' d1
+
+d2 :: PointSet
+d2 = mkD2 [2,10,20,25,100,111,155,200,333,500]
+
+d2' :: PointSet'
+d2' = psToPS' d2
+
+d0' :: PointSet'
+d0' = mkD2' [10,100]
+
+
+mkSquare :: Int -> Int -> PointSet
+mkSquare n m = mkPS [(i, j) | i <- [n..m], j <- [n..m]]
+
+-- input list must contain at least 3 different elements
+mkPointPointSet :: [Int] -> ([Point], PointSet)
+mkPointPointSet xs0
+    = (ps, ps')
+      where
+        xs@(_ : ys@(_:_:_)) = sort xs0
+        xs'                  = init ys
+        ps  =      [mkP i j | i <- xs,  j <- xs ]
+        ps' = mkPS [ (i, j) | i <- xs', j <- xs']
+
+
+ps1 :: PointSet
+xs1 :: [Point]
+(xs1, ps1) =  mkPointPointSet [1,2,10,20,25,100,111,155,200,333,500,505]
+
+lawBijection :: PointSet -> Bool
+lawBijection ps
+    = ps == (ps'ToPS . psToPS' $ ps)
+
+lawPredicateMorphism :: (Point -> Bool) -> (Point' -> Bool) ->
+                        Point -> Bool
+lawPredicateMorphism p p' x
+    = p x == (p' $ pToP' x)
+
+lawPredicate2Morphism :: (Point -> Point -> Bool) -> (Point' -> Point' -> Bool) ->
+                         Point -> Point -> Bool
+lawPredicate2Morphism p2 p2' x y
+    = lawPredicateMorphism (p2 x) (p2' $ pToP' x) y
+
+lawPointSetMorphism :: (PointSet -> PointSet) -> (PointSet' -> PointSet') ->
+                       PointSet -> Bool
+lawPointSetMorphism f f' ps
+    = f ps == (ps'ToPS . f' . psToPS' $ ps)
+
+lawLookupGE :: Point -> PointSet -> Bool
+lawLookupGE p ps = lawPointSetMorphism (lookupGE p) (lookupGE $ pToP' p) ps
+
+lawLookupLE :: Point -> PointSet -> Bool
+lawLookupLE p ps = lawPointSetMorphism (lookupLE p) (lookupLE $ pToP' p) ps
+
+testPointPointSet :: (Point -> PointSet -> Bool) -> ([Point], PointSet) -> [Point]
+testPointPointSet law (xs, ps)
+    = filter (\p -> not $ law p ps) xs
+
+testLookup :: ([Point], PointSet) -> Bool
+testLookup ps
+    = null (testPointPointSet lawLookupLE ps)
+      &&
+      null (testPointPointSet lawLookupGE ps)
+
+theTest :: Bool
+theTest = testLookup $
+       mkPointPointSet [1,2,10,20,25,100,111,155,200,333,500,505]
+
+main :: IO ()
+main = print theTest >> return ()
+
+-- ----------------------------------------
+
