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 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.Collections.Generic;
using System.ComponentModel;
using DelftTools.Utils.Collections;
using DelftTools.Utils.Collections.Generic;
using GeoAPI.CoordinateSystems;
using GeoAPI.Extensions.Feature;
namespace DeltaShell.Plugins.VolumeModel.Models
{
    public class DrainageBasin : INotifyPropertyChanged, INotifyCollectionChanged/// <summary>
    {
/// Drainage basin containing a set of catchments privateand ICoordinateSystema coordinateSystem;coordinatesystem.
    /// Implements INotifyPropertyChanged and privateINotifyCollectionChanged readonlyto IEventedList<IFeature>handle catchments;events
      /// </summary>
    public class DrainageBasin()
 : INotifyPropertyChanged,  INotifyCollectionChanged
    {
        private ICoordinateSystem coordinateSystem;
  catchments  = new EventedList<IFeature>();
  private readonly IEventedList<IFeature> catchments;
       catchments.CollectionChanged +=public DrainageBasin(s, e) =>
        {
        {
    // Add an empty (evented) list of features and subscribe to changes     if (CollectionChanged != null(bubble changes)
            catchments = new EventedList<IFeature>();
     {
       catchments.CollectionChanged += (s, e) =>
               CollectionChanged(s, e); {
                    }
if (CollectionChanged != null)
             };
        }{
        public ICoordinateSystem CoordinateSystem
        {
       CollectionChanged(s, e);
    get { return coordinateSystem; }
            set}
            {
    };
            coordinateSystem = value;}
        public ICoordinateSystem CoordinateSystem
      if (PropertyChanged != null) {
            get { return coordinateSystem; {}
            set
        PropertyChanged(this, new PropertyChangedEventArgs("CoordinateSystem"));    {
                }
coordinateSystem = value;
              }
  // invoke property changed event after }setting coordinateSystem
        public IList<IFeature> Catchments
      if  {(PropertyChanged != null)
            get { return catchments; }{
         }
        public event PropertyChangedEventHandler PropertyChanged(this, new PropertyChangedEventArgs("CoordinateSystem"));
         public event NotifyCollectionChangedEventHandler CollectionChanged;
    }
}

 

 

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

Code Block
using System;
using System.Collections.Generic;
using System.Linq;
using DelftTools.Functions;
using DelftTools.Functions.Generic;
using DelftTools.Shell.Core.Workflow;
using DelftTools.Shell.Core.Workflow.DataItems;
using GeoAPI.CoordinateSystems;
using GeoAPI.Extensions.Feature;
using GeoAPI.Geometries;
using NetTopologySuite.Extensions.Coverages;
namespace DeltaShell.Plugins.VolumeModel.Models
{
            }
        }
        public IList<IFeature> classCatchments
  VolumeModel : ModelBase
    {
        private readonly DrainageBasin basin;
 get { return     private readonly TimeSeries precipitation;catchments; }
        private}
 readonly FeatureCoverage volume;
     public event  /// <summary>PropertyChangedEventHandler PropertyChanged;
        ///public Createsevent a volume modelNotifyCollectionChangedEventHandler 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
    /// </summary>
        public VolumeModel()
        {
    public class VolumeModel : ModelBase
    //{
 Create the input items of the volume model
private readonly DrainageBasin basin;
        private basinreadonly = new DrainageBasin()TimeSeries precipitation;
        private readonly FeatureCoverage volume;
 precipitation = new TimeSeries { Components = { new Variable<double>("Precipitation") } };
/// <summary>
        /// Creates a volume model
   // Create the output item of the volume model
 /// </summary>
        public VolumeModel()
        volume{
 = new FeatureCoverage("Output data")
        // Create the input {
items of the volume model
            IsTimeDependentbasin = true,new DrainageBasin();
            precipitation = new TimeSeries Arguments{ Components = { new Variable<IFeature>Variable<double>("CatchmentPrecipitation") { FixedSize = 0 } },
    } };
            // Create the output item of the volume model
            Componentsvolume = { new Variable<double>FeatureCoverage("VolumeOutput data") },
            };{
            // Wrap fields as input/outputIsTimeDependent data= itemstrue,
            DataItems.Add(new DataItem(precipitation, "Precipitation", typeof(TimeSeries), DataItemRole.Input, "PrecipitationTag"));
         Arguments = { new Variable<IFeature>("Catchment") { FixedSize = 0 } },
      DataItems.Add(new DataItem(basin, "Basin", typeof(DrainageBasin), DataItemRole.Input, "BasinTag"));
     Components = {     DataItems.Add(new DataItem(volume, new Variable<double>("Volume") },
  typeof(FeatureCoverage), DataItemRole.Output, "VolumeTag"));
        };
            /// <summary>
    Wrap fields as input/output data items
     /// The precipitation time series: P = P(t) [L/T]. Input of the model.DataItems.Add(new DataItem(precipitation, "Precipitation", typeof(TimeSeries), DataItemRole.Input, "PrecipitationTag"));
        /// </summary>
   DataItems.Add(new DataItem(basin, "Basin", typeof(DrainageBasin),  public TimeSeries Precipitation
DataItemRole.Input, "BasinTag"));
          {
  DataItems.Add(new DataItem(volume, "Volume", typeof(FeatureCoverage),       get { return precipitation; }DataItemRole.Output, "VolumeTag"));
        }
        /// <summary>
        /// The drainage basin (set of catchments)precipitation time series: P = P(t) [L/T]. Input of the model.
        /// </summary>
        public DrainageBasinTimeSeries BasinPrecipitation
        {
            get { return basinprecipitation; }
        }
        /// <summary>
        /// Time-dependentThe featuredrainage coverage containing the volume of water per catchment: V = V(t, c) [L3/T]. Outputbasin (set of catchments). Input of the model.
        /// </summary>
        public FeatureCoverageDrainageBasin VolumeBasin
        {
            get { return volumebasin; }
        }
        /// <summary>
        /// TheTime-dependent initializationfeature ofcoverage modelcontaining runs
the volume of water per catchment: V = V(t, c) [L3/T]. Output of the model.
        /// </summary>
        protectedpublic override void OnInitialize()FeatureCoverage Volume
        {
            //get Clear{ anyreturn previousvolume; output}
        }
    volume.Clear();
    /// <summary>
        // Ensure the coordinate system of the volume output is the same as the catchments input (basin)
      / The initialization of model runs
        /// </summary>
        protected override void OnInitialize()
      volume.CoordinateSystem = basin.CoordinateSystem;{
            // EnsureClear atany leastprevious oneoutput
 catchment and one precipitation value is present
            ValidateInputDatavolume.Clear();
            // InitializeEnsure the outputcoordinate featuresystem coverage
of the volume output is the same as the catchments  input volume.Features.AddRange(basin.Catchments);
            volume.FeatureVariable.FixedSizeCoordinateSystem = basin.Catchments.Count()(GeoAPI.Extensions.CoordinateSystems.ICoordinateSystem) basin.CoordinateSystem;
            volume.FeatureVariable.AddValues(basin.Catchments);
        }// Ensure at least one catchment and one precipitation value is present
        /// <summary>
   ValidateInputData();
     /// The actual calculation during model run
    // Initialize the output /// </summary>feature coverage
        protected  override void OnExecute()
 volume.Features.AddRange(basin.Catchments);
       {
     volume.FeatureVariable.FixedSize = basin.Catchments.Count();
     // Loop all times
    volume.FeatureVariable.AddValues(basin.Catchments);
        foreach}
 (var time in precipitation.Time.Values)
    /// <summary>
       {
 /// The actual calculation during model run
         /// Obtain the precipitation value for the current time
   </summary>
        protected override void OnExecute()
        {
     var p = (double)precipitation[time];
    // Loop all times
         // Calculate a volumeforeach value(var fortime every catchment based on catchment area and precipitation value
in precipitation.Time.Values)
            {
           var volumes = basin.Catchments.Select(f => f.Geometry).Select(pol => pol.Area * p);
     // Obtain the precipitation value for the current time
            // Add the calculated volumevar valuesp to the output feature coverage
= (double)precipitation[time];
                // Calculate a volume[time] = volumes;
        value for every catchment based on catchment area and precipitation value
     }
           var Statusvolumes = ActivityStatus.Done;
        }basin.Catchments.Select(f => f.Geometry).Select(pol => pol.Area * p);
        private void ValidateInputData()
      // Add {
the calculated volume values to the output feature coverage
    var hasCatchments = basin.Catchments.Any();
            var hasPrecipitationData = precipitation.Time.Values.Any()volume[time] = volumes;
            if}
 (!hasCatchments && !hasPrecipitationData)
         Status   {= ActivityStatus.Done;
        }
        throwprivate newvoid InvalidOperationExceptionValidateInputData("At least one catchment and one precipitation value should be present");
)
        {
            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 (! basin.Catchments.All(c => c.Geometry is IPolygon || c.Geometry is IMultiPolygon))
            hasCatchments)
            {
                throw new InvalidOperationException("AllAt least one catchment features should be polygonspresent");
            }
            if (!hasPrecipitationData)
             basin.Catchments.All(c => c.Geometry is IPolygon || c.Geometry is IMultiPolygon))
            {
                throw new InvalidOperationException("AtAll leastcatchment onefeatures precipitation value should be presentpolygons");
            }
          }
  if (!hasPrecipitationData)
     }
} 
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:

       {
                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
    
Code Block
public override IEnumerable<ModelInfo> GetModelInfos()
{
    yield return new ModelInfo
        {
        public string Name { Nameget ={ "Volumereturn Model",
Shape file importer"; }  }
       Category = "Volume models",
        public string Category { CreateModelget ={ o => new Models.VolumeModel()
  return "General"; } }
      };
}

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

        public Bitmap Image { get; private set; }
        public IEnumerable<Type> SupportedItemTypes
        {
        public string Name { get { yield return "Shape file importer"; }}typeof(DrainageBasin); }
        }
        public stringbool CategoryCanImportOnRootLevel { get { return "General"false; } }
        public Bitmapstring ImageFileFilter { get { return "Shape file|*.shp"; private set;} }
        public IEnumerable<Type>string SupportedItemTypes
TargetDataDirectory { get;      {set; }
        public bool ShouldCancel { get{; yield return typeof(DrainageBasin);set; }
        }
public ImportProgressChangedDelegate ProgressChanged { get; set;   }
        public bool CanImportOnRootLevelOpenViewAfterImport { get { return false; } }
        
        public string FileFilter { get { return "Shape file|*.shp"; }}public bool CanImportOn(object targetObject)
        {
        public   string TargetDataDirectoryreturn {targetObject get;is setDrainageBasin; }
        }
        public object bool ShouldCancel { get; set; }ImportItem(string path, object target = null)
        {
        public ImportProgressChangedDelegate ProgressChanged { get; set; }
    var basin = target as DrainageBasin;
          
  if (basin == null)
   public bool OpenViewAfterImport { get { return false; } }{
        
        publicthrow boolnew CanImportOnException(object targetObject)
    "Can only import on drainage basins");
    {
        }
    return targetObject is DrainageBasin;
     
   }
        public object ImportItem(string path, object target = null)
 basin.Catchments.Clear();
            {
            var basinshapeFile = target as DrainageBasinnew ShapeFile(path);
            if (basin == null)
            {
foreach (var feature in shapeFile.Features.OfType<IFeature>())
            throw{
 new Exception("Can only import on drainage basins");
            }
basin.Catchments.Add(new Feature
             basin.Catchments.Clear();   {
            var shapeFile = new ShapeFile(path);
    Geometry = feature.Geometry,
      foreach (var feature in shapeFile.Features.OfType<IFeature>())
          Attributes = {feature.Attributes
                basin.Catchments.Add(feature});
            }
            basin.CoordinateSystem = shapeFile.CoordinateSystem;
             returnreturn basin;
        }
    }
}

Also register the importer 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  Also download and unzip the shape files contained in the following archive: Gemeenten.zip. You will use all these data along the 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:



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 Removed

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 RemovedImage Added

As indicated in the error messages, some precipitation and catchment input data must be available in order for the model to be 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 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):



Next, start importing a shape file on the basin Basin item (right click the basin Basin item | Import...)  and select the file Gemeente.shp.
Now, run the model again and notice that, this time, no new error messages have been 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 Removed

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:

Wiki Markup

scrollbar

...