Versions Compared

Key

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

...

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
{
    /// <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
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;
        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
languagec#
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
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
        {
            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;
        }
    }
}

...