/// <summary> /// Retrieves soil types from ApSoil /// Configures any missing crop ll, kl values /// </summary> /// <param name="simulation"></param> /// <param name="apsoilService"></param> /// <returns></returns> public static void DoSoils(AusFarmSpec simulation) { APSOIL.Service apsoilService = null; Soil soil; for (int i = 0; i < simulation.OnFarmSoilTypes.Count; i++) { FarmSoilType soilType = simulation.OnFarmSoilTypes[i]; soil = soilType.SoilDescr; if (soil == null) { // Look for a <SoilName> and if found go get the soil from the Apsoil web service. if (apsoilService == null) { apsoilService = new APSOIL.Service(); } string soilXml = apsoilService.SoilXML(soilType.SoilPath); if (soilXml == string.Empty) { throw new Exception("Cannot find soil: " + soilType.SoilPath); } soil = SoilUtilities.FromXML(soilXml); } // Other crop types not listed here will have their ll, kll, xf values calculated later // Remove any initwater nodes. soil.InitialWater = null; foreach (Sample sample in soil.Samples) { CheckSample(soil, sample); } // get rid of <soiltype> from the soil // this is necessary because NPD uses this field and puts in really long // descriptive classifications. Soiln2 bombs with an FString internal error. soil.SoilType = ""; // Set the soil name to 'soil' //soil.Name = "Soil"; soilType.SoilDescr = soil; //store the changed description } }
/// <summary> /// Sets the soil parameters for the crop types in this paddock /// </summary> /// <param name="soilConfig">Soil crop parameters</param> /// <param name="paddocknode">The paddock node for the paddock</param> /// <param name="maxRootDepth">The maximum root depth for any plant allowed on this soil type. mm</param> /// <returns></returns> private void SetSoilCrops(Soil soilConfig, XmlNode paddocknode, FarmSoilType soilArea) { XmlNode apsimCompNode; CropSpec crop; //set the ll, kl, xf values of the crop apsimCompNode = paddocknode.SelectSingleNode("system/component[@class=\"Plant.Wheat\"]"); crop = soilArea.CropRotationList.Find(c => c.Name.Equals("wheat", StringComparison.InvariantCultureIgnoreCase)); setSoilCrop(apsimCompNode, "wheat", soilConfig, crop); apsimCompNode = paddocknode.SelectSingleNode("system/component[@class=\"Plant.Barley\"]"); crop = soilArea.CropRotationList.Find(c => c.Name.Equals("barley", StringComparison.InvariantCultureIgnoreCase)); setSoilCrop(apsimCompNode, "barley", soilConfig, crop); apsimCompNode = paddocknode.SelectSingleNode("system/component[@class=\"Plant.Canola\"]"); crop = soilArea.CropRotationList.Find(c => c.Name.Equals("canola", StringComparison.InvariantCultureIgnoreCase)); setSoilCrop(apsimCompNode, "canola", soilConfig, crop); apsimCompNode = paddocknode.SelectSingleNode("system/component[@class=\"Plant.Oats\"]"); crop = soilArea.CropRotationList.Find(c => c.Name.Equals("oats", StringComparison.InvariantCultureIgnoreCase)); setSoilCrop(apsimCompNode, "oats", soilConfig, crop); apsimCompNode = paddocknode.SelectSingleNode("system/component[@class=\"Plant.ChickPea\"]"); crop = soilArea.CropRotationList.Find(c => c.Name.Equals("chickpea", StringComparison.InvariantCultureIgnoreCase)); setSoilCrop(apsimCompNode, "chickpea", soilConfig, crop); apsimCompNode = paddocknode.SelectSingleNode("system/component[@class=\"Plant.FieldPea\"]"); crop = soilArea.CropRotationList.Find(c => c.Name.Equals("fieldpea", StringComparison.InvariantCultureIgnoreCase)); setSoilCrop(apsimCompNode, "fieldpea", soilConfig, crop); apsimCompNode = paddocknode.SelectSingleNode("system/component[@class=\"Plant.Fababean\"]"); crop = soilArea.CropRotationList.Find(c => c.Name.Equals("fababean", StringComparison.InvariantCultureIgnoreCase)); setSoilCrop(apsimCompNode, "fababean", soilConfig, crop); apsimCompNode = paddocknode.SelectSingleNode("system/component[@class=\"Plant.Lupin\"]"); crop = soilArea.CropRotationList.Find(c => c.Name.Equals("lupin", StringComparison.InvariantCultureIgnoreCase)); setSoilCrop(apsimCompNode, "lupin", soilConfig, crop); //sorghum }
/// <summary> /// Configure the pasture components in the paddock. Set the rooting depths. /// </summary> /// <param name="defaultPaddock"></param> /// <param name="depth">The cumulative soil depth</param> /// <param name="paddocknode">The padddock to set</param> private void SetPastureComponents(FarmPaddockType defaultPaddock, double depth, XmlNode paddocknode, FarmSoilType soilArea) { XmlNodeList pastureNodes = paddocknode.SelectNodes("component[attribute::class=\"Pasture\"]"); foreach (XmlNode pastureNode in pastureNodes) { // get the current rooting depth from the pasture component in the simulation string sDepth = GetValue(pastureNode, "max_rtdep"); double maxRtDep = (sDepth.Length > 0) ? Convert.ToDouble(sDepth) : 0; string simCompName = XmlUtilities.NameAttr(pastureNode); // find one that matches in the cropping rotation list and see if it specifies a new depth CropSpec pastureCrop = soilArea.CropRotationList.Find(c => c.Name.Equals(simCompName, StringComparison.InvariantCultureIgnoreCase)); if (pastureCrop.MaxRootDepth > 0) { maxRtDep = pastureCrop.MaxRootDepth; SetValue(pastureNode, "max_rtdep", Math.Min(maxRtDep, depth * 0.95)); // allow up to 95% of soil depth } else { if (maxRtDep > (depth * 0.95)) // this won't need setting if it is less than the limit SetValue(pastureNode, "max_rtdep", Math.Min(maxRtDep, depth * 0.95)); // allow up to 95% of soil depth } } }
/// <summary> /// Initialise the SOM translator /// </summary> /// <param name="compNode"></param> /// <param name="aSoil"></param> private void initSOM_Trans(XmlNode compNode, Soil aSoil, FarmSoilType farmSoil) { TSDMLValue init = GetTypedInit(compNode, "surfaceom_types"); bool found; // for each crop type in the rotation list there should be a residue item in the translator for (int res = 0; res < farmSoil.CropRotationList.Count; res++) { string cropName = CropPhases.CropFromLanduse(farmSoil.CropRotationList[res].Name); if (CropPhases.IsValidCropName(cropName)) { uint i = 1; found = false; while (!found && (i <= init.count())) { if (init.item(i).asStr() == cropName) found = true; i++; } if (!found) { init.setElementCount(init.count() + 1); init.item(init.count()).setValue(cropName); } } } SetTypedInit(compNode, "surfaceom_types", init); //may not need to do this in an already configured simulation init = GetTypedInit(compNode, "published_events"); if (init.count() < 1) { init.setElementCount(2); init.item(1).member("name").setValue(".model.potentialresiduedecompositioncalculated"); init.item(1).member("connects").setElementCount(1); init.item(1).member("connects").item(1).setValue("..nitrogen.model.potentialresiduedecompositioncalculated"); init.item(2).member("name").setValue(".model.incorpfompool"); init.item(2).member("connects").setElementCount(1); init.item(2).member("connects").item(1).setValue("..nitrogen.model.incorpfompool"); SetTypedInit(compNode, "published_events", init); } }
/// <summary> /// Initialise the Soil water translator /// </summary> /// <param name="compNode"></param> /// <param name="aSoil"></param> private void initSoilWat_Trans(XmlNode compNode, Soil aSoil, FarmSoilType farmSoil) { TSDMLValue init = GetTypedInit(compNode, "psd"); init.member("sand").setElementCount((uint)aSoil.SoilWater.Thickness.Length); init.member("clay").setElementCount((uint)aSoil.SoilWater.Thickness.Length); double value; for (uint x = 0; x <= aSoil.SoilWater.Thickness.Length - 1; x++) { if ((aSoil.Analysis.ParticleSizeSand != null) && (aSoil.Analysis.ParticleSizeSand.Length > x)) { value = aSoil.Analysis.ParticleSizeSand[x]; if (value.Equals(double.NaN)) value = 0.0; // probably should be interpolated from any existing values init.member("sand").item(x + 1).setValue(value * 0.01); value = aSoil.Analysis.ParticleSizeClay[x]; if (value.Equals(double.NaN)) value = 0.0; init.member("clay").item(x + 1).setValue(value * 0.01); } else { // ## not suitable but it will allow the simulation to run init.member("sand").item(x + 1).setValue(0.0); init.member("clay").item(x + 1).setValue(0.0); } } SetTypedInit(compNode, "psd", init); // configure the published events for this translator so each crop component in the rotation is supported init = GetTypedInit(compNode, "published_events"); // if not the correct number of published events if (init.count() < 2) { init.setElementCount(2); init.item(1).member("name").setValue(".model.new_profile"); init.item(1).member("connects").setElementCount(1); init.item(1).member("connects").item(1).setValue("..nitrogen.model.new_profile"); init.item(2).member("name").setValue(".model.nitrogenchanged"); init.item(2).member("connects").setElementCount(1); init.item(2).member("connects").item(1).setValue("..nitrogen.model.nitrogenchanged"); } // now ensure that the connections go to each crop component // there should be a connection item for every plant component in the paddock TTypedValue connects = null; uint idx = 1; // find the correct item in the published events array while ((connects == null) && idx <= 2) { if (init.item(idx).member("name").asStr() == ".model.new_profile") connects = init.item(idx).member("connects"); idx++; } string connectionName; // for each item in the crop rotation list for (int crop = 0; crop < farmSoil.CropRotationList.Count; crop++) { string landuse = farmSoil.CropRotationList[crop].Name; string cropName = CropPhases.CropFromLanduse(landuse); if (CropPhases.IsValidCropName(cropName)) { idx = 1; bool found = false; while (!found && (idx <= connects.count())) { connectionName = connects.item(idx).asStr().ToLower(); if (connectionName.Contains(cropName.ToLower())) found = true; idx++; } //if not found int the connections then add it to the list if (!found) { connects.setElementCount(connects.count() + 1); connects.item(connects.count()).setValue(".." + cropName + ".model.new_profile"); } } } SetTypedInit(compNode, "published_events", init); }