...
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 { /// <summary> /// Drainage basin containing a set of catchments and a coordinatesystem. /// Implements INotifyPropertyChanged and INotifyCollectionChanged to handle events /// </summary> public class DrainageBasin : INotifyPropertyChanged, INotifyCollectionChanged { private ICoordinateSystem coordinateSystem; private readonly IEventedList<IFeature> catchments; public DrainageBasin() { // Add an empty (evented) list of features and subscribe to changes (bubble changes) catchments = new EventedList<IFeature>(); catchments.CollectionChanged += (s, e) => { if (CollectionChanged != null) { CollectionChanged(s, e); } }; } public ICoordinateSystem CoordinateSystem { get { return coordinateSystem; } set { coordinateSystem = value; // invoke property changed event after setting coordinateSystem if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs("CoordinateSystem")); } } } public IList<IFeature> Catchments { get { return catchments; } } public event PropertyChangedEventHandler PropertyChanged; public event NotifyCollectionChangedEventHandler CollectionChanged; } } |
...
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 { public class VolumeModel : ModelBase { private readonly DrainageBasin basin; private readonly TimeSeries precipitation; private readonly FeatureCoverage volume; /// <summary> /// Creates a volume model /// </summary> public VolumeModel() { // Create the input items of the volume model basin = new DrainageBasin(); precipitation = new TimeSeries { Components = { new Variable<double>("Precipitation") } }; // Create the output item of the volume model volume = new FeatureCoverage("Output data") { IsTimeDependent = true, Arguments = { new Variable<IFeature>("Catchment") { FixedSize = 0 } }, Components = { new Variable<double>("Volume") }, }; // Wrap fields as input/output data items DataItems.Add(new DataItem(precipitation, "Precipitation", typeof(TimeSeries), DataItemRole.Input, "PrecipitationTag")); DataItems.Add(new DataItem(basin, "Basin", typeof(DrainageBasin), DataItemRole.Input, "BasinTag")); DataItems.Add(new DataItem(volume, "Volume", typeof(FeatureCoverage), DataItemRole.Output, "VolumeTag")); } /// <summary> /// The precipitation time series: P = P(t) [L/T]. Input of the model. /// </summary> public TimeSeries Precipitation { get { return precipitation; } } /// <summary> /// The drainage basin (set of catchments). Input of the model. /// </summary> public DrainageBasin Basin { get { return basin; } } /// <summary> /// 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 { get { return volume; } } /// <summary> /// The initialization of model runs /// </summary> protected override void OnInitialize() { // Clear any previous output volume.Clear(); // Ensure the coordinate system of the volume output is the same as the catchments input (basin) volume.CoordinateSystem = (GeoAPI.Extensions.CoordinateSystems.ICoordinateSystem) basin.CoordinateSystem; // Ensure at least one catchment and one precipitation value is present ValidateInputData(); // Initialize the output feature coverage volume.Features.AddRange(basin.Catchments); volume.FeatureVariable.FixedSize = basin.Catchments.Count(); volume.FeatureVariable.AddValues(basin.Catchments); } /// <summary> /// The actual calculation during model run /// </summary> protected override void OnExecute() { // Loop all times foreach (var time in precipitation.Time.Values) { // Obtain the precipitation value for the current 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"); } } } } |
...
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() }; } |
...
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 { public string Name { get { return "Shape file importer"; } } public string Category { get { return "General"; } } public Bitmap Image { get; private set; } public IEnumerable<Type> SupportedItemTypes { get { yield return typeof(DrainageBasin); } } public bool CanImportOnRootLevel { get { return false; } } public string FileFilter { get { return "Shape file|*.shp"; } } public string TargetDataDirectory { get; set; } public bool ShouldCancel { get; set; } public ImportProgressChangedDelegate ProgressChanged { get; set; } public bool OpenViewAfterImport { get { return false; } } public bool CanImportOn(object targetObject) { return targetObject is DrainageBasin; } public object ImportItem(string path, object target = null) { var basin = target as DrainageBasin; if (basin == null) { throw new Exception("Can only import on drainage basins"); } basin.Catchments.Clear(); var shapeFile = new ShapeFile(path); foreach (var feature in shapeFile.Features.OfType<IFeature>()) { basin.Catchments.Add(new Feature { Geometry = feature.Geometry, Attributes = feature.Attributes }); } basin.CoordinateSystem = shapeFile.CoordinateSystem; return basin; } } } |
...