use of org.opengis.referencing.crs.TemporalCRS in project sis by apache.
the class Store method parseEnvelope.
/**
* Parses the envelope described by the header line starting with {@code @stboundedby}.
* The envelope returned by this method will be stored in the {@link #envelope} field.
*
* <p>Example:</p>
* {@preformat text
* @stboundedby, urn:ogc:def:crs:CRS:1.3:84, 2D, 50.23 9.23, 50.31 9.27, 2012-01-17T12:33:41Z, 2012-01-17T12:37:00Z, sec
* }
*
* This method sets {@link #timeEncoding} and {@link #spatialDimensionCount} as a side-effect.
*
* @param elements the line elements. The first elements should be {@code "@stboundedby"}.
* @return the envelope, or {@code null} if the given list does not contain enough elements.
*/
@SuppressWarnings("fallthrough")
private GeneralEnvelope parseEnvelope(final List<String> elements) throws DataStoreException, FactoryException {
CoordinateReferenceSystem crs = null;
int spatialDimensionCount = 2;
boolean isDimExplicit = false;
double[] lowerCorner = ArraysExt.EMPTY_DOUBLE;
double[] upperCorner = ArraysExt.EMPTY_DOUBLE;
Instant startTime = null;
Instant endTime = null;
Unit<Time> timeUnit = Units.SECOND;
boolean isTimeAbsolute = false;
int ordinal = -1;
for (final String element : elements) {
ordinal++;
if (!element.isEmpty()) {
switch(ordinal) {
// The "@stboundedby" header.
case 0:
continue;
case 1:
crs = CRS.forCode(element);
continue;
case 2:
if (element.length() == 2 && Character.toUpperCase(element.charAt(1)) == 'D') {
isDimExplicit = true;
spatialDimensionCount = element.charAt(0) - '0';
if (spatialDimensionCount < 1 || spatialDimensionCount > 3) {
throw new DataStoreReferencingException(errors().getString(Errors.Keys.IllegalCoordinateSystem_1, element));
}
continue;
}
/*
* According the Moving Feature specification, the [dim] element is optional.
* If we did not recognized the dimension, assume that we have the next element
* (i.e. the lower corner). Fall-through so we can process it.
*/
// Fall through
ordinal++;
case 3:
lowerCorner = CharSequences.parseDoubles(element, ORDINATE_SEPARATOR);
continue;
case 4:
upperCorner = CharSequences.parseDoubles(element, ORDINATE_SEPARATOR);
continue;
case 5:
startTime = Instant.parse(element);
continue;
case 6:
endTime = Instant.parse(element);
continue;
case 7:
switch(element.toLowerCase(Locale.US)) {
case "sec":
case "second":
/* Already SECOND. */
continue;
case "minute":
timeUnit = Units.MINUTE;
continue;
case "hour":
timeUnit = Units.HOUR;
continue;
case "day":
timeUnit = Units.DAY;
continue;
case "absolute":
isTimeAbsolute = true;
continue;
default:
throw new DataStoreReferencingException(errors().getString(Errors.Keys.UnknownUnit_1, element));
}
}
// If we reach this point, there is some remaining unknown elements. Ignore them.
break;
}
}
/*
* Complete the CRS by adding a vertical component if needed, then a temporal component.
* Only after the CRS has been completed we can create the envelope.
*
* Vertical component:
* Ideally, should be part of the CRS created from the authority code. But if the authority
* code is only for a two-dimensional CRS, we default to an arbitrary height component.
*
* Temporal component:
* Assumed never part of the authority code. We need to build the temporal component ourselves
* in order to set the origin to the start time.
*/
final GeneralEnvelope envelope;
if (crs != null) {
int count = 0;
final CoordinateReferenceSystem[] components = new CoordinateReferenceSystem[3];
components[count++] = crs;
/*
* If the coordinates are three-dimensional but the CRS is 2D, add a vertical axis.
* The vertical axis shall be the third one, however we do not enforce that rule
* since Apache SIS should work correctly even if the vertical axis is elsewhere.
*/
int dimension = crs.getCoordinateSystem().getDimension();
if (isDimExplicit) {
if (spatialDimensionCount > dimension) {
components[count++] = CommonCRS.Vertical.MEAN_SEA_LEVEL.crs();
dimension++;
}
if (dimension != spatialDimensionCount) {
throw new DataStoreReferencingException(errors().getString(Errors.Keys.MismatchedDimension_3, "@stboundedby(CRS)", spatialDimensionCount, dimension));
}
}
if (dimension > Short.MAX_VALUE) {
throw new DataStoreReferencingException(errors().getString(Errors.Keys.ExcessiveNumberOfDimensions_1, dimension));
}
spatialDimensionCount = dimension;
/*
* Add a temporal axis if we have a start time (no need for end time).
* This block presumes that the CRS does not already have a time axis.
* If a time axis was already present, an exception will be thrown at
* builder.createCompoundCRS(…) invocation time.
*/
final GeodeticObjectBuilder builder = new GeodeticObjectBuilder();
String name = crs.getName().getCode();
if (startTime != null) {
final TemporalCRS temporal;
if (isTimeAbsolute) {
temporal = TimeEncoding.DEFAULT.crs();
timeEncoding = TimeEncoding.ABSOLUTE;
} else {
temporal = builder.createTemporalCRS(Date.from(startTime), timeUnit);
timeEncoding = new TimeEncoding(temporal.getDatum(), timeUnit);
}
components[count++] = temporal;
name = name + " + " + temporal.getName().getCode();
}
crs = builder.addName(name).createCompoundCRS(ArraysExt.resize(components, count));
envelope = new GeneralEnvelope(crs);
} else {
/*
* While illegal in principle, Apache SIS accepts missing CRS.
* In such case, use only the number of dimensions.
*/
int dim = spatialDimensionCount;
// Same criterion than in above block.
if (startTime != null)
dim++;
envelope = new GeneralEnvelope(dim);
}
/*
* At this point we got the three- or four-dimensional spatio-temporal CRS.
* We can now set the envelope coordinate values, including temporal values.
*/
int dim;
if ((dim = lowerCorner.length) != spatialDimensionCount || (dim = upperCorner.length) != spatialDimensionCount) {
throw new DataStoreReferencingException(errors().getString(Errors.Keys.MismatchedDimension_3, "@stboundedby(BBOX)", spatialDimensionCount, dim));
}
for (int i = 0; i < spatialDimensionCount; i++) {
envelope.setRange(i, lowerCorner[i], upperCorner[i]);
}
if (startTime != null) {
envelope.setRange(spatialDimensionCount, timeEncoding.toCRS(startTime.toEpochMilli()), (endTime == null) ? Double.NaN : timeEncoding.toCRS(endTime.toEpochMilli()));
}
this.spatialDimensionCount = (short) spatialDimensionCount;
return envelope;
}
use of org.opengis.referencing.crs.TemporalCRS in project sis by apache.
the class CoordinateFormat method initialize.
/**
* Computes the value of transient fields from the given CRS.
*/
private void initialize(final CoordinateReferenceSystem crs) {
types = null;
formats = null;
units = null;
toFormatUnit = null;
unitSymbols = null;
epochs = null;
negate = 0;
lastCRS = crs;
if (crs == null) {
return;
}
/*
* If no CRS were specified, we will format everything as numbers. Working with null CRS
* is sometime useful because null CRS are allowed in DirectPosition according ISO 19107.
* Otherwise (if a CRS is given), infer the format subclasses from the axes.
*/
final CoordinateSystem cs = crs.getCoordinateSystem();
final int dimension = cs.getDimension();
final byte[] types = new byte[dimension];
final Format[] formats = new Format[dimension];
for (int i = 0; i < dimension; i++) {
final CoordinateSystemAxis axis = cs.getAxis(i);
final Unit<?> unit = axis.getUnit();
/*
* Formatter for angular units. Target unit is DEGREE_ANGLE.
* Type is LONGITUDE, LATITUDE or ANGLE depending on axis direction.
*/
if (Units.isAngular(unit)) {
byte type = ANGLE;
final AxisDirection dir = axis.getDirection();
if (AxisDirection.NORTH.equals(dir)) {
type = LATITUDE;
} else if (AxisDirection.EAST.equals(dir)) {
type = LONGITUDE;
} else if (AxisDirection.SOUTH.equals(dir)) {
type = LATITUDE;
negate(i);
} else if (AxisDirection.WEST.equals(dir)) {
type = LONGITUDE;
negate(i);
}
types[i] = type;
formats[i] = getFormat(Angle.class);
setConverter(dimension, i, unit.asType(javax.measure.quantity.Angle.class).getConverterTo(Units.DEGREE));
continue;
}
/*
* Formatter for temporal units. Target unit is MILLISECONDS.
* Type is DATE.
*/
if (Units.isTemporal(unit)) {
final CoordinateReferenceSystem t = CRS.getComponentAt(crs, i, i + 1);
if (t instanceof TemporalCRS) {
if (epochs == null) {
epochs = new long[dimension];
}
types[i] = DATE;
formats[i] = getFormat(Date.class);
epochs[i] = ((TemporalCRS) t).getDatum().getOrigin().getTime();
setConverter(dimension, i, unit.asType(Time.class).getConverterTo(Units.MILLISECOND));
if (AxisDirection.PAST.equals(axis.getDirection())) {
negate(i);
}
continue;
}
types[i] = TIME;
// Fallthrough: formatted as number.
}
/*
* Formatter for all other units. Do NOT set types[i] since it may have been set
* to a non-zero value by previous case. If not, the default value (zero) is the
* one we want.
*/
formats[i] = getFormat(Number.class);
if (unit != null) {
if (units == null) {
units = new Unit<?>[dimension];
}
units[i] = unit;
final String symbol = getFormat(Unit.class).format(unit);
if (!symbol.isEmpty()) {
if (unitSymbols == null) {
unitSymbols = new String[dimension];
}
unitSymbols[i] = symbol;
}
}
}
// Assign only on success.
this.types = types;
this.formats = formats;
}
use of org.opengis.referencing.crs.TemporalCRS in project sis by apache.
the class SubTypes method castOrCopy.
/**
* Returns a SIS implementation for the given coordinate reference system.
*
* @see AbstractCRS#castOrCopy(CoordinateReferenceSystem)
*/
static AbstractCRS castOrCopy(final CoordinateReferenceSystem object) {
if (object instanceof DerivedCRS) {
return DefaultDerivedCRS.castOrCopy((DerivedCRS) object);
}
if (object instanceof ProjectedCRS) {
return DefaultProjectedCRS.castOrCopy((ProjectedCRS) object);
}
if (object instanceof GeodeticCRS) {
if (object instanceof GeographicCRS) {
return DefaultGeographicCRS.castOrCopy((GeographicCRS) object);
}
if (object instanceof GeocentricCRS) {
return DefaultGeocentricCRS.castOrCopy((GeocentricCRS) object);
}
/*
* The GeographicCRS and GeocentricCRS types are not part of ISO 19111.
* ISO uses a single type, GeodeticCRS, for both of them and infer the
* geographic or geocentric type from the coordinate system. We do this
* check here for instantiating the most appropriate SIS type, but only
* if we need to create a new object anyway (see below for rational).
*/
if (object instanceof DefaultGeodeticCRS) {
/*
* Result of XML unmarshalling — keep as-is. We avoid creating a new object because it
* would break object identities specified in GML document by the xlink:href attribute.
* However we may revisit this policy in the future. See SC_CRS.setElement(AbstractCRS).
*/
return (DefaultGeodeticCRS) object;
}
final Map<String, ?> properties = IdentifiedObjects.getProperties(object);
final GeodeticDatum datum = ((GeodeticCRS) object).getDatum();
final CoordinateSystem cs = object.getCoordinateSystem();
if (cs instanceof EllipsoidalCS) {
return new DefaultGeographicCRS(properties, datum, (EllipsoidalCS) cs);
}
if (cs instanceof SphericalCS) {
return new DefaultGeocentricCRS(properties, datum, (SphericalCS) cs);
}
if (cs instanceof CartesianCS) {
return new DefaultGeocentricCRS(properties, datum, (CartesianCS) cs);
}
}
if (object instanceof VerticalCRS) {
return DefaultVerticalCRS.castOrCopy((VerticalCRS) object);
}
if (object instanceof TemporalCRS) {
return DefaultTemporalCRS.castOrCopy((TemporalCRS) object);
}
if (object instanceof EngineeringCRS) {
return DefaultEngineeringCRS.castOrCopy((EngineeringCRS) object);
}
if (object instanceof ImageCRS) {
return DefaultImageCRS.castOrCopy((ImageCRS) object);
}
if (object instanceof CompoundCRS) {
return DefaultCompoundCRS.castOrCopy((CompoundCRS) object);
}
/*
* Intentionally check for AbstractCRS after the interfaces because user may have defined his own
* subclass implementing the interface. If we were checking for AbstractCRS before the interfaces,
* the returned instance could have been a user subclass without the JAXB annotations required
* for XML marshalling.
*/
if (object == null || object instanceof AbstractCRS) {
return (AbstractCRS) object;
}
return new AbstractCRS(object);
}
use of org.opengis.referencing.crs.TemporalCRS in project sis by apache.
the class GeodeticObjectBuilder method createTemporalCRS.
/**
* Creates a temporal CRS from the given origin and temporal unit. For this method, the CRS name is optional:
* if no {@code addName(…)} method has been invoked, then a default name will be used.
*
* @param origin the epoch in milliseconds since January 1st, 1970 at midnight UTC.
* @param unit the unit of measurement.
* @return a temporal CRS using the given origin and units.
* @throws FactoryException if an error occurred while building the temporal CRS.
*/
public TemporalCRS createTemporalCRS(final Date origin, final Unit<Time> unit) throws FactoryException {
/*
* Try to use one of the pre-defined datum and coordinate system if possible.
* This not only saves a little bit of memory, but also provides better names.
*/
TimeCS cs = null;
TemporalDatum datum = null;
for (final CommonCRS.Temporal c : CommonCRS.Temporal.values()) {
if (datum == null) {
final TemporalDatum candidate = c.datum();
if (origin.equals(candidate.getOrigin())) {
datum = candidate;
}
}
if (cs == null) {
final TemporalCRS crs = c.crs();
final TimeCS candidate = crs.getCoordinateSystem();
if (unit.equals(candidate.getAxis(0).getUnit())) {
if (datum == candidate && properties.isEmpty()) {
return crs;
}
cs = candidate;
}
}
}
/*
* Create the datum and coordinate system before the CRS if we were not able to use a pre-defined object.
* In the datum case, we will use the same metadata than the CRS (domain of validity, scope, etc.) except
* the identifier and the remark.
*/
onCreate(false);
try {
if (cs == null) {
final CSFactory csFactory = getCSFactory();
// To be used as a template, except for units.
cs = CommonCRS.Temporal.JAVA.crs().getCoordinateSystem();
cs = csFactory.createTimeCS(name(cs), csFactory.createCoordinateSystemAxis(name(cs.getAxis(0)), "t", AxisDirection.FUTURE, unit));
}
if (properties.get(TemporalCRS.NAME_KEY) == null) {
properties.putAll(name(cs));
}
if (datum == null) {
final Object remarks = properties.remove(TemporalCRS.REMARKS_KEY);
final Object identifier = properties.remove(TemporalCRS.IDENTIFIERS_KEY);
datum = getDatumFactory().createTemporalDatum(properties, origin);
properties.put(TemporalCRS.IDENTIFIERS_KEY, identifier);
properties.put(TemporalCRS.REMARKS_KEY, remarks);
// Share the Identifier instance.
properties.put(TemporalCRS.NAME_KEY, datum.getName());
}
return getCRSFactory().createTemporalCRS(properties, datum, cs);
} finally {
onCreate(true);
}
}
use of org.opengis.referencing.crs.TemporalCRS in project sis by apache.
the class CommonCRSTest method testTemporal.
/**
* Verifies the epoch values of temporal enumeration compared to the Julian epoch.
*
* @see <a href="http://en.wikipedia.org/wiki/Julian_day">Wikipedia: Julian day</a>
*/
@Test
public void testTemporal() {
final double julianEpoch = CommonCRS.Temporal.JULIAN.datum().getOrigin().getTime() / DAY_LENGTH;
assertTrue(julianEpoch < 0);
for (final CommonCRS.Temporal e : CommonCRS.Temporal.values()) {
final String epoch;
final double days;
switch(e) {
// Fall through
case JAVA:
case UNIX:
epoch = "1970-01-01 00:00:00";
days = 2440587.5;
break;
case TRUNCATED_JULIAN:
epoch = "1968-05-24 00:00:00";
days = 2440000.5;
break;
case DUBLIN_JULIAN:
epoch = "1899-12-31 12:00:00";
days = 2415020.0;
break;
case MODIFIED_JULIAN:
epoch = "1858-11-17 00:00:00";
days = 2400000.5;
break;
case JULIAN:
epoch = "4713-01-01 12:00:00";
days = 0;
break;
default:
throw new AssertionError(e);
}
final String name = e.name();
final TemporalDatum datum = e.datum();
final TemporalCRS crs = e.crs();
final Date origin = datum.getOrigin();
Validators.validate(crs);
// Datum before CRS creation.
assertSame(name, datum, e.datum());
// Datum after CRS creation.
assertSame(name, crs.getDatum(), e.datum());
assertEquals(name, epoch, format(origin));
assertEquals(name, days, origin.getTime() / DAY_LENGTH - julianEpoch, 0);
}
}
Aggregations