org.kalypso.ui.rrm.internal.calccase.CatchmentModelHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.kalypso.ui.rrm.internal.calccase.CatchmentModelHelper.java

Source

/*----------------    FILE HEADER KALYPSO ------------------------------------------
 *
 *  This file is part of kalypso.
 *  Copyright (C) 2004 by:
 *
 *  Technical University Hamburg-Harburg (TUHH)
 *  Institute of River and coastal engineering
 *  Denickestrae 22
 *  21073 Hamburg, Germany
 *  http://www.tuhh.de/wb
 *
 *  and
 *
 *  Bjoernsen Consulting Engineers (BCE)
 *  Maria Trost 3
 *  56070 Koblenz, Germany
 *  http://www.bjoernsen.de
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  Contact:
 *
 *  E-Mail:
 *  belger@bjoernsen.de
 *  schlienger@bjoernsen.de
 *  v.doemming@tuhh.de
 *
 *  ---------------------------------------------------------------------------*/
package org.kalypso.ui.rrm.internal.calccase;

import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TimeZone;

import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;

import org.apache.commons.lang3.ObjectUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Duration;
import org.joda.time.Interval;
import org.joda.time.LocalTime;
import org.joda.time.Period;
import org.kalypso.contribs.eclipse.core.runtime.IStatusCollector;
import org.kalypso.contribs.eclipse.core.runtime.StatusCollector;
import org.kalypso.contribs.java.math.IntervalUtilities;
import org.kalypso.contribs.java.net.UrlResolver;
import org.kalypso.contribs.java.util.DateUtilities;
import org.kalypso.core.KalypsoCorePlugin;
import org.kalypso.model.hydrology.binding.cm.ICatchment;
import org.kalypso.model.hydrology.binding.cm.ILinearSumGenerator;
import org.kalypso.model.hydrology.binding.cm.IMultiGenerator;
import org.kalypso.model.hydrology.binding.control.NAControl;
import org.kalypso.model.hydrology.binding.model.Catchment;
import org.kalypso.model.hydrology.binding.model.NaModell;
import org.kalypso.model.hydrology.binding.timeseriesMappings.IMappingElement;
import org.kalypso.model.hydrology.binding.timeseriesMappings.ITimeseriesMapping;
import org.kalypso.model.hydrology.binding.timeseriesMappings.TimeseriesMappingType;
import org.kalypso.model.hydrology.project.RrmSimulation;
import org.kalypso.model.hydrology.util.cm.CatchmentHelper;
import org.kalypso.model.rcm.binding.IRainfallGenerator;
import org.kalypso.ogc.gml.serialize.GmlSerializer;
import org.kalypso.ogc.sensor.DateRange;
import org.kalypso.ogc.sensor.IAxis;
import org.kalypso.ogc.sensor.IObservation;
import org.kalypso.ogc.sensor.ITupleModel;
import org.kalypso.ogc.sensor.SensorException;
import org.kalypso.ogc.sensor.TIMESERIES_TYPE;
import org.kalypso.ogc.sensor.metadata.ITimeseriesConstants;
import org.kalypso.ogc.sensor.timeseries.AxisUtils;
import org.kalypso.ogc.sensor.timeseries.TimeseriesUtils;
import org.kalypso.ogc.sensor.util.ZmlLink;
import org.kalypso.ui.rrm.internal.KalypsoUIRRMPlugin;
import org.kalypso.ui.rrm.internal.cm.view.MultiBean;
import org.kalypso.ui.rrm.internal.i18n.Messages;
import org.kalypso.zml.obslink.TimeseriesLinkType;
import org.kalypsodeegree.model.feature.Feature;
import org.kalypsodeegree.model.feature.GMLWorkspace;
import org.kalypsodeegree.model.feature.IFeatureBindingCollection;
import org.kalypsodeegree.model.feature.IXLinkedFeature;
import org.kalypsodeegree_impl.model.feature.gmlxpath.GMLXPath;

import com.google.common.base.Charsets;

/**
 * This class contains functions for dealing with catchment models.
 *
 * @author Holger Albert
 */
public class CatchmentModelHelper {
    /**
     * The constructor.
     */
    private CatchmentModelHelper() {
    }

    /**
     * This function bulds a link for the timeseries of the given catchment.
     *
     * @param simulation
     *          The rrm simulation.
     * @param prefix
     *          This prefix will be used, if set. May be null.
     * @param parameterType
     *          The parameter type of the timeseries.
     * @param catchment
     *          The catchment, the timeseries is for.
     */
    public static String buildLink(final RrmSimulation simulation, final String prefix, final String folderName,
            final String parameterType, final Feature modelElement) throws UnsupportedEncodingException {
        String relativeLink = null;
        if (prefix == null || prefix.length() == 0)
            relativeLink = String.format("%s/%s_%s.zml", folderName, parameterType, //$NON-NLS-1$
                    URLEncoder.encode(modelElement.getName(), Charsets.UTF_8.name()));
        else
            relativeLink = String.format("%s/%s_%s_%s.zml", folderName, prefix, parameterType, //$NON-NLS-1$
                    URLEncoder.encode(modelElement.getName(), Charsets.UTF_8.name()));

        final IFolder simulationFolder = simulation.getSimulationFolder();
        final IPath simulationPath = simulationFolder.getFullPath();
        final IPath fullLink = simulationPath.append(relativeLink);

        return UrlResolver.createProjectPath(fullLink);
    }

    public static String getTargetLinkFolderName(final RrmSimulation simulation, final String parameterType) {
        switch (parameterType) {
        case ITimeseriesConstants.TYPE_RAINFALL:
            return simulation.getPrecipitationFolder().getName();
        case ITimeseriesConstants.TYPE_MEAN_TEMPERATURE:
            return simulation.getClimateFolder().getName();
        case ITimeseriesConstants.TYPE_EVAPORATION_LAND_BASED:
            return simulation.getClimateFolder().getName();
        }

        throw new IllegalArgumentException();
    }

    /**
     * This function sets a link to the catchment.
     *
     * @param catchment
     *          This catchment will get the link set.
     * @param targetLink
     *          The qname of the property for setting the link.
     * @param link
     *          The link to set.
     */
    public static void setLink(final Catchment catchment, final QName targetLink, final String link) {
        /* Create the timeseries link type. */
        final TimeseriesLinkType tsLink = new TimeseriesLinkType();
        tsLink.setHref(link);

        /* Set the property. */
        catchment.setProperty(targetLink, tsLink);
    }

    /**
     * This function checks, if the sub generators contained in the multi generator apply to special rules.<br/>
     * <br/>
     * It will check the following rules:
     * <ul>
     * <li>All generators must be of the type ILinearSumGenerator.</li>
     * <li>The timestep must be the same in all generators.</li>
     * <li>The timestamp must be the same in all generators.</li>
     * <li>The areas must be the same and must have the same order in all generators.</li>
     * <li>Generators may not overlap. Touch is ok.</li>
     * <li>There are no gaps allowed between the validity ranges of adjacent generators.</li>
     * </ul>
     *
     * @param multiGenerator
     *          The multi generator.
     * @param control
     *          The na control.
     * @return A status. If the severity is ERROR, the validation has failed.
     */
    public static IStatus validateMultiGenerator(final IMultiGenerator multiGenerator, final NAControl control) {
        /* Get the generators. */
        final IFeatureBindingCollection<IRainfallGenerator> subGenerators = multiGenerator.getSubGenerators();
        final IRainfallGenerator[] generators = subGenerators.toArray(new IRainfallGenerator[] {});

        /* Get the simulation start and end dates. */
        final Date simulationStart = control.getSimulationStart();
        final Date simulationEnd = control.getSimulationEnd();

        /* Get the description. */
        final String description = multiGenerator.getDescription();

        return performValidation(generators, simulationStart, simulationEnd, description);
    }

    /**
     * This function checks, if the sub generators contained in the multi bean apply to special rules.<br/>
     * <br/>
     * It will check the following rules:
     * <ul>
     * <li>All generators must be of the type ILinearSumGenerator.</li>
     * <li>The timestep must be the same in all generators.</li>
     * <li>The timestamp must be the same in all generators.</li>
     * <li>The areas must be the same and must have the same order in all generators.</li>
     * <li>Generators may not overlap. Touch is ok.</li>
     * <li>There are no gaps allowed between the validity ranges of adjacent generators.</li>
     * </ul>
     *
     * @param multiBean
     *          The multi bean.
     * @return A status. If the severity is ERROR, the validation has failed.
     */
    public static IStatus validateMultiBean(final MultiBean multiBean) {
        /* Get the generators. */
        final ILinearSumGenerator[] generators = multiBean.getSubGenerators();

        /* Get the simulation start and end dates. */
        /* The validity range of the multi bean will be used. */
        final XMLGregorianCalendar start = (XMLGregorianCalendar) multiBean
                .getProperty(IMultiGenerator.PROPERTY_VALID_FROM);
        final XMLGregorianCalendar end = (XMLGregorianCalendar) multiBean
                .getProperty(IMultiGenerator.PROPERTY_VALID_TO);
        final Date simulationStart = DateUtilities.toDate(start);
        final Date simulationEnd = DateUtilities.toDate(end);

        /* Get the description. */
        final String description = (String) multiBean.getProperty(IMultiGenerator.QN_DESCRIPTION);

        return performValidation(generators, simulationStart, simulationEnd, description);
    }

    /**
     * This function checks, if the generators apply to special rules.<br/>
     * <br/>
     * It will check the following rules:
     * <ul>
     * <li>All generators must be of the type ILinearSumGenerator.</li>
     * <li>The timestep must be the same in all generators.</li>
     * <li>The timestamp must be the same in all generators.</li>
     * <li>The areas must be the same and must have the same order in all generators.</li>
     * <li>Generators may not overlap. Touch is ok.</li>
     * <li>There are no gaps allowed between the validity ranges of adjacent generators.</li>
     * </ul>
     *
     * @param generators
     *          The generators to validate.
     * @param simulationStart
     *          The simulation start date.
     * @param simulationEnd
     *          The simulation end date.
     * @return A status. If the severity is ERROR, the validation has failed.
     */
    private static IStatus performValidation(final IRainfallGenerator[] generators, final Date simulationStart,
            final Date simulationEnd, final String description) {
        /* The status collector. */
        final StatusCollector collector = new StatusCollector(KalypsoUIRRMPlugin.getID());

        /* No generators available. */
        if (generators.length == 0) {
            collector.add(new Status(IStatus.ERROR, KalypsoUIRRMPlugin.getID(),
                    String.format(Messages.getString("CatchmentModelHelper_0"), description))); //$NON-NLS-1$
            return collector
                    .asMultiStatus(String.format(Messages.getString("CatchmentModelHelper_1"), description)); //$NON-NLS-1$
        }

        /* The values of the first generator will be the reference for the others. */
        final ILinearSumGenerator firstGenerator = (ILinearSumGenerator) generators[0];

        /* Check each generator. */
        for (final IRainfallGenerator generator : generators) {
            /* Check all generators, if their validity ranges are set. */
            final Date validFrom = generator.getValidFrom();
            final Date validTo = generator.getValidTo();
            if (validFrom == null || validTo == null) {
                collector.add(new Status(IStatus.ERROR, KalypsoUIRRMPlugin.getID(),
                        String.format(Messages.getString("CatchmentModelHelper.0"), generator.getDescription()))); //$NON-NLS-1$
                continue;
            }

            /* Perfomance: Do not check the first generator with itself. */
            if (generator.getId().equals(firstGenerator.getId()))
                continue;

            /* (1) All generators must be of the type ILinearSumGenerator. */
            if (!(generator instanceof ILinearSumGenerator)) {
                collector.add(new Status(IStatus.ERROR, KalypsoUIRRMPlugin.getID(),
                        String.format(Messages.getString("CatchmentModelHelper_2"), generator.getDescription()))); //$NON-NLS-1$
                continue;
            }

            /* Cast. */
            final ILinearSumGenerator linearSumGenerator = (ILinearSumGenerator) generator;

            /* (2) The timestep must be the same in all generators. */
            /* (3) The timestamp must be the same in all generators. */
            final IStatus generalStatus = compareGeneralProperties(firstGenerator, linearSumGenerator);
            if (!generalStatus.isOK()) {
                collector.add(generalStatus);
                continue;
            }

            /* (4) The areas must be the same and must have the same order in all generators. */
            if (!compareGeneratorCatchments(firstGenerator, linearSumGenerator, false)) {
                collector.add(new Status(IStatus.ERROR, KalypsoUIRRMPlugin.getID(),
                        String.format(Messages.getString("CatchmentModelHelper_3"), generator.getDescription(), //$NON-NLS-1$
                                firstGenerator.getDescription())));
                continue;
            }

            /* HINT: If there is only one generator, we do not reach the code here. */
            /* HINT: If we do reach here, it will be the 2nd loop or one after. */

            /* (5) Generators may not overlap. Touch is ok. */
            if (!compareGeneratorValidityOverlap(linearSumGenerator, generators)) {
                collector.add(new Status(IStatus.ERROR, KalypsoUIRRMPlugin.getID(),
                        String.format(Messages.getString("CatchmentModelHelper_4"), generator.getDescription()))); //$NON-NLS-1$
                continue;
            }
        }

        /* (6) There are no gaps allowed between the validity ranges of adjacent generators. */
        if (!compareGeneratorValidityGaps(generators, simulationStart, simulationEnd, firstGenerator.getTimestep(),
                firstGenerator.getTimestamp()))
            collector.add(new Status(IStatus.ERROR, KalypsoUIRRMPlugin.getID(),
                    String.format(Messages.getString("CatchmentModelHelper_5"), description))); //$NON-NLS-1$

        return collector.asMultiStatus(String.format(Messages.getString("CatchmentModelHelper_6"), description)); //$NON-NLS-1$
    }

    /**
     * This function checks two linear sum generators for its general properties.<br/>
     * <br/>
     * It will check the following rules:
     * <ul>
     * <li>The timestep must be the same.</li>
     * <li>The timestamp must be the same.</li>
     * </ul>
     *
     * @param generator1
     *          The first linear sum generator.
     * @param generator2
     *          The second linear sum generator.
     * @return A status. If the severity is ERROR, the validation has failed.
     */
    private static IStatus compareGeneralProperties(final ILinearSumGenerator generator1,
            final ILinearSumGenerator generator2) {
        final Integer timestep1 = generator1.getTimestep();
        final Integer timestep2 = generator2.getTimestep();
        if (!ObjectUtils.equals(timestep1, timestep2))
            return new Status(IStatus.ERROR, KalypsoUIRRMPlugin.getID(),
                    String.format(Messages.getString("CatchmentModelHelper_7"), generator2.getDescription(), //$NON-NLS-1$
                            generator1.getDescription()));

        final LocalTime timestamp1 = generator1.getTimestamp();
        final LocalTime timestamp2 = generator2.getTimestamp();
        if (!ObjectUtils.equals(timestamp1, timestamp2))
            return new Status(IStatus.ERROR, KalypsoUIRRMPlugin.getID(),
                    String.format(Messages.getString("CatchmentModelHelper_8"), generator2.getDescription(), //$NON-NLS-1$
                            generator1.getDescription()));

        return new Status(IStatus.OK, KalypsoUIRRMPlugin.getID(),
                String.format(Messages.getString("CatchmentModelHelper_9"), generator2.getDescription(), //$NON-NLS-1$
                        generator1.getDescription()));
    }

    /**
     * This function compares the catchments of two linear sum generators.<br/>
     * <br/>
     * It will check the following:
     * <ul>
     * <li>The number of catchments.</li>
     * <li>The order of the catchments.</li>
     * <li>Optional: The factors and timeseries in the catchments.</li>
     * </ul>
     *
     * @param generator1
     *          The first linear sum generator.
     * @param generator2
     *          The second linear sum generator.
     * @param includeTimeseries
     *          If true, the factors and timeseries in the catchments are compared, too.
     * @return True, if the catchments of the linear sum generators are equal. False otherwise.
     */
    public static boolean compareGeneratorCatchments(final ILinearSumGenerator generator1,
            final ILinearSumGenerator generator2, final boolean includeTimeseries) {
        /* Get the catchments. */
        final IFeatureBindingCollection<ICatchment> catchments1 = generator1.getCatchments();
        final IFeatureBindingCollection<ICatchment> catchments2 = generator2.getCatchments();
        if (catchments1.size() != catchments2.size())
            return false;

        /* Compare the catchments. */
        for (int i = 0; i < catchments1.size(); i++) {
            /* Get the catchments. */
            final ICatchment catchment1 = catchments1.get(i);
            final ICatchment catchment2 = catchments2.get(i);

            /* If the linked areas do not match, this are completely different lists or not in the same order. */
            final String areaHref1 = ((IXLinkedFeature) catchment1.getProperty(ICatchment.PROPERTY_AREA_LINK))
                    .getHref();
            final String areaHref2 = ((IXLinkedFeature) catchment2.getProperty(ICatchment.PROPERTY_AREA_LINK))
                    .getHref();
            if (!areaHref1.equals(areaHref2))
                return false;

            /* If true, the factors and timeseries in the catchments are compared, too. */
            if (includeTimeseries) {
                /* Build the hash. */
                final String hash1 = CatchmentHelper.buildHash(catchment1);
                final String hash2 = CatchmentHelper.buildHash(catchment2);
                if (!hash1.equals(hash2))
                    return false;
            }
        }

        return true;
    }

    /**
     * This function compares the validity ranges of the generators.
     *
     * @param compareGenerator
     *          The compare generator.
     * @param generators
     *          All generators the compare generator will be compared against. If the compare generator is contained, it
     *          will be ignored.
     * @return <ul>
     *         <li>True: The validity range of the compare generator does not overlap the validity ranges of the other
     *         generators.</li>
     *         <li>False: The validity range of the compare generator overlaps one validity range of the other generators.
     *         </li>
     *         </ul>
     */
    private static boolean compareGeneratorValidityOverlap(final IRainfallGenerator compareGenerator,
            final IRainfallGenerator[] generators) {
        /* No generators available, to compare to. */
        if (generators.length == 0)
            return true;

        /* The interval of the compare generator. */
        final Interval compareInterval = new Interval(new DateTime(compareGenerator.getValidFrom()),
                new DateTime(compareGenerator.getValidTo()));

        /* Check if the interval overlaps one of the other intervals. */
        for (final IRainfallGenerator generator : generators) {
            /* Do not compare the compare generator with itself. */
            if (compareGenerator.getId().equals(generator.getId()))
                continue;

            /* The interval of the generator. */
            final Interval interval = new Interval(new DateTime(generator.getValidFrom()),
                    new DateTime(generator.getValidTo()));
            if (compareInterval.overlaps(interval)) {
                final Interval overlapInterval = compareInterval.overlap(interval);
                final Duration overlapDuration = overlapInterval.toDuration();
                final long standardMinutes = overlapDuration.getStandardMinutes();
                if (standardMinutes > 0)
                    return false;
            }
        }

        return true;
    }

    /**
     * This function checks the validity ranges of the generators for gaps.
     *
     * @param generators
     *          The generators to be checked.
     * @param simulationStart
     *          The start of the simulation.
     * @param simulationEnd
     *          The end of the simulation.
     * @param timestep
     *          The timestep.
     * @param timestamp
     *          The timestamp.
     * @return True, if the validity ranges of the generators do not have gaps or only timestep sized gaps. False
     *         otherwise.
     */
    private static boolean compareGeneratorValidityGaps(final IRainfallGenerator[] generators,
            final Date simulationStart, final Date simulationEnd, final Integer timestep,
            final LocalTime timestamp) {
        /* No generators available. */
        /* Only one generator available. */
        if (generators.length <= 1)
            return true;

        /* Build the simulation interval. */
        final DateTime simulationStartDateTime = new DateTime(simulationStart);
        final DateTime simulationEndDateTime = new DateTime(simulationEnd);
        final DateRange simulationRange = modifyWithTimestamp(timestamp, simulationStartDateTime,
                simulationEndDateTime);
        final Date simulationStartDate = simulationRange.getFrom();
        final Date simulationEndDate = simulationRange.getTo();
        final long simulationStartTime = simulationStartDate.getTime();
        final long simulationEndTime = simulationEndDate.getTime();
        final org.kalypso.contribs.java.math.Interval simulationInterval = new org.kalypso.contribs.java.math.Interval(
                simulationStartTime, simulationEndTime);
        org.kalypso.contribs.java.math.Interval[] simulationRest = new org.kalypso.contribs.java.math.Interval[] {
                simulationInterval };

        /* Check each generator. */
        for (final IRainfallGenerator generator : generators) {
            /* Get the generator dates. */
            DateTime generatorStartDateTime = new DateTime(generator.getValidFrom());
            final DateTime generatorEndDateTime = new DateTime(generator.getValidTo());

            /* Adjust the check for sum values. */
            final String parameterType = generator.getParameterType();
            final TIMESERIES_TYPE type = TimeseriesUtils.getType(parameterType);
            if (type.equals(TIMESERIES_TYPE.eSumValue))
                generatorStartDateTime = generatorStartDateTime.plusDays(1);

            /* Build the generator interval. */
            final Date generatorStartDate = generatorStartDateTime.toDate();
            final Date generatorEndDate = generatorEndDateTime.toDate();
            final long generatorStartTime = generatorStartDate.getTime();
            final long generatorEndTime = generatorEndDate.getTime();
            final org.kalypso.contribs.java.math.Interval generatorInterval = new org.kalypso.contribs.java.math.Interval(
                    generatorStartTime, generatorEndTime);

            /* Substract the generator interval from all rest intervals. */
            simulationRest = IntervalUtilities.difference(simulationRest, generatorInterval);
        }

        /* No gaps. */
        if (simulationRest.length == 0)
            return true;

        for (final org.kalypso.contribs.java.math.Interval restInterval : simulationRest) {
            /* The gaps are only allowed to be of the size of the timestep. */
            /* HINT: The timestep is in minutes -> convert to milliseconds. */
            if (restInterval.getWidth() > timestep.intValue() * 60 * 1000)
                return false;
        }

        return true;
    }

    private static DateRange modifyWithTimestamp(final LocalTime timestamp, final DateTime simulationStart,
            final DateTime simulationEnd) {
        /* Nothing to do. */
        if (timestamp == null)
            return new DateRange(simulationStart.toDate(), simulationEnd.toDate());

        /* Convert to a date with the kalypso timezone. */
        /* The date fields are ignored. */
        final DateTime timestampUTC = timestamp
                .toDateTimeToday(DateTimeZone.forTimeZone(TimeZone.getTimeZone("UTC"))); //$NON-NLS-1$
        final DateTime timestampDate = new DateTime(timestampUTC.toDate(),
                DateTimeZone.forTimeZone(KalypsoCorePlugin.getDefault().getTimeZone()));

        /* Further adjust range by predefined time. */
        final DateTime startWithTime = simulationStart.withTime(timestampDate.getHourOfDay(),
                timestampDate.getMinuteOfHour(), timestampDate.getSecondOfMinute(),
                timestampDate.getMillisOfSecond());
        final DateTime endWithTime = simulationEnd.withTime(timestampDate.getHourOfDay(),
                timestampDate.getMinuteOfHour(), timestampDate.getSecondOfMinute(),
                timestampDate.getMillisOfSecond());

        return new DateRange(startWithTime.toDate(), endWithTime.toDate());
    }

    /**
     * This function calculates the range for the timeseries to be generated. The range equals the range defined in the
     * simulation adjusted as follows:
     * <ul>
     * <li>1 timestep earlier</li>
     * <li>3 timesteps later</li>
     * </ul>
     *
     * @param control
     *          The na control.
     * @param timestep
     *          The timestep.
     * @param timestamp
     *          The timestamp in UTC.
     * @return The date range.
     */
    public static DateRange getRange(final NAControl control, final Period timestep, final LocalTime timestamp) {
        final Date simulationStart = control.getSimulationStart();
        final Date simulationEnd = control.getSimulationEnd();

        final DateTime start = new DateTime(simulationStart);
        final DateTime end = new DateTime(simulationEnd);

        final DateTime adjustedStart = start.minus(timestep);
        final DateTime adjustedEnd = end.plus(timestep).plus(timestep).plus(timestep);

        if (timestep.getDays() == 0 || timestamp == null)
            return new DateRange(adjustedStart.toDate(), adjustedEnd.toDate());

        /* Convert to a date with the kalypso timezone. */
        /* The date fields are ignored. */
        final DateTime timestampUTC = timestamp
                .toDateTimeToday(DateTimeZone.forTimeZone(TimeZone.getTimeZone("UTC"))); //$NON-NLS-1$
        final DateTime timestampDate = new DateTime(timestampUTC.toDate(),
                DateTimeZone.forTimeZone(KalypsoCorePlugin.getDefault().getTimeZone()));

        /* Further adjust range by predefined time. */
        final DateTime startWithTime = adjustedStart.withTime(timestampDate.getHourOfDay(),
                timestampDate.getMinuteOfHour(), timestampDate.getSecondOfMinute(),
                timestampDate.getMillisOfSecond());
        final DateTime endWithTime = adjustedEnd.withTime(timestampDate.getHourOfDay(),
                timestampDate.getMinuteOfHour(), timestampDate.getSecondOfMinute(),
                timestampDate.getMillisOfSecond());

        /* New start must always be before unadjusted start, fix, if this is not the case. */
        DateTime startWithTimeFixed;
        if (startWithTime.isAfter(adjustedStart))
            startWithTimeFixed = startWithTime.minus(timestep);
        else
            startWithTimeFixed = startWithTime;

        /* New end must always be after unadjusted end, fix, if this is not the case. */
        DateTime endWithTimeFixed;
        if (endWithTime.isBefore(adjustedEnd))
            endWithTimeFixed = endWithTime.plus(timestep);
        else
            endWithTimeFixed = endWithTime;

        return new DateRange(startWithTimeFixed.toDate(), endWithTimeFixed.toDate());

    }

    public static NAControl loadControl(final RrmSimulation simulation) throws Exception {
        final IFile calculationGml = simulation.getCalculationGml();
        final GMLWorkspace workspace = GmlSerializer.createGMLWorkspace(calculationGml);
        return (NAControl) workspace.getRootFeature();
    }

    /**
     * This function compares the timeseries of two simulations.<br>
     * <br/>
     * Details:
     * <ul>
     * <li>It will use the second simulation as reference.</li>
     * <li>All dates/values in the second simulation must exist and be equal (to a degree) in the first simulation.</li>
     * <li>If a date/value of the second simulation misses or is not equal in the first simulation, this is a difference.</li>
     * <li>It will not compare the length or metadata of the timeseries.</li>
     * </ul>
     *
     * @param simulationFolder
     *          The folder of the first simulation.
     * @param simulationTmpFolder
     *          The folder of the second simulation.
     * @return A WARNING status, if there are differences. A OK status otherwise.
     */
    public static IStatus compareTimeseries(final IFolder simulationFolder, final IFolder simulationTmpFolder)
            throws Exception {
        /* The models. */
        NaModell model = null;
        NaModell tmpModel = null;

        try {
            /* The status collector. */
            final IStatusCollector collector = new StatusCollector(KalypsoUIRRMPlugin.getID());

            /* Load the models. */
            model = loadModel(new RrmSimulation(simulationFolder));
            tmpModel = loadModel(new RrmSimulation(simulationTmpFolder));

            /* Get the contexts. */
            final URL context = model.getWorkspace().getContext();
            final URL tmpContext = tmpModel.getWorkspace().getContext();

            /* Compare the catchments. */
            final QName[] linkProperties = new QName[] { Catchment.PROP_PRECIPITATION_LINK,
                    Catchment.PROP_TEMPERATURE_LINK, Catchment.PROP_EVAPORATION_LINK };
            final String[] linkLabels = new String[] { Messages.getString("CatchmentModelHelper_12"), //$NON-NLS-1$
                    Messages.getString("CatchmentModelHelper_13"), Messages.getString("CatchmentModelHelper_14") }; //$NON-NLS-1$ //$NON-NLS-2$
            for (int i = 0; i < linkLabels.length; i++) {
                final IStatus status = compareCatchments(model, tmpModel, context, tmpContext, linkProperties[i],
                        linkLabels[i]);
                if (status != null)
                    collector.add(status);
            }

            /* Compare other timeseries (created by timeseries mappings). */
            for (final TimeseriesMappingType mappingType : TimeseriesMappingType.values()) {
                final IStatus status = compareMapping(model, tmpModel, context, tmpContext, mappingType);
                if (status != null)
                    collector.add(status);
            }

            return collector.asMultiStatus(Messages.getString("CatchmentModelHelper_15")); //$NON-NLS-1$
        } finally {
            /* Dispose the model. */
            if (model != null)
                model.getWorkspace().dispose();

            /* Dispose the temporary model. */
            if (tmpModel != null)
                tmpModel.getWorkspace().dispose();
        }
    }

    /**
     * This function loads the na model.
     *
     * @param simulation
     *          The simulation.
     * @return The na model.
     */
    public static NaModell loadModel(final RrmSimulation simulation) throws Exception {
        final IFile modelGml = simulation.getModelGml();
        final GMLWorkspace workspace = GmlSerializer.createGMLWorkspace(modelGml);
        return (NaModell) workspace.getRootFeature();
    }

    private static IStatus compareCatchments(final NaModell model, final NaModell tmpModel, final URL context,
            final URL tmpContext, final QName linkProperty, final String linkLabel) {
        /* The status collector. */
        final StatusCollector collector = new StatusCollector(KalypsoUIRRMPlugin.getID());

        /* Both lists must be in the same order, because the temporary one is a copy. */
        final IFeatureBindingCollection<Catchment> catchments = model.getCatchments();
        final IFeatureBindingCollection<Catchment> tmpCatchments = tmpModel.getCatchments();

        for (int i = 0; i < catchments.size(); i++) {
            final Catchment catchment = catchments.get(i);
            final Catchment tmpCatchment = tmpCatchments.get(i);

            final IStatus status = compareCatchment(catchment, tmpCatchment, context, tmpContext, linkProperty);
            if (status != null)
                collector.add(status);
        }

        return collector.asMultiStatus(String.format(Messages.getString("CatchmentModelHelper_16"), linkLabel)); //$NON-NLS-1$
    }

    private static IStatus compareCatchment(final Catchment catchment, final Catchment tmpCatchment,
            final URL context, final URL tmpContext, final QName linkProperty) {
        /* Get the timeseries links of the catchment. */
        final ZmlLink zmlLink = new ZmlLink(catchment, new GMLXPath(linkProperty), context);

        /* Get the timeseries links of the temporary catchment. */
        final ZmlLink tmpZmlLink = new ZmlLink(tmpCatchment, new GMLXPath(linkProperty), tmpContext);

        /* Compare the temperature timeseries. */
        return compareTimeseries(zmlLink, tmpZmlLink, catchment.getName());
    }

    private static IStatus compareMapping(final NaModell model, final NaModell tmpModel, final URL context,
            final URL tmpContext, final TimeseriesMappingType mappingType) {
        /* The status collector. */
        final IStatusCollector collector = new StatusCollector(KalypsoUIRRMPlugin.getID());

        /* Both lists must be in the same order, because the temporary one is a copy. */
        final Feature[] modelElements = mappingType.getModelElements(model);
        final Feature[] tmpModelElements = mappingType.getModelElements(tmpModel);

        final QName linkProperty = mappingType.getModelLinkProperty();
        for (int i = 0; i < modelElements.length; i++) {
            final Feature modelElement = modelElements[i];
            final Feature tmpModelElement = tmpModelElements[i];

            final ZmlLink zmlLink = new ZmlLink(modelElement, new GMLXPath(linkProperty), context);
            final ZmlLink tmpZmlLink = new ZmlLink(tmpModelElement, new GMLXPath(linkProperty), tmpContext);

            final IStatus status = compareTimeseries(zmlLink, tmpZmlLink, modelElement.getName());
            if (status != null)
                collector.add(status);
        }

        return collector.asMultiStatus(
                String.format(Messages.getString("CatchmentModelHelper_17"), mappingType.getLabel())); //$NON-NLS-1$
    }

    private static IStatus compareTimeseries(final ZmlLink link, final ZmlLink tmpLink, final String statusLabel) {
        /* No links? */
        if (link == null && tmpLink == null)
            return new Status(IStatus.OK, KalypsoUIRRMPlugin.getID(), statusLabel);

        /* The status collector. */
        final StatusCollector collector = new StatusCollector(KalypsoUIRRMPlugin.getID());

        try {
            /* If there is no link in the new model, no status. */
            if (!(tmpLink.isLinkSet() && tmpLink.isLinkExisting()))
                return null;

            /* If there is no link in the old model, create a warning. */
            if (!(link.isLinkSet() && link.isLinkExisting()))
                return new Status(IStatus.WARNING, KalypsoUIRRMPlugin.getID(),
                        Messages.getString("CatchmentModelHelper.1")); //$NON-NLS-1$

            /* Load the timeseries. */
            /* The time zone may be different to that of the newly generated timeseries. */
            final IObservation observation = link.loadObservation();

            /* Load the temporary timeseries. */
            /* The time zone may be different to that of the original timeseries. */
            final IObservation tmpObservation = tmpLink.loadObservation();

            /* Get the values of both timeseries. */
            final ITupleModel values = observation.getValues(null);
            final ITupleModel tmpValues = tmpObservation.getValues(null);

            /* Build a hash date->value for the old timeseries. */
            final Map<Long, Double> hash = buildHash(values);

            /* Build a hash date->value for the new timeseries. */
            final Map<Long, Double> tmpHash = buildHash(tmpValues);

            /* Loop through the new hash. */
            int differences = 0;
            for (final Entry<Long, Double> tmpEntry : tmpHash.entrySet()) {
                /* Get the key and the value of the new timeseries. */
                final Long tmpKey = tmpEntry.getKey();
                final Double tmpValue = tmpEntry.getValue();

                /* Get the value of the old timeseries. */
                final Double value = hash.get(tmpKey);

                /* Compare the values of the new timeseries with the ones in the old timeseries. */
                // TODO 0.01 different with other datatypes?
                if (value == null || Math.abs(tmpValue.doubleValue() - value.doubleValue()) > 0.01)
                    differences++;
            }

            /* Calculate the procentual difference. */
            if (differences > 0) {
                final int percent = (differences * 100) / tmpHash.size();
                collector.add(IStatus.WARNING, Messages.getString("CatchmentModelHelper_18"), null, percent, //$NON-NLS-1$
                        differences);
            }

            return collector.asMultiStatusOrOK(statusLabel, statusLabel);
        } catch (final Exception ex) {
            collector.add(IStatus.WARNING, Messages.getString("CatchmentModelHelper_19"), null, //$NON-NLS-1$
                    ex.getLocalizedMessage());
            return collector.asMultiStatus(statusLabel);
        }
    }

    private static Map<Long, Double> buildHash(final ITupleModel values) throws SensorException {
        /* Memory for the hash. */
        final Map<Long, Double> hash = new LinkedHashMap<>();

        /* Find the needed axes. */
        final IAxis[] axes = values.getAxes();
        final IAxis dateAxis = AxisUtils.findDateAxis(axes);
        final IAxis[] valueAxes = AxisUtils.findValueAxes(axes, false);
        final IAxis valueAxis = valueAxes[0];

        /* Store each date->value pair. */
        for (int i = 0; i < values.size(); i++) {
            final Date date = (Date) values.get(i, dateAxis);
            final Double value = (Double) values.get(i, valueAxis);

            hash.put(new Long(date.getTime()), value);
        }

        return hash;
    }

    /**
     * This function compares the catchments of the na model with the catchment of the given generator.
     *
     * @param model
     *          The na model.
     * @param generator
     *          The generator.
     * @return A ERROR status, if the catchments do not match. A OK status otherwise.
     */
    public static IStatus compareCatchments(final NaModell model, final ILinearSumGenerator generator) {
        /* These catchments must be matched. */
        final IFeatureBindingCollection<Catchment> modelCatchments = model.getCatchments();

        /* Thease are the catchments, that must match the others. */
        final IFeatureBindingCollection<ICatchment> generatorCatchments = generator.getCatchments();

        /* Does the size match? */
        if (modelCatchments.size() != generatorCatchments.size())
            return new Status(IStatus.ERROR, KalypsoUIRRMPlugin.getID(),
                    Messages.getString("CatchmentModelHelper_20")); //$NON-NLS-1$

        /* Does the order match? */
        for (int i = 0; i < modelCatchments.size(); i++) {
            final Catchment modelCatchment = modelCatchments.get(i);
            final String modelId = modelCatchment.getId();

            final ICatchment generatorCatchment = generatorCatchments.get(i);
            final IXLinkedFeature generatorLink = (IXLinkedFeature) generatorCatchment.getAreaLink();
            final String generatorId = generatorLink.getFeatureId();

            if (!modelId.equals(generatorId))
                return new Status(IStatus.ERROR, KalypsoUIRRMPlugin.getID(),
                        Messages.getString("CatchmentModelHelper_21")); //$NON-NLS-1$
        }

        return new Status(IStatus.OK, KalypsoUIRRMPlugin.getID(), Messages.getString("CatchmentModelHelper_22")); //$NON-NLS-1$
    }

    public static boolean compareMultiGenerators(final IMultiGenerator generator1,
            final IMultiGenerator generator2) {
        final IFeatureBindingCollection<IRainfallGenerator> subGenerators1 = generator1.getSubGenerators();
        final IFeatureBindingCollection<IRainfallGenerator> subGenerators2 = generator2.getSubGenerators();

        if (subGenerators1.size() != subGenerators2.size())
            return false;

        /* The order and types must be identical. */
        for (int i = 0; i < subGenerators1.size(); i++) {
            final IRainfallGenerator subGenerator1 = subGenerators1.get(i);
            final IRainfallGenerator subGenerator2 = subGenerators2.get(i);

            if ((subGenerator1 instanceof ILinearSumGenerator && subGenerator2 instanceof IMultiGenerator)
                    || (subGenerator1 instanceof IMultiGenerator && subGenerator2 instanceof ILinearSumGenerator))
                return false;

            if (subGenerator1 instanceof ILinearSumGenerator && subGenerator2 instanceof ILinearSumGenerator) {
                if (!compareGeneratorCatchments((ILinearSumGenerator) subGenerator1,
                        (ILinearSumGenerator) subGenerator2, true))
                    return false;

                continue;
            }

            if (subGenerator1 instanceof IMultiGenerator && subGenerator2 instanceof IMultiGenerator) {
                if (!compareMultiGenerators((IMultiGenerator) subGenerator1, (IMultiGenerator) subGenerator2))
                    return false;

                continue;
            }
        }

        return true;
    }

    public static boolean compareTimeseriesMappings(final ITimeseriesMapping existingMapping,
            final ITimeseriesMapping mapping) {
        final IFeatureBindingCollection<IMappingElement> existingMappings = existingMapping.getMappings();
        final IFeatureBindingCollection<IMappingElement> mappings = mapping.getMappings();

        if (existingMappings.size() != mappings.size())
            return false;

        for (int i = 0; i < existingMappings.size(); i++) {
            final IMappingElement existingElement = existingMappings.get(i);
            final IMappingElement element = mappings.get(i);

            final ZmlLink existingLink = existingElement.getLinkedTimeseries();
            final ZmlLink link = element.getLinkedTimeseries();

            final String existingHref = existingLink.getHref();
            final String href = link.getHref();
            if (!ObjectUtils.equals(existingHref, href))
                return false;
        }

        return true;
    }
}