From 74d2d078f626e5bbba16ca142d3903257d646c1f Mon Sep 17 00:00:00 2001 From: Daniel Vedder <daniel.vedder@idiv.de> Date: Fri, 9 Aug 2024 12:01:44 +0200 Subject: [PATCH] Added data recording and visualisation for the farm model --- src/Persefone.jl | 1 + src/analysis/makieplots.jl | 42 ++++++++++++++++++++++++++++++++------ src/crop/almass.jl | 2 +- src/farm/farm.jl | 6 +----- src/farm/farmdata.jl | 41 +++++++++++++++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 12 deletions(-) create mode 100644 src/farm/farmdata.jl diff --git a/src/Persefone.jl b/src/Persefone.jl index bff3e22..f5aa7b4 100644 --- a/src/Persefone.jl +++ b/src/Persefone.jl @@ -149,6 +149,7 @@ include("crop/almass.jl") include("crop/simplecrop.jl") include("farm/farm.jl") +include("farm/farmdata.jl") include("nature/insects.jl") include("nature/energy.jl") diff --git a/src/analysis/makieplots.jl b/src/analysis/makieplots.jl index fd92184..30cacef 100644 --- a/src/analysis/makieplots.jl +++ b/src/analysis/makieplots.jl @@ -61,7 +61,7 @@ function populationtrends(model::SimulationModel) dates = @param(core.startdate):@param(core.enddate) axlimits = (1, length(dates), 0, maximum(pops[!,:Abundance])) ax = Axis(f[1,1], xlabel="Date", ylabel="Population size", - limits=axlimits, xticks=gettickmarks(dates)) + limits=axlimits, xticks=datetickmarks(dates)) for s in @param(nature.targetspecies) points = @select!(@subset(pops, :Species .== s), :Abundance) iszero(size(points)[1]) && continue @@ -84,7 +84,7 @@ function skylarkpopulation(model::SimulationModel) dates = @param(core.startdate):@param(core.enddate) axlimits = (1, length(dates), 0, maximum(pops[!,:TotalAbundance])) ax = Axis(f[1,1], xlabel="Date", ylabel="Population size", - limits=axlimits, xticks=gettickmarks(dates)) + limits=axlimits, xticks=datetickmarks(dates)) lines!(f[1,1], Vector{Float32}(pops.TotalAbundance), linewidth=3, label="Total abundance") lines!(f[1,1], Vector{Float32}(pops.Mating), linewidth=3, label="Mating") lines!(f[1,1], Vector{Float32}(pops.Breeding), linewidth=3, label="Breeding") @@ -126,12 +126,44 @@ function skylarkstats(model::SimulationModel) end """ - gettickmarks(dates) + croptrends(model) + +Plot a dual line graph of cropped area and average plant height per crop over time. +Returns a Makie figure object. +""" +function croptrends(model::SimulationModel) + cropdata = @data("fields") + f = Figure(size=(1000,1000)) + dates = @param(core.startdate):@param(core.enddate) + # plot cropped area over time + axlimitsarea = (1, length(dates), 0, maximum(cropdata[!,:Area])*1.2) + ax1 = Axis(f[1,1], xlabel="Date", ylabel="Cropped area (ha)", + limits=axlimitsarea, xticks=datetickmarks(dates)) + for c in unique(cropdata.Crop) + points = @select!(@subset(cropdata, :Crop .== c), :Area) + (iszero(size(points)[1]) || iszero(sum(points.Area))) && continue + lines!(f[1,1], Vector{Float32}(points.Area), linewidth=3, label=c) + end + axislegend("Crop"; position=:lt) + # plot average crop height over time + axlimitsheight = (1, length(dates), 0, maximum(cropdata[!,:Height])*1.2) + ax2 = Axis(f[2,1], xlabel="Date", ylabel="Average plant height (cm)", + limits=axlimitsheight, xticks=datetickmarks(dates)) + for c in unique(cropdata.Crop) + points = @select!(@subset(cropdata, :Crop .== c), :Height) + (iszero(size(points)[1]) || iszero(sum(points.Height))) && continue + lines!(f[2,1], Vector{Float32}(points.Height), linewidth=3, label=c) + end + f +end + +""" + datetickmarks(dates) Given a vector of dates, construct a selection to use as tick mark locations. Helper function for `[populationtrends](@ref)` """ -function gettickmarks(dates) +function datetickmarks(dates) ticks = (Int[], String[]) for i in 1:length(dates) if Day(dates[i]) == Day(1) @@ -147,5 +179,3 @@ function gettickmarks(dates) end #XXX add animation with record()? https://docs.makie.org/stable/explanations/animation/ - -#TODO causes of mortality, nesting habitats, territory sizes diff --git a/src/crop/almass.jl b/src/crop/almass.jl index 6c020cc..c6fc177 100644 --- a/src/crop/almass.jl +++ b/src/crop/almass.jl @@ -239,7 +239,7 @@ function growcrop!(cs::CropState, model::SimulationModel) points = curve.GDD[cs.phase] for p in 1:length(points) if points[p] == 99999 - cs.mature = true #FIXME is this correct? + !(cs.phase in (janfirst, sow)) && (cs.mature = true) #FIXME only in the last phase? return # the marker that there is no further growth this phase elseif points[p] == -1 # the marker to set all variables to specified values cs.height = curve.height[cs.phase][p] diff --git a/src/farm/farm.jl b/src/farm/farm.jl index a3aec89..7a3ad80 100644 --- a/src/farm/farm.jl +++ b/src/farm/farm.jl @@ -3,11 +3,6 @@ ### This file is responsible for managing the farm module(s). ### -##TODO what data do we need to gather from the farm submodel? -## - area covered by each crop over time -## - average height of each crop over time -## - total income per year - #XXX Initially, we're only working with a single simple crop rotation. # Later on, we need to figure out how to integrate several. const CROPROTATION = ["winter rape", "winter wheat", "maize", "winter barley"] @@ -79,6 +74,7 @@ function initfarms!(model::SimulationModel) farmer.sowdates[field.id] = @rand(nextcrop.minsowdate:nextcrop.maxsowdate) end end + initfarmdata(model) end """ diff --git a/src/farm/farmdata.jl b/src/farm/farmdata.jl new file mode 100644 index 0000000..945d9ef --- /dev/null +++ b/src/farm/farmdata.jl @@ -0,0 +1,41 @@ +### Persefone.jl - a model of agricultural landscapes and ecosystems in Europe. +### +### This file handles the data output of the farm submodel. +### + +""" + initfarmdata() + +Create output files for each data group collected by the farm model. +""" +function initfarmdata(model::SimulationModel) + newdataoutput!(model, "fields", ["Date", "Crop", "Area", "Height"], + @param(farm.fieldoutfreq), savefielddata, croptrends) + #XXX add later: income per year +end + +""" + savefielddata(model) + +Return a data table (to be printed to `fields.csv`), giving the current +date, and the area and average of each crop in the landscape. May be called +never, daily, monthly, yearly, or at the end of a simulation, depending on +the parameter `farm.fieldoutfreq`. +""" +function savefielddata(model::SimulationModel) + croparea = Dict(c => 0.0 for c in keys(model.crops)) + cropheights = Dict(c => [0.0, 0.0] for c in keys(model.crops)) + for f in model.farmplots + c = cropname(f) + croparea[c] += ustrip(ha, @areaof(length(f.pixels))) # total area with this crop + cropheights[c][1] += 1 # number of fields with this crop + cropheights[c][2] += ustrip(cm, cropheight(f)) # summed height of the plants of this crop + end + data = [] + for p in keys(croparea) + averageheight = cropheights[p][2] == 0 ? 0 : + round(cropheights[p][2]/cropheights[p][1], digits=2) + push!(data, [model.date, p, croparea[p], averageheight]) + end + data +end -- GitLab