diff --git a/src/core/landscape.jl b/src/core/landscape.jl
index eba5b73b9b86daff20e109cf9cb3522e9d58ff94..3ea00c3f322cd1159c1d4f85e6a3fc89a839b0bb 100644
--- a/src/core/landscape.jl
+++ b/src/core/landscape.jl
@@ -84,9 +84,9 @@ function updateevents!(model::AgentBasedModel)
 end
 
 """
-    createevent(model, pos, name, duration=1)
+    createevent!(model, pixels, name, duration=1)
 
-Add a farm event to the specified pixels for a given duration.
+Add a farm event to the specified pixels (a vector of position tuples) for a given duration.
 """
 function createevent!(model::AgentBasedModel, pixels::Vector{Tuple{Int64,Int64}},
                      name::EventType, duration::Int64=1)
@@ -97,7 +97,7 @@ function createevent!(model::AgentBasedModel, pixels::Vector{Tuple{Int64,Int64}}
 end
 
 """
-    landcover(model, position)
+    landcover(position, model)
 
 Return the land cover class at this position (utility wrapper).
 """
@@ -106,7 +106,7 @@ function landcover(pos::Tuple{Int64,Int64}, model::AgentBasedModel)
 end
 
 """
-    farmplot(model, position)
+    farmplot(position, model)
 
 Return the farm plot at this position, or nothing if there is none (utility wrapper).
 """
@@ -115,27 +115,60 @@ function farmplot(pos::Tuple{Int64,Int64}, model::AgentBasedModel)
               model[model.landscape[pos...].fieldid]
 end
 
+
 """
-    distanceto(model, pos, habitattype)
+    distanceto(pos, model, habitatdescriptor)
+
+Calculate the distance from the given location to the closest location matching the
+habitat descriptor function. Caution: can be computationally expensive!
+"""
+function distanceto(pos::Tuple{Int64,Int64}, model::AgentBasedModel, habitatdescriptor::Function)
+    (habitatdescriptor(pos, model)) && (return 0)
+    dist = 1
+    width, height = size(model.landscape)
+    while dist <= width || dist <= height
+        # check the upper and lower bounds of the enclosing rectangle
+        y1 = pos[2] - dist
+        y2 = pos[2] + dist
+        minx = maximum((pos[1]-dist, 1))
+        maxx = minimum((pos[1]+dist, width))
+        for x in minx:maxx
+            (y1 > 0 && habitatdescriptor((x,y1), model)) && (return dist)
+            (y2 <= height && habitatdescriptor((x,y2), model)) && (return dist)
+        end
+        # check the left and right bounds of the enclosing rectangle
+        x1 = pos[1] - dist
+        x2 = pos[1] + dist
+        miny = maximum((pos[2]-dist+1, 1))
+        maxy = minimum((pos[2]+dist-1, height))
+        for y in miny:maxy
+            (x1 > 0 && habitatdescriptor((x1,y), model)) && (return dist)
+            (x2 <= width && habitatdescriptor((x2,y), model)) && (return dist)
+        end
+        dist += 1
+    end
+    return Inf
+end
+
+"""
+    distanceto(pos, model, habitattype)
 
 Calculate the distance from the given location to the closest habitat of the specified type.
 Caution: can be computationally expensive!
 """
 function distanceto(pos::Tuple{Int64,Int64}, model::AgentBasedModel, habitattype::LandCover)
-    #XXX can I make this check for both land cover type and crop type?
-    # (Or even take a full habitat descriptor?)
-    #TODO
+    # can't use @habitat here because nature.jl is loaded later than this file
+    distanceto(pos, model, function(p,m) landcover(p,m) == habitattype end)
 end
 
 """
-    distancetoedge(model, pos)
+    distancetoedge(pos, model)
 
 Calculate the distance from the given location to the closest neighbouring habitat.
 Caution: can be computationally expensive!
 """
 function distancetoedge(pos::Tuple{Int64,Int64}, model::AgentBasedModel)
-    lc = landcover(model, pos)
-    dist = 1
-    #TODO
+    # can't use @habitat here because nature.jl is loaded later than this file
+    distanceto(pos, model, function(p,m) landcover(p,m) != landcover(pos, model) end)
 end
 
diff --git a/test/landscape_tests.jl b/test/landscape_tests.jl
index 11422d28b3efd2c28a47dbb33fd1a1b31c6c58f0..c54ca64785c93d92db9ecdd2098852c60b1877f6 100644
--- a/test/landscape_tests.jl
+++ b/test/landscape_tests.jl
@@ -13,16 +13,16 @@
     Persephone.initfields!(model)
     # these tests are specific to the Jena maps
     @test size(model.landscape) == (1754, 1602)
-    @test Persephone.landcover(model, (100,100)) == Persephone.forest
-    @test Persephone.landcover(model, (300,1)) == Persephone.soil
-    @test Persephone.landcover(model, (500,1)) == Persephone.nodata
-    @test Persephone.landcover(model, (400,400)) == Persephone.grass
-    @test Persephone.landcover(model, (800,800)) == Persephone.agriculture
-    @test Persephone.landcover(model, (1100,1100)) == Persephone.builtup
+    @test Persephone.landcover((100,100), model) == Persephone.forest
+    @test Persephone.landcover((300,1), model) == Persephone.soil
+    @test Persephone.landcover((500,1), model) == Persephone.nodata
+    @test Persephone.landcover((400,400), model) == Persephone.grass
+    @test Persephone.landcover((800,800), model) == Persephone.agriculture
+    @test Persephone.landcover((1100,1100), model) == Persephone.builtup
     @test Persephone.countfields(model) == 2092
     @test Persephone.averagefieldsize(model) == 5.37
     @test count(f -> ismissing(f.fieldid), model.landscape) == 1685573
-    @test length(Persephone.farmplot(model, (800,800)).pixels) == 4049                
+    @test length(Persephone.farmplot((800,800), model).pixels) == 4049                
 end
 
 @testset "Event system" begin
@@ -49,3 +49,26 @@ end
     @test model.landscape[2,1].events == []
     @test model.landscape[2,2].events == []
 end
+
+@testset "Landscape functions" begin
+    # initialise a simple 6x6 test landscape
+    landscape = Matrix{Pixel}(undef, 6, 6)
+    for x in 1:6
+        for y in 1:6
+            (x in (1:2) || y in (1:4)) ? lc = Persephone.forest : lc = Persephone.grass
+            landscape[x,y] = Pixel(lc, 0, [])
+        end
+    end
+    landscape[6,4] = Pixel(Persephone.water, 0, [])
+    space = GridSpace(size(landscape), periodic=false)
+    properties = Dict{Symbol,Any}(:landscape=>landscape)
+    model = AgentBasedModel(Animal, space, properties=properties, warn=false)
+    # test the distance functions
+    @test Persephone.distanceto((2,3), model, Persephone.forest) == 0
+    @test Persephone.distanceto((2,3), model, Persephone.grass) == 2
+    @test Persephone.distanceto((2,3), model, Persephone.water) == 4
+    @test Persephone.distanceto((2,3), model, Persephone.soil) == Inf
+    @test Persephone.distancetoedge((1,1), model) == 4
+    @test Persephone.distancetoedge((4,4), model) == 1
+    @test Persephone.distancetoedge((6,6), model) == 2
+end