use of org.opengis.referencing.crs.GeneralDerivedCRS in project sis by apache.
the class CoordinateOperationMethods method computeUnionOfAllDomainOfValidity.
/**
* For each {@link OperationMethod} (identified by their name), computes the union of the domain of validity
* of all CRS using that operation method. The result is a map where keys are {@link OperationMethod} names,
* and values are the union of the domain of validity of all CRS using that {@code OperationMethod}.
*
* <p>This is a costly operation.</p>
*
* @todo This method is not yet used. This is pending the implementation of {@code CRSAuthorityFactory} is SIS.
*
* @param factory the factory to use for getting CRS.
* @return the union of domain of validity of all map projections using a method of the given name.
* @throws FactoryException if an error occurred while fetching the list of CRS.
*/
public static Map<String, DefaultGeographicBoundingBox> computeUnionOfAllDomainOfValidity(final CRSAuthorityFactory factory) throws FactoryException {
final Map<String, DefaultGeographicBoundingBox> domainOfValidity = new HashMap<>();
for (final String code : factory.getAuthorityCodes(GeneralDerivedCRS.class)) {
final CoordinateReferenceSystem crs;
try {
crs = factory.createCoordinateReferenceSystem(code);
} catch (FactoryException e) {
// Ignore and inspect the next element.
continue;
}
if (crs instanceof GeneralDerivedCRS) {
final GeographicBoundingBox candidate = CRS.getGeographicBoundingBox(crs);
if (candidate != null) {
final String name = ((GeneralDerivedCRS) crs).getConversionFromBase().getMethod().getName().getCode();
DefaultGeographicBoundingBox validity = domainOfValidity.get(name);
if (validity == null) {
validity = new DefaultGeographicBoundingBox(candidate);
domainOfValidity.put(name, validity);
} else {
validity.add(candidate);
}
}
}
}
return domainOfValidity;
}
use of org.opengis.referencing.crs.GeneralDerivedCRS in project sis by apache.
the class CoordinateOperationSet method createObject.
/**
* Creates a coordinate operation for the specified EPSG code.
*/
@Override
protected CoordinateOperation createObject(final String code) throws FactoryException {
final Integer base = projections.get(code);
if (base != null) {
/*
* First case documented in class Javadoc:
*
* SELECT PROJECTION_CONV_CODE FROM "Coordinate Reference System" …
*
* The result is usually a ProjectedCRS, but not always.
*/
CoordinateReferenceSystem crs;
crs = ((CRSAuthorityFactory) factory).createCoordinateReferenceSystem(String.valueOf(base));
if (crs instanceof GeneralDerivedCRS) {
return ((GeneralDerivedCRS) crs).getConversionFromBase();
}
}
/*
* Following line is either for the second case documented in class Javadoc, or the first case
* when the result is not a derived CRS. Note that we could create a derived CRS here as below:
*
* CoordinateOperation op = …,
* if (crs != null && op instanceof Conversion) {
* return DefaultDerivedCRS.create(IdentifiedObjects.getProperties(crs), baseCRS,
* (Conversion) op, crs.getCoordinateSystem()).getConversionFromBase();
* }
*
* We don't do that for now because because EPSGDataAccess.createCoordinateReferenceSystem(String)
* would be a better place, by generalizing the work done for ProjectedCRS.
*
* https://issues.apache.org/jira/browse/SIS-357
*/
return ((CoordinateOperationAuthorityFactory) factory).createCoordinateOperation(code);
}
use of org.opengis.referencing.crs.GeneralDerivedCRS in project sis by apache.
the class DefaultOperationMethod method formatTo.
/**
* Formats this operation as a <cite>Well Known Text</cite> {@code Method[…]} element.
*
* @return {@code "Method"} (WKT 2) or {@code "Projection"} (WKT 1).
*
* @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#118">WKT 2 specification §17.2.3</a>
*/
@Override
protected String formatTo(final Formatter formatter) {
final boolean isWKT1 = formatter.getConvention().majorVersion() == 1;
/*
* The next few lines below are basically a copy of the work done by super.formatTo(formatter),
* which search for the name to write inside METHOD["name"]. The difference is in the fallback
* executed if we do not find a name for the given authority.
*/
final Citation authority = formatter.getNameAuthority();
String name = IdentifiedObjects.getName(this, authority);
ElementKind kind = ElementKind.METHOD;
if (name == null) {
/*
* No name found for the given authority. We may use the primary name as a fallback.
* But before doing that, maybe we can find the name that we are looking for in the
* hard-coded values in the 'org.apache.sis.internal.referencing.provider' package.
* The typical use case is when this DefaultOperationMethod has been instantiated
* by the EPSG factory using only the information found in the EPSG database.
*
* We can find the hard-coded names by looking at the ParameterDescriptorGroup of the
* enclosing ProjectedCRS or DerivedCRS. This is because that parameter descriptor was
* typically provided by the 'org.apache.sis.internal.referencing.provider' package in
* order to create the MathTransform associated with the enclosing CRS. The enclosing
* CRS is either the immediate parent in WKT 1, or the parent of the parent in WKT 2.
*/
final FormattableObject parent = formatter.getEnclosingElement(isWKT1 ? 1 : 2);
if (parent instanceof GeneralDerivedCRS) {
final Conversion conversion = ((GeneralDerivedCRS) parent).getConversionFromBase();
if (conversion != null) {
// Should never be null, but let be safe.
final ParameterDescriptorGroup descriptor;
if (conversion instanceof Parameterized) {
// Usual case in SIS implementation.
descriptor = ((Parameterized) conversion).getParameterDescriptors();
} else {
descriptor = conversion.getParameterValues().getDescriptor();
}
name = IdentifiedObjects.getName(descriptor, authority);
}
}
if (name == null) {
name = IdentifiedObjects.getName(this, null);
if (name == null) {
name = Vocabulary.getResources(formatter.getLocale()).getString(Vocabulary.Keys.Unnamed);
// Because the "Unnamed" string is not a real OperationMethod name.
kind = ElementKind.NAME;
}
}
}
formatter.append(name, kind);
if (isWKT1) {
/*
* The WKT 1 keyword is "PROJECTION", which imply that the operation method should be of type
* org.opengis.referencing.operation.Projection. So strictly speaking only the first check in
* the following 'if' statement is relevant.
*
* Unfortunately in many cases we do not know the operation type, because the method that we
* invoked - getOperationType() - is not a standard OGC/ISO property, so this information is
* usually not provided in XML documents for example. The user could also have instantiated
* DirectOperationMethod directly without creating a subclass. Consequently we also accept to
* format the keyword as "PROJECTION" if the operation type *could* be a projection. This is
* the second check in the following 'if' statement.
*
* In other words, the combination of those two checks exclude the following operation types:
* Transformation, ConcatenatedOperation, PassThroughOperation, or any user-defined type that
* do not extend Projection. All other operation types are accepted.
*/
final Class<? extends SingleOperation> type = getOperationType();
if (Projection.class.isAssignableFrom(type) || type.isAssignableFrom(Projection.class)) {
return WKTKeywords.Projection;
}
formatter.setInvalidWKT(this, null);
}
return WKTKeywords.Method;
}
use of org.opengis.referencing.crs.GeneralDerivedCRS 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;
}
use of org.opengis.referencing.crs.GeneralDerivedCRS in project sis by apache.
the class DefinitionVerifier method diffCode.
/**
* Returns a code indicating in which part the two given CRS differ. The given iterators usually iterate over
* exactly one element, but may iterate over more elements if the CRS were instance of {@code CompoundCRS}.
* The returned value is one of {@link #METHOD}, {@link #CONVERSION}, {@link #CS}, {@link #DATUM},
* {@link #PRIME_MERIDIAN} or {@link #OTHER} constants.
*/
private static int diffCode(final Iterator<SingleCRS> authoritative, final Iterator<SingleCRS> given) {
while (authoritative.hasNext() && given.hasNext()) {
final SingleCRS crsA = authoritative.next();
final SingleCRS crsG = given.next();
if (!Utilities.equalsApproximatively(crsA, crsG)) {
if (crsA instanceof GeneralDerivedCRS && crsG instanceof GeneralDerivedCRS) {
final Conversion cnvA = ((GeneralDerivedCRS) crsA).getConversionFromBase();
final Conversion cnvG = ((GeneralDerivedCRS) crsG).getConversionFromBase();
if (!Utilities.equalsApproximatively(cnvA, cnvG)) {
return Utilities.equalsApproximatively(cnvA.getMethod(), cnvG.getMethod()) ? CONVERSION : METHOD;
}
}
if (!Utilities.equalsApproximatively(crsA.getCoordinateSystem(), crsG.getCoordinateSystem())) {
return CS;
}
final Datum datumA = crsA.getDatum();
final Datum datumG = crsG.getDatum();
if (!Utilities.equalsApproximatively(datumA, datumG)) {
if ((datumA instanceof GeodeticDatum) && (datumG instanceof GeodeticDatum) && !Utilities.equalsApproximatively(((GeodeticDatum) datumA).getPrimeMeridian(), ((GeodeticDatum) datumG).getPrimeMeridian())) {
return PRIME_MERIDIAN;
}
return DATUM;
}
break;
}
}
return OTHER;
}
Aggregations