| /* |
| * 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.provider; |
| |
| import java.util.Map; |
| import java.util.HashMap; |
| import java.util.NoSuchElementException; |
| import javax.xml.bind.annotation.XmlTransient; |
| import javax.measure.Unit; |
| import org.opengis.util.FactoryException; |
| import org.opengis.util.InternationalString; |
| import org.opengis.metadata.Identifier; |
| import org.opengis.metadata.citation.Citation; |
| import org.opengis.parameter.ParameterDescriptor; |
| import org.opengis.parameter.GeneralParameterDescriptor; |
| import org.opengis.util.GenericName; |
| import org.opengis.parameter.ParameterValueGroup; |
| import org.opengis.parameter.ParameterDescriptorGroup; |
| import org.opengis.parameter.ParameterNotFoundException; |
| import org.opengis.referencing.operation.OperationMethod; |
| import org.opengis.referencing.operation.MathTransform; |
| import org.opengis.referencing.operation.MathTransformFactory; |
| import org.opengis.referencing.operation.Projection; |
| import org.apache.sis.internal.referencing.Resources; |
| import org.apache.sis.internal.util.Constants; |
| import org.apache.sis.measure.MeasurementRange; |
| import org.apache.sis.measure.Units; |
| import org.apache.sis.referencing.NamedIdentifier; |
| import org.apache.sis.referencing.IdentifiedObjects; |
| import org.apache.sis.referencing.operation.projection.NormalizedProjection; |
| import org.apache.sis.referencing.ImmutableIdentifier; |
| import org.apache.sis.metadata.iso.citation.Citations; |
| import org.apache.sis.parameter.DefaultParameterDescriptor; |
| import org.apache.sis.parameter.ParameterBuilder; |
| import org.apache.sis.parameter.Parameters; |
| import org.apache.sis.util.resources.Errors; |
| import org.apache.sis.util.Debug; |
| |
| import static org.opengis.metadata.Identifier.AUTHORITY_KEY; |
| |
| |
| /** |
| * Base class for all map projection providers defined in this package. This base class defines some descriptors |
| * for the most commonly used parameters. Subclasses will declare additional parameters and group them in a |
| * {@linkplain ParameterDescriptorGroup descriptor group} named {@code PARAMETERS}. |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 1.1 |
| * @since 0.6 |
| * @module |
| */ |
| @XmlTransient |
| public abstract class MapProjection extends AbstractProvider { |
| /** |
| * Serial number for inter-operability with different versions. |
| */ |
| private static final long serialVersionUID = 6280666068007678702L; |
| |
| /** |
| * All names known to Apache SIS for the <cite>semi-major</cite> parameter. |
| * This parameter is mandatory and has no default value. The range of valid values is (0 … ∞). |
| * |
| * <p>Some names for this parameter are {@code "semi_major"}, {@code "SemiMajorAxis"} and {@code "a"}.</p> |
| * |
| * <!-- Generated by ParameterNameTableGenerator --> |
| * <table class="sis"> |
| * <caption>Parameter names</caption> |
| * <tr><td> OGC: </td><td> semi_major </td></tr> |
| * <tr><td> ESRI: </td><td> Semi_Major </td></tr> |
| * <tr><td> NetCDF: </td><td> semi_major_axis </td></tr> |
| * <tr><td> GeoTIFF: </td><td> SemiMajorAxis </td></tr> |
| * <tr><td> Proj4: </td><td> a </td></tr> |
| * </table> |
| * <b>Notes:</b> |
| * <ul> |
| * <li>Value domain: (0.0 … ∞) m</li> |
| * <li>No default value</li> |
| * </ul> |
| */ |
| public static final DefaultParameterDescriptor<Double> SEMI_MAJOR; |
| |
| /** |
| * All names known to Apache SIS for the <cite>semi-minor</cite> parameter. |
| * This parameter is mandatory and has no default value. The range of valid values is (0 … ∞). |
| * |
| * <p>Some names for this parameter are {@code "semi_minor"}, {@code "SemiMinorAxis"} and {@code "b"}.</p> |
| * |
| * <!-- Generated by ParameterNameTableGenerator --> |
| * <table class="sis"> |
| * <caption>Parameter names</caption> |
| * <tr><td> OGC: </td><td> semi_minor </td></tr> |
| * <tr><td> ESRI: </td><td> Semi_Minor </td></tr> |
| * <tr><td> NetCDF: </td><td> semi_minor_axis </td></tr> |
| * <tr><td> GeoTIFF: </td><td> SemiMinorAxis </td></tr> |
| * <tr><td> Proj4: </td><td> b </td></tr> |
| * </table> |
| * <b>Notes:</b> |
| * <ul> |
| * <li>Value domain: (0.0 … ∞) m</li> |
| * <li>No default value</li> |
| * </ul> |
| */ |
| public static final DefaultParameterDescriptor<Double> SEMI_MINOR; |
| |
| /** |
| * The ellipsoid eccentricity, computed from the semi-major and semi-minor axis lengths. |
| * This a SIS-specific parameter used mostly for debugging purpose. |
| * |
| * <!-- Generated by ParameterNameTableGenerator --> |
| * <table class="sis"> |
| * <caption>Parameter names</caption> |
| * <tr><td> SIS: </td><td> eccentricity </td></tr> |
| * </table> |
| * <b>Notes:</b> |
| * <ul> |
| * <li>Value domain: [0.0 … 1.0]</li> |
| * <li>No default value</li> |
| * </ul> |
| */ |
| @Debug |
| public static final DefaultParameterDescriptor<Double> ECCENTRICITY; |
| static { |
| final MeasurementRange<Double> valueDomain = MeasurementRange.createGreaterThan(0, Units.METRE); |
| final GenericName[] aliases = { |
| new NamedIdentifier(Citations.ESRI, "Semi_Major"), |
| new NamedIdentifier(Citations.NETCDF, "semi_major_axis"), |
| new NamedIdentifier(Citations.GEOTIFF, "SemiMajorAxis"), |
| new NamedIdentifier(Citations.PROJ4, "a") |
| }; |
| final Map<String,Object> properties = new HashMap<>(4); |
| properties.put(AUTHORITY_KEY, Citations.OGC); |
| properties.put(NAME_KEY, Constants.SEMI_MAJOR); |
| properties.put(ALIAS_KEY, aliases); |
| properties.put(IDENTIFIERS_KEY, new ImmutableIdentifier(Citations.GEOTIFF, null, "2057")); |
| SEMI_MAJOR = new DefaultParameterDescriptor<>(properties, 1, 1, Double.class, valueDomain, null, null); |
| /* |
| * Change in-place the name and aliases (we do not need to create new objects) |
| * before to create the SEMI_MINOR descriptor. |
| */ |
| properties.put(NAME_KEY, Constants.SEMI_MINOR); |
| aliases[0] = new NamedIdentifier(Citations.ESRI, "Semi_Minor"); |
| aliases[1] = new NamedIdentifier(Citations.NETCDF, "semi_minor_axis"); |
| aliases[2] = new NamedIdentifier(Citations.GEOTIFF, "SemiMinorAxis"); |
| aliases[3] = new NamedIdentifier(Citations.PROJ4, "b"); |
| properties.put(IDENTIFIERS_KEY, new ImmutableIdentifier(Citations.GEOTIFF, null, "2058")); |
| SEMI_MINOR = new DefaultParameterDescriptor<>(properties, 1, 1, Double.class, valueDomain, null, null); |
| /* |
| * SIS-specific parameter for debugging purpose only. |
| */ |
| properties.clear(); |
| properties.put(AUTHORITY_KEY, Citations.SIS); |
| properties.put(NAME_KEY, "eccentricity"); |
| ECCENTRICITY = new DefaultParameterDescriptor<>(properties, 1, 1, Double.class, |
| MeasurementRange.create(0d, true, 1d, true, null), null, null); |
| } |
| |
| /** |
| * The three-dimensional counterpart of this two-dimensional map projection. |
| * This is created when first needed. |
| * |
| * @see #redimension(int, int) |
| * @see GeodeticOperation#redimensioned |
| */ |
| private OperationMethod redimensioned; |
| |
| /** |
| * Constructs a math transform provider from a set of parameters. The provider |
| * {@linkplain #getIdentifiers() identifiers} will be the same than the parameter ones. |
| * |
| * @param parameters The set of parameters (never {@code null}). |
| */ |
| protected MapProjection(final ParameterDescriptorGroup parameters) { |
| super(2, 2, parameters); |
| } |
| |
| /** |
| * Returns the operation type for this map projection. |
| * |
| * @return {@code Projection.class} or a sub-type. |
| */ |
| @Override |
| public Class<? extends Projection> getOperationType() { |
| return Projection.class; |
| } |
| |
| /** |
| * Returns this operation method with the specified number of dimensions. |
| * The number of dimensions can be only 2 or 3, and must be the same for source and target CRS. |
| * |
| * @return the redimensioned projection method, or {@code this} if no change is needed. |
| * |
| * @since 0.8 |
| */ |
| @Override |
| public final OperationMethod redimension(final int sourceDimensions, final int targetDimensions) { |
| if (sourceDimensions != 3 || targetDimensions != 3) { |
| return super.redimension(sourceDimensions, targetDimensions); |
| } else synchronized (this) { |
| if (redimensioned == null) { |
| redimensioned = new MapProjection3D(this); |
| } |
| return redimensioned; |
| } |
| } |
| |
| /** |
| * Validates the given parameter value. This method duplicates the verification already |
| * done by {@link org.apache.sis.parameter.DefaultParameterValue#setValue(Object, Unit)}. |
| * But we check again because we have no guarantee that the parameters given by the user |
| * were instances of {@code DefaultParameterValue}, or that the descriptor associated to |
| * the user-specified {@code ParameterValue} has sufficient information. |
| * |
| * @param descriptor the descriptor that specify the parameter to validate. |
| * @param value the parameter value in the units given by the descriptor. |
| * @throws IllegalArgumentException if the given value is out of bounds. |
| * |
| * @see #createZeroConstant(ParameterBuilder) |
| */ |
| public static void validate(final ParameterDescriptor<? extends Number> descriptor, final double value) |
| throws IllegalArgumentException |
| { |
| if (!Double.isFinite(value)) { |
| throw new IllegalArgumentException(Resources.format(Resources.Keys.IllegalParameterValue_2, |
| descriptor.getName(), value)); |
| } |
| final Comparable<? extends Number> min = descriptor.getMinimumValue(); |
| final Comparable<? extends Number> max = descriptor.getMaximumValue(); |
| final double minValue = (min instanceof Number) ? ((Number) min).doubleValue() : Double.NaN; |
| final double maxValue = (max instanceof Number) ? ((Number) max).doubleValue() : Double.NaN; |
| if (value < minValue || value > maxValue) { |
| /* |
| * RATIONAL: why we do not check the bounds if (min == max): |
| * The only case when our descriptor have (min == max) is when a parameter can only be zero, |
| * because of the way the map projection is defined (see e.g. Mercator1SP.LATITUDE_OF_ORIGIN). |
| * But in some cases, it would be possible to deal with non-zero values, even if in principle |
| * we should not. In such case we let the caller decides. |
| * |
| * Above check should be revisited if createZeroConstant(ParameterBuilder) is modified. |
| */ |
| if (minValue != maxValue) { // Compare as 'double' because we want (-0 == +0) to be true. |
| throw new IllegalArgumentException(Errors.format(Errors.Keys.ValueOutOfRange_4, |
| descriptor.getName(), min, max, value)); |
| } |
| } |
| } |
| |
| /** |
| * Creates a map projection from the specified group of parameter values. |
| * |
| * @param factory the factory to use for creating and concatenating the (de)normalization transforms. |
| * @param parameters the group of parameter values. |
| * @return the map projection created from the given parameter values. |
| * @throws ParameterNotFoundException if a required parameter was not found. |
| * @throws FactoryException if the map projection can not be created. |
| */ |
| @Override |
| public final MathTransform createMathTransform(final MathTransformFactory factory, final ParameterValueGroup parameters) |
| throws ParameterNotFoundException, FactoryException |
| { |
| return createProjection(Parameters.castOrWrap(parameters)).createMapProjection(factory); |
| } |
| |
| /** |
| * Creates a map projection on an ellipsoid having a semi-major axis length of 1. |
| * |
| * @param parameters the group of parameter values. |
| * @return the map projection created from the given parameter values. |
| * @throws ParameterNotFoundException if a required parameter was not found. |
| */ |
| protected abstract NormalizedProjection createProjection(final Parameters parameters) throws ParameterNotFoundException; |
| |
| /** |
| * Notifies {@code DefaultMathTransformFactory} that map projections require |
| * values for the {@code "semi_major"} and {@code "semi_minor"} parameters. |
| * |
| * @return 1, meaning that the operation requires a source ellipsoid. |
| */ |
| @Override |
| public final int getEllipsoidsMask() { |
| return 1; |
| } |
| |
| |
| |
| |
| ////////////////////////////////////////////////////////////////////////////////////////// |
| //////// //////// |
| //////// HELPER METHODS FOR SUBCLASSES //////// |
| //////// //////// |
| //////// Following methods are defined for sharing the same GenericName or //////// |
| //////// Identifier instances when possible. //////// |
| ////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Returns the name of the given authority declared in the given parameter descriptor. |
| * This method is used only as a way to avoid creating many instances of the same name. |
| */ |
| static GenericName sameNameAs(final Citation authority, final GeneralParameterDescriptor parameters) { |
| for (final GenericName candidate : parameters.getAlias()) { |
| if (candidate instanceof Identifier && ((Identifier) candidate).getAuthority() == authority) { |
| return candidate; |
| } |
| } |
| throw new NoSuchElementException(); |
| } |
| |
| /** |
| * Copies name, aliases and identifiers of the given {@code template}, except the alias and identifiers of the |
| * given authority which are replaced by the alias and identifiers of the same authority in {@code replacement}. |
| * |
| * @param template the parameter from which to copy names and identifiers. |
| * @param toRename authority of the alias to rename. |
| * @param replacement the parameter from which to get the new name for the alias to rename. |
| * @param builder an initially clean builder where to add the names and identifiers. |
| * @return the given {@code builder}, for method call chaining. |
| * |
| * @since 0.8 |
| */ |
| static ParameterBuilder renameAlias(final ParameterDescriptor<Double> template, final Citation toRename, |
| final ParameterDescriptor<Double> replacement, final ParameterBuilder builder) |
| { |
| builder.addName(template.getName()); |
| GenericName newName = sameNameAs(toRename, replacement); |
| Identifier newCode = IdentifiedObjects.getIdentifier(replacement, toRename); |
| for (GenericName alias : template.getAlias()) { |
| if (((Identifier) alias).getAuthority() == toRename) { |
| if (newName == null) continue; |
| alias = newName; |
| newName = null; |
| } |
| builder.addName(alias); |
| } |
| for (Identifier id : template.getIdentifiers()) { |
| if (id.getAuthority() == toRename) { |
| if (newCode == null) continue; |
| id = newCode; |
| newCode = null; |
| } |
| builder.addIdentifier(id); |
| } |
| return builder; |
| } |
| |
| /** |
| * Creates a remarks for parameters that are not formally EPSG parameter. |
| * |
| * @param origin the name of the projection for where the parameter is formally used. |
| * @return a remarks saying that the parameter is actually defined in {@code origin}. |
| */ |
| static InternationalString notFormalParameter(final String origin) { |
| return Resources.formatInternational(Resources.Keys.NotFormalProjectionParameter_1, origin); |
| } |
| } |