Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

scrollbar

...

Exercise outline

The goal of this exercise is to create a simple hydrological volume model. In the end, it should be possible to run the volume model and inspect some (very simple) spatio-temporal output results.

...

Add to the plugin project a new folder named Models. In this folder, create a new class named VolumeModelnamed DrainageBasin.cs and adapt the contents as shown below:

Note

In order to successfully build the code below, references need to be added to:

  • GeoAPI
  • GeoAPI.Extensions
  • NetTopologySuite.Extensions
  • SharpMap
  • SharpMap.API

These Dlls can all be found in the packages folder of the solution (D:\VolumeModel\packages\DeltaShell.Framework.1.1.1.34867\lib\net40\DeltaShell).
After adding the references be sure to set the copylocal property of the references to false to prevent duplication of dlls in the bin folder.

Code Block
languagec#
using System;
using System.Collections.Generic;
using System.LinqComponentModel;
using DelftTools.Functions;
using DelftTools.Functions.GenericUtils.Collections;
using DelftTools.ShellUtils.Core.Workflow;
using DelftTools.Shell.Core.Workflow.DataItemsCollections.Generic;
using GeoAPI.CoordinateSystems;
using GeoAPI.Extensions.Feature;
using GeoAPI.Geometries;
using NetTopologySuite.Extensions.Coverages;
namespace DeltaShell.Plugins.VolumeModel.Models
namespace DeltaShell.Plugins.VolumeModel.Models
{
    public class VolumeModel : ModelBase/// <summary>
    {
/// Drainage basin containing a set of catchments privateand readonly DrainageBasin basin;a coordinatesystem.
    /// Implements INotifyPropertyChanged and privateINotifyCollectionChanged readonlyto TimeSerieshandle precipitation;events
    /// </summary>
   private readonlypublic FeatureCoverageclass volume;
DrainageBasin : INotifyPropertyChanged, INotifyCollectionChanged
     /// <summary>{
        ///private CreatesICoordinateSystem acoordinateSystem;
 volume model
      private readonly /// </summary>IEventedList<IFeature> catchments;
        public VolumeModelDrainageBasin()
        {
            // CreateAdd an theempty input(evented) itemslist of the volume modelfeatures and subscribe to changes (bubble changes)
            basincatchments = new DrainageBasinEventedList<IFeature>();
            precipitationcatchments.CollectionChanged += new TimeSeries { Components(s, e) =>
 { new Variable<double>("Precipitation") } };
           {
 // Create the output item of the volume model
           if volume(CollectionChanged != new FeatureCoverage("Output data")null)
            {
        {
        IsTimeDependent = true,
              CollectionChanged(s, e);
 Arguments = { new Variable<IFeature>("Catchment") { FixedSize = 0 } },
         }
       Components = { new Variable<double>("Volume") },
    };
        };
        public ICoordinateSystem CoordinateSystem
   // Wrap fields as input/output data items {
            DataItems.Add(new DataItem(precipitation, "Precipitation", typeof(TimeSeries), DataItemRole.Input, "PrecipitationTag"));
get { return coordinateSystem; }
            set
 DataItems.Add(new DataItem(basin, "Basin", typeof(DrainageBasin), DataItemRole.Input, "BasinTag"));
        {
             DataItems.Add(new DataItem(volume, "Volume", typeof(FeatureCoverage), DataItemRole.Output, "VolumeTag")) coordinateSystem = value;
        }
        /// invoke <summary>
property changed event after setting coordinateSystem
   /// The precipitation time series:  P = P(t) [L/T]. Input of the model.
if (PropertyChanged != null)
     /// </summary>
        public TimeSeries Precipitation{
        {
            get { return precipitation; }PropertyChanged(this, new PropertyChangedEventArgs("CoordinateSystem"));
        }
        /// <summary>
}
         /// The drainage basin (set of catchments). Input of the model. }
        /// </summary>}
        public DrainageBasinIList<IFeature> BasinCatchments
        {
            get { return basincatchments; }
        }
         /// <summary>public event PropertyChangedEventHandler PropertyChanged;
        public event NotifyCollectionChangedEventHandler CollectionChanged;
    }
}
Info

The comments in the code explain the different parts of the DrainageBasin implementation.

 

Now also add VolumeModel.cs to the Models folder and add the following code:

Code Block
languagec#
using System;
using System.Linq;
using DelftTools.Functions;
using DelftTools.Functions.Generic;
using DelftTools.Shell.Core.Workflow;
using DelftTools.Shell.Core.Workflow.DataItems;
using GeoAPI.Extensions.Feature;
using GeoAPI.Geometries;
using NetTopologySuite.Extensions.Coverages;
namespace DeltaShell.Plugins.VolumeModel.Models
{
    public class VolumeModel : ModelBase
    {
        private readonly DrainageBasin basin;/// Time-dependent feature coverage containing the volume of water per catchment: V = V(t, c) [L3/T]. Output of the model.
        /// </summary>
        public FeatureCoverage Volume
        {
        private readonly TimeSeries precipitation;
 get { return volume; }
   private readonly FeatureCoverage   }volume;
        /// <summary>
        /// TheCreates initializationa ofvolume model runs
        /// </summary>
        protected override void OnInitializepublic VolumeModel()
        {
            // Clear any previous outputCreate the input items of the volume model
            volume.Clearbasin = new DrainageBasin();
            precipitation //= Ensurenew theTimeSeries coordinate{ systemComponents of= the{ volume output is the same as the catchments input (basin)
new Variable<double>("Precipitation") } };
            // Create the output item of the volume.CoordinateSystem = basin.CoordinateSystem; model
            //volume Ensure= at least one catchment and one precipitation value is present
new FeatureCoverage("Output data")
            {
       ValidateInputData();
         IsTimeDependent = true,
 // Initialize the output feature coverage
          Arguments  volume.Features.AddRange(basin.Catchments);
      = { new Variable<IFeature>("Catchment") { FixedSize = 0 } },
      volume.FeatureVariable.FixedSize = basin.Catchments.Count();
        Components = { new volume.FeatureVariable.AddValues(basin.Catchments);
Variable<double>("Volume") },
            };
        ///  <summary>
        /// TheWrap actualfields calculationas duringinput/output modeldata runitems
        /// </summary>
   DataItems.Add(new DataItem(precipitation, "Precipitation", typeof(TimeSeries),  protected override void OnExecute()DataItemRole.Input, "PrecipitationTag"));
        {
    DataItems.Add(new DataItem(basin, "Basin", typeof(DrainageBasin),     // Loop all timesDataItemRole.Input, "BasinTag"));
            foreachDataItems.Add(new DataItem(var time in precipitation.Time.Values)volume, "Volume", typeof(FeatureCoverage), DataItemRole.Output, "VolumeTag"));
        }
    {     /// <summary>
        /// The precipitation time series: P = P(t) // Obtain[L/T]. Input of the precipitationmodel.
  value for the current time
  /// </summary>
        public TimeSeries Precipitation
   var p = (double)precipitation[time];
  {
            get { //return Calculateprecipitation; a}
 volume value for every catchment based on catchment}
 area and precipitation value
    /// <summary>
        /// The drainage varbasin volumes(set = basin.Catchments.Select(f => f.Geometry).OfType<IPolygon>().Select(pol => pol.Area * p);
of catchments). Input of the model.
        /// </summary>
        public //DrainageBasin AddBasin
 the calculated volume values to the output feature coverage{
            get { return  volume[time] = volumes;
    basin; }
        }
         /// <summary>
  Status = ActivityStatus.Done;
    /// Time-dependent feature coverage }
containing the volume of water per catchment: V private void ValidateInputData()
        {
= V(t, c) [L3/T]. Output of the model.
        /// </summary>
      var hasCatchments = basin.Catchments.Any();public FeatureCoverage Volume
         {
   var hasPrecipitationData = precipitation.Time.Values.Any();
      get { return volume; }
  if (!hasCatchments && !hasPrecipitationData)
   }
        /// {<summary>
        /// The initialization of model runs
   throw new InvalidOperationException("At least one catchment and one precipitation value should be present"); /// </summary>
        protected override void OnInitialize()
        {
    }
        // Clear any  if (!hasCatchments)previous output
            {volume.Clear();
            // Ensure the coordinate throwsystem new InvalidOperationException("At least one catchment should be present");
    of the volume output is the same as the catchments input (basin)
        }
    volume.CoordinateSystem        if (! basin.Catchments.All(c => c.Geometry is IPolygon))= (GeoAPI.Extensions.CoordinateSystems.ICoordinateSystem) basin.CoordinateSystem;
            {
// Ensure at least one catchment and one precipitation value is present
     throw new InvalidOperationException("All catchment features should be polygons"ValidateInputData();
            }
// Initialize the output feature coverage
       if (!hasPrecipitationData)
    volume.Features.AddRange(basin.Catchments);
        {
    volume.FeatureVariable.FixedSize = basin.Catchments.Count();
          throw new InvalidOperationException("At least one precipitation value should be present");volume.FeatureVariable.AddValues(basin.Catchments);
        }
        /// <summary>
   }
     /// The actual }
calculation during model run
 }
    public class DrainageBasin
 /// </summary>
  {
      protected override publicvoid DrainageBasinOnExecute()
        {
            Catchments= new List<IFeature>();
// Loop all times
            foreach  }
(var time in precipitation.Time.Values)
         public ICoordinateSystem CoordinateSystem {
 get;   set; }
        public IList<IFeature> Catchments {// get;Obtain privatethe set;precipitation }
value for the current }
} 
Info

The model class is derived from the ModelBase class in order to automatically implement some basic time dependent modeling logic.

The comments in the code explain the different parts of the model implementation.

Note

The model uses some basic data structures like data items, (feature) coverages and timeseries (functions). A description on the background and usage of these data structures is not part of this tutorial.

Register the model in the application plugin class

Register the model in the application plugin by adding the following code to VolumeModelApplicationPlugin.cs:

time
                var p = (double)precipitation[time];
                // Calculate a volume value for every catchment based on catchment area and precipitation value
                var volumes = basin.Catchments.Select(f => f.Geometry).Select(pol => pol.Area * p);
                // Add the calculated volume values to the output feature coverage
                volume[time] = volumes;
            }
            Status = ActivityStatus.Done;
        }
        private void ValidateInputData()
        {
            var hasCatchments = basin.Catchments.Any();
            var hasPrecipitationData = precipitation.Time.Values.Any();
            if (!hasCatchments && !hasPrecipitationData)
            {
                throw new InvalidOperationException("At least one catchment and one precipitation value should be present");
            }
            if (!hasCatchments)
            {
                throw new InvalidOperationException("At least one catchment should be present");
            }
            if (! basin.Catchments.All(c => c.Geometry is IPolygon || c.Geometry is IMultiPolygon))
            {
                throw new InvalidOperationException("All catchment features should be polygons");
            }
            if (!hasPrecipitationData)
            {
                throw new InvalidOperationException("At least one precipitation value should be present");
            }
        }
    }
} 
Info

The model class is derived from the ModelBase class in order to automatically implement some basic time dependent modeling logic.

The comments in the code explain the different parts of the model implementation.

Note

The model uses some basic data structures like data items, (feature) coverages and timeseries (functions). A description on the background and usage of these data structures is not part of this tutorial.

Register the model in the application plugin class

Register the model in the application plugin by adding the following code to VolumeModelApplicationPlugin.cs:

Code Block
languagec#
public override IEnumerable<ModelInfo> GetModelInfos()
{
    yield return new ModelInfo
        {
            Name = "Volume Model",
            Category = "Volume models",
            CreateModel = o => new Models.VolumeModel()
        };
}

Delta Shell should now be able to create and run volume models.

Add importer for DrainageBasin

We now only need to add a small importer for importing our custom DrainageBasin from a shapefile. Add a new class DrainageBasinImporter in the folder Importers and add the following code :

Code Block
languagec#
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using DelftTools.Shell.Core;
using DeltaShell.Plugins.VolumeModel.Models;
using GeoAPI.Extensions.Feature;
using NetTopologySuite.Extensions.Features;
using SharpMap.Data.Providers;
namespace DeltaShell.Plugins.VolumeModel.Importers
{
    public class DrainageBasinImporter : IFileImporter
    {
        public string Name { get { return "Shape file importer"; } }
        
        public string Category { get { return "General"; } }
        
        public Bitmap Image { get; private set; }
        public IEnumerable<Type> SupportedItemTypes
Code Block
public override IEnumerable<ModelInfo> GetModelInfos()
{
    yield return new ModelInfo
        {
            Name = "Volume Model",
            Category = "Volume models",
         {
   CreateModel = o => new Models.VolumeModel()
    get { yield return };
}

Delta Shell should now be able to create and run volume models.

We now only need to add a small importer for importing our custom DrainageBasin from a shapefile. Add a new class DrainageBasinImporter in the folder importers and add the following code :

Code Block
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using DelftTools.Shell.Core;
using DeltaShell.Plugins.VolumeModel.Models;
using GeoAPI.Extensions.Feature;
using SharpMap.Data.Providers;
namespace DeltaShell.Plugins.VolumeModel.Importers
{
    public class DrainageBasinImporter : IFileImporter
    {typeof(DrainageBasin); }
        }
        public bool CanImportOnRootLevel { get { return false; } }
        public string FileFilter { get { return "Shape file|*.shp"; } }
        public string TargetDataDirectory { get; set; }
        public stringbool NameShouldCancel { get { return "Shape file importer"; set; }}
        public stringImportProgressChangedDelegate CategoryProgressChanged { get { return "General"; set; } }
        public Bitmapbool ImageOpenViewAfterImport { get; { privatereturn setfalse; } }
        public IEnumerable<Type> SupportedItemTypesbool CanImportOn(object targetObject)
        {
            get{return yieldtargetObject returnis typeof(DrainageBasin);}
        }
        
public object ImportItem(string path, object target = null)
 public bool CanImportOnRootLevel { get { return false; } } {
        
    var basin = target publicas stringDrainageBasin;
 FileFilter { get { return "Shape file|*.shp"; }}
    if (basin == null)
 
        public string TargetDataDirectory {
 get; set; }
        
     throw new Exception("Can publiconly boolimport ShouldCancelon {drainage getbasins");
 set; }
        
  }
      public ImportProgressChangedDelegate ProgressChanged { get; set; }
        
    basin.Catchments.Clear();
    public bool OpenViewAfterImport { get { return false; } }
  
      
      var shapeFile public= boolnew CanImportOnShapeFile(object targetObjectpath);
        {    
            return targetObject is DrainageBasin;
foreach (var feature in shapeFile.Features.OfType<IFeature>())
            }{
          public object ImportItem(string path, object target = null) basin.Catchments.Add(new Feature
        {
        {
     var basin = target as DrainageBasin;
          Geometry = if (basin == null)
feature.Geometry,
               {
     Attributes = feature.Attributes
         throw new Exception("Can only import on drainage basins"});
            }
            basin.Catchments.Clear()CoordinateSystem = shapeFile.CoordinateSystem;
            return varbasin;
 shapeFile = new ShapeFile(path);
    }
        foreach (var feature in shapeFile.Features.OfType<IFeature>())
            {
                basin.Catchments.Add(feature);
            }
            return basin;
        }
    }
}

Also register the importer at the GetFileImporters method in the VolumeModelApplicationPlugin class.

Exercise results

First of all, download the following WaterML2 XML file: WaterML2_precipitation_data.XML. Also download and unzip the shape files contained in the following archive: Gemeenten.zip. You will use all these data along the exercise.

Next, run the application and start creating a new model item (right click on project | Add | New Model ...). Make sure that the new model is selected in the dialog:

...

}
}

Important: this importer also needs to be registered at the GetFileImporters method in the VolumeModelApplicationPlugin class. Otherwise, it won't be accessible.

Exercise results

First of all, download the following WaterML2 XML file: WaterML2_precipitation_data.XML, if not done before. Also download and unzip the shape files contained in the following archive: Gemeenten.zip. You will use all these data along this exercise.

Next, run the application and start creating a new model item (right click on project | Add | New Model ...). Make sure that the new model is selected in the dialog:

Image Added

If you now click on OK, a new model item should be added to the project with a structure as shown in the following image:

Image Added


Try to run the model (right click on the volume model item | Run Model) and check the Messages window. The following error messages will be generated:

Image Added

As indicated in the error messages, some precipitation and catchment input data must be available for the model to be successfully run.

First, start importing some WaterML2 data on the precipitation time series item (right click the precipitation item | Import...). A file selection dialog automatically pops up. Select the previously downloaded file WATERML2_precipitation_data.xml.
After finishing the import, the precipitation item should contain data as shown in the following image (double click the precipitation item in the Project window):

Image Added

Next, start importing a shape file on the Basin item (right click the Basin item | Import...)  and select the file Gemeente.shp.
Now, run the model again and notice that, this time, no new error messages are sent to the Messages window.
Open the volume output (double click the volume item in the Project window and select the Map view) and check that the model results agree with the ones shown in the following image:

Image Added

Info

In order to inspect time dependent (output) data, open the Time Navigator window and move the slider or click one of the play buttons:

Image Added

scrollbar

Image Removed

As indicated in the error messages, some precipitation and catchment input data must be available in order to successfully run the model.

First, start importing some WaterML2 data on the precipitation time series item (right click the precipitation item | Import...). A file selection dialog automatically pops up. Select the previously downloaded WaterML2 XML file.

After finishing the import, the precipitation item should contain data as shown in the following image (double click the precipitation item in the Project window):

Image Removed
Next, start importing a shape file on the basin item (right click the basin item | Import...). A GIS import wizard automatically pops up. Press on Next to enter the wizard: