...
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:
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). |
Code Block | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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:
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:
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:
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 |
---|
...