Exercise outline

The goal of this exercise is to create and register a WaterML2 importer class. At the end, it should be possible to import WaterML2 files (containing time series data) into a Delta Shell project.

Information on WaterML2, a global standard for hydrological time series, can be found at http://www.waterml2.org/.

Add resources for image

First we start by creating project resources. This will be the place to add all kinds of static resources. For now we will use it to add an icon image to be used in the importer.
Go to the project settings resource page and click on the link:

Now the resources are created, go to images.
Download weather-rain.png and drag the image from windows explorer to the images space.

Create a new importer class

Add to the plugin project a new folder named Importers. In this folder, create a new class named WaterML2TimeSeriesImporter.cs and adapt the contents as shown below.

 

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using DelftTools.Functions;
using DelftTools.Functions.Generic;
using DelftTools.Shell.Core;
using log4net;
namespace DeltaShell.Plugins.VolumeModel.Importers
{
    /// <summary>
    /// Importer for importing WaterML2 data to time series objects
    /// </summary>
    public class WaterML2TimeSeriesImporter : IFileImporter
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(WaterML2TimeSeriesImporter)); // Handle for writing log messages
        /// <summary>
        /// The name of the importer
        /// </summary>
        /// <remarks>Used in importer selection dialogs</remarks>
        public string Name
        {
            get { return "WaterML2 time series importer"; }
        }
        /// <summary>
        /// The category of the importer
        /// </summary>
        /// <remarks>Used in importer selection dialogs</remarks>
        public string Category
        {
            get { return "Volume model importers"; }
        }
        /// <summary>
        /// The image of the importer
        /// </summary>
        /// <remarks>Used in importer selection dialogs</remarks>
        public Bitmap Image
        {
            get { return Properties.Resources.weather_rain; }
        }
        /// <summary>
        /// The data types supported by the importer
        /// </summary>
        public IEnumerable<Type> SupportedItemTypes
        {
            get { yield return typeof(TimeSeries); }
        }
        /// <summary>
        /// Indicates that the importer can import at root level (folder/project). In other
        /// words, indicates that the <see cref="ImportItem"/> method can be called without
        /// specifying a time series target...
        /// </summary>
        public bool CanImportOnRootLevel
        {
            get { return true; }
        }
        /// <summary>
        /// The file filter of the importer
        /// </summary>
        /// <remarks>Used in file selection dialogs</remarks>
        public string FileFilter
        {
            get { return "WaterML2 files|*.XML"; }
        }
        /// <summary>
        /// Path where external data files can be copied into
        /// </summary>
        /// <remarks>Not relevant in this tutorial</remarks>
        public string TargetDataDirectory { get; set; }
        /// <summary>
        /// Whether or not an import task should be cancelled
        /// </summary>
        /// <remarks>Not part of this tutorial</remarks>
        public bool ShouldCancel { get; set; }
        /// <summary>
        /// Fired when progress has been changed
        /// </summary>
        /// <remarks>Not part in this tutorial</remarks>
        public ImportProgressChangedDelegate ProgressChanged { get; set; }
        /// <summary>
        /// Whether or not to try and open a view for the imported item
        /// </summary>
        public bool OpenViewAfterImport
        {
            get { return true; }
        }
        /// <summary>
        /// Check if an import can be done on the targetObject
        /// </summary>
        /// <param name="targetObject">Object to check</param>
        public bool CanImportOn(object targetObject)
        {
            return targetObject is TimeSeries;
        }
        /// <summary>
        /// Imports WaterML2 data from the file with path <paramref name="path"/> to the
        /// time series <paramref name="target"/>
        /// </summary>
        /// <remarks>
        /// The target parameter is optional. If a target time series is specified, the
        /// importer should import the WaterML2 data to this existing time series. When
        //  no target is set, the importer should create a new time series.
        /// </remarks>
        public object ImportItem(string path, object target = null)
        {
            // Check the file path
            if (!File.Exists(path))
            {
                Log.Error("File does not exist");
                return null;
            }
            // Obtain a new time series or check the provided target for being a time series
            var timeSeries = target == null
                ? new TimeSeries { Name = Path.GetFileNameWithoutExtension(path), Components = { new Variable<double>() } }
                : target as TimeSeries;
            if (timeSeries == null)
            {
                Log.Error("Target is of the wrong type (should be time series)");
                return null;
            }
            // Load the WaterML2 XML document
            var doc = XDocument.Load(path);
            // Obtain the document elements
            var xElements = doc.Descendants();
            // Obtain the measurement TVP tags
            var measurements = xElements.Where(element => element.Name.LocalName == "MeasurementTVP");
            // Get the corresponding time and value for each measurement tag
            foreach (var measurement in measurements)
            {
                var time = DateTime.Parse(measurement.Elements().First(e => e.Name.LocalName == "time").Value);
                var value = double.Parse(measurement.Elements().First(e => e.Name.LocalName == "value").Value);
                timeSeries[time] = value;
            }
            // Return the time series
            return timeSeries;
        }
    }
}

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

  • DelftTools.Functions
  • DelftTools.Units
  • DelftTools.Utils

These dll's 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.

The importer class is derived from the IFileImporter interface so that it can be registered in the application plugin (see the next step).

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

Register the importer in the application plugin class

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

using System.Collections.Generic;
using DeltaShell.Plugins.VolumeModel.Importers;

and

public override IEnumerable<IFileImporter> GetFileImporters()
{
    yield return new WaterML2TimeSeriesImporter();
}

Delta Shell should now be able to find the importer when importing data on new or existing time series objects.

Exercise results

First of all, download the following WaterML2 XML: WATERML2_precipitation_data.XML.

Then, run the application and start importing a new project item (right click on project | Import...). Make sure that the new importer is selected in the dialog:



If you next click on OK, a file selection dialog pops up. Select the previously downloaded WaterML2 XML file and continue with the wizard.
After finishing the import, a new time series item, containing the data as shown in the following image, should be added to the project (double click the precipitation item in the Project window):



In the steps above, a project level import has been performed which creates a completely new time series item. The importer, however, is also able to import WaterML2 data into existing time series. Although this feature will be further used in some of the upcoming exercises, it can already be tested by sequentially:

  • clearing (a part of) the imported time series data via the table view;
  • importing the downloaded WaterML2 XML file directly onto this just modified time series item (right click on the time series item | Import ...).

 

  • No labels