001/* 002 * GeoAPI - Java interfaces for OGC/ISO standards 003 * http://www.geoapi.org 004 * 005 * Copyright (C) 2014-2019 Open Geospatial Consortium, Inc. 006 * All Rights Reserved. http://www.opengeospatial.org/ogc/legal 007 * 008 * Permission to use, copy, and modify this software and its documentation, with 009 * or without modification, for any purpose and without fee or royalty is hereby 010 * granted, provided that you include the following on ALL copies of the software 011 * and documentation or portions thereof, including modifications, that you make: 012 * 013 * 1. The full text of this NOTICE in a location viewable to users of the 014 * redistributed or derivative work. 015 * 2. Notice of any changes or modifications to the OGC files, including the 016 * date changes were made. 017 * 018 * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE 019 * NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 020 * TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT 021 * THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY 022 * PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. 023 * 024 * COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR 025 * CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION. 026 * 027 * The name and trademarks of copyright holders may NOT be used in advertising or 028 * publicity pertaining to the software without specific, written prior permission. 029 * Title to copyright in this software and any associated documentation will at all 030 * times remain with copyright holders. 031 */ 032package org.opengis.test.referencing; 033 034import java.util.Date; 035import javax.measure.Unit; 036import javax.measure.UnitConverter; 037import javax.measure.IncommensurableException; 038import javax.measure.quantity.Angle; 039import javax.measure.quantity.Length; 040import org.opengis.metadata.Identifier; 041import org.opengis.metadata.extent.Extent; 042import org.opengis.metadata.extent.GeographicBoundingBox; 043import org.opengis.metadata.extent.GeographicDescription; 044import org.opengis.metadata.extent.GeographicExtent; 045import org.opengis.metadata.extent.TemporalExtent; 046import org.opengis.metadata.extent.VerticalExtent; 047import org.opengis.parameter.ParameterValue; 048import org.opengis.parameter.ParameterValueGroup; 049import org.opengis.referencing.IdentifiedObject; 050import org.opengis.referencing.datum.Ellipsoid; 051import org.opengis.referencing.datum.PrimeMeridian; 052import org.opengis.referencing.datum.GeodeticDatum; 053import org.opengis.referencing.cs.AxisDirection; 054import org.opengis.referencing.cs.CoordinateSystem; 055import org.opengis.referencing.cs.CoordinateSystemAxis; 056import org.opengis.referencing.crs.CoordinateReferenceSystem; 057import org.opengis.referencing.crs.VerticalCRS; 058import org.opengis.referencing.cs.VerticalCS; 059import org.opengis.temporal.Instant; 060import org.opengis.temporal.Period; 061import org.opengis.temporal.TemporalPrimitive; 062import org.opengis.test.TestCase; 063import org.opengis.util.Factory; 064 065import static java.lang.Double.isNaN; 066import static org.opengis.test.Assert.*; 067 068 069/** 070 * Base class of {@link CoordinateReferenceSystem} implementation tests. 071 * This base class provides {@code verify(…)} methods that subclasses can override if they need to alter 072 * the object verifications. 073 * 074 * @author Martin Desruisseaux (Geomatys) 075 * @version 3.1 076 * @since 3.1 077 */ 078public strictfp abstract class ReferencingTestCase extends TestCase { 079 /** 080 * Creates a new test without factory. This constructor is provided for subclasses that 081 * instantiate their {@link CoordinateReferenceSystem} directly, without using any factory. 082 */ 083 protected ReferencingTestCase() { 084 } 085 086 /** 087 * Creates a test case initialized to default values. 088 * 089 * @param factories the factories to be used by the test. Those factories passed verbatim 090 * to the {@linkplain TestCase#TestCase(Factory[]) super-class constructor}. 091 */ 092 @SuppressWarnings("unchecked") 093 protected ReferencingTestCase(final Factory... factories) { 094 super(factories); 095 } 096 097 /** 098 * Returns the given wrapper as a primitive value, or NaN if null. 099 */ 100 private static double toPrimitive(final Double value) { 101 return (value != null) ? value : Double.NaN; 102 } 103 104 /** 105 * Converts the given date to Julian days. 106 */ 107 private static double julian(final Date time) { 108 return (time.getTime() - (-2440588 * (24*60*60*1000L) + (12*60*60*1000L))) / (24*60*60*1000.0); 109 } 110 111 /** 112 * Infers a value from the extent as a {@link Date} object and computes the union with a lower or upper bounds. 113 * 114 * @param bound the current lower ({@code begin == true}) or upper ({@code begin == false}) bound. 115 * @param begin {@code true} for the start time, or {@code false} for the end time. 116 */ 117 private static Date union(final Date bound, final TemporalPrimitive extent, final boolean begin) { 118 final Instant instant; 119 if (extent instanceof Instant) { 120 instant = (Instant) extent; 121 } else if (extent instanceof Period) { 122 instant = begin ? ((Period) extent).getBeginning() : ((Period) extent).getEnding(); 123 } else { 124 return bound; 125 } 126 final Date t = instant.getDate(); 127 if (t != null && (bound == null || (begin ? t.before(bound) : t.after(bound)))) { 128 return t; 129 } 130 return bound; 131 } 132 133 /** 134 * Compares the name and identifier of the given {@code object} against the expected values. 135 * This method allows for some flexibilities: 136 * 137 * <ul> 138 * <li>For {@link IdentifiedObject#getName()}: 139 * <ul> 140 * <li>Only the value returned by {@link Identifier#getCode()} is verified. 141 * The code space, authority and version are ignored.</li> 142 * <li>Only the characters that are valid for Unicode identifiers are compared (ignoring case), as documented in 143 * {@link org.opengis.test.Assert#assertUnicodeIdentifierEquals Assert.assertUnicodeIdentifierEquals(…)}.</li> 144 * </ul> 145 * </li> 146 * <li>For {@link IdentifiedObject#getIdentifiers()}: 147 * <ul> 148 * <li>Only the value returned by {@link Identifier#getCode()} is verified. 149 * The code space, authority and version are ignored.</li> 150 * <li>The identifiers collection can contain more identifiers than the expected one, 151 * and the expected identifier does not need to be first.</li> 152 * <li>The comparison is case-insensitive.</li> 153 * </ul> 154 * </li> 155 * </ul> 156 * 157 * If the given {@code object} is {@code null}, then this method does nothing. 158 * Deciding if {@code null} objects are allowed or not is {@link org.opengis.test.Validator}'s job. 159 * 160 * @param object the object to verify, or {@code null} if none. 161 * @param name the expected name (ignoring code space), or {@code null} if unrestricted. 162 * @param identifier the expected identifier code (ignoring code space), or {@code null} if unrestricted. 163 */ 164 protected void verifyIdentification(final IdentifiedObject object, final String name, final String identifier) { 165 if (object != null) { 166 if (name != null) { 167 assertUnicodeIdentifierEquals("getName().getCode()", name, Utilities.getName(object), true); 168 } 169 if (identifier != null) { 170 for (final Identifier id : object.getIdentifiers()) { 171 assertNotNull("getName().getIdentifiers()", id); 172 if (identifier.equalsIgnoreCase(id.getCode())) { 173 return; 174 } 175 } 176 fail("getName().getIdentifiers(): element “" + identifier + "” not found."); 177 } 178 } 179 } 180 181 /** 182 * Compares the name, axis lengths and inverse flattening factor of the given ellipsoid against the expected values. 183 * This method allows for some flexibilities: 184 * 185 * <ul> 186 * <li>{@link Ellipsoid#getName()} allows for the same flexibilities than the one documented in 187 * {@link #verifyIdentification verifyIdentification(…)}.</li> 188 * <li>{@link Ellipsoid#getSemiMajorAxis()} does not need to use the unit of measurement given 189 * by the {@code axisUnit} argument. Unit conversion will be applied as needed.</li> 190 * </ul> 191 * 192 * The tolerance thresholds are 0.5 unit of the last digits of the values found in the EPSG database: 193 * <ul> 194 * <li>3 decimal digits for {@code semiMajor} values in metres.</li> 195 * <li>9 decimal digits for {@code inverseFlattening} values.</li> 196 * </ul> 197 * 198 * If the given {@code ellipsoid} is {@code null}, then this method does nothing. 199 * Deciding if {@code null} datum are allowed or not is {@link org.opengis.test.Validator}'s job. 200 * 201 * @param ellipsoid the ellipsoid to verify, or {@code null} if none. 202 * @param name the expected name (ignoring code space), or {@code null} if unrestricted. 203 * @param semiMajor the expected semi-major axis length, in units given by the {@code axisUnit} argument. 204 * @param inverseFlattening the expected inverse flattening factor. 205 * @param axisUnit the unit of the {@code semiMajor} argument (not necessarily the actual unit of the ellipsoid). 206 * 207 * @see GeodeticDatum#getEllipsoid() 208 */ 209 protected void verifyFlattenedSphere(final Ellipsoid ellipsoid, final String name, 210 final double semiMajor, final double inverseFlattening, final Unit<Length> axisUnit) 211 { 212 if (ellipsoid != null) { 213 if (name != null) { 214 assertUnicodeIdentifierEquals("Ellipsoid.getName().getCode()", 215 name, Utilities.getName(ellipsoid), true); 216 } 217 final Unit<Length> actualUnit = ellipsoid.getAxisUnit(); 218 assertNotNull("Ellipsoid.getAxisUnit()", actualUnit); 219 assertEquals("Ellipsoid.getSemiMajorAxis()", semiMajor, 220 actualUnit.getConverterTo(axisUnit).convert(ellipsoid.getSemiMajorAxis()), 221 units.metre().getConverterTo(axisUnit).convert(5E-4)); 222 assertEquals("Ellipsoid.getInverseFlattening()", inverseFlattening, ellipsoid.getInverseFlattening(), 5E-10); 223 } 224 } 225 226 /** 227 * Compares the name and Greenwich longitude of the given prime meridian against the expected values. 228 * This method allows for some flexibilities: 229 * 230 * <ul> 231 * <li>{@link PrimeMeridian#getName()} allows for the same flexibilities than the one documented in 232 * {@link #verifyIdentification verifyIdentification(…)}.</li> 233 * <li>{@link PrimeMeridian#getGreenwichLongitude()} does not need to use the unit of measurement given 234 * by the {@code angularUnit} argument. Unit conversion will be applied as needed.</li> 235 * </ul> 236 * 237 * The tolerance threshold is 0.5 unit of the last digit of the values found in the EPSG database: 238 * <ul> 239 * <li>7 decimal digits for {@code greenwichLongitude} values in degrees.</li> 240 * </ul> 241 * 242 * If the given {@code primeMeridian} is {@code null}, then this method does nothing. 243 * Deciding if {@code null} prime meridians are allowed or not is {@link org.opengis.test.Validator}'s job. 244 * 245 * @param primeMeridian the prime meridian to verify, or {@code null} if none. 246 * @param name the expected name (ignoring code space), or {@code null} if unrestricted. 247 * @param greenwichLongitude the expected Greenwich longitude, in units given by the {@code angularUnit} argument. 248 * @param angularUnit the unit of the {@code greenwichLongitude} argument (not necessarily the actual unit of the prime meridian). 249 * 250 * @see GeodeticDatum#getPrimeMeridian() 251 */ 252 protected void verifyPrimeMeridian(final PrimeMeridian primeMeridian, final String name, 253 final double greenwichLongitude, final Unit<Angle> angularUnit) 254 { 255 if (primeMeridian != null) { 256 if (name != null) { 257 assertUnicodeIdentifierEquals("PrimeMeridian.getName().getCode()", 258 name, Utilities.getName(primeMeridian), true); 259 } 260 final Unit<Angle> actualUnit = primeMeridian.getAngularUnit(); 261 assertNotNull("PrimeMeridian.getAngularUnit()", actualUnit); 262 assertEquals("PrimeMeridian.getGreenwichLongitude()", greenwichLongitude, 263 actualUnit.getConverterTo(angularUnit).convert(primeMeridian.getGreenwichLongitude()), 264 units.degree().getConverterTo(angularUnit).convert(5E-8)); 265 } 266 } 267 268 /** 269 * Compares the type, axis units and directions of the given coordinate system against the expected values. 270 * This method does not verify the coordinate system name because it is usually not significant. 271 * This method does not verify axis names neither because the names specified by ISO 19111 and ISO 19162 differ. 272 * 273 * <p>If the given {@code cs} is {@code null}, then this method does nothing. 274 * Deciding if {@code null} coordinate systems are allowed or not is {@link org.opengis.test.Validator}'s job.</p> 275 * 276 * @param cs the coordinate system to verify, or {@code null} if none. 277 * @param type the expected coordinate system type. 278 * @param directions the expected axis directions. The length of this array determines the expected {@code cs} dimension. 279 * @param axisUnits the expected axis units. If the array length is less than the {@code cs} dimension, 280 * then the last unit is repeated for all remaining dimensions. 281 * If the array length is greater, than extra units are ignored. 282 * 283 * @see CoordinateReferenceSystem#getCoordinateSystem() 284 */ 285 protected void verifyCoordinateSystem(final CoordinateSystem cs, final Class<? extends CoordinateSystem> type, 286 final AxisDirection[] directions, final Unit<?>... axisUnits) 287 { 288 if (cs != null) { 289 assertEquals("CoordinateSystem.getDimension()", directions.length, cs.getDimension()); 290 for (int i=0; i<directions.length; i++) { 291 final CoordinateSystemAxis axis = cs.getAxis(i); 292 assertNotNull("CoordinateSystem.getAxis(*)", axis); 293 assertEquals ("CoordinateSystem.getAxis(*).getDirection()", directions[i], axis.getDirection()); 294 assertEquals ("CoordinateSystem.getAxis(*).getUnit()", axisUnits[Math.min(i, axisUnits.length-1)], axis.getUnit()); 295 } 296 } 297 } 298 299 /** 300 * Compares an operation parameter against the expected value. 301 * This method allows for some flexibilities: 302 * 303 * <ul> 304 * <li>The parameter does not need to use the unit of measurement given by the {@code unit} argument. 305 * Unit conversion should be applied as needed by the {@link ParameterValue#doubleValue(Unit)} method.</li> 306 * </ul> 307 * 308 * If the given {@code group} is {@code null}, then this method does nothing. 309 * Deciding if {@code null} parameters are allowed or not is {@link org.opengis.test.Validator}'s job. 310 * 311 * @param group the parameter group containing the parameter to test. 312 * @param name the name of the parameter to test. 313 * @param value the expected parameter value when expressed in units given by the {@code unit} argument. 314 * @param unit the units of measurement of the {@code value} argument 315 * (not necessarily the unit actually used by the implementation). 316 */ 317 protected void verifyParameter(final ParameterValueGroup group, final String name, final double value, final Unit<?> unit) { 318 if (group != null) { 319 final ParameterValue<?> param = group.parameter(name); 320 assertNotNull(name, param); 321 assertEquals(name, param.doubleValue(unit), value, StrictMath.abs(value * 1E-10)); 322 } 323 } 324 325 /** 326 * Compares the geographic description and bounding box of the given extent against the expected values. 327 * This method allows for some flexibilities: 328 * 329 * <ul> 330 * <li>For {@link GeographicDescription} elements: 331 * <ul> 332 * <li>Descriptions are considered optional. If the given {@code extent} does not contain any 333 * {@code GeographicDescription} element, then the given {@code description} argument is ignored.</li> 334 * <li>If the given {@code extent} contains more than one {@code GeographicDescription} element, then only 335 * one of them (not necessarily the first one) needs to have the given {@code description} value. 336 * Other elements are ignored.</li> 337 * </ul> 338 * </li> 339 * <li>For {@link GeographicBoundingBox} elements: 340 * <ul> 341 * <li>Bounding boxes are considered optional. If the given {@code extent} does not contain any 342 * {@code GeographicBoundingBox} element, then all given bound arguments are ignored.</li> 343 * <li>If the given {@code extent} contains more than one {@code GeographicBoundingBox} element, 344 * then the union of them is compared against the given bound arguments.</li> 345 * </ul> 346 * </li> 347 * </ul> 348 * 349 * The tolerance threshold is 0.005° since geographic bounding box are only approximate information. 350 * 351 * <p>If the given {@code extent} is {@code null}, then this method does nothing. 352 * Deciding if {@code null} extents are allowed or not is {@link org.opengis.test.Validator}'s job.</p> 353 * 354 * @param extent the extent to verify, or {@code null} if none. 355 * @param description the expected area, or {@code null} if unrestricted. 356 * @param southBoundLatitude the expected minimum latitude, or NaN if unrestricted. 357 * @param westBoundLongitude the expected minimum longitude, or NaN if unrestricted. 358 * @param northBoundLatitude the expected maximum latitude, or NaN if unrestricted. 359 * @param eastBoundLongitude the expected maximum longitude, or NaN if unrestricted. 360 * 361 * @see CoordinateReferenceSystem#getDomainOfValidity() 362 */ 363 protected void verifyGeographicExtent(final Extent extent, final String description, 364 final double southBoundLatitude, final double westBoundLongitude, 365 final double northBoundLatitude, final double eastBoundLongitude) 366 { 367 if (extent != null) { 368 double ymin = Double.POSITIVE_INFINITY; 369 double xmin = Double.POSITIVE_INFINITY; 370 double ymax = Double.NEGATIVE_INFINITY; 371 double xmax = Double.NEGATIVE_INFINITY; 372 String unknownArea = null; 373 for (final GeographicExtent e : extent.getGeographicElements()) { 374 if (e instanceof GeographicBoundingBox) { 375 final GeographicBoundingBox bbox = (GeographicBoundingBox) e; 376 double t; 377 if ((t = bbox.getSouthBoundLatitude()) < ymin) ymin = t; 378 if ((t = bbox.getWestBoundLongitude()) < xmin) xmin = t; 379 if ((t = bbox.getNorthBoundLatitude()) > ymax) ymax = t; 380 if ((t = bbox.getEastBoundLongitude()) > xmax) xmax = t; 381 } 382 /* 383 * Description: optional, but if present we allow any amount of identifiers 384 * provided that at least one contain the expected string. 385 */ 386 if (description != null && e instanceof GeographicDescription) { 387 final String area = ((GeographicDescription) e).getGeographicIdentifier().getCode(); 388 if (description.equals(area)) { 389 unknownArea = null; 390 break; 391 } 392 if (unknownArea == null) { 393 unknownArea = area; // For reporting an error message if we do not find the expected area. 394 } 395 } 396 } 397 if (unknownArea != null) { 398 assertEquals("GeographicDescription", description, unknownArea); 399 } 400 /* 401 * WKT 2 specification said that BBOX precision should be about 0.01°. 402 */ 403 if (!isNaN(southBoundLatitude) && ymin != Double.POSITIVE_INFINITY) assertEquals("getSouthBoundLatitude()", southBoundLatitude, ymin, 0.005); 404 if (!isNaN(westBoundLongitude) && xmin != Double.POSITIVE_INFINITY) assertEquals("getWestBoundLongitude()", westBoundLongitude, xmin, 0.005); 405 if (!isNaN(northBoundLatitude) && ymax != Double.NEGATIVE_INFINITY) assertEquals("getNorthBoundLatitude()", northBoundLatitude, ymax, 0.005); 406 if (!isNaN(eastBoundLongitude) && xmax != Double.NEGATIVE_INFINITY) assertEquals("getEastBoundLongitude()", eastBoundLongitude, xmax, 0.005); 407 } 408 } 409 410 /** 411 * Compares the vertical elements of the given extent against the expected values. 412 * This method allows for some flexibilities: 413 * 414 * <ul> 415 * <li>Vertical extents are considered optional. If the given {@code extent} does not contain any 416 * {@code VerticalExtent} element, then this method does nothing.</li> 417 * <li>{@link VerticalExtent#getMinimumValue()} and {@link VerticalExtent#getMaximumValue() getMaximumValue()} 418 * do not need to use the unit of measurement given by the {@code unit} argument. Unit conversions will be 419 * applied as needed if {@link VerticalExtent#getVerticalCRS()} returns a non-null value.</li> 420 * <li>If the given {@code extent} contains more than one {@code VerticalExtent} element, 421 * then the union of them is compared against the given bound arguments.</li> 422 * </ul> 423 * 424 * <p>If the given {@code extent} is {@code null}, then this method does nothing. 425 * Deciding if {@code null} extents are allowed or not is {@link org.opengis.test.Validator}'s job.</p> 426 * 427 * @param extent the extent to verify, or {@code null} if none. 428 * @param minimumValue the expected minimal vertical value, or NaN if unrestricted. 429 * @param maximumValue the expected maximal vertical value, or NaN if unrestricted. 430 * @param tolerance the tolerance threshold to use for comparison. 431 * @param unit the unit of {@code minimumValue}, {@code maximumValue} and {@code tolerance} arguments, 432 * or {@code null} for skipping the unit conversion. 433 * 434 * @see CoordinateReferenceSystem#getDomainOfValidity() 435 */ 436 protected void verifyVerticalExtent(final Extent extent, 437 final double minimumValue, final double maximumValue, final double tolerance, final Unit<?> unit) 438 { 439 if (extent != null) { 440 double min = Double.POSITIVE_INFINITY; 441 double max = Double.NEGATIVE_INFINITY; 442 for (final VerticalExtent e : extent.getVerticalElements()) { 443 double minValue = toPrimitive(e.getMinimumValue()); 444 double maxValue = toPrimitive(e.getMaximumValue()); 445 if (unit != null) { 446 final VerticalCRS crs = e.getVerticalCRS(); 447 if (crs != null) { 448 final VerticalCS cs = crs.getCoordinateSystem(); 449 if (cs != null) { 450 assertEquals("VerticalExtent.getVerticalCRS().getCoordinateSystem().getDimension()", 1, cs.getDimension()); 451 final CoordinateSystemAxis axis = cs.getAxis(0); 452 if (axis != null) { 453 final Unit<?> u = axis.getUnit(); 454 if (u != null) { 455 final UnitConverter c; 456 try { 457 c = u.getConverterToAny(unit); 458 } catch (IncommensurableException ex) { 459 throw new AssertionError("Expected VerticalExtent in units of “" 460 + unit + "” but got units of “" + u + "”.", ex); 461 } 462 minValue = c.convert(minValue); 463 maxValue = c.convert(maxValue); 464 } 465 } 466 } 467 } 468 } 469 if (minValue < min) min = minValue; 470 if (maxValue > max) max = maxValue; 471 } 472 if (!isNaN(minimumValue) && min != Double.POSITIVE_INFINITY) assertEquals("VerticalExtent.getMinimumValue()", minimumValue, min, tolerance); 473 if (!isNaN(maximumValue) && max != Double.NEGATIVE_INFINITY) assertEquals("VerticalExtent.getMaximumValue()", maximumValue, max, tolerance); 474 } 475 } 476 477 /** 478 * Compares the temporal elements of the given extent against the expected values. 479 * This method allows for some flexibilities: 480 * 481 * <ul> 482 * <li>Temporal extents are considered optional. If the given {@code extent} does not contain any 483 * {@code TemporalExtent} element, then this method does nothing.</li> 484 * <li>If the given {@code extent} contains more than one {@code TemporalExtent} element, 485 * then the union of them is compared against the given bound arguments.</li> 486 * </ul> 487 * 488 * <p>If the given {@code extent} is {@code null}, then this method does nothing. 489 * Deciding if {@code null} extents are allowed or not is {@link org.opengis.test.Validator}'s job.</p> 490 * 491 * @param extent the extent to verify, or {@code null} if none. 492 * @param startTime the expected start time, or {@code null} if unrestricted. 493 * @param endTime the expected end time, or {@code null} if unrestricted. 494 * @param tolerance the tolerance threshold to use for comparison, in unit of days. 495 * 496 * @see CoordinateReferenceSystem#getDomainOfValidity() 497 */ 498 protected void verifyTimeExtent(final Extent extent, final Date startTime, final Date endTime, final double tolerance) { 499 if (extent != null) { 500 Date min = null; 501 Date max = null; 502 for (final TemporalExtent e : extent.getTemporalElements()) { 503 final TemporalPrimitive p = e.getExtent(); 504 min = union(min, p, true); 505 max = union(max, p, false); 506 } 507 if (startTime != null && min != null) { 508 assertEquals("TemporalExtent start time (julian days)", julian(startTime), julian(min), tolerance); 509 } 510 if (endTime != null && max != null) { 511 assertEquals("TemporalExtent end time (julian days)", julian(endTime), julian(max), tolerance); 512 } 513 } 514 } 515}