/// <summary>Estimates the amount of plant available water in each soil layer of the root zone.</summary>
        /// <remarks>
        /// This is an alternative method, which does not use kl. A factor based on Ksat is used instead. This is further modified
        ///  by soil water content and a plant related factor, defined based on root length density. All three factors are normalised
        ///  (using ReferenceKSat and ReferenceRLD for KSat and root and DUL for soil water content). The effect of all factors are
        ///  assumed to vary between zero and one following exponential functions, such that the effect is 90% at the reference value.
        /// </remarks>
        /// <param name="myZone">The soil information</param>
        /// <returns>The amount of available water in each layer (mm)</returns>
        internal double[] PlantAvailableSoilWaterAlternativeKS(ZoneWaterAndN myZone)
        {
            double[] result       = new double[nLayers];
            SoilCrop soilCropData = (SoilCrop)mySoil.Crop(mySpeciesName);

            for (int layer = 0; layer <= BottomLayer; layer++)
            {
                double condFac = 1.0 - Math.Pow(10.0, -mySoil.KS[layer] / myReferenceKSuptake);
                double rldFac  = 1.0 - Math.Pow(10.0, -RootLengthDensity[layer] / myReferenceRLD);
                double swFac;
                if (mySoil.SoilWater.SWmm[layer] >= mySoil.DULmm[layer])
                {
                    swFac = 1.0;
                }
                else if (mySoil.SoilWater.SWmm[layer] <= mySoil.LL15mm[layer])
                {
                    swFac = 0.0;
                }
                else
                {
                    double waterRatio = (myZone.Water[layer] - mySoil.LL15mm[layer]) /
                                        (mySoil.DULmm[layer] - mySoil.LL15mm[layer]);
                    swFac = 1.0 - Math.Pow(1.0 - waterRatio, myExponentSoilMoisture);
                }

                // Total available water
                result[layer] = Math.Max(0.0, myZone.Water[layer] - soilCropData.LL[layer]) * mySoil.Thickness[layer];

                // Actual plant available water
                result[layer] *= FractionLayerWithRoots(layer) * Math.Min(1.0, rldFac * condFac * swFac);
            }

            return(result);
        }
Beispiel #2
0
        /// <summary>
        /// Growth depth of roots in this zone
        /// </summary>
        public void GrowRootDepth()
        {
            // Do Root Front Advance
            int RootLayer = Soil.LayerIndexOfDepth(Depth, soil.Thickness);

            SoilCrop crop = soil.Crop(plant.Name) as SoilCrop;

            Depth = Depth + root.RootFrontVelocity.Value * crop.XF[RootLayer];

            // Limit root depth for impeded layers
            double MaxDepth = 0;

            for (int i = 0; i < soil.Thickness.Length; i++)
            {
                if (crop.XF[i] > 0)
                {
                    MaxDepth += soil.Thickness[i];
                }
            }

            // Limit root depth for the crop specific maximum depth
            MaxDepth = Math.Min(root.MaximumRootDepth.Value, MaxDepth);

            Depth = Math.Min(Depth, MaxDepth);
        }
Beispiel #3
0
        //----------------------- Public methods -----------------------

        /// <summary>Initialise this root instance (and tissues).</summary>
        /// <param name="zone">The zone the roots belong to.</param>
        /// <param name="minimumLiveWt">Minimum live DM biomass for this organ (kg/ha).</param>
        public void Initialise(Zone zone, double minimumLiveWt)
        {
            // link to soil models parameters
            soil = zone.FindInScope <Soil>();
            if (soil == null)
            {
                throw new Exception($"Cannot find soil in zone {zone.Name}");
            }

            soilPhysical = soil.FindInScope <IPhysical>();
            if (soilPhysical == null)
            {
                throw new Exception($"Cannot find soil physical in soil {soil.Name}");
            }

            waterBalance = soil.FindInScope <ISoilWater>();
            if (waterBalance == null)
            {
                throw new Exception($"Cannot find a water balance model in soil {soil.Name}");
            }

            soilCropData = soil.FindDescendant <SoilCrop>(species.Name + "Soil");
            if (soilCropData == null)
            {
                throw new Exception($"Cannot find a soil crop parameterisation called {species.Name + "Soil"}");
            }

            nutrient = zone.FindInScope <INutrient>();
            if (nutrient == null)
            {
                throw new Exception($"Cannot find SoilNitrogen in zone {zone.Name}");
            }

            no3 = zone.FindInScope("NO3") as ISolute;
            if (no3 == null)
            {
                throw new Exception($"Cannot find NO3 solute in zone {zone.Name}");
            }

            nh4 = zone.FindInScope("NH4") as ISolute;
            if (nh4 == null)
            {
                throw new Exception($"Cannot find NH4 solute in zone {zone.Name}");
            }

            // initialise soil related variables
            zoneName             = soil.Parent.Name;
            nLayers              = soilPhysical.Thickness.Length;
            mySoilNH4Available   = new double[nLayers];
            mySoilNO3Available   = new double[nLayers];
            mySoilWaterAvailable = new double[nLayers];

            // save minimum DM and get target root distribution
            MinimumLiveDM      = minimumLiveWt;
            TargetDistribution = RootDistributionTarget();

            // initialise tissues
            Live.Initialise();
            Dead.Initialise();
        }
        /// <summary>
        /// Formats the GridView. Sets colours, spacing, locks the
        /// depth column, etc.
        /// </summary>
        private void FormatGrid()
        {
            for (int i = 0; i < properties.Count; i++)
            {
                // fixme - ugly hack to work around last column being very wide
                if (i != properties.Count - 1)
                {
                    grid.GetColumn(i).LeftJustification = false;
                }

                grid.GetColumn(i).HeaderLeftJustification = false;
                VariableProperty property = properties[i] as VariableProperty;
                if (!(property.Object is SoilCrop))
                {
                    continue;
                }

                SoilCrop crop       = property.Object as SoilCrop;
                int      index      = Apsim.Children(crop.Parent, typeof(SoilCrop)).IndexOf(crop);
                Color    foreground = ColourUtilities.ChooseColour(index);
                if (property.IsReadOnly)
                {
                    foreground = Color.Red;
                }

                grid.GetColumn(i).ForegroundColour = foreground;
                grid.GetColumn(i).MinimumWidth     = 70;
                grid.GetColumn(i).ReadOnly         = property.IsReadOnly;
            }
            grid.LockLeftMostColumns(1);
        }
Beispiel #5
0
        public void ImporterTests_SoilImports()
        {
            string oldXml = ReflectionUtilities.GetResourceAsString("UnitTests.ImporterTestsSoilImports.xml");

            APSIMImporter importer = new APSIMImporter();
            Simulations   sims     = importer.CreateSimulationsFromXml(oldXml);

            Soil s = sims.Children[0].Children[0] as Soil;

            Assert.AreEqual(s.Name, "Soil");

            InitialWater initWater = s.Children[0] as InitialWater;

            Assert.AreEqual(initWater.FractionFull, 0.5);
            Assert.AreEqual(initWater.PercentMethod, InitialWater.PercentMethodEnum.FilledFromTop);

            Water w = s.Children[1] as Water;

            Assert.AreEqual(w.Thickness, new double[] { 150, 150, 300, 300 });
            Assert.AreEqual(w.BD, new double[] { 1.02, 1.03, 1.02, 1.02 });
            Assert.AreEqual(w.LL15, new double[] { 0.29, 0.29, 0.29, 0.29 });

            SoilWater sw = s.Children[2] as SoilWater;

            Assert.AreEqual(sw.Thickness, new double[] { 150, 150, 300, 300 });
            Assert.AreEqual(sw.SWCON, new double[] { 0.3, 0.3, 0.3, 0.3 });
            Assert.AreEqual(sw.SummerCona, 3.5);
            Assert.AreEqual(sw.SummerU, 6);
            Assert.AreEqual(sw.WinterCona, 2);
            Assert.AreEqual(sw.WinterU, 2);

            Assert.IsTrue(s.Children[3] is SoilNitrogen);

            SoilOrganicMatter som = s.Children[4] as SoilOrganicMatter;

            Assert.AreEqual(som.Thickness, new double[] { 150, 150, 300, 300 });
            Assert.AreEqual(som.OC, new double[] { 1.04, 0.89, 0.89, 0.89 });
            Assert.AreEqual(som.FBiom, new double[] { 0.025, 0.02, 0.015, 0.01 });

            Analysis a = s.Children[5] as Analysis;

            Assert.AreEqual(a.Thickness, new double[] { 150, 150, 300, 300 });
            Assert.AreEqual(a.EC, new double[] { 0.2, 0.25, 0.31, 0.40 });
            Assert.AreEqual(a.PH, new double[] { 8.4, 8.8, 9.0, 9.2 });

            Sample sam = s.Children[6] as Sample;

            Assert.AreEqual(sam.Thickness, new double[] { 150, 150, 300 });
            Assert.AreEqual(sam.NO3, new double[] { 6.5, 2.1, 2.1 });
            Assert.AreEqual(sam.NH4, new double[] { 0.5, 0.1, 0.1 });

            SoilCrop crop = s.Children[1].Children[0] as SoilCrop;

            Assert.AreEqual(crop.Thickness, new double[] { 150, 150, 300, 300 });
            Assert.AreEqual(crop.LL, new double[] { 0.29, 0.29, 0.32, 0.38 });
            Assert.AreEqual(crop.KL, new double[] { 0.1, 0.1, 0.08, 0.06 });
            Assert.AreEqual(crop.XF, new double[] { 1, 1, 1, 1 });
        }
Beispiel #6
0
 private void OnSimulationCommencing(object sender, EventArgs e)
 {
     soilCrop = this.Soil.Crop(this.Plant.Name) as SoilCrop;
     if (soilCrop == null)
     {
         throw new ApsimXException(this, "Cannot find a soil crop parameterisation for " + Name);
     }
     Clear();
 }
Beispiel #7
0
        /// <summary>
        /// Setup the grid as a water grid.
        /// </summary>
        private void AddCropColumns()
        {
            Color[] CropColors          = { Color.FromArgb(173, 221, 142), Color.FromArgb(247, 252, 185) };
            Color[] PredictedCropColors = { Color.FromArgb(233, 191, 255), Color.FromArgb(244, 226, 255) };

            //            DataGridViewColumn SAT = Grid.Columns["SAT\r\n(mm/mm)"];
            //            SAT.Frozen = true;
            Grid.Columns[Grid.ColumnCount - 1].Frozen = true;

            int CropIndex          = 0;
            int PredictedCropIndex = 0;

            foreach (string CropName in Soil.CropNames.Union(Soil.PredictedCropNames, StringComparer.OrdinalIgnoreCase))
            {
                SoilCrop Crop = Soil.Crop(CropName);

                bool  IsReadonly;
                Color CropColour;
                Color ForeColour = Color.Black;
                if (Crop.LLMetadata != null && Crop.LLMetadata.First() == "Estimated")
                {
                    CropColour = PredictedCropColors[PredictedCropIndex];
                    ForeColour = Color.Gray;
                    IsReadonly = true;
                    PredictedCropIndex++;
                    if (PredictedCropIndex >= PredictedCropColors.Length)
                    {
                        PredictedCropIndex = 0;
                    }
                }
                else
                {
                    CropColour = CropColors[CropIndex];
                    IsReadonly = false;
                    CropIndex++;
                    if (CropIndex >= CropColors.Length)
                    {
                        CropIndex = 0;
                    }
                }

                double[] PAWCmm = MathUtility.Multiply(Soil.PAWCCropAtWaterThickness(CropName),
                                                       Soil.Water.Thickness);

                DataGridViewColumn LL   = GridUtility.AddColumn(Grid, CropName + " LL\r\n(mm/mm)", Crop.LL, "f3", CropColour, ForeColour, ToolTips: Crop.LLMetadata, ReadOnly: IsReadonly);
                DataGridViewColumn PAWC = GridUtility.AddColumn(Grid, CropName + " PAWC\r\n", PAWCmm, "f1", CropColour, Color.Gray,
                                                                ReadOnly: true,
                                                                ToolTips: StringManip.CreateStringArray("Calculated from crop LL and DUL", PAWCmm.Length));
                DataGridViewColumn KL = GridUtility.AddColumn(Grid, CropName + " KL\r\n(/day)", Crop.KL, "f2", CropColour, ForeColour, ToolTips: Crop.KLMetadata, ReadOnly: IsReadonly);
                DataGridViewColumn XF = GridUtility.AddColumn(Grid, CropName + " XF\r\n(0-1)", Crop.XF, "f1", CropColour, ForeColour, ToolTips: Crop.XFMetadata, ReadOnly: IsReadonly);

                PAWC.ToolTipText = "Calculated from crop LL and DUL";
                PAWC.ReadOnly    = true;
                UpdateTotal(PAWC);
            }
        }
Beispiel #8
0
        /// <summary>Convert the crop to the specified thickness. Ensures LL is between AirDry and DUL.</summary>
        /// <param name="crop">The crop to convert</param>
        /// <param name="thickness">The thicknesses to convert the crop to.</param>
        /// <param name="physical">The soil the crop belongs to.</param>
        private static void SetCropThickness(SoilCrop crop, double[] thickness, IPhysical physical)
        {
            if (!MathUtilities.AreEqual(thickness, physical.Thickness))
            {
                crop.LL = MapConcentration(crop.LL, physical.Thickness, thickness, MathUtilities.LastValue(crop.LL));
                crop.KL = MapConcentration(crop.KL, physical.Thickness, thickness, MathUtilities.LastValue(crop.KL));
                crop.XF = MapConcentration(crop.XF, physical.Thickness, thickness, MathUtilities.LastValue(crop.XF));

                crop.LL = MathUtilities.Constrain(crop.LL, AirDryMapped(physical, thickness), DULMapped(physical, thickness));
            }
        }
Beispiel #9
0
        private void OnSimulationCommencing(object sender, EventArgs e)
        {
            Uptakes = new List <ZoneWaterAndN>();
            EP      = 0;

            soilCrop = Soil.FindDescendant <SoilCrop>(Name + "Soil");
            if (soilCrop == null)
            {
                throw new Exception($"Cannot find a soil crop parameterisation called {Name}Soil");
            }
        }
Beispiel #10
0
        public void ImporterTests_SoilImports()
        {
            string oldXml = ReflectionUtilities.GetResourceAsString("UnitTests.ImporterTestsSoilImports.xml");

            var         importer = new Importer();
            Simulations sims     = importer.CreateSimulationsFromXml(oldXml);

            Soil s = sims.Children[0].Children[0] as Soil;

            Assert.AreEqual(s.Name, "Soil");

            InitialWater initWater = s.Children[0] as InitialWater;

            Assert.AreEqual(initWater.FractionFull, 0.5);
            Assert.AreEqual(initWater.PercentMethod, InitialWater.PercentMethodEnum.FilledFromTop);

            Physical w = s.Children[1] as Physical;

            Assert.AreEqual(w.Thickness, new double[] { 150, 150, 300, 300 });
            Assert.AreEqual(w.BD, new double[] { 1.02, 1.03, 1.02, 1.02 });
            Assert.AreEqual(w.LL15, new double[] { 0.29, 0.29, 0.29, 0.29 });

            ISoilWater sw = s.Children[2] as ISoilWater;

            Assert.AreEqual(sw.Thickness, new double[] { 150, 150, 300, 300 });

            Assert.IsTrue(s.Children[3] is SoilNitrogen);
            Assert.IsTrue(s.Children[4] is CERESSoilTemperature);
            Organic som = s.Children[5] as Organic;

            Assert.AreEqual(som.Thickness, new double[] { 150, 150, 300, 300 });
            Assert.AreEqual(som.Carbon, new double[] { 1.04, 0.89, 0.89, 0.89 });
            Assert.AreEqual(som.FBiom, new double[] { 0.025, 0.02, 0.015, 0.01 });

            Chemical a = s.Children[6] as Chemical;

            Assert.AreEqual(a.Thickness, new double[] { 150, 150, 300, 300 });
            Assert.AreEqual(a.NO3N, new double[] { 6.5, 2.1, 2.1, 1.0 });
            Assert.AreEqual(a.NH4N, new double[] { 0.5, 0.1, 0.1, 0.2 });
            Assert.AreEqual(a.EC, new double[] { 0.2, 0.25, 0.31, 0.40 });
            Assert.AreEqual(a.PH, new double[] { 8.4, 8.8, 9.0, 9.2 });

            Sample sam = s.Children[7] as Sample;

            Assert.AreEqual(sam.Thickness, new double[] { 150, 150, 300 });

            SoilCrop crop = s.Children[1].Children[0] as SoilCrop;

            Assert.AreEqual(crop.LL, new double[] { 0.29, 0.29, 0.32, 0.38 });
            Assert.AreEqual(crop.KL, new double[] { 0.1, 0.1, 0.08, 0.06 });
            Assert.AreEqual(crop.XF, new double[] { 1, 1, 1, 1 });
        }
        /// <summary>Estimates the amount of plant available water in each soil layer of the root zone.</summary>
        /// <remarks>This is the default APSIM method, with kl representing the daily rate for water extraction</remarks>
        /// <param name="myZone">The soil information</param>
        /// <returns>The amount of available water in each layer (mm)</returns>
        internal double[] PlantAvailableSoilWaterDefault(ZoneWaterAndN myZone)
        {
            double[] result       = new double[nLayers];
            SoilCrop soilCropData = (SoilCrop)mySoil.Crop(mySpeciesName);

            for (int layer = 0; layer <= BottomLayer; layer++)
            {
                result[layer]  = Math.Max(0.0, myZone.Water[layer] - (soilCropData.LL[layer] * mySoil.Thickness[layer]));
                result[layer] *= FractionLayerWithRoots(layer) * soilCropData.KL[layer] * KLModiferDueToDamage(layer);
            }

            return(result);
        }
        /// <summary>Computes the target (or ideal) distribution of roots in the soil profile.</summary>
        /// <remarks>
        /// This distribution is solely based on root parameters (maximum depth and distribution parameters)
        /// These values will be used to allocate initial rootDM as well as any growth over the profile
        /// </remarks>
        /// <returns>A weighting factor for each soil layer (mm equivalent)</returns>
        public double[] RootDistributionTarget()
        {
            // 1. Base distribution calculated using a combination of linear and power functions:
            //  It considers homogeneous distribution from surface down to a fraction of root depth (DepthForConstantRootProportion),
            //   below this depth the proportion of root decrease following a power function (with exponent ExponentRootDistribution),
            //   it reaches zero slightly below the MaximumRootDepth (defined by rootBottomDistributionFactor), but the function is
            //   truncated at MaximumRootDepth. The values are not normalised.
            //  The values are further adjusted using the values of XF (so there will be less roots in those layers)

            double[] result          = new double[nLayers];
            SoilCrop soilCropData    = (SoilCrop)mySoil.Crop(mySpeciesName);
            double   depthTop        = 0.0;
            double   depthBottom     = 0.0;
            double   depthFirstStage = Math.Min(myRootDepthMaximum, myRootDistributionDepthParam);

            for (int layer = 0; layer < nLayers; layer++)
            {
                depthBottom += mySoil.Thickness[layer];
                if (depthTop >= myRootDepthMaximum)
                {
                    // totally out of root zone
                    result[layer] = 0.0;
                }
                else if (depthBottom <= depthFirstStage)
                {
                    // totally in the first stage
                    result[layer] = mySoil.Thickness[layer] * soilCropData.XF[layer];
                }
                else
                {
                    // at least partially on second stage
                    double maxRootDepth = myRootDepthMaximum * myRootBottomDistributionFactor;
                    result[layer] = Math.Pow(maxRootDepth - Math.Max(depthTop, depthFirstStage), myRootDistributionExponent + 1)
                                    - Math.Pow(maxRootDepth - Math.Min(depthBottom, myRootDepthMaximum), myRootDistributionExponent + 1);
                    result[layer] /= (myRootDistributionExponent + 1) * Math.Pow(maxRootDepth - depthFirstStage, myRootDistributionExponent);
                    if (depthTop < depthFirstStage)
                    {
                        // partially in first stage
                        result[layer] += depthFirstStage - depthTop;
                    }

                    result[layer] *= soilCropData.XF[layer];
                }

                depthTop += mySoil.Thickness[layer];
            }

            return(result);
        }
Beispiel #13
0
 /// <summary>
 ///  Save all crop columns.
 /// </summary>
 private void SaveCropColumns()
 {
     for (int Col = 7; Col < Grid.Columns.Count; Col += 4)
     {
         string   CropName = CropNameFromColumn(Col);
         SoilCrop Crop     = Soil.Crop(CropName);
         if (Crop.LLMetadata == null || Crop.LLMetadata.First() != "Estimated")
         {
             Crop.Thickness  = Soil.ToThickness(GridUtility.GetColumnAsStrings(Grid, 0));
             Crop.LL         = GridUtility.GetColumnAsDoubles(Grid, Col);
             Crop.LLMetadata = GridUtility.GetColumnOfToolTips(Grid, Col);
             Crop.KL         = GridUtility.GetColumnAsDoubles(Grid, Col + 2);
             Crop.KLMetadata = GridUtility.GetColumnOfToolTips(Grid, Col + 2);
             Crop.XF         = GridUtility.GetColumnAsDoubles(Grid, Col + 3);
             Crop.XFMetadata = GridUtility.GetColumnOfToolTips(Grid, Col + 3);
         }
     }
 }
Beispiel #14
0
        /// <summary>Test setup routine.</summary>
        /// <returns>A soil that can be used for testing.</returns>
        private static Soil Setup()
        {
            Soil soil = new Soil();

            soil.Water           = new Water();
            soil.Water.Thickness = new double[] { 100, 300, 300, 300, 300, 300 };
            soil.Water.BD        = new double[] { 1.36, 1.216, 1.24, 1.32, 1.372, 1.368 };
            soil.Water.AirDry    = new double[] { 0.135, 0.214, 0.261, 0.261, 0.261, 0.261 };
            soil.Water.LL15      = new double[] { 0.27, 0.267, 0.261, 0.261, 0.261, 0.261 };
            soil.Water.DUL       = new double[] { 0.365, 0.461, 0.43, 0.412, 0.402, 0.404 };

            // Add a wheat crop.
            SoilCrop crop = new SoilCrop();

            crop.Thickness   = soil.Water.Thickness;
            crop.Name        = "Wheat";
            crop.KL          = new double[] { 0.06, 0.060, 0.060, 0.060, 0.060, 0.060 };
            crop.LL          = new double[] { 0.27, 0.267, 0.261, 0.315, 0.402, 0.402 };
            soil.Water.Crops = new List <SoilCrop>();
            soil.Water.Crops.Add(crop);

            // Add OC values into SoilOrganicMatter.
            soil.SoilOrganicMatter           = new SoilOrganicMatter();
            soil.SoilOrganicMatter.Thickness = soil.Water.Thickness;
            soil.SoilOrganicMatter.OC        = new double[] { 2, 1, 0.5, 0.4, 0.3, 0.2 };

            // Add in CL into analysis.
            soil.Analysis           = new Analysis();
            soil.Analysis.Thickness = soil.Water.Thickness;
            soil.Analysis.CL        = new double[] { 38, double.NaN, 500, 490, 500, 500 };

            // Add a sample.
            Sample sample = new Sample();

            sample.Thickness = new double[] { 100, 300, 300, 300 };
            sample.SW        = new double[] { 0.103, 0.238, 0.253, 0.247 };
            sample.NO3       = new double[] { 23, 7, 2, 1 };
            sample.OC        = new double[] { 1.35, double.NaN, double.NaN, double.NaN };
            sample.SWUnits   = Sample.SWUnitsEnum.Gravimetric;
            soil.Samples     = new List <Sample>();
            soil.Samples.Add(sample);
            return(soil);
        }
Beispiel #15
0
        /// <summary>Calculate the potential sw uptake for today</summary>
        /// <param name="soilstate"></param>
        /// <returns>list of uptakes</returns>
        /// <exception cref="ApsimXException">Could not find root zone in Zone  + this.Parent.Name +  for SimpleTree</exception>
        public List <ZoneWaterAndN> GetWaterUptakeEstimates(SoilState soilstate)
        {
            ZoneWaterAndN MyZone = new ZoneWaterAndN(this.Parent as Zone);

            foreach (ZoneWaterAndN Z in soilstate.Zones)
            {
                if (Z.Zone.Name == this.Parent.Name)
                {
                    MyZone = Z;
                }
            }


            double[] PotSWUptake = new double[Soil.LL15.Length];
            SWUptake = new double[Soil.LL15.Length];

            SoilCrop soilCrop = Soil.Crop(this.Name) as SoilCrop;

            for (int j = 0; j < Soil.LL15mm.Length; j++)
            {
                PotSWUptake[j] = Math.Max(0.0, RootProportion(j, RootDepth) * soilCrop.KL[j] * (MyZone.Water[j] - Soil.LL15mm[j]));
            }

            double TotPotSWUptake = MathUtilities.Sum(PotSWUptake);

            for (int j = 0; j < Soil.LL15mm.Length; j++)
            {
                SWUptake[j] = PotSWUptake[j] * Math.Min(1.0, PotentialEP / TotPotSWUptake);
            }

            List <ZoneWaterAndN> Uptakes = new List <ZoneWaterAndN>();
            ZoneWaterAndN        Uptake  = new ZoneWaterAndN(this.Parent as Zone);

            Uptake.Water = SWUptake;
            Uptake.NO3N  = new double[SWUptake.Length];
            Uptake.NH4N  = new double[SWUptake.Length];
            Uptake.NH4N  = new double[SWUptake.Length];
            Uptake.PlantAvailableNO3N = new double[SWUptake.Length];
            Uptake.PlantAvailableNH4N = new double[SWUptake.Length];
            Uptakes.Add(Uptake);
            return(Uptakes);
        }
Beispiel #16
0
        /// <summary>Checks the crop for missing values.</summary>
        /// <param name="crop">The crop.</param>
        /// <param name="soil">The soil.</param>
        private static void CheckCropForMissingValues(SoilCrop crop, Soil soil)
        {
            var water = Apsim.Child(soil, typeof(Water)) as Water;

            for (int i = 0; i < crop.Thickness.Length; i++)
            {
                if (crop.LL != null && double.IsNaN(crop.LL[i]))
                {
                    crop.LL[i] = water.LL15[i];
                }
                if (crop.KL != null && double.IsNaN(crop.KL[i]))
                {
                    crop.KL[i] = 0.06;
                }
                if (crop.XF != null && double.IsNaN(crop.XF[i]))
                {
                    crop.XF[i] = 1;
                }
            }
        }
Beispiel #17
0
        /// <summary>Checks the crop for missing values.</summary>
        /// <param name="crop">The crop.</param>
        /// <param name="soil">The soil.</param>
        private static void CheckCropForMissingValues(SoilCrop crop, Soil soil)
        {
            var water = soil.FindChild <Physical>();

            for (int i = 0; i < water.Thickness.Length; i++)
            {
                if (crop.LL != null && double.IsNaN(crop.LL[i]))
                {
                    crop.LL[i] = water.LL15[i];
                }
                if (crop.KL != null && double.IsNaN(crop.KL[i]))
                {
                    crop.KL[i] = 0.06;
                }
                if (crop.XF != null && double.IsNaN(crop.XF[i]))
                {
                    crop.XF[i] = 1;
                }
            }
        }
Beispiel #18
0
        /// <summary>Placeholder for SoilArbitrator</summary>
        /// <param name="soilstate">soil state</param>
        /// <returns></returns>
        public List <ZoneWaterAndN> GetNUptakes(SoilState soilstate)
        {
            ZoneWaterAndN MyZone = new ZoneWaterAndN();

            foreach (ZoneWaterAndN Z in soilstate.Zones)
            {
                if (Z.Name == this.Parent.Name)
                {
                    MyZone = Z;
                }
            }

            double[] PotNO3Uptake = new double[Soil.NO3N.Length];
            double[] PotNH4Uptake = new double[Soil.NH4N.Length];
            NO3Uptake = new double[Soil.NO3N.Length];
            NH4Uptake = new double[Soil.NH4N.Length];

            SoilCrop soilCrop = Soil.Crop(this.Name) as SoilCrop;

            for (int j = 0; j < Soil.SoilWater.LL15mm.Length; j++)
            {
                PotNO3Uptake[j] = Math.Max(0.0, RootProportion(j, RootDepth) * soilCrop.KL[j] * MyZone.NO3N[j]);
                PotNH4Uptake[j] = Math.Max(0.0, RootProportion(j, RootDepth) * soilCrop.KL[j] * MyZone.NH4N[j]);
            }
            double TotPotNUptake = MathUtilities.Sum(PotNO3Uptake) + MathUtilities.Sum(PotNH4Uptake);

            for (int j = 0; j < Soil.NO3N.Length; j++)
            {
                NO3Uptake[j] = PotNO3Uptake[j] * Math.Min(1.0, NDemand / TotPotNUptake);
                NH4Uptake[j] = PotNH4Uptake[j] * Math.Min(1.0, NDemand / TotPotNUptake);
            }
            List <ZoneWaterAndN> Uptakes = new List <ZoneWaterAndN>();
            ZoneWaterAndN        Uptake  = new ZoneWaterAndN();

            Uptake.Name  = this.Parent.Name;
            Uptake.NO3N  = NO3Uptake;
            Uptake.NH4N  = NH4Uptake;
            Uptake.Water = new double[NO3Uptake.Length];
            Uptakes.Add(Uptake);
            return(Uptakes);
        }
Beispiel #19
0
        /// <summary>Fills in KL for crop.</summary>
        /// <param name="crop">The crop.</param>
        private static void FillInKLForCrop(SoilCrop crop)
        {
            if (crop.Name == null)
            {
                throw new Exception("Crop has no name");
            }
            int i = StringUtilities.IndexOfCaseInsensitive(cropNames, crop.Name);

            if (i != -1)
            {
                double[] KLs = GetRowOfArray(defaultKLs, i);

                double[] cumThickness = APSIM.Shared.APSoil.SoilUtilities.ToCumThickness(crop.Thickness);
                crop.KL = new double[crop.Thickness.Length];
                for (int l = 0; l < crop.Thickness.Length; l++)
                {
                    bool didInterpolate;
                    crop.KL[l] = MathUtilities.LinearInterpReal(cumThickness[l], defaultKLThickness, KLs, out didInterpolate);
                }
            }
        }
Beispiel #20
0
        /// <summary>
        /// Modify the KL values for subsoil constraints.
        /// </summary>
        /// <remarks>
        /// From:
        /// Hochman, Z., Dang, Y.P., Schwenke, G.D., Dalgliesh, N.P., Routley, R., McDonald, M.,
        ///     Daniells, I.G., Manning, W., Poulton, P.L., 2007.
        ///     Simulating the effects of saline and sodic subsoils on wheat crops
        ///     growing on Vertosols. Australian Journal of Agricultural Research 58, 802–810. doi:10.1071/ar06365
        /// </remarks>
        /// <param name="crop"></param>
        /// <param name="soil">The soil the crop belongs to.</param>
        private static void ModifyKLForSubSoilConstraints(SoilCrop crop, Soil soil)
        {
            var soilPhysical      = soil.FindChild <IPhysical>();
            var initialConditions = soil.Children.Find(child => child is Sample) as Sample;

            double[] cl = initialConditions.CL;
            if (MathUtilities.ValuesInArray(cl))
            {
                crop.KL = Layers.MapConcentration(StandardKL, StandardThickness, soilPhysical.Thickness, StandardKL.Last());
                for (int i = 0; i < soilPhysical.Thickness.Length; i++)
                {
                    crop.KL[i] *= Math.Min(1.0, 4.0 * Math.Exp(-0.005 * cl[i]));
                }
            }
            else
            {
                double[] esp = initialConditions.ESP;
                if (MathUtilities.ValuesInArray(esp))
                {
                    crop.KL = Layers.MapConcentration(StandardKL, StandardThickness, soilPhysical.Thickness, StandardKL.Last());
                    for (int i = 0; i < soilPhysical.Thickness.Length; i++)
                    {
                        crop.KL[i] *= Math.Min(1.0, 10.0 * Math.Exp(-0.15 * esp[i]));
                    }
                }
                else
                {
                    double[] ec = initialConditions.EC;
                    if (MathUtilities.ValuesInArray(ec))
                    {
                        crop.KL = Layers.MapConcentration(StandardKL, StandardThickness, soilPhysical.Thickness, StandardKL.Last());
                        for (int i = 0; i < soilPhysical.Thickness.Length; i++)
                        {
                            crop.KL[i] *= Math.Min(1.0, 3.0 * Math.Exp(-1.3 * ec[i]));
                        }
                    }
                }
            }
        }
Beispiel #21
0
        /// <summary>Gets or sets the water supply.</summary>
        /// <param name="zone">The zone.</param>
        public double[] CalculateWaterSupply(ZoneWaterAndN zone)
        {
            ZoneState myZone = Zones.Find(z => z.Name == zone.Zone.Name);

            if (myZone == null)
            {
                return(null);
            }

            SoilCrop crop = myZone.soil.Crop(Plant.Name) as SoilCrop;

            double[] supply         = new double[myZone.soil.Thickness.Length];
            double[] layerMidPoints = Soil.ToMidPoints(myZone.soil.Thickness);
            for (int layer = 0; layer < myZone.soil.Thickness.Length; layer++)
            {
                if (layer <= Soil.LayerIndexOfDepth(myZone.Depth, myZone.soil.Thickness))
                {
                    supply[layer] = Math.Max(0.0, crop.KL[layer] * KLModifier.ValueForX(layerMidPoints[layer]) *
                                             (zone.Water[layer] - crop.LL[layer] * myZone.soil.Thickness[layer]) * Soil.ProportionThroughLayer(layer, myZone.Depth, myZone.soil.Thickness));
                }
            }

            return(supply);
        }
Beispiel #22
0
        /// <summary>Do all soil related settings.</summary>
        /// <param name="simulation">The specification to use</param>
        /// <param name="workingFolder">The folder where files shoud be created.</param>
        private static void DoSoil(APSIMSpecification simulation, string workingFolder)
        {
            Soil soil;

            if (simulation.Soil == null)
            {
                if (simulation.SoilPath.StartsWith("<Soil"))
                {
                    // Soil and Landscape grid
                    XmlDocument doc = new XmlDocument();
                    doc.LoadXml(simulation.SoilPath);
                    soil = XmlUtilities.Deserialise(doc.DocumentElement, typeof(Soil)) as Soil;
                }
                else if (simulation.SoilPath.StartsWith("http"))
                {
                    // Soil and Landscape grid
                    string xml;
                    using (var client = new WebClient())
                    {
                        xml = client.DownloadString(simulation.SoilPath);
                    }
                    XmlDocument doc = new XmlDocument();
                    doc.LoadXml(xml);
                    List <XmlNode> soils = XmlUtilities.ChildNodes(doc.DocumentElement, "Soil");
                    if (soils.Count == 0)
                    {
                        throw new Exception("Cannot find soil in Soil and Landscape Grid");
                    }
                    soil = XmlUtilities.Deserialise(soils[0], typeof(Soil)) as Soil;
                }
                else
                {
                    // Apsoil web service.
                    APSOIL.Service apsoilService = new APSOIL.Service();
                    string         soilXml       = apsoilService.SoilXML(simulation.SoilPath.Replace("\r\n", ""));
                    if (soilXml == string.Empty)
                    {
                        throw new Exception("Cannot find soil: " + simulation.SoilPath);
                    }
                    soil = SoilUtilities.FromXML(soilXml);
                }
            }
            else
            {
                // Just use the soil we already have
                soil = simulation.Soil;
            }

            // Make sure we have a soil crop parameterisation. If not then try creating one
            // based on wheat.
            Sow sowing = YieldProphetUtility.GetCropBeingSown(simulation.Management);

            string[] cropNames = soil.Water.Crops.Select(c => c.Name).ToArray();
            if (cropNames.Length == 0)
            {
                throw new Exception("Cannot find any crop parameterisations in soil: " + simulation.SoilPath);
            }

            if (sowing != null && !StringUtilities.Contains(cropNames, sowing.Crop))
            {
                SoilCrop wheat = soil.Water.Crops.Find(c => c.Name.Equals("wheat", StringComparison.InvariantCultureIgnoreCase));
                if (wheat == null)
                {
                    // Use the first crop instead.
                    wheat = soil.Water.Crops[0];
                }

                SoilCrop newSoilCrop = new SoilCrop();
                newSoilCrop.Name      = sowing.Crop;
                newSoilCrop.Thickness = wheat.Thickness;
                newSoilCrop.LL        = wheat.LL;
                newSoilCrop.KL        = wheat.KL;
                newSoilCrop.XF        = wheat.XF;
                soil.Water.Crops.Add(newSoilCrop);
            }

            // Remove any initwater nodes.
            soil.InitialWater = null;

            // Transfer the simulation samples to the soil
            if (simulation.Samples != null)
            {
                soil.Samples = simulation.Samples;
            }

            if (simulation.InitTotalWater != 0)
            {
                soil.InitialWater = new InitialWater();
                soil.InitialWater.PercentMethod = InitialWater.PercentMethodEnum.FilledFromTop;

                double pawc;
                if (sowing == null || sowing.Crop == null)
                {
                    pawc = MathUtilities.Sum(PAWC.OfSoilmm(soil));
                    soil.InitialWater.RelativeTo = "LL15";
                }
                else
                {
                    SoilCrop crop = soil.Water.Crops.Find(c => c.Name.Equals(sowing.Crop, StringComparison.InvariantCultureIgnoreCase));
                    pawc = MathUtilities.Sum(PAWC.OfCropmm(soil, crop));
                    soil.InitialWater.RelativeTo = crop.Name;
                }

                soil.InitialWater.FractionFull = Convert.ToDouble(simulation.InitTotalWater) / pawc;
            }

            if (simulation.InitTotalNitrogen != 0)
            {
                // Add in a sample.
                Sample nitrogenSample = new Sample();
                nitrogenSample.Name = "NitrogenSample";
                soil.Samples.Add(nitrogenSample);
                nitrogenSample.Thickness = new double[] { 150, 150, 3000 };
                nitrogenSample.NO3Units  = Nitrogen.NUnitsEnum.kgha;
                nitrogenSample.NH4Units  = Nitrogen.NUnitsEnum.kgha;
                nitrogenSample.NO3       = new double[] { 6.0, 2.1, 0.1 };
                nitrogenSample.NH4       = new double[] { 0.5, 0.1, 0.1 };
                nitrogenSample.OC        = new double[] { double.NaN, double.NaN, double.NaN };
                nitrogenSample.EC        = new double[] { double.NaN, double.NaN, double.NaN };
                nitrogenSample.PH        = new double[] { double.NaN, double.NaN, double.NaN };

                double Scale = Convert.ToDouble(simulation.InitTotalNitrogen) / MathUtilities.Sum(nitrogenSample.NO3);
                nitrogenSample.NO3 = MathUtilities.Multiply_Value(nitrogenSample.NO3, Scale);
            }

            // Add in soil temperature. Needed for Aflatoxin risk.
            soil.SoilTemperature = new SoilTemperature();
            soil.SoilTemperature.BoundaryLayerConductance = 15;
            soil.SoilTemperature.Thickness = new double[] { 2000 };
            soil.SoilTemperature.InitialSoilTemperature = new double[] { 22 };
            if (soil.Analysis.ParticleSizeClay == null)
            {
                soil.Analysis.ParticleSizeClay = MathUtilities.CreateArrayOfValues(60, soil.Analysis.Thickness.Length);
            }
            InFillMissingValues(soil.Analysis.ParticleSizeClay);

            foreach (Sample sample in soil.Samples)
            {
                CheckSample(soil, sample, sowing);
            }

            Defaults.FillInMissingValues(soil);

            // Correct the CONA / U parameters depending on LAT/LONG
            CorrectCONAU(simulation, soil, workingFolder);

            // 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 = null;

            // Set the soil name to 'soil'
            soil.Name = "Soil";

            simulation.Soil = soil;
        }
        /// <summary>Constructor, initialise tissues for the roots.</summary>
        /// <param name="zone">The zone the roots belong in.</param>
        /// <param name="initialDM">Initial dry matter weight</param>
        /// <param name="initialDepth">Initial root depth</param>
        /// <param name="minLiveDM">The minimum biomass for this organ</param>
        public void Initialise(Zone zone, double initialDM, double initialDepth,
                               double minLiveDM)
        {
            soil = zone.FindInScope <Soil>();
            if (soil == null)
            {
                throw new Exception($"Cannot find soil in zone {zone.Name}");
            }

            soilPhysical = soil.FindInScope <IPhysical>();
            if (soilPhysical == null)
            {
                throw new Exception($"Cannot find soil physical in soil {soil.Name}");
            }

            waterBalance = soil.FindInScope <ISoilWater>();
            if (waterBalance == null)
            {
                throw new Exception($"Cannot find a water balance model in soil {soil.Name}");
            }

            soilCropData = soil.FindDescendant <SoilCrop>(species.Name + "Soil");
            if (soilCropData == null)
            {
                throw new Exception($"Cannot find a soil crop parameterisation called {species.Name + "Soil"}");
            }

            nutrient = zone.FindInScope <INutrient>();
            if (nutrient == null)
            {
                throw new Exception($"Cannot find SoilNitrogen in zone {zone.Name}");
            }

            no3 = zone.FindInScope("NO3") as ISolute;
            if (no3 == null)
            {
                throw new Exception($"Cannot find NO3 solute in zone {zone.Name}");
            }
            nh4 = zone.FindInScope("NH4") as ISolute;
            if (nh4 == null)
            {
                throw new Exception($"Cannot find NH4 solute in zone {zone.Name}");
            }

            // link to soil and initialise related variables
            zoneName           = soil.Parent.Name;
            nLayers            = soilPhysical.Thickness.Length;
            dulMM              = soilPhysical.DULmm;
            ll15MM             = soilPhysical.LL15mm;
            mySoilNH4Available = new double[nLayers];
            mySoilNO3Available = new double[nLayers];

            // save minimum DM and get target root distribution
            Depth         = initialDepth;
            minimumLiveDM = minLiveDM;
            CalculateRootZoneBottomLayer();
            TargetDistribution = RootDistributionTarget();

            // initialise tissues
            double[] initialDMByLayer = MathUtilities.Multiply_Value(CurrentRootDistributionTarget(), initialDM);
            double[] initialNByLayer  = MathUtilities.Multiply_Value(initialDMByLayer, NConcOptimum);
            Live = tissue[0];
            Dead = tissue[1];
            Live.Initialise(initialDMByLayer, initialNByLayer);
            Dead.Initialise(null, null);
        }
Beispiel #24
0
        private void OnSimulationCommencing(object sender, EventArgs e)
        {
            CropType              = "Slurp";
            uptakeWater           = new double[Soil.Thickness.Length];
            uptakeNitrogen        = new double[Soil.Thickness.Length];
            uptakeNitrogenPropNO3 = new double[Soil.Thickness.Length];

            // set the canopy and root properties here - no need to capture the sets from any Managers as they directly set the properties
            CanopyProperties.Name         = "Slurp";
            CanopyProperties.CoverGreen   = 1.0 - Math.Exp(-1 * localLightExtinction * localLAIGreen);
            CanopyProperties.CoverTot     = 1.0 - Math.Exp(-1 * localLightExtinction * localLAItot);
            CanopyProperties.CanopyDepth  = localCanopyDepth;
            CanopyProperties.CanopyHeight = localCanopyHeight;
            CanopyProperties.LAIGreen     = localLAIGreen;
            CanopyProperties.LAItot       = localLAItot;
            CanopyProperties.MaximumStomatalConductance = localMaximumStomatalConductance;
            CanopyProperties.HalfSatStomatalConductance = 200.0;  // should this be on the UI?
            CanopyProperties.CanopyEmissivity           = 0.96;
            CanopyProperties.Frgr = localFrgr;

            SoilCrop soilCrop = this.Soil.Crop(Name) as SoilCrop;

            RootProperties.KL = soilCrop.KL;

            RootProperties.MinNO3ConcForUptake = new double[Soil.Thickness.Length];
            RootProperties.MinNH4ConcForUptake = new double[Soil.Thickness.Length];

            RootProperties.LowerLimitDep = new double[Soil.Thickness.Length];

            for (int j = 0; j < Soil.Thickness.Length; j++)
            {
                RootProperties.LowerLimitDep[j]       = soilCrop.LL[j] * Soil.Thickness[j];
                RootProperties.MinNO3ConcForUptake[j] = 0.0;
                RootProperties.MinNH4ConcForUptake[j] = 0.0;
            }
            RootProperties.RootDepth = localRootDepth;
            RootProperties.KNO3      = localKNO3;
            RootProperties.KNH4      = localKNH4;

            RootProperties.UptakePreferenceByLayer = new double[Soil.Thickness.Length];
            for (int j = 0; j < Soil.Thickness.Length; j++)
            {
                RootProperties.UptakePreferenceByLayer[j] = 1.0;
            }

            localRootExplorationByLayer    = new double[Soil.Thickness.Length];
            localRootLengthDensityByVolume = new double[Soil.Thickness.Length];

            uptakeWater = new double[Soil.Thickness.Length];

            tempDepthUpper  = 0.0;
            tempDepthMiddle = 0.0;
            tempDepthLower  = 0.0;

            demandWater = localDemandWater;

            // calculate root exploration (proprotion of the layer occupied by the roots) for each layer
            for (int j = 0; j < Soil.Thickness.Length; j++)
            {
                tempDepthLower += Soil.Thickness[j];  // increment soil depth thorugh the layers
                tempDepthMiddle = tempDepthLower - Soil.Thickness[j] * 0.5;
                tempDepthUpper  = tempDepthLower - Soil.Thickness[j];
                if (tempDepthUpper < localRootDepth)        // set the root exploration
                {
                    localRootExplorationByLayer[j] = 1.0;
                }
                else if (tempDepthLower <= localRootDepth)
                {
                    localRootExplorationByLayer[j] = MathUtilities.Divide(localRootDepth - tempDepthUpper, Soil.Thickness[j], 0.0);
                }
                else
                {
                    localRootExplorationByLayer[j] = 0.0;
                }
                // set a triangular root length density by scaling layer depth against maximum rooting depth, constrain the multiplier between 0 and 1
                localRootLengthDensityByVolume[j] = localSurfaceRootLengthDensity * localRootExplorationByLayer[j] * (1.0 - MathUtilities.Constrain(MathUtilities.Divide(tempDepthMiddle, localRootDepth, 0.0), 0.0, 1.0));
            }
            RootProperties.RootExplorationByLayer    = localRootExplorationByLayer;
            RootProperties.RootLengthDensityByVolume = localRootLengthDensityByVolume;
        }
Beispiel #25
0
        /// <summary>Crop lower limit - mapped to the specified layer structure. Units: mm/mm        /// </summary>
        /// <param name="crop">The crop.</param>
        /// <param name="ToThickness">To thickness.</param>
        /// <returns></returns>
        private static double[] LLMapped(SoilCrop crop, double[] ToThickness)
        {
            var waterNode = crop.Parent as Physical;

            return(MapConcentration(crop.LL, waterNode.Thickness, ToThickness, MathUtilities.LastValue(crop.LL)));
        }
Beispiel #26
0
 /// <summary>Crop lower limit - mapped to the specified layer structure. Units: mm/mm        /// </summary>
 /// <param name="crop">The crop.</param>
 /// <param name="ToThickness">To thickness.</param>
 /// <returns></returns>
 private static double[] LLMapped(SoilCrop crop, double[] ToThickness)
 {
     return(MapConcentration(crop.LL, crop.Thickness, ToThickness, MathUtilities.LastValue(crop.LL)));
 }
Beispiel #27
0
        /// This alternative approach for obtaining ISRIC soil data need a little bit more work, but is largely complete
        /// There are still bits of the soil organic matter initialisation that should be enhanced.
        /// We probably don't really need two different ways to get to ISRIC data, but it may be interesting to see how the
        /// two compare. The initial motiviation was what appears to be an order-of-magnitude problem with soil carbon
        /// in the World Modellers version.
        /// <summary>
        /// Gets and ISRIC soil description directly from SoilGrids
        /// </summary>
        /// <returns>True if successful</returns>
        private IEnumerable <SoilFromDataSource> GetISRICSoils()
        {
            var    soils = new List <SoilFromDataSource>();
            string url   = "https://rest.soilgrids.org/query?lon=" +
                           longitudeEditBox.Text + "&lat=" + latitudeEditBox.Text;

            try
            {
                double[] bd       = new double[7];
                double[] coarse   = new double[7];
                double[] clay     = new double[7];
                double[] silt     = new double[7];
                double[] sand     = new double[7];
                double[] thetaSat = new double[7];
                double[] awc20    = new double[7];
                double[] awc23    = new double[7];
                double[] awc25    = new double[7];
                double[] thetaWwp = new double[7];
                double[] ocdrc    = new double[7];
                double[] phWater  = new double[7];
                double[] cationEC = new double[7];
                double[] texture  = new double[7];
                string   soilType = String.Empty;
                double   maxTemp  = 0.0;
                double   minTemp  = 0.0;
                double   ppt      = 0.0;
                double   bedrock  = 2500.0;

                string[]     textureClasses = new string[] { "Clay", "Silty Clay", "Sandy Clay", "Clay Loam", "Silty Clay Loam", "Sandy Clay Loam", "Loam", "Silty Loam", "Sandy Loam", "Silt", "Loamy Sand", "Sand", "NO DATA" };
                double[]     textureToAlb   = new double[] { 0.12, 0.12, 0.13, 0.13, 0.12, 0.13, 0.13, 0.14, 0.13, 0.13, 0.16, 0.19, 0.13 };
                double[]     textureToCN2   = new double[] { 73.0, 73.0, 73.0, 73.0, 73.0, 73.0, 73.0, 73.0, 68.0, 73.0, 68.0, 68.0, 73.0 };
                double[]     textureToSwcon = new double[] { 0.25, 0.3, 0.3, 0.4, 0.5, 0.5, 0.5, 0.5, 0.6, 0.5, 0.6, 0.75, 0.5 };
                MemoryStream stream         = WebUtilities.ExtractDataFromURL(url);
                stream.Position = 0;
                JsonTextReader reader = new JsonTextReader(new StreamReader(stream));
                while (reader.Read())
                {
                    if (reader.TokenType == JsonToken.PropertyName && reader.Value.Equals("properties") && reader.Depth == 1)
                    {
                        reader.Read(); // Read the "start object" token
                        while (reader.Read())
                        {
                            if (reader.TokenType == JsonToken.PropertyName)
                            {
                                string   propName   = reader.Value.ToString();
                                double[] dest       = null;
                                double   multiplier = 1.0;
                                if (propName == "TAXNWRBMajor")
                                {
                                    soilType = reader.ReadAsString();
                                }
                                else if (propName == "TMDMOD_2011")
                                {
                                    maxTemp = 0.0;
                                    reader.Read();
                                    while (reader.Read() && reader.TokenType != JsonToken.EndObject)
                                    {
                                        if (reader.TokenType == JsonToken.PropertyName && reader.Value.Equals("M"))
                                        {
                                            reader.Read(); // Read start of object token
                                            for (int i = 0; i < 12; i++)
                                            {
                                                reader.Read(); // Read a month name
                                                maxTemp += (double)reader.ReadAsDouble();
                                            }
                                            maxTemp /= 12.0;
                                        }
                                    }
                                }
                                else if (propName == "TMNMOD_2011")
                                {
                                    minTemp = 0.0;
                                    reader.Read();
                                    while (reader.Read() && reader.TokenType != JsonToken.EndObject)
                                    {
                                        if (reader.TokenType == JsonToken.PropertyName && reader.Value.Equals("M"))
                                        {
                                            reader.Read(); // Read start of object token
                                            for (int i = 0; i < 12; i++)
                                            {
                                                reader.Read(); // Read a month name
                                                minTemp += (double)reader.ReadAsDouble();
                                            }
                                            minTemp /= 12.0;
                                        }
                                    }
                                }
                                else if (propName == "PREMRG")
                                {
                                    ppt = 0.0;
                                    reader.Read();
                                    while (reader.Read() && reader.TokenType != JsonToken.EndObject)
                                    {
                                        if (reader.TokenType == JsonToken.PropertyName && reader.Value.Equals("M"))
                                        {
                                            reader.Read(); // Read start of object token
                                            for (int i = 0; i < 12; i++)
                                            {
                                                reader.Read(); // Read a month name
                                                ppt += (double)reader.ReadAsDouble();
                                            }
                                        }
                                    }
                                }
                                else if (propName == "BDTICM")  // Is this the best metric to use for find the "bottom" of the soil?
                                {
                                    reader.Read();
                                    while (reader.Read() && reader.TokenType != JsonToken.EndObject)
                                    {
                                        if (reader.TokenType == JsonToken.PropertyName && reader.Value.Equals("M"))
                                        {
                                            reader.Read(); // Read start of object token
                                            reader.Read(); // Read property name (which ought to be BDTICM_M)
                                            bedrock = 10.0 * (double)reader.ReadAsDouble();
                                            reader.Skip();
                                        }
                                    }
                                }
                                else if (propName == "AWCh1")
                                {
                                    dest       = awc20;
                                    multiplier = 0.01;
                                }
                                else if (propName == "AWCh2")
                                {
                                    dest       = awc23;
                                    multiplier = 0.01;
                                }
                                else if (propName == "AWCh3")
                                {
                                    dest       = awc25;
                                    multiplier = 0.01;
                                }
                                else if (propName == "AWCtS")
                                {
                                    dest       = thetaSat;
                                    multiplier = 0.01;
                                }
                                else if (propName == "BLDFIE")
                                {
                                    dest       = bd;
                                    multiplier = 0.001;
                                }
                                else if (propName == "CECSOL")
                                {
                                    dest       = cationEC;
                                    multiplier = 1.0;
                                }
                                else if (propName == "CLYPPT")
                                {
                                    dest       = clay;
                                    multiplier = 1.0;
                                }
                                else if (propName == "CRFVOL")
                                {
                                    dest       = coarse;
                                    multiplier = 1.0;
                                }
                                else if (propName == "ORCDRC")
                                {
                                    dest       = ocdrc;
                                    multiplier = 0.1;
                                }
                                else if (propName == "PHIHOX")
                                {
                                    dest       = phWater;
                                    multiplier = 0.1;
                                }
                                else if (propName == "SLTPPT")
                                {
                                    dest       = silt;
                                    multiplier = 1.0;
                                }
                                else if (propName == "SNDPPT")
                                {
                                    dest       = sand;
                                    multiplier = 1.0;
                                }
                                else if (propName == "TEXMHT")
                                {
                                    dest       = texture;
                                    multiplier = 1.0;
                                }
                                else if (propName == "WWP")
                                {
                                    dest       = thetaWwp;
                                    multiplier = 0.01;
                                }

                                if (dest != null)
                                {
                                    reader.Read();
                                    while (reader.Read() && reader.TokenType != JsonToken.EndObject)
                                    {
                                        if (reader.TokenType == JsonToken.PropertyName && reader.Value.Equals("M"))
                                        {
                                            while (reader.Read() && reader.TokenType != JsonToken.EndObject)
                                            {
                                                if (reader.TokenType == JsonToken.PropertyName)
                                                {
                                                    string tokenName = reader.Value.ToString();
                                                    if (tokenName.StartsWith("sl"))
                                                    {
                                                        int index = Int32.Parse(tokenName.Substring(2)) - 1;
                                                        dest[index] = (double)reader.ReadAsDouble() * multiplier;
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                                else
                                {
                                    reader.Skip();
                                }
                            }
                        }
                    }
                }

                var          newSoil         = new Soil();
                Chemical     analysis        = new Chemical();
                Physical     waterNode       = new Physical();
                Organic      organicMatter   = new Organic();
                WaterBalance soilWater       = new WaterBalance();
                InitialWater initialWater    = new InitialWater();
                Sample       initialNitrogen = new Sample();
                SoilNitrogen soilN           = new SoilNitrogen();

                SoilCrop wheat = new SoilCrop();
                waterNode.Children.Add(wheat);
                wheat.Name = "WheatSoil";
                waterNode.ParentAllDescendants();

                Model nh4 = new SoilNitrogenNH4();
                nh4.Name = "NH4";
                soilN.Children.Add(nh4);
                Model no3 = new SoilNitrogenNO3();
                no3.Name = "NO3";
                soilN.Children.Add(no3);
                Model urea = new SoilNitrogenUrea();
                urea.Name = "Urea";
                soilN.Children.Add(urea);
                Model plantAvailNH4 = new SoilNitrogenPlantAvailableNH4();
                plantAvailNH4.Name = "PlantAvailableNH4";
                soilN.Children.Add(plantAvailNH4);
                Model plantAvailNO3 = new SoilNitrogenPlantAvailableNO3();
                plantAvailNO3.Name = "PlantAvailableNO3";
                soilN.Children.Add(plantAvailNO3);
                soilN.ParentAllDescendants();

                newSoil.Children.Add(waterNode);
                newSoil.Children.Add(soilWater);
                newSoil.Children.Add(soilN);
                newSoil.Children.Add(organicMatter);
                newSoil.Children.Add(analysis);
                newSoil.Children.Add(initialWater);
                newSoil.Children.Add(initialNitrogen);
                newSoil.Children.Add(new CERESSoilTemperature());
                newSoil.ParentAllDescendants();
                newSoil.OnCreated();

                newSoil.Name       = "Synthetic soil derived from ISRIC SoilGrids REST API";
                newSoil.DataSource = "ISRIC SoilGrids";
                newSoil.SoilType   = soilType;
                newSoil.Latitude   = Convert.ToDouble(latitudeEditBox.Text, System.Globalization.CultureInfo.InvariantCulture);
                newSoil.Longitude  = Convert.ToDouble(longitudeEditBox.Text, System.Globalization.CultureInfo.InvariantCulture);

                // ISRIC values are for "levels", not "intervals", so we need to convert to layers
                // Following Andrew Moore's lead on layer thickness and weightings.

                double[] thickness  = new double[] { 150.0, 150.0, 150.0, 150.0, 200.0, 200.0, 200.0, 200.0, 300.0, 300.0 };
                double[] depth      = new double[thickness.Length];
                int      layerCount = thickness.Length;
                for (int i = 0; i < thickness.Length; i++)
                {
                    depth[i] = thickness[i] + (i > 0 ? depth[i - 1] : 0.0);
                    if ((i > 0) && (layerCount == thickness.Length) && (bedrock < depth[i] + 20.0))
                    {
                        layerCount   = i + 1;
                        thickness[i] = Math.Min(thickness[i], Math.Max(0.0, bedrock - (depth[i] - thickness[i])));
                        if (i == 1)
                        {
                            thickness[i] = Math.Max(50.0, thickness[i]);
                        }
                        Array.Resize(ref thickness, layerCount);
                    }
                }

                analysis.Thickness      = thickness;
                waterNode.Thickness     = thickness;
                soilWater.Thickness     = thickness;
                organicMatter.Thickness = thickness;

                initialWater.Name          = "Initial water";
                initialWater.PercentMethod = InitialWater.PercentMethodEnum.FilledFromTop;
                initialWater.FractionFull  = 0.0;

                // Initialise nitrogen to 0.0
                initialNitrogen.Name = "Initial nitrogen";
                initialNitrogen.NH4  = new double[layerCount];
                initialNitrogen.NO3  = new double[layerCount];

                double tAvg = (maxTemp + minTemp) / 2.0;
                soilWater.CNCov      = 0.0;
                soilWater.CNRed      = 20.0;
                soilWater.SummerDate = newSoil.Latitude <= 0.0 ? "1-nov" : "1-may";
                soilWater.WinterDate = newSoil.Latitude <= 0.0 ? "1-apr" : "1-oct";
                soilWater.SummerCona = 6.0;
                soilWater.SummerU    = 6.0;
                soilWater.WinterCona = tAvg < 21.0 ? 2.5 : 6.0;
                soilWater.WinterU    = tAvg < 21.0 ? 4.0 : 6.0;
                soilWater.Salb       = textureToAlb[(int)Math.Round(texture[0] - 1)];
                soilWater.CN2Bare    = textureToCN2[(int)Math.Round(texture[0] - 1)];
                double[] swcon = new double[7];
                for (int i = 0; i < 7; i++)
                {
                    swcon[i] = textureToSwcon[(int)Math.Round(texture[i] - 1)];
                }
                soilWater.SWCON = ConvertLayers(swcon, layerCount);

                waterNode.BD     = ConvertLayers(bd, layerCount);
                waterNode.LL15   = ConvertLayers(thetaWwp, layerCount);
                waterNode.SAT    = ConvertLayers(thetaSat, layerCount);
                waterNode.AirDry = ConvertLayers(MathUtilities.Divide_Value(thetaWwp, 3.0), layerCount);
                double[] dul = new double[7];
                for (int i = 0; i < 7; i++)
                {
                    dul[i] = thetaWwp[i] + awc20[i];  // This could be made Moore complex
                }
                waterNode.DUL = ConvertLayers(dul, layerCount);

                waterNode.ParticleSizeSand = ConvertLayers(sand, layerCount);
                waterNode.ParticleSizeSilt = ConvertLayers(silt, layerCount);
                waterNode.ParticleSizeClay = ConvertLayers(clay, layerCount);
                // waterNode.Rocks = ConvertLayers(coarse, layerCount);
                analysis.PH = ConvertLayers(phWater, layerCount);
                // Obviously using the averaging in "ConvertLayers" for texture classes is not really correct, but should be OK as a first pass if we don't have sharply contrasting layers
                double[] classes  = ConvertLayers(texture, layerCount);
                string[] textures = new string[layerCount];
                for (int i = 0; i < layerCount; i++)
                {
                    textures[i] = textureClasses[(int)Math.Round(classes[i]) - 1];
                }


                double[] xf          = new double[layerCount];
                double[] kl          = new double[layerCount];
                double[] ll          = new double[layerCount];
                double   p1          = 1.4;
                double   p2          = 1.60 - p1;
                double   p3          = 1.80 - p1;
                double   topEffDepth = 0.0;
                double   klMax       = 0.06;
                double   depthKl     = 900.0;
                double   depthRoot   = 1900.0;

                for (int i = 0; i < layerCount; i++)
                {
                    xf[i] = 1.0 - (waterNode.BD[i] - (p1 + p2 * 0.01 * waterNode.ParticleSizeSand[i])) / p3;
                    xf[i] = Math.Max(0.1, Math.Min(1.0, xf[i]));
                    double effectiveThickness = thickness[i] * xf[i];
                    double bottomEffDepth     = topEffDepth + effectiveThickness;
                    double propMaxKl          = Math.Max(0.0, Math.Min(bottomEffDepth, depthKl) - topEffDepth) / effectiveThickness;
                    double propDecrKl         = Math.Max(Math.Max(0.0, Math.Min(bottomEffDepth, depthRoot) - topEffDepth) / effectiveThickness - propMaxKl, 0.0);
                    double propZeroKl         = 1.0 - propMaxKl - propDecrKl;
                    double ratioTopDepth      = Math.Max(0.0, Math.Min((depthRoot - topEffDepth) / (depthRoot - depthKl), 1.0));
                    double ratioBottomDepth   = Math.Max(0.0, Math.Min((depthRoot - bottomEffDepth) / (depthRoot - depthKl), 1.0));
                    double meanDecrRatio      = 0.5 * (ratioTopDepth + ratioBottomDepth);
                    double weightedRatio      = propMaxKl * 1.0 + propDecrKl * meanDecrRatio + propZeroKl * 0.0;
                    kl[i] = klMax * weightedRatio;
                    ll[i] = waterNode.LL15[i] + (waterNode.DUL[i] - waterNode.LL15[i]) * (1.0 - weightedRatio);
                    if (kl[i] <= 0.0)
                    {
                        xf[i] = 0.0;
                    }
                    topEffDepth = bottomEffDepth;
                }
                wheat.XF = xf;
                wheat.KL = kl;
                wheat.LL = ll;

                organicMatter.Carbon = ConvertLayers(ocdrc, layerCount);

                double rootWt = Math.Max(0.0, Math.Min(3000.0, 2.5 * (ppt - 100.0)));
                // For AosimX, root wt needs to be distributed across layers. This conversion logic is adapted from that used in UpgradeToVersion52
                double[] rootWtFraction = new double[layerCount];
                double   profileDepth   = depth[layerCount - 1];
                double   cumDepth       = 0.0;
                for (int layer = 0; layer < layerCount; layer++)
                {
                    double fracLayer = Math.Min(1.0, MathUtilities.Divide(profileDepth - cumDepth, thickness[layer], 0.0));
                    cumDepth += thickness[layer];
                    rootWtFraction[layer] = fracLayer * Math.Exp(-3.0 * Math.Min(1.0, MathUtilities.Divide(cumDepth, profileDepth, 0.0)));
                }
                // get the actuall FOM distribution through layers (adds up to one)
                double totFOMfraction = MathUtilities.Sum(rootWtFraction);
                for (int layer = 0; layer < thickness.Length; layer++)
                {
                    rootWtFraction[layer] /= totFOMfraction;
                }
                organicMatter.FOM = MathUtilities.Multiply_Value(rootWtFraction, rootWt);

                double[] fBiom = { 0.04,                                                                                                                             0.04 - 0.03 * (225.0 - 150.0) / (400.0 - 150.0),
                                   (400.0 - 300.0) / (450.0 - 300.0) * (0.04 - 0.03 * (350.0 - 150.0) / (400.0 - 150.0)) + (450.0 - 400.0) / (450.0 - 300.0) * 0.01,
                                   0.01,                                                                                                                                                                        0.01,0.01, 0.01, 0.01, 0.01, 0.01 };
                Array.Resize(ref fBiom, layerCount);
                double   inert_c = 0.95 * ocdrc[4];
                double[] fInert  = new double[7];
                for (int layer = 0; layer < 7; layer++)
                {
                    fInert[layer] = Math.Min(0.99, inert_c / ocdrc[layer]);
                }
                organicMatter.FInert      = ConvertLayers(fInert, layerCount); // Not perfect, but should be good enough
                organicMatter.FBiom       = fBiom;
                organicMatter.FOMCNRatio  = 40.0;
                organicMatter.SoilCNRatio = Enumerable.Repeat(11.0, layerCount).ToArray(); // Is there any good way to estimate this? ISRIC provides no N data

                newSoil.Children.Add(new CERESSoilTemperature());
                newSoil.OnCreated();

                soils.Add(new SoilFromDataSource()
                {
                    DataSource = "ISRIC",
                    Soil       = newSoil
                });
            }
            catch (Exception)
            {
            }
            return(soils);
        }