/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2008, Open Source Geospatial Foundation (OSGeo) * (C) 2009, 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.Collection; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Map; import java.util.Objects; import javax.measure.quantity.Time; import javax.measure.Unit; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; import org.apache.sis.measure.Units; import org.apache.sis.util.iso.SimpleInternationalString; import org.geotoolkit.metadata.Citations; import org.apache.sis.referencing.NamedIdentifier; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.ComparisonMode; import org.geotoolkit.temporal.object.DefaultCalendarDate; import org.geotoolkit.temporal.object.DefaultDateAndTime; import org.geotoolkit.temporal.object.DefaultJulianDate; import org.opengis.referencing.IdentifiedObject; import org.opengis.temporal.Calendar; import org.opengis.temporal.CalendarDate; import org.opengis.temporal.CalendarEra; import org.opengis.temporal.Clock; import org.opengis.temporal.ClockTime; import org.opengis.temporal.JulianDate; import org.opengis.temporal.DateAndTime; import org.opengis.temporal.TemporalCoordinateSystem; import org.opengis.temporal.TemporalReferenceSystem; /** * A discrete temporal reference system that provides a * basis for defining temporal position to a resolution of one day. * * @author Mehdi Sidhoum (Geomatys) * @version 4.0 * @since 4.0 * @see TemporalReferenceSystem */ @XmlType(name = "TimeCalendar_Type", propOrder = { "referenceFrame" }) @XmlRootElement(name = "TimeCalendar") public class DefaultCalendar extends DefaultTemporalReferenceSystem implements Calendar { /** * The {@linkplain CalendarEra calendar eras} associated with the calendar being described. */ private Collection<CalendarEra> referenceFrame; /** * The {@linkplain Clock time basis} that is use with this calendar to define * temporal position within a calendar day. */ private Clock timeBasis; /** * Creates a new {@link Calendar} implementation initialize by given parameters. * The properties given in argument follow the same rules than for the * {@linkplain DefaultTemporalCRS#DefaultTemporalCRS(java.util.Map, org.opengis.referencing.datum.TemporalDatum, org.opengis.referencing.cs.TimeCS) super-class constructor}. * * @param properties The properties to be given to the coordinate reference system. * @param referenceFrame The {@linkplain CalendarEra calendar eras} associated with the calendar being described. * @param timeBasis The {@linkplain Clock time basis} that is use with this calendar to define temporal position within a calendar day. * @see DefaultTemporalReferenceSystem#DefaultTemporalReferenceSystem(java.util.Map, org.opengis.referencing.datum.TemporalDatum, org.opengis.referencing.cs.TimeCS) */ public DefaultCalendar(Map<String, ?> properties, Collection<CalendarEra> referenceFrame, Clock timeBasis ) { super(properties); ArgumentChecks.ensureNonNull("referenceFrame", referenceFrame); this.referenceFrame = referenceFrame; this.timeBasis = timeBasis; } /** * Empty constructor only use for XML binding. */ private DefaultCalendar() { super(); } /** * 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 Calendar to copy values from, or {@code null} if none. * * @see #castOrCopy(Calendar) * @throws NullArgumentException if referenceFrame is {@code null}. */ private DefaultCalendar(final Calendar object) { super(object); if (object != null) { referenceFrame = object.getReferenceFrame(); ArgumentChecks.ensureNonNull("referenceFrame", referenceFrame); timeBasis = object.getTimeBasis(); } } /** * 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 DefaultCalendar}, then it is returned unchanged.</li> * <li>Otherwise a new {@code DefaultCalendar} instance is created using the * {@linkplain #DefaultCalendar(CalendarEra) 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 DefaultCalendar castOrCopy(final Calendar object) { if (object == null || object instanceof DefaultCalendar) { return (DefaultCalendar) object; } return new DefaultCalendar(object); } /** * Converts a {@linkplain CalendarDate date} in this calendar to a * {@linkplain JulianDate julian date}. * <blockquote><font size="-1">date may be {@code null}, time may be {@code null} but not both.</font></blockquote> * * @param date The {@linkplain CalendarDate date} which will be converted, may be {@code null}. * @param time The {@linkplain Clock time basis} which will be converted, may be {@code null}. * @return {@linkplain CalendarDate date} from this calendar to a {@linkplain JulianDate julian date} convertion. */ @Override public JulianDate dateTrans(final CalendarDate calDate, final ClockTime time) { JulianDate response; if (calDate != null && time != null) { DateAndTime dateAndTime = new DefaultDateAndTime(this, calDate.getIndeterminatePosition(), calDate.getCalendarEraName(), calDate.getCalendarDate(), time.getClockTime()); return dateTrans(dateAndTime); } GregorianCalendar gc = new GregorianCalendar(-4713, 1, 1); gc.set(GregorianCalendar.ERA, GregorianCalendar.BC); final int julianGre = 15 + 31 * (10 + 12 * 1582); Number coordinateValue = 0; final Map<String, Object> properties = new HashMap<>(); final NamedIdentifier name = new NamedIdentifier(Citations.CRS, new SimpleInternationalString("Julian calendar")); final Unit<Time> interval = Units.DAY; properties.put(IdentifiedObject.NAME_KEY, name); // properties.put(TemporalCoordinateSystem.INTERVAL_KEY, interval); final TemporalCoordinateSystem refSystem = new DefaultTemporalCoordinateSystem(properties, interval, gc.getTime()); // TemporalCoordinateSystem refSystem = new DefaultTemporalCoordinateSystem( // new NamedIdentifier(Citations.CRS, new SimpleInternationalString("Julian calendar")), // null, gc.getTime(), new SimpleInternationalString("day")); if (calDate != null) { int[] cal = calDate.getCalendarDate(); int year = 0; int month = 0; int day = 0; if (cal.length > 3) { throw new IllegalArgumentException("The CalendarDate integer array is malformed ! see ISO 8601 format."); } else { year = cal[0]; if (cal.length > 0) { month = cal[1]; } if (cal.length > 1) { day = cal[2]; } int julianYear = year; if (year < 0) { julianYear++; } int julianMonth = month; if (month > 2) { julianMonth++; } else { julianYear--; julianMonth += 13; } double julian = (java.lang.Math.floor(365.25 * julianYear) + java.lang.Math.floor(30.6001 * julianMonth) + day + 1720995.0); if (day + 31 * (month + 12 * year) >= julianGre) { // change over to Gregorian calendar int ja = (int) (0.01 * julianYear); julian += 2 - ja + (0.25 * ja); } coordinateValue = java.lang.Math.floor(julian); response = new DefaultJulianDate(refSystem, null, coordinateValue); return response; } } else if (time != null) { Number[] clk = time.getClockTime(); Number hour = 0; Number minute = 0; Number second = 0; if (clk.length > 3) { throw new IllegalArgumentException("The ClockTime Number array is malformed ! see ISO 8601 format."); } else { hour = clk[0]; if (clk.length > 0) { minute = clk[1]; } if (clk.length > 1) { second = clk[2]; } double julian = ((hour.doubleValue() - 12) / 24) + (minute.doubleValue() / 1440) + (second.doubleValue() / 86400); coordinateValue = julian; response = new DefaultJulianDate(refSystem, null, coordinateValue); return response; } } else { throw new IllegalArgumentException("the both CalendarDate and ClockTime cannot be null !"); } } /** * This method is called by the Overrided method dateTrans() which take 2 arguments CalendarDate and ClockTime. * @param dateAndTime * @return JulianDate */ public JulianDate dateTrans(final DateAndTime dateAndTime) { JulianDate response; GregorianCalendar gc = new GregorianCalendar(-4713, 1, 1); gc.set(GregorianCalendar.ERA, GregorianCalendar.BC); final int julianGre = 15 + 31 * (10 + 12 * 1582); final Map<String, Object> properties = new HashMap<>(); final NamedIdentifier name = new NamedIdentifier(Citations.CRS, new SimpleInternationalString("Julian calendar")); // final InternationalString interval = new SimpleInternationalString("day"); properties.put(IdentifiedObject.NAME_KEY, name); // properties.put(TemporalCoordinateSystem.INTERVAL_KEY, interval); final TemporalCoordinateSystem refSystem = new DefaultTemporalCoordinateSystem(properties, Units.DAY, gc.getTime()); // TemporalCoordinateSystem refSystem = new DefaultTemporalCoordinateSystem(new NamedIdentifier(Citations.CRS, new SimpleInternationalString("Julian calendar")), // null, gc.getTime(), new SimpleInternationalString("day")); Number coordinateValue = 0; int year = 0, month = 0, day = 0; Number hour = 0, minute = 0, second = 0; if (dateAndTime == null) { throw new IllegalArgumentException("The DateAndTime cannot be null ! "); } if (dateAndTime.getCalendarDate() != null) { int[] cal = dateAndTime.getCalendarDate(); if (cal.length > 3) { throw new IllegalArgumentException("The CalendarDate integer array is malformed ! see ISO 8601 format."); } else { year = cal[0]; if (cal.length > 0) { month = cal[1]; } if (cal.length > 1) { day = cal[2]; } int julianYear = year; if (year < 0) { julianYear++; } int julianMonth = month; if (month > 2) { julianMonth++; } else { julianYear--; julianMonth += 13; } double julian = (java.lang.Math.floor(365.25 * julianYear) + java.lang.Math.floor(30.6001 * julianMonth) + day + 1720995.0); if (day + 31 * (month + 12 * year) >= julianGre) { int ja = (int) (0.01 * julianYear); julian += 2 - ja + (0.25 * ja); } coordinateValue = java.lang.Math.floor(julian); } } if (dateAndTime.getClockTime() != null) { Number[] clk = dateAndTime.getClockTime(); if (clk.length > 3) { throw new IllegalArgumentException("The ClockTime Number array is malformed ! see ISO 8601 format."); } else { hour = clk[0]; if (clk.length > 0) { minute = clk[1]; } if (clk.length > 1) { second = clk[2]; } double julian = ((hour.doubleValue() - 12) / 24) + (minute.doubleValue() / 1440) + (second.doubleValue() / 86400); coordinateValue = coordinateValue.doubleValue() + julian; } } response = new DefaultJulianDate(refSystem, null, coordinateValue); return response; } /** * Returns convertion of {@linkplain JulianDate julian date} to a {@linkplain CalendarDate date} * in this calendar. * * @param julian The {@linkplain JulianDate julian date} which will be converted. * @return {@linkplain JulianDate julian date} to a {@linkplain CalendarDate date} convertion. */ @Override public CalendarDate julTrans(final JulianDate jdt) { if (jdt == null) return null; CalendarDate response = null; int JGREG = 15 + 31 * (10 + 12 * 1582); int jalpha, ja, jb, jc, jd, je, year, month, day; ja = (int) jdt.getCoordinateValue().intValue(); if (ja >= JGREG) { jalpha = (int) (((ja - 1867216) - 0.25) / 36524.25); ja = ja + 1 + jalpha - jalpha / 4; } jb = ja + 1524; jc = (int) (6680.0 + ((jb - 2439870) - 122.1) / 365.25); jd = 365 * jc + jc / 4; je = (int) ((jb - jd) / 30.6001); day = jb - jd - (int) (30.6001 * je); month = je - 1; if (month > 12) { month = month - 12; } year = jc - 4715; if (month > 2) { year--; } if (year <= 0) { year--; } int[] calendarDate = {year, month, day}; response = new DefaultCalendarDate(this, null, null, calendarDate); return response; } /** * {@inheritDoc } */ @Override public boolean equals(Object object, ComparisonMode mode) { if (object instanceof Calendar) { final Calendar that = (Calendar) object; return Objects.equals(this.getName(), that.getName()) && Objects.equals(this.getDomainOfValidity(), that.getDomainOfValidity()) && Objects.equals(this.referenceFrame, that.getReferenceFrame()) &&Objects.equals(this.timeBasis, that.getTimeBasis()); } return false; } /** * {@inheritDoc } */ @Override protected long computeHashCode() { int hash = this.getName().hashCode() + this.getDomainOfValidity().hashCode(); hash = 42 * hash + (this.timeBasis != null ? this.timeBasis.hashCode() : 0); hash = 37 * hash + (this.referenceFrame != null ? this.referenceFrame.hashCode() : 0); return hash; } /** * {@inheritDoc } */ @Override public String toString() { StringBuilder s = new StringBuilder(super.toString()).append('\n').append("Calendar : ").append('\n'); if (timeBasis != null) { s.append("clock:").append(timeBasis).append('\n'); } if (referenceFrame != null) { s.append("basis:").append(referenceFrame).append('\n'); } return super.toString().concat("\n").concat(s.toString()); } /** * {@inheritDoc } */ @Override @XmlElement(name = "referenceFrame", required = true) public Collection<CalendarEra> getReferenceFrame() { return referenceFrame; } /** * {@inheritDoc } */ @Override public Clock getTimeBasis() { return timeBasis; } }