use of org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox in project sis by apache.
the class ServicesForMetadata method setBounds.
/**
* Sets the geographic, vertical and temporal extents with the values inferred from the given envelope.
* If the given {@code target} has more geographic or vertical extents than needed (0 or 1), then the
* extraneous extents are removed.
*
* @param envelope the source envelope.
* @param target the target spatiotemporal extent where to store envelope information.
* @throws TransformException if no temporal component can be extracted from the given envelope.
*/
@Override
public void setBounds(final Envelope envelope, final DefaultSpatialTemporalExtent target) throws TransformException {
final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem();
final SingleCRS horizontalCRS = CRS.getHorizontalComponent(crs);
final VerticalCRS verticalCRS = CRS.getVerticalComponent(crs, true);
final TemporalCRS temporalCRS = CRS.getTemporalComponent(crs);
if (horizontalCRS == null && verticalCRS == null && temporalCRS == null) {
throw new TransformException(dimensionNotFound(Resources.Keys.MissingSpatioTemporalDimension_1, crs));
}
/*
* Try to set the geographic bounding box first, because this operation may fail with a
* TransformException while the other operations (vertical and temporal) should not fail.
* So doing the geographic part first help us to get a "all or nothing" behavior.
*/
DefaultGeographicBoundingBox box = null;
boolean useExistingBox = (horizontalCRS != null);
final Collection<GeographicExtent> spatialExtents = target.getSpatialExtent();
final Iterator<GeographicExtent> it = spatialExtents.iterator();
while (it.hasNext()) {
final GeographicExtent extent = it.next();
if (extent instanceof GeographicBoundingBox) {
if (useExistingBox && (extent instanceof DefaultGeographicBoundingBox)) {
box = (DefaultGeographicBoundingBox) extent;
useExistingBox = false;
} else {
it.remove();
}
}
}
if (horizontalCRS != null) {
if (box == null) {
box = new DefaultGeographicBoundingBox();
spatialExtents.add(box);
}
GeographicCRS normalizedCRS = ReferencingUtilities.toNormalizedGeographicCRS(crs);
if (normalizedCRS == null) {
normalizedCRS = CommonCRS.defaultGeographic();
}
setGeographicExtent(envelope, box, crs, normalizedCRS);
}
/*
* Other dimensions (vertical and temporal).
*/
if (verticalCRS != null) {
VerticalExtent e = target.getVerticalExtent();
if (!(e instanceof DefaultVerticalExtent)) {
e = new DefaultVerticalExtent();
target.setVerticalExtent(e);
}
setVerticalExtent(envelope, (DefaultVerticalExtent) e, crs, verticalCRS);
} else {
target.setVerticalExtent(null);
}
if (temporalCRS != null) {
setTemporalExtent(envelope, target, crs, temporalCRS);
} else {
target.setExtent(null);
}
}
use of org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox in project sis by apache.
the class EPSGDataAccess method createExtent.
/**
* Creates information about spatial, vertical, and temporal extent (usually a domain of validity) from a code.
*
* <div class="note"><b>Example:</b>
* some EPSG codes for extents are:
*
* <table class="sis" summary="EPSG codes examples">
* <tr><th>Code</th> <th>Description</th></tr>
* <tr><td>1262</td> <td>World</td></tr>
* <tr><td>3391</td> <td>World - between 80°S and 84°N</td></tr>
* </table></div>
*
* @param code value allocated by EPSG.
* @return the extent for the given code.
* @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
* @throws FactoryException if the object creation failed for some other reason.
*
* @see #createCoordinateReferenceSystem(String)
* @see #createDatum(String)
* @see org.apache.sis.metadata.iso.extent.DefaultExtent
*/
@Override
public synchronized Extent createExtent(final String code) throws NoSuchAuthorityCodeException, FactoryException {
ArgumentChecks.ensureNonNull("code", code);
Extent returnValue = null;
try (ResultSet result = executeQuery("Area", "AREA_CODE", "AREA_NAME", "SELECT AREA_OF_USE," + " AREA_SOUTH_BOUND_LAT," + " AREA_NORTH_BOUND_LAT," + " AREA_WEST_BOUND_LON," + " AREA_EAST_BOUND_LON" + " FROM [Area]" + " WHERE AREA_CODE = ?", code)) {
while (result.next()) {
final String description = getOptionalString(result, 1);
double ymin = getOptionalDouble(result, 2);
double ymax = getOptionalDouble(result, 3);
double xmin = getOptionalDouble(result, 4);
double xmax = getOptionalDouble(result, 5);
DefaultGeographicBoundingBox bbox = null;
if (!Double.isNaN(ymin) || !Double.isNaN(ymax) || !Double.isNaN(xmin) || !Double.isNaN(xmax)) {
/*
* Fix an error found in EPSG:3790 New Zealand - South Island - Mount Pleasant mc
* for older database (this error is fixed in EPSG database 8.2).
*
* Do NOT apply anything similar for the x axis, because xmin > xmax is not error:
* it describes a bounding box spanning the anti-meridian (±180° of longitude).
*/
if (ymin > ymax) {
final double t = ymin;
ymin = ymax;
ymax = t;
}
bbox = new DefaultGeographicBoundingBox(xmin, xmax, ymin, ymax);
}
if (description != null || bbox != null) {
DefaultExtent extent = new DefaultExtent(description, bbox, null, null);
extent.freeze();
returnValue = ensureSingleton(extent, returnValue, code);
}
}
} catch (SQLException exception) {
throw databaseFailure(Extent.class, code, exception);
}
if (returnValue == null) {
throw noSuchAuthorityCode(Extent.class, code);
}
return returnValue;
}
use of org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox in project sis by apache.
the class AbstractReferenceSystemTest method testWKT.
/**
* Tests WKT formatting with a name that contains the quote character and optional information.
* We test that the closing quote character is doubled and the optional information properly formatted.
*/
@Test
@DependsOnMethod("testCreateFromMap")
public void testWKT() {
final Map<String, Object> properties = new HashMap<>(8);
assertNull(properties.put(NAME_KEY, "My “object”."));
assertNull(properties.put(SCOPE_KEY, "Large scale topographic mapping and cadastre."));
assertNull(properties.put(REMARKS_KEY, "注です。"));
assertNull(properties.put(IDENTIFIERS_KEY, new ImmutableIdentifier(Citations.EPSG, "EPSG", "4326", "8.2", null)));
assertNull(properties.put(DOMAIN_OF_VALIDITY_KEY, new DefaultExtent("Netherlands offshore.", new DefaultGeographicBoundingBox(2.54, 6.40, 51.43, 55.77), new DefaultVerticalExtent(10, 1000, VerticalCRSMock.DEPTH), // TODO: needs sis-temporal module for testing that one.
new DefaultTemporalExtent())));
final AbstractReferenceSystem object = new AbstractReferenceSystem(properties);
assertTrue(object.toString(Convention.WKT1).startsWith("ReferenceSystem[\"My “object”.\", AUTHORITY[\"EPSG\", \"4326\"]]"));
assertWktEquals(Convention.WKT1, "ReferenceSystem[“My \"object\".”, AUTHORITY[“EPSG”, “4326”]]", object);
assertWktEquals(Convention.WKT2, // Quotes replaced
"ReferenceSystem[“My \"object\".”,\n" + " SCOPE[“Large scale topographic mapping and cadastre.”],\n" + " AREA[“Netherlands offshore.”],\n" + " BBOX[51.43, 2.54, 55.77, 6.40],\n" + " VERTICALEXTENT[-1000, -10, LENGTHUNIT[“metre”, 1]],\n" + " ID[“EPSG”, 4326, “8.2”, URI[“urn:ogc:def:referenceSystem:EPSG:8.2:4326”]],\n" + " REMARK[“注です。”]]", object);
assertWktEquals(Convention.WKT2_SIMPLIFIED, "ReferenceSystem[“My \"object\".”,\n" + " Scope[“Large scale topographic mapping and cadastre.”],\n" + " Area[“Netherlands offshore.”],\n" + " BBox[51.43, 2.54, 55.77, 6.40],\n" + " VerticalExtent[-1000, -10],\n" + " Id[“EPSG”, 4326, “8.2”, URI[“urn:ogc:def:referenceSystem:EPSG:8.2:4326”]],\n" + " Remark[“注です。”]]", object);
assertWktEquals(Convention.INTERNAL, // Quote doubled
"ReferenceSystem[“My “object””.”,\n" + " Scope[“Large scale topographic mapping and cadastre.”],\n" + " Area[“Netherlands offshore.”],\n" + " BBox[51.43, 2.54, 55.77, 6.40],\n" + " VerticalExtent[-1000, -10],\n" + " Id[“EPSG”, 4326, “8.2”],\n" + " Remark[“注です。”]]", object);
}
use of org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox in project sis by apache.
the class ServicesForMetadataTest method testSetGeographicBoundsFrom3D.
/**
* Tests (indirectly) {@link ServicesForMetadata#setBounds(Envelope, DefaultGeographicBoundingBox)}
* from a three-dimensional envelope.
*
* @throws TransformException should never happen.
*/
@Test
public void testSetGeographicBoundsFrom3D() throws TransformException {
final DefaultGeographicBoundingBox box = new DefaultGeographicBoundingBox();
box.setBounds(createEnvelope(HardCodedCRS.WGS84_3D));
verifySpatialExtent(box);
}
use of org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox in project sis by apache.
the class CRS method suggestCommonTarget.
/**
* Suggests a coordinate reference system which could be a common target for coordinate operations having the
* given sources. This method compares the {@linkplain #getGeographicBoundingBox(CoordinateReferenceSystem)
* domain of validity} of all given CRSs. If a CRS has a domain of validity that contains the domain of all other
* CRS, than that CRS is returned. Otherwise this method verifies if a {@linkplain GeneralDerivedCRS#getBaseCRS()
* base CRS} (usually a {@linkplain org.apache.sis.referencing.crs.DefaultGeographicCRS geographic CRS} instance)
* would be suitable. If no suitable CRS is found, then this method returns {@code null}.
*
* <div class="note"><b>Use case:</b>
* before to test if two arbitrary envelopes {@linkplain GeneralEnvelope#intersects(Envelope) intersect} each other,
* they need to be {@linkplain Envelopes#transform(Envelope, CoordinateReferenceSystem) transformed} in the same CRS.
* However if one CRS is a Transverse Mercator projection while the other CRS is a world-wide geographic CRS, then
* attempts to use the Transverse Mercator projection as the common CRS is likely to fail since the geographic envelope
* may span an area far outside the projection domain of validity. This {@code suggestCommonTarget(…)} method can used
* for choosing a common CRS which is less likely to fail.</div>
*
* @param regionOfInterest the geographic area for which the coordinate operations will be applied,
* or {@code null} if unknown.
* @param sourceCRS the coordinate reference systems for which a common target CRS is desired.
* @return a CRS that may be used as a common target for all the given source CRS in the given region of interest,
* or {@code null} if this method did not find a common target CRS. The returned CRS may be different than
* all given CRS.
*
* @since 0.8
*/
public static CoordinateReferenceSystem suggestCommonTarget(GeographicBoundingBox regionOfInterest, CoordinateReferenceSystem... sourceCRS) {
CoordinateReferenceSystem bestCRS = null;
/*
* Compute the union of the domain of validity of all CRS. If a CRS does not specify a domain of validity,
* then assume that the CRS is valid for the whole world if the CRS is geodetic or return null otherwise.
* Opportunistically remember the domain of validity of each CRS in this loop since we will need them later.
*/
boolean worldwide = false;
DefaultGeographicBoundingBox domain = null;
final GeographicBoundingBox[] domains = new GeographicBoundingBox[sourceCRS.length];
for (int i = 0; i < sourceCRS.length; i++) {
final CoordinateReferenceSystem crs = sourceCRS[i];
final GeographicBoundingBox bbox = getGeographicBoundingBox(crs);
if (bbox == null) {
/*
* If no domain of validity is specified and we can not fallback
* on some knowledge about what the CRS is, abandon.
*/
if (!(crs instanceof GeodeticCRS)) {
return null;
}
/*
* Geodetic CRS (geographic or geocentric) can generally be presumed valid in a worldwide area.
* The 'worldwide' flag is a little optimization for remembering that we do not need to compute
* the union anymore, but we still need to continue the loop for fetching all bounding boxes.
*/
// Fallback to be used if we don't find anything better.
bestCRS = crs;
worldwide = true;
} else {
domains[i] = bbox;
if (!worldwide) {
if (domain == null) {
domain = new DefaultGeographicBoundingBox(bbox);
} else {
domain.add(bbox);
}
}
}
}
/*
* At this point we got the union of the domain of validity of all CRS. We are interested only in the
* part that intersect the region of interest. If the union is whole world, we do not need to compute
* the intersection; we can just leave the region of interest unchanged.
*/
if (domain != null && !worldwide) {
if (regionOfInterest != null) {
domain.intersect(regionOfInterest);
}
regionOfInterest = domain;
domain = null;
}
/*
* Iterate again over the domain of validity of all CRS. For each domain of validity, compute the area
* which is inside the domain or interest and the area which is outside. The "best CRS" will be the one
* which comply with the following rules, in preference order:
*
* 1) The CRS which is valid over the largest area of the region of interest.
* 2) If two CRS are equally good according rule 1, then the CRS with the smallest "outside area".
*
* Example: given two source CRS, a geographic one and a projected one:
*
* - If the projected CRS contains fully the region of interest, then it will be returned.
* The preference is given to the projected CRS because geometric are likely to be more
* accurate in that space. Furthermore forward conversions from geographic to projected
* CRS are usually faster than inverse conversions.
*
* - Otherwise (i.e. if the region of interest is likely to be wider than the projected CRS
* domain of validity), then the geographic CRS will be returned.
*/
// NaN if 'regionOfInterest' is null.
final double roiArea = Extents.area(regionOfInterest);
double maxInsideArea = 0;
double minOutsideArea = Double.POSITIVE_INFINITY;
boolean tryDerivedCRS = false;
do {
for (int i = 0; i < domains.length; i++) {
final GeographicBoundingBox bbox = domains[i];
if (bbox != null) {
double insideArea = Extents.area(bbox);
double outsideArea = 0;
if (regionOfInterest != null) {
if (domain == null) {
domain = new DefaultGeographicBoundingBox(bbox);
} else {
domain.setBounds(bbox);
}
domain.intersect(regionOfInterest);
final double area = insideArea;
insideArea = Extents.area(domain);
outsideArea = area - insideArea;
}
if (insideArea > maxInsideArea || (insideArea == maxInsideArea && outsideArea < minOutsideArea)) {
maxInsideArea = insideArea;
minOutsideArea = outsideArea;
bestCRS = sourceCRS[i];
}
}
}
/*
* If the best CRS does not cover fully the region of interest, then we will redo the check again
* but using base CRS instead. For example if the list of source CRS had some projected CRS, we
* will try with the geographic CRS on which those projected CRS are based.
*/
if (maxInsideArea < roiArea) {
// Do not try twice.
if (tryDerivedCRS)
break;
final CoordinateReferenceSystem[] derivedCRS = new CoordinateReferenceSystem[sourceCRS.length];
for (int i = 0; i < derivedCRS.length; i++) {
GeographicBoundingBox bbox = null;
final CoordinateReferenceSystem crs = sourceCRS[i];
if (crs instanceof GeneralDerivedCRS) {
final CoordinateReferenceSystem baseCRS = ((GeneralDerivedCRS) crs).getBaseCRS();
bbox = getGeographicBoundingBox(baseCRS);
if (bbox == null && bestCRS == null && baseCRS instanceof GeodeticCRS) {
// Fallback to be used if we don't find anything better.
bestCRS = baseCRS;
}
tryDerivedCRS = true;
derivedCRS[i] = baseCRS;
}
domains[i] = bbox;
}
sourceCRS = derivedCRS;
} else {
break;
}
} while (tryDerivedCRS);
return bestCRS;
}
Aggregations