diff --git a/src/core/landscape.jl b/src/core/landscape.jl
index 665e94fe981f7610b4270ddf891c5e07dcfcc1a0..eba5b73b9b86daff20e109cf9cb3522e9d58ff94 100644
--- a/src/core/landscape.jl
+++ b/src/core/landscape.jl
@@ -101,16 +101,41 @@ end
 
 Return the land cover class at this position (utility wrapper).
 """
-function landcover(model::AgentBasedModel, pos::Tuple{Int64,Int64})
+function landcover(pos::Tuple{Int64,Int64}, model::AgentBasedModel)
     model.landscape[pos...].landcover
 end
 
 """
     farmplot(model, position)
 
-Return the farm plot at this position (utility wrapper).
+Return the farm plot at this position, or nothing if there is none (utility wrapper).
 """
-function farmplot(model::AgentBasedModel, pos::Tuple{Int64,Int64})
-    model[model.landscape[pos...].fieldid]
+function farmplot(pos::Tuple{Int64,Int64}, model::AgentBasedModel)
+    ismissing(model.landscape[pos...].fieldid) ? nothing :
+              model[model.landscape[pos...].fieldid]
+end
+
+"""
+    distanceto(model, pos, 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
+end
+
+"""
+    distancetoedge(model, pos)
+
+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
 end
 
diff --git a/src/crop/crops.jl b/src/crop/crops.jl
index ec4520fb6d71aff653bafd6e6870ad91d72153a2..c96c7c075cc68232410f7da0d5459efe4896a7df 100644
--- a/src/crop/crops.jl
+++ b/src/crop/crops.jl
@@ -3,6 +3,9 @@
 ### This file is responsible for managing the crop growth modules.
 ###
 
+"The crop types simulated by the model"
+@enum CropType fallow wheat barley maize rapeseed
+
 #XXX not sure whether it makes sense to have this as an agent type,
 # or perhaps better a grid property?
 """
@@ -11,9 +14,9 @@
 This represents one field, i.e. a collection of pixels with the same management.
 This is the spatial unit with which the crop growth model and the farm model work.
 """
-@agent FarmPlot NoSpaceAgent begin
+@agent FarmPlot GridAgent{2} begin
     pixels::Vector{Tuple{Int64, Int64}}
-    croptype::String
+    croptype::CropType
     height::Float64
     biomass::Float64
     #TODO
@@ -49,7 +52,7 @@ function initfields!(model::AgentBasedModel)
                 model.landscape[x,y].fieldid = objectid
                 push!(model[objectid].pixels, (x,y))
             else
-                fp = add_agent!(FarmPlot, model, [(x,y)], "fallow", 0.0, 0.0)
+                fp = add_agent!(FarmPlot, model, [(x,y)], fallow, 0.0, 0.0)
                 model.landscape[x,y].fieldid = fp.id
                 convertid[rawid] = fp.id
                 n += 1
@@ -86,5 +89,25 @@ function averagefieldsize(model::AgentBasedModel)
         push!(sizes, size(a.pixels)[1]/conversionfactor)
     end
     round(sum(sizes)/size(sizes)[1], digits=2)
-    #sizes
+end
+
+"""
+    croptype(model, position)
+
+Return the crop at this position, or nothing if there is no crop here (utility wrapper).
+"""
+function croptype(pos::Tuple{Int64,Int64}, model::AgentBasedModel)
+    ismissing(model.landscape[pos...].fieldid) ? nothing :
+              model[model.landscape[pos...].fieldid].croptype
+end
+
+"""
+    cropheight(model, position)
+
+Return the height of the crop at this position, or nothing if there is no crop here
+(utility wrapper).
+"""
+function cropheight(pos::Tuple{Int64,Int64}, model::AgentBasedModel)
+    ismissing(model.landscape[pos...].fieldid) ? nothing :
+              model[model.landscape[pos...].fieldid].height
 end
diff --git a/src/nature/lifehistory.jl b/src/nature/lifehistory.jl
index c48618a11b1a3a206d0c779eece24611fb0db2fa..fd2f5c3a790f0ecb7e79bf7d7939974319761680 100644
--- a/src/nature/lifehistory.jl
+++ b/src/nature/lifehistory.jl
@@ -25,7 +25,9 @@ function initrandompopulation(popsize::Union{Int64,Float64}, asexual::Bool=true)
     return initfunc
 end
 
-#TODO initpopulation
+#TODO initpopulation with habitat descriptor
+#TODO initpopulation with dispersal from an original source
+#TODO initpopulation based on known occurences in real-life
 
 """
     reproduce!(animal, model, n=1)
@@ -57,3 +59,16 @@ function kill!(animal::Animal, model::AgentBasedModel, probability::Float64=1.0,
     end
     return false
 end
+
+"""
+    countanimals(model, pos, speciesname)
+
+Count the number of animals of the given species in this location.
+"""
+macro countanimals(model::AgentBasedModel, pos::Tuple{Int64,Int64}, speciesname::String)
+    n = 0
+    for a in nearby_ids(pos, model, 0)
+        typeof(model[a]) == Animal && model[a].traits.name == speciesname && (n += 1)
+    end
+    return n
+end
diff --git a/src/nature/nature.jl b/src/nature/nature.jl
index a13faefafcf0d20213f1b61cbfdc654b6aca6b74..51e21bfa71878ef209abd0e92546e2ea63148b3e 100644
--- a/src/nature/nature.jl
+++ b/src/nature/nature.jl
@@ -3,6 +3,9 @@
 ### This file is responsible for managing the animal modules.
 ###
 
+
+### FUNCTIONS AND TYPES INTEGRATING THE NATURE MODEL WITH THE REST OF PERSEPHONE
+
 ## An enum used to assign a sex to each animal
 @enum Sex hermaphrodite male female
 
@@ -30,6 +33,31 @@ function animalid(a::Animal)
     return "$(a.traits["name"]) $(a.id)"
 end
 
+"""
+    stepagent!(animal, model)
+
+Update an animal by one day, executing it's currently active phase function.
+"""
+function stepagent!(animal::Animal, model::AgentBasedModel)
+    animal.age += 1
+    animal.traits[animal.traits["phase"]](animal,model)
+end
+
+"""
+    initnature!(model)
+
+Initialise the model with all simulated animal populations.
+"""
+function initnature!(model::AgentBasedModel)
+    # The config file determines which species are simulated in this run
+    for speciesname in param("nature.targetspecies")
+        species = @eval $(Symbol(speciesname))($model)
+        species["initialise!"](species, model)
+    end
+    # Initialise the data output
+    initecologicaldata()
+end
+
 
 ### MACROS IMPLEMENTING THE DOMAIN-SPECIFIC LANGUAGE FOR DEFINING SPECIES
 
@@ -104,7 +132,7 @@ variables:
     information).
 
 Several utility macros can be used within the body of `@phase` as a short-hand for
-common expressions: `@respond`, `@trait`, `@here`, `@kill`, `@reproduce`
+common expressions: `@respond`, `@trait`, `@here`, `@kill`, `@reproduce`.
 
 To transition an individual to another phase, simply redefine its phase variable:
 `@trait(phase) = "newphase"`.
@@ -122,7 +150,6 @@ This can only be used nested within `@phase`.
 """
 macro respond(eventname, body)
     quote
-        #TODO test this
         if $(esc(eventname)) in @here(events)
             $body
         end
@@ -175,30 +202,105 @@ end
 
 #XXX add a macro @f to call a function with animal and model as first parameters?
 # e.g. @f(nearby_agents, distance)
+# -> rather create wrapper macros
+
+"""
+    @habitat
+
+Specify habitat suitability for spatial ecological processes.
 
-### FUNCTIONS INTEGRATING THE NATURE MODEL WITH THE REST OF PERSEPHONE
+This macro works by creating an anonymous function that takes in a model object
+and a position, and returns `true` or `false` depending on the conditions
+specified in the macro body.
 
+Several utility macros can be used within the body of `@habitat` as a short-hand for
+common expressions: `@landcover`, `@croptype`, `@cropheight`, `@distanceto`,
+`@distancetoedge`, `@countanimals`. The variables `model` and `pos` can be used
+for checks that don't have a macro available.
+
+Two example uses of `@habitat` might look like this:
+
+```julia
+movementhabitat = @habitat(@landcover() in (grass agriculture soil))
+
+nestinghabitat = @habitat((@landcover() == grass || 
+                           (@landcover() == agriculture && @croptype() != maize &&
+                            @cropheight() < 10)) &&
+                          @distanceto(forest) > 20)
+```
+
+For more complex habitat suitability checks, the use of this macro can be
+circumvented by directly creating an equivalent function.
 """
-    stepagent!(animal, model)
+macro habitat(body)
+    quote
+        function($(esc(:pos)), $(esc(:model)))
+            if $(esc(body))
+                return true
+            else
+                return false
+            end
+        end
+    end
+end
 
-Update an animal by one day, executing it's currently active phase function.
 """
-function stepagent!(animal::Animal, model::AgentBasedModel)
-    animal.age += 1
-    animal.traits[animal.traits["phase"]](animal,model)
+    @landcover
+
+Returns the local landcover. This is a utility wrapper that can only be used
+nested within `@habitat`.
+"""
+macro landcover()
+    :(landcover($(esc(:pos)), $(esc(:model))))
 end
 
 """
-    initnature!(model)
+    @croptype
 
-Initialise the model with all simulated animal populations.
+Return the local croptype, or nothing if there is no crop here.
+This is a utility wrapper that can only be used nested within `@habitat`.
 """
-function initnature!(model::AgentBasedModel)
-    # The config file determines which species are simulated in this run
-    for speciesname in param("nature.targetspecies")
-        species = @eval $(Symbol(speciesname))($model)
-        species["initialise!"](species, model)
-    end
-    # Initialise the data output
-    initecologicaldata()
+macro croptype()
+    :(croptype($(esc(:pos)), $(esc(:model))))
+end
+
+"""
+    @cropheight
+
+Return the height of the crop at this position, or 0 if there is no crop here.
+This is a utility wrapper that can only be used nested within `@habitat`.
+"""
+macro cropheight()
+    :(cropheight($(esc(:pos)), $(esc(:model))))
+end
+
+"""
+    @distanceto(habitattype)
+
+Calculate the distance to the closest habitat of the specified type.
+This is a utility wrapper that can only be used nested within `@habitat`.
+"""
+macro distanceto(habitattype)
+    :(distanceto($(esc(:pos)), $(esc(:model)), $habitattype))
+end
+
+"""
+    @distancetoedge
+
+Calculate the distance to the closest neighbouring habitat.
+This is a utility wrapper that can only be used nested within `@habitat`.
+"""
+macro distancetoedge()
+    :(distancetoedge($(esc(:pos)), $(esc(:model))))
+end
+
+"""
+    @countanimals(speciesname)
+
+Count the number of animals of the given species in this location.
+This is a utility wrapper that can only be used nested within `@habitat`.
+"""
+macro countanimals(speciesname)
+    #XXX this also counts the enquiring agent
+    :(countanimals($(esc(:pos)), $(esc(:model)), $speciesname))
 end
diff --git a/src/nature/species/skylark.jl b/src/nature/species/skylark.jl
index 4843816234b5afa6f9720df61a2a98d6303b7bea..9542cd420c17743d26e762eb5a0c0e1cccf25efb 100644
--- a/src/nature/species/skylark.jl
+++ b/src/nature/species/skylark.jl
@@ -4,7 +4,7 @@
 ###
 
 ##XXX At the moment, this is just a skeleton to show what I want to be able to interpret
-## with the @species and @phase macros
+## with the @species, @phase, and @habitat macros (and their helper macros)
 
 @species Skylark begin
 
@@ -16,6 +16,11 @@
     
     initialise! = initrandompopulation(popsize)
     phase = "egg"
+
+    habitats = @habitat((@landcover() == grass || 
+                         (@landcover() == agriculture && @croptype() != maize &&
+                          @cropheight() < 10)) &&
+                        @distanceto(forest) > 20)
     
     @phase egg begin
         @kill(@trait(eggpredationmortality), "predation")
diff --git a/test/nature_tests.jl b/test/nature_tests.jl
index b5cb2cf609c95f621b1b095504717c0cff76f278..fb15b97effa5e6ea9f6edba4abeff9cb66d4336b 100644
--- a/test/nature_tests.jl
+++ b/test/nature_tests.jl
@@ -3,4 +3,8 @@
 ### These are the tests for the nature model (excluding individual species).
 ###
 
+@testset "Species macros" begin
+    #TODO
+end
+
 #TODO