/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2008, Open Source Geospatial Foundation (OSGeo)
* (C) 2014, Geomatys
*
* 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;
* version 2.1 of the License.
*
* 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.
*/
package org.geotoolkit.temporal.reference;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import javax.measure.UnitConverter;
import javax.measure.quantity.Time;
import javax.measure.Unit;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.measure.Units;
import org.geotoolkit.temporal.object.DefaultTemporalCoordinate;
import org.opengis.temporal.TemporalCoordinate;
import org.opengis.temporal.TemporalCoordinateSystem;
/**
* A temporal coordinate system to simplify the computation of temporal distances
* between points and the functional description of temporal operations.
*
* @author Mehdi Sidhoum (Geomatys)
* @module
* @version 4.0
* @since 4.0
*/
@XmlType(name = "TimeCoordinateSystem_Type", propOrder = {
"origin",
"interval"
})
@XmlRootElement(name = "TimeCoordinateSystem")
public class DefaultTemporalCoordinateSystem extends DefaultTemporalReferenceSystem implements TemporalCoordinateSystem {
/**
* Milli-second unity.
*
* @see #transformCoord(org.opengis.temporal.TemporalCoordinate)
* @see #transformDateTime(java.util.Date)
*/
private static Unit<Time> UNIT_MS = Units.SECOND.divide(1000);
/**
* The origin of the scale, it must be specified in the Gregorian calendar with time of day in UTC.
*/
private Date origin;
/**
* The name of a single unit of measure used as the base interval for the scale.
* it shall be one of those units of measure for time specified by ISO 31-1,
* or a multiple of one of those units, as specified by ISO 1000.
*/
private Unit<Time> interval;
/**
* Converter use to convert units from this {@link DefaultTemporalCoordinateSystem} to milli-second.
*
* @see #transformCoord(org.opengis.temporal.TemporalCoordinate)
*/
private UnitConverter unitToMS = null;
/**
* Converter use to convert unit in milli-second into unit from this {@link DefaultTemporalCoordinateSystem}.
*
* @see #transformDateTime(java.util.Date)
*/
private UnitConverter msToUnit = null;
/**
* Create a default {@link TemporalCoordinateSystem} implementation initialize with the given parameters.
* The properties given in argument follow the same rules than for the
* {@linkplain DefaultTemporalReferenceSystem#DefaultTemporalReferenceSystem(java.util.Map, org.opengis.referencing.datum.TemporalDatum, org.opengis.referencing.cs.TimeCS) super-class constructor}.
* The following table is a reminder of current main (not all) properties:
*
* <table class="ISO 19108">
* <caption>Recognized properties (non exhaustive list)</caption>
* <tr>
* <th>Property name</th>
* <th>Value type</th>
* <th>Returned by</th>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
* <td>{@link org.opengis.referencing.Identifier} or {@link String}</td>
* <td>{@link #getName()}</td>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.datum.Datum#DOMAIN_OF_VALIDITY_KEY}</td>
* <td>{@link org.opengis.metadata.extent.Extent}</td>
* <td>{@link #getDomainOfValidity()}</td>
* </tr>
* </table>
*
* @param properties The properties to be given to the coordinate reference system.
* @param interval unit of measure used as the base interval for the scale.
* @param origin position of the origin of the scale on which the temporal coordinate system is based
* expressed as a date in the Gregorian calendar and time of day in UTC.
*/
public DefaultTemporalCoordinateSystem(Map<String, ?> properties, Unit<Time> interval, Date origin) {
super(properties);
this.origin = origin;
ArgumentChecks.ensureNonNull("interval", interval);
this.interval = interval;
}
// /**
// * Empty constructor only use for XML marshalling.
// */
// DefaultTemporalCoordinateSystem() {
// }
/**
* Constructs a new instance initialized with the values from the specified metadata object.
* This is a <cite>shallow</cite> copy constructor, since the other metadata contained in the
* given object are not recursively copied.
*
* @param object The TemporalReferenceSystem to copy values from, or {@code null} if none.
*
* @see #castOrCopy(TemporalCoordinateSystem)
*/
public DefaultTemporalCoordinateSystem(final TemporalCoordinateSystem object) {
super(object);
if (object != null) {
this.origin = object.getOrigin();
final Unit<Time> inter = object.getInterval();
ArgumentChecks.ensureNonNull("interval", inter);
this.interval = inter;
}
}
/**
* Returns a Geotk implementation with the values of the given arbitrary implementation.
* This method performs the first applicable action in the following choices:
*
* <ul>
* <li>If the given object is {@code null}, then this method returns {@code null}.</li>
* <li>Otherwise if the given object is already an instance of
* {@code DefaultTemporalCoordinateSystem}, then it is returned unchanged.</li>
* <li>Otherwise a new {@code DefaultTemporalCoordinateSystem} instance is created using the
* {@linkplain #DefaultTemporalCoordinateSystem(TemporalCoordinateSystem) copy constructor}
* and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
* metadata contained in the given object are not recursively copied.</li>
* </ul>
*
* @param object The object to get as a Geotk implementation, or {@code null} if none.
* @return A Geotk implementation containing the values of the given object (may be the
* given object itself), or {@code null} if the argument was null.
*/
public static DefaultTemporalCoordinateSystem castOrCopy(final TemporalCoordinateSystem object) {
if (object == null || object instanceof DefaultTemporalCoordinateSystem) {
return (DefaultTemporalCoordinateSystem) object;
}
return new DefaultTemporalCoordinateSystem(object);
}
/**
* Returns position of the origin of the scale on which the temporal coordinate system is based.
* <blockquote><font size="-1">The origin shall be specified in the Gregorian
* calendar with time of day in UTC. The {@linkplain Date DateTime} may be truncated
* to the appropriate level of resolution}.</font></blockquote>
*
* @return position of the origin of the scale on which the temporal coordinate system is based.
*/
@Override
@XmlElement(name = "originPosition", required = true)
public Date getOrigin() {
return origin;
}
/**
* Returns the name of the single unit of measure used as the base interval for the scale.
* <blockquote><font size="-1">The time interval selected as appropriate for the application,
* but it shall be one of those units of measure for time specified by ISO 31-1,
* or multiple of one of those units, as specified by ISO 1000.</font></blockquote>
*
* @return Standard unit of time used to measure duration on the axis of the coordinate system.
*/
@Override
public Unit<Time> getInterval() {
return interval;
}
/**
* Only use for XML binding.
*
* @return An {@link Interval} object adapted for XML binding.
*/
@XmlElement(name = "interval", required = true)
private Interval getinterval() {
return new Interval(interval);
}
/**
* Returns a transformation from a value of a {@linkplain TemporalCoordinate coordinate} within this
* temporal coordinate system and returns the equivalent {@linkplain DateAndTime date
* and time} in the Gregorian Calendar and UTC.
*
* @param c_value The {@linkplain TemporalCoordinate coordinate} which will be transformed.
* @return Convertion of a {@linkplain TemporalCoordinate coordinate} in this coordinate system
* to a date in the Gregorian calendar and a time in UTC.
*/
@Override
public Date transformCoord(final TemporalCoordinate c_value) {
if (unitToMS == null) unitToMS = interval.getConverterTo(UNIT_MS);
Date response;
DefaultTemporalCoordinate value = (DefaultTemporalCoordinate) c_value;
// Number f = 0;
if (value.getFrame() != null && value.getFrame() instanceof TemporalCoordinateSystem) {
if (value.getCoordinateValue() != null) {
final String interStr = interval.toString();
final float n = value.getCoordinateValue().floatValue();
double f = unitToMS.convert(n);
// if (YEAR_STR.equals(interStr)) {
// f = n * (float) YEAR_MS;
// } else if (MONTH_STR.equals(interStr)) {
// f = n * (float) MONTH_MS;
// } else if (WEEK_STR.equals(interStr)) {
// f = n * (float) WEEK_MS;
// } else if (DAY_STR.equals(interStr)) {
// f = n * (float) DAY_MS;
// } else if (HOUR_STR.equals(interStr)) {
// f = n * (float) HOUR_MS;
// } else if (MINUTE_STR.equals(interStr)) {
// f = n * (float) MINUTE_MS;
// } else if (SECOND_STR.equals(interStr)) {
// f = n * (float) SECOND_MS;
// } else if (MILLISECOND_STR.equals(interStr)) {
// f = n;
// } else {
// throw new IllegalArgumentException("The name of a single unit of measure used as the base interval for the scale in this current TemporalCoordinateSystem is not supported !");
// }
// response = new Date(origin.getTime() + f.longValue());
response = new Date(origin.getTime() + Double.doubleToLongBits(f));
return response;
} else {
return null;
}
} else {
throw new IllegalArgumentException("The TemporalCoordinate argument must be a TemporalCoordinate ! ");
}
}
/**
* Returns transformation of a {@linkplain DateAndTime date and time} in the Gregorian Calendar and UTC
* to an equivalent {@linkplain TemporalCoordinate coordinate} within this temporal
* coordinate system.
*
* @param dateTime The {@linkplain DateAndTime date and time} which will be converted.
* @return Convertion of a date in the Gregorian calendar and a time in UTC to a coordinate
* in this temporal coordinate system.
*/
@Override
public TemporalCoordinate transformDateTime(final Date dateTime) {
if (msToUnit == null) msToUnit = (unitToMS != null) ? unitToMS.inverse() : UNIT_MS.getConverterTo(interval);
// final String intervalStr = interval.toString();
Number coordinateValue = Math.abs(dateTime.getTime() - origin.getTime());
final float val = coordinateValue.floatValue();
coordinateValue = msToUnit.convert(val);
// if (YEAR_STR.equals(intervalStr)) {
// coordinateValue = Float.valueOf( val / YEAR_MS );
// } else if (MONTH_STR.equals(intervalStr)) {
// coordinateValue = Float.valueOf( val / MONTH_MS );
// } else if (WEEK_STR.equals(intervalStr)) {
// coordinateValue = Float.valueOf( val / WEEK_MS );
// } else if (DAY_STR.equals(intervalStr)) {
// coordinateValue = Float.valueOf( val / DAY_MS );
// } else if (HOUR_STR.equals(intervalStr)) {
// coordinateValue = Float.valueOf( val / HOUR_MS );
// } else if (MINUTE_STR.equals(intervalStr)) {
// coordinateValue = Float.valueOf( val / MINUTE_MS );
// } else if (SECOND_STR.equals(intervalStr)) {
// coordinateValue = Float.valueOf( val / SECOND_MS );
// }
return new DefaultTemporalCoordinate(this, null, coordinateValue);
}
/**
* {@inheritDoc }
*/
@Override
public boolean equals(Object object, ComparisonMode mode) {
if (object == this) return true;
final boolean sup = super.equals(object, mode);
if (!sup) return false;
// if (object instanceof DefaultTemporalCoordinateSystem && super.equals(object)) {
if (object instanceof DefaultTemporalCoordinateSystem) {
final DefaultTemporalCoordinateSystem that = (DefaultTemporalCoordinateSystem) object;
return Objects.equals(this.interval, that.interval) &&
Objects.equals(this.origin, that.origin);
}
// }
return false;
}
/**
* {@inheritDoc }
*/
@Override
protected long computeHashCode() {
int hash = 5;
hash = 37 * hash + (this.interval != null ? this.interval.hashCode() : 0);
hash = 37 * hash + (this.origin != null ? this.origin.hashCode() : 0);
return hash;
}
/**
* {@inheritDoc }
*/
@Override
public String toString() {
StringBuilder s = new StringBuilder(super.toString()).append('\n').append("TemporalCoordinateSystem:").append('\n');
if (interval != null) {
s.append("interval:").append(interval).append('\n');
}
if (origin != null) {
s.append("origin:").append(origin).append('\n');
}
return s.toString();
}
/**
* Internal class only use to adapt ISO to XML in relation with "interval" XML element.
*/
private static final class Interval {
@XmlValue
private final static double VALUE = 1.0;
@XmlAttribute
private final Unit<Time> unit;
private Interval(Unit<Time> unit) {
this.unit = unit;
}
}
}