| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.sis.internal.referencing; |
| |
| import java.util.Map; |
| import java.util.Iterator; |
| import java.util.Collection; |
| import java.util.Locale; |
| import java.util.TimeZone; |
| import java.text.Format; |
| |
| import org.opengis.util.FactoryException; |
| import org.opengis.util.InternationalString; |
| import org.opengis.util.NoSuchIdentifierException; |
| import org.opengis.parameter.ParameterDescriptor; |
| import org.opengis.referencing.IdentifiedObject; |
| import org.opengis.referencing.datum.Datum; |
| import org.opengis.referencing.datum.DatumFactory; |
| import org.opengis.referencing.crs.SingleCRS; |
| import org.opengis.referencing.crs.VerticalCRS; |
| import org.opengis.referencing.crs.GeographicCRS; |
| import org.opengis.referencing.crs.CoordinateReferenceSystem; |
| import org.opengis.referencing.crs.CRSFactory; |
| import org.opengis.referencing.cs.CSFactory; |
| import org.opengis.referencing.cs.CoordinateSystem; |
| import org.opengis.referencing.cs.CoordinateSystemAxis; |
| import org.opengis.referencing.operation.MathTransformFactory; |
| import org.opengis.referencing.operation.TransformException; |
| import org.opengis.referencing.operation.OperationMethod; |
| import org.opengis.referencing.operation.SingleOperation; |
| import org.opengis.referencing.operation.CoordinateOperation; |
| import org.opengis.referencing.operation.CoordinateOperationFactory; |
| import org.opengis.referencing.ReferenceIdentifier; |
| import org.opengis.metadata.citation.Citation; |
| import org.opengis.metadata.citation.OnLineFunction; |
| import org.opengis.metadata.citation.OnlineResource; |
| import org.opengis.metadata.extent.GeographicBoundingBox; |
| import org.opengis.metadata.extent.GeographicExtent; |
| import org.opengis.metadata.extent.VerticalExtent; |
| import org.opengis.geometry.DirectPosition; |
| import org.opengis.geometry.Envelope; |
| |
| import org.apache.sis.geometry.Envelopes; |
| import org.apache.sis.geometry.AbstractEnvelope; |
| import org.apache.sis.geometry.DirectPosition2D; |
| import org.apache.sis.geometry.CoordinateFormat; |
| import org.apache.sis.internal.metadata.NameToIdentifier; |
| import org.apache.sis.referencing.CRS; |
| import org.apache.sis.referencing.CommonCRS; |
| import org.apache.sis.referencing.IdentifiedObjects; |
| import org.apache.sis.referencing.cs.DefaultParametricCS; |
| import org.apache.sis.referencing.datum.DefaultParametricDatum; |
| import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory; |
| import org.apache.sis.referencing.factory.GeodeticObjectFactory; |
| import org.apache.sis.referencing.factory.InvalidGeodeticParameterException; |
| import org.apache.sis.parameter.DefaultParameterDescriptor; |
| import org.apache.sis.metadata.iso.extent.DefaultExtent; |
| import org.apache.sis.metadata.iso.extent.DefaultVerticalExtent; |
| import org.apache.sis.metadata.iso.extent.DefaultTemporalExtent; |
| import org.apache.sis.metadata.iso.extent.DefaultSpatialTemporalExtent; |
| import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox; |
| import org.apache.sis.metadata.iso.citation.DefaultCitation; |
| import org.apache.sis.measure.Latitude; |
| import org.apache.sis.measure.Longitude; |
| import org.apache.sis.internal.metadata.ReferencingServices; |
| import org.apache.sis.internal.system.DefaultFactories; |
| import org.apache.sis.internal.system.Modules; |
| import org.apache.sis.internal.util.Constants; |
| import org.apache.sis.util.resources.Vocabulary; |
| import org.apache.sis.util.resources.Errors; |
| import org.apache.sis.util.logging.Logging; |
| import org.apache.sis.util.Deprecable; |
| import org.apache.sis.util.Exceptions; |
| import org.apache.sis.util.Utilities; |
| |
| import static java.util.logging.Logger.getLogger; |
| |
| |
| /** |
| * Implements the referencing services needed by the {@code "sis-metadata"} module. |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 1.1 |
| * @since 0.5 |
| * @module |
| */ |
| public final class ServicesForMetadata extends ReferencingServices { |
| /** |
| * Name of an {@link OnLineFunction} code list value, used for transferring information about the EPSG database. |
| */ |
| public static final String CONNECTION = "CONNECTION"; |
| |
| /** |
| * Creates a new instance. This constructor is invoked by reflection only. |
| */ |
| public ServicesForMetadata() { |
| } |
| |
| |
| |
| |
| /////////////////////////////////////////////////////////////////////////////////////// |
| //// //// |
| //// SERVICES FOR ISO 19115 METADATA //// |
| //// //// |
| /////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Creates an exception message for a spatial, vertical or temporal dimension not found. |
| * The given key must be one of {@code Resources.Keys} constants. |
| */ |
| private static String dimensionNotFound(final short resourceKey, final CoordinateReferenceSystem crs) { |
| if (crs == null) { |
| return Errors.format(Errors.Keys.UnspecifiedCRS); |
| } else { |
| return Resources.format(resourceKey, crs.getName()); |
| } |
| } |
| |
| /** |
| * Implementation of the public {@code setBounds(…, DefaultGeographicBoundingBox, …)} methods for |
| * the horizontal extent. If the {@code crs} argument is null, then it is caller's responsibility |
| * to ensure that the given envelope is two-dimensional. |
| * |
| * <p>If {@code findOpCaller} is non-null, then this method will be executed in optional mode: |
| * some failures will cause this method to return {@code null} instead of throwing an exception. |
| * Note that {@link TransformException} may still be thrown but not directly by this method. |
| * Warning may be logged, but in such case this method presumes that public caller is the named |
| * method from {@link Envelopes} — typically {@link Envelopes#findOperation(Envelope, Envelope)}.</p> |
| * |
| * @param envelope the source envelope. |
| * @param target the target bounding box, or {@code null} for creating it automatically. |
| * @param crs the envelope CRS, or {@code null} if unknown. |
| * @param normalizedCRS the horizontal component of the given CRS, or null if the {@code crs} argument is null. |
| * @param findOpCaller non-null for replacing some (not all) exceptions by {@code null} return value. |
| * @return the bounding box or {@code null} on failure. Never {@code null} if {@code findOpCaller} argument is {@code null}. |
| * @throws TransformException if the given envelope can not be transformed. |
| */ |
| private static DefaultGeographicBoundingBox setGeographicExtent(Envelope envelope, DefaultGeographicBoundingBox target, |
| final CoordinateReferenceSystem crs, final GeographicCRS normalizedCRS, final String findOpCaller) throws TransformException |
| { |
| if (normalizedCRS != null) { |
| // No need to check for dimension, since GeodeticCRS can not have less than 2. |
| final CoordinateSystem cs1 = crs.getCoordinateSystem(); |
| final CoordinateSystem cs2 = normalizedCRS.getCoordinateSystem(); |
| if (!Utilities.equalsIgnoreMetadata(cs2.getAxis(0), cs1.getAxis(0)) || |
| !Utilities.equalsIgnoreMetadata(cs2.getAxis(1), cs1.getAxis(1))) |
| { |
| final CoordinateOperation operation; |
| final CoordinateOperationFactory factory = CoordinateOperations.factory(); |
| try { |
| operation = factory.createOperation(crs, normalizedCRS); |
| } catch (FactoryException e) { |
| if (findOpCaller != null) { |
| // See javadoc for the assumption that optional mode is used by Envelopes.findOperation(…). |
| Logging.recoverableException(getLogger(Modules.REFERENCING), Envelopes.class, findOpCaller, e); |
| return null; |
| } |
| throw new TransformException(Resources.format(Resources.Keys.CanNotTransformEnvelopeToGeodetic), e); |
| } |
| envelope = Envelopes.transform(operation, envelope); |
| } |
| } |
| /* |
| * At this point, the envelope should use (longitude, latitude) coordinates in degrees. |
| * The envelope may cross the anti-meridian if the envelope implementation is an Apache SIS one. |
| * For other implementations, the longitude range may be conservatively expanded to [-180 … 180]°. |
| */ |
| double westBoundLongitude, eastBoundLongitude; |
| double southBoundLatitude, northBoundLatitude; |
| if (envelope instanceof AbstractEnvelope) { |
| final AbstractEnvelope ae = (AbstractEnvelope) envelope; |
| westBoundLongitude = ae.getLower(0); |
| eastBoundLongitude = ae.getUpper(0); // Cross anti-meridian if eastBoundLongitude < westBoundLongitude. |
| southBoundLatitude = ae.getLower(1); |
| northBoundLatitude = ae.getUpper(1); |
| } else { |
| westBoundLongitude = envelope.getMinimum(0); |
| eastBoundLongitude = envelope.getMaximum(0); // Expanded to [-180 … 180]° if it was crossing the anti-meridian. |
| southBoundLatitude = envelope.getMinimum(1); |
| northBoundLatitude = envelope.getMaximum(1); |
| } |
| /* |
| * The envelope transformation at the beginning of this method intentionally avoided to apply datum shift. |
| * This implies that the prime meridian has not been changed and may be something else than Greenwich. |
| * We need to take it in account manually. |
| * |
| * Note that there is no need to normalize the longitudes back to the [-180 … +180]° range after the rotation, or |
| * to verify if the longitude span is 360°. Those verifications will be done automatically by target.setBounds(…). |
| */ |
| if (normalizedCRS != null) { |
| final double rotation = CRS.getGreenwichLongitude(normalizedCRS); |
| westBoundLongitude += rotation; |
| eastBoundLongitude += rotation; |
| } |
| /* |
| * In the particular case where this method is invoked (indirectly) for Envelopes.findOperation(…) purposes, |
| * replace NaN values by the whole world. We do that only for Envelopes.findOperation(…) since we know that |
| * the geographic bounding box will be used for choosing a CRS, and a conservative approach is to select the |
| * CRS valid in the widest area. If this method is invoked for other usages, then we keep NaN values because |
| * we don't know the context (union, intersection, something else?). |
| */ |
| if (findOpCaller != null) { |
| if (Double.isNaN(southBoundLatitude)) southBoundLatitude = Latitude.MIN_VALUE; |
| if (Double.isNaN(northBoundLatitude)) northBoundLatitude = Latitude.MAX_VALUE; |
| if (Double.isNaN(eastBoundLongitude) || Double.isNaN(westBoundLongitude)) { |
| // Conservatively set the two bounds because may be crossing the anti-meridian. |
| eastBoundLongitude = Longitude.MIN_VALUE; |
| westBoundLongitude = Longitude.MAX_VALUE; |
| } |
| } |
| if (target == null) { |
| target = new DefaultGeographicBoundingBox(); |
| } |
| target.setBounds(westBoundLongitude, eastBoundLongitude, southBoundLatitude, northBoundLatitude); |
| target.setInclusion(Boolean.TRUE); |
| return target; |
| } |
| |
| /** |
| * Implementation of the public {@code setBounds} methods for the vertical extent. |
| * If the {@code crs} argument is null, then it is caller's responsibility to ensure |
| * that the given envelope is one-dimensional. |
| * |
| * @param envelope the source envelope. |
| * @param target the target vertical extent. |
| * @param crs the envelope CRS, or {@code null} if unknown. |
| * @param verticalCRS the vertical component of the given CRS, or null if the {@code crs} argument is null. |
| */ |
| private static void setVerticalExtent(final Envelope envelope, final DefaultVerticalExtent target, |
| final CoordinateReferenceSystem crs, final VerticalCRS verticalCRS) |
| { |
| final int dim; |
| if (verticalCRS == null) { |
| dim = 0; |
| } else { |
| dim = AxisDirections.indexOfColinear(crs.getCoordinateSystem(), verticalCRS.getCoordinateSystem()); |
| assert dim >= 0 : crs; // Should not fail since 'verticalCRS' has been extracted from 'crs' by the caller. |
| } |
| target.setMinimumValue(envelope.getMinimum(dim)); |
| target.setMaximumValue(envelope.getMaximum(dim)); |
| target.setVerticalCRS(verticalCRS); |
| } |
| |
| /** |
| * Sets a geographic bounding box from the specified envelope. |
| * If the envelope has no CRS, then (<var>longitude</var>, <var>latitude</var>) axis order is assumed. |
| * If the envelope CRS is not geographic, then the envelope will be transformed to a geographic CRS. |
| * If {@code findOpCaller} is {@code true}, then some failures will cause this method to return {@code null} |
| * instead of throwing an exception, and warning may be logged with assumption that caller is the named |
| * method from {@link Envelopes} — typically {@link Envelopes#findOperation(Envelope, Envelope)}. |
| * |
| * @param envelope the source envelope. |
| * @param target the target bounding box, or {@code null} for creating it automatically. |
| * @param findOpCaller non-null for replacing some (not all) exceptions by {@code null} return value. |
| * @return the bounding box or {@code null} on failure. Never {@code null} if {@code findOpCaller} argument is {@code null}. |
| * @throws TransformException if the given envelope can not be transformed. |
| */ |
| @Override |
| public DefaultGeographicBoundingBox setBounds(final Envelope envelope, final DefaultGeographicBoundingBox target, |
| final String findOpCaller) throws TransformException |
| { |
| final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem(); |
| GeographicCRS normalizedCRS = ReferencingUtilities.toNormalizedGeographicCRS(crs, false, false); |
| if (normalizedCRS == null) { |
| if (crs != null) { |
| normalizedCRS = CommonCRS.defaultGeographic(); |
| } else if (envelope.getDimension() != 2) { |
| if (findOpCaller != null) return null; |
| throw new TransformException(dimensionNotFound(Resources.Keys.MissingHorizontalDimension_1, crs)); |
| } |
| } |
| return setGeographicExtent(envelope, target, crs, normalizedCRS, findOpCaller); |
| } |
| |
| /** |
| * Sets a vertical extent with the value inferred from the given envelope. |
| * Only the vertical coordinates are extracted; all other coordinates are ignored. |
| * |
| * @param envelope the source envelope. |
| * @param target the target vertical extent where to store envelope information. |
| * @throws TransformException if no vertical component can be extracted from the given envelope. |
| */ |
| @Override |
| public void setBounds(final Envelope envelope, final DefaultVerticalExtent target) throws TransformException { |
| final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem(); |
| final VerticalCRS verticalCRS = CRS.getVerticalComponent(crs, true); |
| if (verticalCRS == null && envelope.getDimension() != 1) { |
| throw new TransformException(dimensionNotFound(Resources.Keys.MissingVerticalDimension_1, crs)); |
| } |
| setVerticalExtent(envelope, target, crs, verticalCRS); |
| } |
| |
| /** |
| * Sets a temporal extent with the value inferred from the given envelope. |
| * Only the vertical coordinates are extracted; all other coordinates are ignored. |
| * |
| * @param envelope the source envelope. |
| * @param target the target temporal 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 DefaultTemporalExtent target) throws TransformException { |
| final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem(); |
| final TemporalAccessor accessor = TemporalAccessor.of(crs, 0); |
| if (accessor == null) { // Mandatory for the conversion from numbers to dates. |
| throw new TransformException(dimensionNotFound(Resources.Keys.MissingTemporalDimension_1, crs)); |
| } |
| accessor.setTemporalExtent(envelope, target); |
| } |
| |
| /** |
| * 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 TemporalAccessor accessor = TemporalAccessor.of(crs, 0); |
| if (horizontalCRS == null && verticalCRS == null && accessor == 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, false, false); |
| if (normalizedCRS == null) { |
| normalizedCRS = CommonCRS.defaultGeographic(); |
| } |
| setGeographicExtent(envelope, box, crs, normalizedCRS, null); |
| } |
| /* |
| * 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 (accessor != null) { |
| accessor.setTemporalExtent(envelope, target); |
| } else { |
| target.setExtent(null); |
| } |
| } |
| |
| /** |
| * Initializes a horizontal, vertical and temporal extent with the values inferred from the given envelope. |
| * |
| * @param envelope the source envelope. |
| * @param target the target extent where to store envelope information. |
| * @throws TransformException if a coordinate transformation was required and failed. |
| * @throws UnsupportedOperationException if this method requires an Apache SIS module |
| * which has been found on the classpath. |
| */ |
| @Override |
| public void addElements(final Envelope envelope, final DefaultExtent target) throws TransformException { |
| final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem(); |
| final SingleCRS horizontalCRS = CRS.getHorizontalComponent(crs); |
| final VerticalCRS verticalCRS = CRS.getVerticalComponent(crs, true); |
| final TemporalAccessor accessor = TemporalAccessor.of(crs, 0); |
| if (horizontalCRS == null && verticalCRS == null && accessor == null) { |
| throw new TransformException(dimensionNotFound(Resources.Keys.MissingSpatioTemporalDimension_1, crs)); |
| } |
| if (horizontalCRS != null) { |
| target.getGeographicElements().add(setBounds(envelope, null, null)); |
| } |
| if (verticalCRS != null) { |
| final DefaultVerticalExtent extent = new DefaultVerticalExtent(); |
| setVerticalExtent(envelope, extent, crs, verticalCRS); |
| target.getVerticalElements().add(extent); |
| } |
| if (accessor != null) { |
| final DefaultTemporalExtent extent = new DefaultTemporalExtent(); |
| accessor.setTemporalExtent(envelope, extent); |
| target.getTemporalElements().add(extent); |
| } |
| } |
| |
| /** |
| * Creates a two-dimensional geographic position associated to the default geographic CRS. |
| * Axis order is (longitude, latitude). |
| * |
| * @param λ the longitude value. |
| * @param φ the latitude value. |
| * @return the direct position for the given geographic coordinate. |
| * |
| * @since 0.8 |
| */ |
| @Override |
| public DirectPosition geographic(final double λ, final double φ) { |
| return new DirectPosition2D(CommonCRS.defaultGeographic(), λ, φ); |
| } |
| |
| /** |
| * Returns an identifier for the given object, giving precedence to EPSG identifier if available. |
| * The returned string should be of the form {@code "AUTHORITY:CODE"} if possible (no guarantees). |
| * |
| * @param object the object for which to get an identifier. |
| * @return an identifier for the given object, with preference given to EPSG codes. |
| * @throws FactoryException if an error occurred while searching for the EPSG code. |
| * |
| * @since 1.0 |
| */ |
| @Override |
| public String getPreferredIdentifier(final IdentifiedObject object) throws FactoryException { |
| final Integer code = IdentifiedObjects.lookupEPSG(object); |
| if (code != null) { |
| return Constants.EPSG + Constants.DEFAULT_SEPARATOR + code; |
| } |
| /* |
| * If above code did not found an EPSG code, discard EPSG codes that |
| * we may find in the loop below because they are probably invalid. |
| */ |
| for (final ReferenceIdentifier id : object.getIdentifiers()) { |
| if (!Constants.EPSG.equalsIgnoreCase(id.getCodeSpace())) { |
| return IdentifiedObjects.toString(id); |
| } |
| } |
| return IdentifiedObjects.getSimpleNameOrIdentifier(object); |
| } |
| |
| |
| |
| |
| /////////////////////////////////////////////////////////////////////////////////////// |
| //// //// |
| //// OTHER REFERENCING SERVICES //// |
| //// //// |
| /////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Returns a fully implemented parameter descriptor. |
| * |
| * @param parameter a partially implemented parameter descriptor, or {@code null}. |
| * @return a fully implemented parameter descriptor, or {@code null} if the given argument was null. |
| */ |
| @Override |
| public ParameterDescriptor<?> toImplementation(final ParameterDescriptor<?> parameter) { |
| return DefaultParameterDescriptor.castOrCopy(parameter); |
| } |
| |
| /** |
| * Creates a parametric CS. This method requires the SIS factory |
| * since parametric CRS were not available in GeoAPI 3.0. |
| * |
| * <p>This method is actually not needed anymore for {@code sis-metadata} module, |
| * but is still defined here for historical reason. This method is removed on SIS |
| * branches using a GeoAPI versions more recent than 3.0.</p> |
| * |
| * @param properties the coordinate system name, and optionally other properties. |
| * @param axis the axis of the parametric coordinate system. |
| * @param factory the factory to use for creating the coordinate system. |
| * @return a parametric coordinate system using the given axes. |
| * @throws FactoryException if the parametric object creation failed. |
| * |
| * @since 0.7 |
| */ |
| public static CoordinateSystem createParametricCS(final Map<String,?> properties, final CoordinateSystemAxis axis, |
| CSFactory factory) throws FactoryException |
| { |
| if (!(factory instanceof GeodeticObjectFactory)) { |
| factory = DefaultFactories.forBuildin(CSFactory.class, GeodeticObjectFactory.class); |
| } |
| return ((GeodeticObjectFactory) factory).createParametricCS(properties, axis); |
| } |
| |
| /** |
| * Creates a parametric datum. This method requires the SIS factory |
| * since parametric CRS were not available in GeoAPI 3.0. |
| * |
| * <p>This method is actually not needed anymore for {@code sis-metadata} module, |
| * but is still defined here for historical reason. This method is removed on SIS |
| * branches using a GeoAPI versions more recent than 3.0.</p> |
| * |
| * @param properties the datum name, and optionally other properties. |
| * @param factory the factory to use for creating the datum. |
| * @return a parametric datum using the given name. |
| * @throws FactoryException if the parametric object creation failed. |
| * |
| * @since 0.7 |
| */ |
| public static Datum createParametricDatum(final Map<String,?> properties, DatumFactory factory) |
| throws FactoryException |
| { |
| if (!(factory instanceof GeodeticObjectFactory)) { |
| factory = DefaultFactories.forBuildin(DatumFactory.class, GeodeticObjectFactory.class); |
| } |
| return ((GeodeticObjectFactory) factory).createParametricDatum(properties); |
| } |
| |
| /** |
| * Creates a parametric CRS. This method requires the SIS factory |
| * since parametric CRS were not available in GeoAPI 3.0. |
| * |
| * <p>This method is actually not needed anymore for {@code sis-metadata} module, |
| * but is still defined here for historical reason. This method is removed on SIS |
| * branches using a GeoAPI versions more recent than 3.0.</p> |
| * |
| * @param properties the coordinate reference system name, and optionally other properties. |
| * @param datum the parametric datum. |
| * @param cs the parametric coordinate system. |
| * @param factory the factory to use for creating the coordinate reference system. |
| * @return a parametric coordinate system using the given axes. |
| * @throws FactoryException if the parametric object creation failed. |
| * |
| * @since 0.7 |
| */ |
| public static SingleCRS createParametricCRS(final Map<String,?> properties, final Datum datum, |
| final CoordinateSystem cs, CRSFactory factory) throws FactoryException |
| { |
| if (!(factory instanceof GeodeticObjectFactory)) { |
| factory = DefaultFactories.forBuildin(CRSFactory.class, GeodeticObjectFactory.class); |
| } |
| try { |
| return ((GeodeticObjectFactory) factory).createParametricCRS(properties, |
| (DefaultParametricDatum) datum, (DefaultParametricCS) cs); |
| } catch (ClassCastException e) { |
| throw new InvalidGeodeticParameterException(e.toString(), e); |
| } |
| } |
| |
| /** |
| * Creates a format for {@link DirectPosition} instances. |
| * |
| * @param locale the locale for the new {@code Format}, or {@code null} for {@code Locale.ROOT}. |
| * @param timezone the timezone, or {@code null} for UTC. |
| * @return a {@link org.apache.sis.geometry.CoordinateFormat}. |
| * |
| * @since 0.8 |
| */ |
| @Override |
| public Format createCoordinateFormat(final Locale locale, final TimeZone timezone) { |
| return new CoordinateFormat(locale, timezone); |
| } |
| |
| /** |
| * Returns the default coordinate operation factory. |
| * |
| * @return the coordinate operation factory to use. |
| */ |
| @Override |
| public CoordinateOperationFactory getCoordinateOperationFactory() { |
| return CoordinateOperations.factory(); |
| } |
| |
| /** |
| * Returns the coordinate operation method for the given classification. |
| * This method checks if the given {@code opFactory} is a SIS implementation |
| * before to fallback on a slower fallback. |
| * |
| * <p>This method is actually not needed anymore for {@code sis-metadata} module, |
| * but is still defined here for historical reason. This method is removed on SIS |
| * branches using a GeoAPI versions more recent than 3.0.</p> |
| * |
| * @param opFactory The coordinate operation factory to use if it is a SIS implementation. |
| * @param mtFactory The math transform factory to use as a fallback. |
| * @param identifier The name or identifier of the operation method to search. |
| * @return The coordinate operation method for the given name or identifier. |
| * @throws FactoryException if an error occurred which searching for the given method. |
| * |
| * @since 0.6 |
| */ |
| public static OperationMethod getOperationMethod(final CoordinateOperationFactory opFactory, |
| final MathTransformFactory mtFactory, final String identifier) throws FactoryException |
| { |
| if (opFactory instanceof DefaultCoordinateOperationFactory) { |
| ((DefaultCoordinateOperationFactory) opFactory).getOperationMethod(identifier); |
| } |
| final OperationMethod method = getOperationMethod(mtFactory.getAvailableMethods(SingleOperation.class), identifier); |
| if (method != null) { |
| return method; |
| } |
| throw new NoSuchIdentifierException("No such operation method: " + identifier, identifier); |
| } |
| |
| /** |
| * Returns the operation method for the specified name or identifier. The given argument shall be either a |
| * method name (e.g. <cite>"Transverse Mercator"</cite>) or one of its identifiers (e.g. {@code "EPSG:9807"}). |
| * |
| * @param methods the method candidates. |
| * @param identifier the name or identifier of the operation method to search. |
| * @return the coordinate operation method for the given name or identifier, or {@code null} if none. |
| * |
| * @see org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory#getOperationMethod(String) |
| * @see org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory#getOperationMethod(String) |
| * |
| * @since 0.6 |
| */ |
| private static OperationMethod getOperationMethod(final Iterable<? extends OperationMethod> methods, final String identifier) { |
| OperationMethod fallback = null; |
| for (final OperationMethod method : methods) { |
| if (IdentifiedObjects.isHeuristicMatchForName(method, identifier) || |
| NameToIdentifier.isHeuristicMatchForIdentifier(method.getIdentifiers(), identifier)) |
| { |
| /* |
| * Stop the iteration at the first non-deprecated method. |
| * If we find only deprecated methods, take the first one. |
| */ |
| if (!(method instanceof Deprecable) || !((Deprecable) method).isDeprecated()) { |
| return method; |
| } |
| if (fallback == null) { |
| fallback = method; |
| } |
| } |
| } |
| return fallback; |
| } |
| |
| /** |
| * Returns information about the Apache SIS configuration. |
| * See super-class for a list of keys. |
| * |
| * @param key a key identifying the information to return. |
| * @param locale language to use if possible. |
| * @return the information, or {@code null} if none. |
| */ |
| @Override |
| public String getInformation(final String key, final Locale locale) { |
| switch (key) { |
| /* |
| * Get the version of the EPSG database and the version of the database software. |
| * This operation can be relatively costly as it may open a JDBC connection. |
| */ |
| case Constants.EPSG: { |
| final Citation authority; |
| try { |
| authority = CRS.getAuthorityFactory(Constants.EPSG).getAuthority(); |
| } catch (FactoryException e) { |
| final String msg = Exceptions.getLocalizedMessage(e, locale); |
| return (msg != null) ? msg : e.toString(); |
| } |
| if (authority instanceof DefaultCitation) { |
| final OnLineFunction f = OnLineFunction.valueOf(CONNECTION); |
| for (final OnlineResource res : ((DefaultCitation) authority).getOnlineResources()) { |
| if (f.equals(res.getFunction())) { |
| final InternationalString i18n = res.getDescription(); |
| if (i18n != null) return i18n.toString(locale); |
| } |
| } |
| final InternationalString i18n = authority.getTitle(); |
| if (i18n != null) return i18n.toString(locale); |
| } |
| return Vocabulary.getResources(locale).getString(Vocabulary.Keys.Untitled); |
| } |
| // More cases may be added in future SIS versions. |
| } |
| return super.getInformation(key, locale); |
| } |
| } |