blob: d5b763cbb7a735d7ec2c9d5bcf9445abfc73c4a0 [file] [log] [blame]
/*
* 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.referencing;
import java.util.Map;
import java.util.HashMap;
import javax.measure.Unit;
import javax.measure.quantity.Length;
import org.opengis.util.InternationalString;
import org.opengis.util.NoSuchIdentifierException;
import org.opengis.metadata.citation.Citation;
import org.opengis.metadata.citation.PresentationForm;
import org.opengis.referencing.datum.Ellipsoid;
import org.opengis.referencing.datum.PrimeMeridian;
import org.opengis.referencing.datum.GeodeticDatum;
import org.opengis.referencing.datum.VerticalDatum;
import org.opengis.referencing.cs.RangeMeaning;
import org.opengis.referencing.cs.AxisDirection;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.cs.EllipsoidalCS;
import org.opengis.referencing.cs.CartesianCS;
import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.referencing.crs.ProjectedCRS;
import org.opengis.referencing.crs.VerticalCRS;
import org.opengis.referencing.operation.OperationMethod;
import org.opengis.parameter.ParameterValueGroup;
import static org.opengis.referencing.IdentifiedObject.NAME_KEY;
import static org.opengis.referencing.IdentifiedObject.ALIAS_KEY;
import static org.opengis.referencing.IdentifiedObject.REMARKS_KEY;
import static org.opengis.referencing.IdentifiedObject.IDENTIFIERS_KEY;
import org.apache.sis.util.SimpleInternationalString;
import org.apache.sis.util.privy.Constants;
import org.apache.sis.metadata.privy.AxisNames;
import org.apache.sis.referencing.internal.Resources;
import org.apache.sis.referencing.operation.DefaultConversion;
import org.apache.sis.referencing.operation.provider.TransverseMercator;
import org.apache.sis.referencing.operation.provider.PolarStereographicA;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.metadata.iso.extent.Extents;
import org.apache.sis.metadata.iso.citation.Citations;
import org.apache.sis.metadata.iso.citation.DefaultCitation;
import org.apache.sis.referencing.datum.DefaultEllipsoid;
import org.apache.sis.referencing.datum.DefaultPrimeMeridian;
import org.apache.sis.referencing.datum.DefaultGeodeticDatum;
import org.apache.sis.referencing.datum.DefaultVerticalDatum;
import org.apache.sis.referencing.cs.CoordinateSystems;
import org.apache.sis.referencing.cs.DefaultVerticalCS;
import org.apache.sis.referencing.cs.DefaultCartesianCS;
import org.apache.sis.referencing.cs.DefaultSphericalCS;
import org.apache.sis.referencing.cs.DefaultEllipsoidalCS;
import org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis;
import org.apache.sis.referencing.crs.DefaultGeographicCRS;
import org.apache.sis.referencing.crs.DefaultVerticalCRS;
import org.apache.sis.referencing.crs.DefaultProjectedCRS;
import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
import org.apache.sis.measure.Longitude;
import org.apache.sis.measure.Latitude;
import org.apache.sis.measure.Units;
import static org.apache.sis.metadata.privy.ReferencingServices.AUTHALIC_RADIUS;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import static org.opengis.referencing.ObjectDomain.DOMAIN_OF_VALIDITY_KEY;
/**
* Definitions of referencing objects identified by the {@link CommonCRS} enumeration values.
* This class is used only as a fallback if the objects cannot be fetched from the EPSG database.
* This class should not be loaded when a connection to an EPSG geodetic dataset is available.
*
* <p>This class uses data available in public sources, with all EPSG metadata omitted except the identifiers.
* The EPSG identifiers are provided as references where to find the complete definitions.</p>
*
* @author Martin Desruisseaux (Geomatys)
*/
final class StandardDefinitions {
/**
* The EPSG database version that most closely match the fallback CRS.
* We refer to latest version of the 9.x series instead of more recent
* data because the fallback CRS does not use datum ensemble.
*/
static final String VERSION = "9.9.1";
/**
* The EPSG code for Greenwich meridian.
*
* @see org.apache.sis.util.privy.Constants#EPSG_GREENWICH
*/
static final String GREENWICH = "8901";
/**
* Notice about the provenance of those data.
* This is provided as a small clarification because EPSG data should be licensed under EPSG Terms of Use.
* The approach in this class is to use only the data that are available from public sources,
* and to add only the EPSG codes as citation references. The notice text is:
*
* <blockquote>Definitions from public sources. When a definition corresponds to an EPSG object (ignoring metadata),
* the EPSG code is provided as a reference where to find the complete definition.</blockquote>
*/
private static final InternationalString NOTICE =
Resources.formatInternational(Resources.Keys.FallbackAuthorityNotice);
/**
* The authority for this subset of EPSG database.
*/
static final Citation AUTHORITY;
static {
final DefaultCitation c = new DefaultCitation();
c.setTitle(Vocabulary.formatInternational(Vocabulary.Keys.SubsetOf_1, Constants.EPSG));
c.setEdition(new SimpleInternationalString(StandardDefinitions.VERSION));
c.getPresentationForms().add(PresentationForm.DOCUMENT_DIGITAL);
c.getOtherCitationDetails().add(NOTICE);
c.transitionTo(DefaultCitation.State.FINAL);
AUTHORITY = c;
}
/**
* Do not allow instantiation of this class.
*/
private StandardDefinitions() {
}
/**
* Returns a map of properties for the given EPSG code, name and alias.
*
* @param code the EPSG code, or 0 if none.
* @param name the object name.
* @param alias the alias, or {@code null} if none.
* @param world {@code true} if the properties shall have an entry for the domain of validity.
* @return the map of properties to give to constructors or factory methods.
*/
private static Map<String,Object> properties(final int code, final String name, final String alias, final boolean world) {
final Map<String,Object> map = new HashMap<>(8);
if (code != 0) {
map.put(IDENTIFIERS_KEY, new NamedIdentifier(AUTHORITY, Constants.EPSG, String.valueOf(code), VERSION, null));
}
map.put(NAME_KEY, new NamedIdentifier(AUTHORITY, null, name, null, null));
if (alias != null) {
map.put(ALIAS_KEY, alias);
}
if (world) {
map.put(DOMAIN_OF_VALIDITY_KEY, Extents.WORLD);
}
map.put(REMARKS_KEY, NOTICE);
return map;
}
/**
* Adds to the given properties an additional identifier in the {@code "CRS"} namespace.
* This method presumes that the only identifier that existed before this method call was the EPSG one.
*/
private static void addWMS(final Map<String,Object> properties, final String code) {
properties.put(IDENTIFIERS_KEY, new NamedIdentifier[] {
(NamedIdentifier) properties.get(IDENTIFIERS_KEY),
new NamedIdentifier(Citations.WMS, code)
});
}
/**
* Creates a Universal Transverse Mercator (UTM) or a Universal Polar Stereographic (UPS) projected CRS
* using the Apache SIS factory implementation. This method restricts the factory to SIS implementation
* instead of arbitrary factory in order to met the contract saying that {@link CommonCRS} methods
* should never fail.
*
* @param code the EPSG code, or 0 if none.
* @param baseCRS the geographic CRS on which the projected CRS is based.
* @param isUTM {@code true} for UTM or {@code false} for UPS. Note: redundant with the given latitude.
* @param latitude a latitude in the zone of the desired projection, to be snapped to 0°, 90°S or 90°N.
* @param longitude a longitude in the zone of the desired projection, to be snapped to UTM central meridian.
* @param derivedCS the projected coordinate system.
*/
static ProjectedCRS createUniversal(final int code, final GeographicCRS baseCRS, final boolean isUTM,
final double latitude, final double longitude, final CartesianCS derivedCS)
{
final OperationMethod method;
try {
method = DefaultMathTransformFactory.provider()
.getOperationMethod(isUTM ? TransverseMercator.NAME : PolarStereographicA.NAME);
} catch (NoSuchIdentifierException e) {
throw new IllegalStateException(e); // Should not happen with SIS implementation.
}
final ParameterValueGroup parameters = method.getParameters().createValue();
String name = isUTM ? TransverseMercator.Zoner.UTM.setParameters(parameters, latitude, longitude)
: PolarStereographicA.setParameters(parameters, latitude >= 0);
final DefaultConversion conversion = new DefaultConversion(properties(0, name, null, false), method, null, parameters);
name = baseCRS.getName().getCode() + " / " + name;
return new DefaultProjectedCRS(properties(code, name, null, false), baseCRS, conversion, derivedCS);
}
/**
* Creates a geodetic CRS from hard-coded values for the given code.
*
* @param code the EPSG code.
* @param datum the geodetic reference frame.
* @param cs the coordinate system.
* @return the geographic CRS for the given code.
*/
static GeographicCRS createGeographicCRS(final short code, final GeodeticDatum datum, final EllipsoidalCS cs) {
final String name;
boolean world = false;
switch (code) {
case 4326: name = "WGS 84"; world = true; break;
case 4322: name = "WGS 72"; world = true; break;
case 4258: name = "ETRS89"; break;
case 4269: name = "NAD83"; break;
case 4267: name = "NAD27"; break;
case 4230: name = "ED50"; break;
case 4019: name = "Unknown datum based upon the GRS 1980 ellipsoid"; world = true; break;
case 4047: name = "Unspecified datum based upon the GRS 1980 Authalic Sphere"; world = true; break;
default: throw new AssertionError(code);
}
final Map<String, Object> properties = properties(code, name, null, world);
return new DefaultGeographicCRS(properties, datum, cs);
}
/**
* Creates a geodetic reference frame from hard-coded values for the given code.
*
* @param code the EPSG code.
* @param ellipsoid the datum ellipsoid.
* @param meridian the datum prime meridian.
* @return the geodetic reference frame for the given code.
*/
static GeodeticDatum createGeodeticDatum(final short code, final Ellipsoid ellipsoid, final PrimeMeridian meridian) {
final String name;
final String alias;
boolean world = false;
switch (code) {
case 6326: name = "World Geodetic System 1984"; alias = "WGS 84"; world = true; break;
case 6322: name = "World Geodetic System 1972"; alias = "WGS 72"; world = true; break;
case 6258: name = "European Terrestrial Reference System 1989"; alias = "ETRS89"; break;
case 6269: name = "North American Datum 1983"; alias = "NAD83"; break;
case 6267: name = "North American Datum 1927"; alias = "NAD27"; break;
case 6230: name = "European Datum 1950"; alias = "ED50"; break;
case 6019: name = "Not specified (based on GRS 1980 ellipsoid)"; alias = null; world = true; break;
case 6047: name = "Not specified (based on GRS 1980 Authalic Sphere)"; alias = null; world = true; break;
default: throw new AssertionError(code);
}
return new DefaultGeodeticDatum(properties(code, name, alias, world), ellipsoid, meridian);
}
/**
* Creates an ellipsoid from hard-coded values for the given code.
*
* @param code the EPSG code.
* @return the ellipsoid for the given code.
*/
static Ellipsoid createEllipsoid(final short code) {
String name; // No default value
String alias = null;
double semiMajorAxis; // No default value
double other; // No default value
boolean ivfDefinitive = true;
Unit<Length> unit = Units.METRE;
switch (code) {
case 7030: name = "WGS 1984"; semiMajorAxis = 6378137.0; other = 298.257223563; break;
case 7043: name = "WGS 1972"; semiMajorAxis = 6378135.0; other = 298.26; break;
case 7019: name = "GRS 1980"; semiMajorAxis = 6378137.0; other = 298.257222101; break;
case 7022: name = "International 1924"; semiMajorAxis = 6378388.0; other = 297.0; break;
case 7008: name = "Clarke 1866"; semiMajorAxis = 6378206.4; other = 6356583.8; ivfDefinitive = false; break;
case 7048: name = "GRS 1980 Authalic Sphere"; semiMajorAxis = other = AUTHALIC_RADIUS; ivfDefinitive = false; break;
default: throw new AssertionError(code);
}
final Map<String,Object> map = properties(code, name, alias, false);
if (ivfDefinitive) {
return DefaultEllipsoid.createFlattenedSphere(map, semiMajorAxis, other, unit);
} else {
return DefaultEllipsoid.createEllipsoid(map, semiMajorAxis, other, unit);
}
}
/**
* Creates the Greenwich prime meridian. This is the only prime meridian supported by SIS convenience shortcuts.
* If another prime meridian is desired, the EPSG database shall be used.
*/
static PrimeMeridian primeMeridian() {
final Map<String,Object> properties = new HashMap<>(4);
properties.put(NAME_KEY, new NamedIdentifier(AUTHORITY, "Greenwich")); // Name is fixed by ISO 19111.
properties.put(IDENTIFIERS_KEY, new NamedIdentifier(AUTHORITY, GREENWICH));
return new DefaultPrimeMeridian(properties, 0, Units.DEGREE);
}
/**
* Creates a vertical CRS from hard-coded values for the given code.
*
* @param code the EPSG code.
* @param datum the vertical datum.
* @return the vertical CRS for the given code.
*/
static VerticalCRS createVerticalCRS(final short code, final VerticalDatum datum) {
String csName = "Vertical CS. Axis: height (H)."; // Default coordinate system
short csCode = 6499; // EPSG code of above coordinate system.
short axis = 114; // Axis of above coordinate system.
String wms = null;
final String name, alias;
switch (code) {
case 5703: wms = "88";
name = "NAVD88 height";
alias = "North American Vertical Datum 1988 height";
break;
case 5714: name = "MSL height";
alias = "Mean Sea Level height";
break;
case 5715: name = "MSL depth";
alias = "Mean Sea Level depth";
csName = "Vertical CS. Axis: depth (D).";
csCode = 6498;
axis = 113;
break;
default: throw new AssertionError(code);
}
Map<String,Object> properties = properties(csCode, csName, null, false);
final DefaultVerticalCS cs = new DefaultVerticalCS(properties, createAxis(axis, true));
properties = properties(code, name, alias, true);
if (wms != null) {
addWMS(properties, wms);
}
return new DefaultVerticalCRS(properties, datum, cs);
}
/**
* Creates a vertical datum from hard-coded values for the given code.
*
* @param code the EPSG code.
* @return the vertical datum for the given code.
*/
static VerticalDatum createVerticalDatum(final short code) {
final String name;
final String alias;
switch (code) {
case 5100: name = "Mean Sea Level"; alias = "MSL"; break;
case 5103: name = "North American Vertical Datum 1988"; alias = "NAVD88"; break;
default: throw new AssertionError(code);
}
return new DefaultVerticalDatum(properties(code, name, alias, true), null);
}
/**
* EPSG codes of coordinate systems supported by this class. We provide constants only for
* coordinate systems because those codes appear directly in method bodies, contrarily to
* other kinds of object where the code are stored in {@link CommonCRS} fields.
*/
static final short ELLIPSOIDAL_2D = (short) 6422,
ELLIPSOIDAL_3D = (short) 6423,
SPHERICAL = (short) 6404,
EARTH_CENTRED = (short) 6500,
CARTESIAN_2D = (short) 4400,
UPS_NORTH = (short) 1026,
UPS_SOUTH = (short) 1027;
/**
* Creates a coordinate system from hard-coded values for the given code.
* The coordinate system names used by this method contains only the first
* part of the names declared in the EPSG database.
*
* @param code the EPSG code.
* @param mandatory whether to fail or return {@code null} if the given code is unknown.
* @return the coordinate system for the given code.
*/
@SuppressWarnings("fallthrough")
static CoordinateSystem createCoordinateSystem(final short code, final boolean mandatory) {
final String name;
int type = 0; // 0= Cartesian (default), 1= Spherical, 2= Ellipsoidal
int dim = 2; // Number of dimension, default to 2.
short axisCode; // Code of first axis + dim (or code after the last axis).
switch (code) {
case ELLIPSOIDAL_2D: name = "Ellipsoidal 2D"; type = 2; axisCode = 108; break;
case ELLIPSOIDAL_3D: name = "Ellipsoidal 3D"; type = 2; dim = 3; axisCode = 111; break;
case SPHERICAL: name = "Spherical"; type = 1; dim = 3; axisCode = 63; break;
case EARTH_CENTRED: name = "Cartesian 3D (geocentric)"; dim = 3; axisCode = 118; break;
case CARTESIAN_2D: name = "Cartesian 2D"; axisCode = 3; break;
case UPS_NORTH: name = "Cartesian 2D for UPS north"; axisCode = 1067; break;
case UPS_SOUTH: name = "Cartesian 2D for UPS south"; axisCode = 1059; break;
default: if (!mandatory) return null;
throw new AssertionError(code);
}
final Map<String,?> properties = properties(code, name, null, false);
CoordinateSystemAxis xAxis = null, yAxis = null, zAxis = null;
switch (dim) {
default: throw new AssertionError(dim);
case 3: zAxis = createAxis(--axisCode, true);
case 2: yAxis = createAxis(--axisCode, true);
case 1: xAxis = createAxis(--axisCode, true);
case 0: break;
}
switch (type) {
default: throw new AssertionError(type);
case 0: return (zAxis != null) ? new DefaultCartesianCS (properties, xAxis, yAxis, zAxis)
: new DefaultCartesianCS (properties, xAxis, yAxis);
case 1: return new DefaultSphericalCS (properties, xAxis, yAxis, zAxis);
case 2: return (zAxis != null) ? new DefaultEllipsoidalCS(properties, xAxis, yAxis, zAxis)
: new DefaultEllipsoidalCS(properties, xAxis, yAxis);
}
}
/**
* Creates an axis from hard-coded values for the given code.
*
* @param code the EPSG code.
* @param mandatory whether to fail or return {@code null} if the given code is unknown.
* @return the coordinate system axis for the given code.
*/
static CoordinateSystemAxis createAxis(final short code, final boolean mandatory) {
final String name, abrv;
Unit<?> unit = Units.METRE;
double min = Double.NEGATIVE_INFINITY;
double max = Double.POSITIVE_INFINITY;
RangeMeaning rm = null;
final AxisDirection dir;
switch (code) {
case 1: name = "Easting";
abrv = "E";
dir = AxisDirection.EAST;
break;
case 2: name = "Northing";
abrv = "N";
dir = AxisDirection.NORTH;
break;
case 60: name = "Spherical latitude";
abrv = "Ω"; // See HardCodedAxes.SPHERICAL_LATITUDE in tests.
unit = Units.DEGREE;
dir = AxisDirection.NORTH;
min = Latitude.MIN_VALUE;
max = Latitude.MAX_VALUE;
rm = RangeMeaning.EXACT;
break;
case 61: name = "Spherical longitude";
abrv = "θ"; // See HardCodedAxes.SPHERICAL_LONGITUDE in tests.
unit = Units.DEGREE;
dir = AxisDirection.EAST;
min = Longitude.MIN_VALUE;
max = Longitude.MAX_VALUE;
rm = RangeMeaning.WRAPAROUND;
break;
case 62: name = "Geocentric radius";
abrv = "r"; // See HardCodedAxes.GEOCENTRIC_RADIUS in tests.
dir = AxisDirection.UP;
rm = RangeMeaning.EXACT;
min = 0;
break;
case 108: // Used in Ellipsoidal 3D.
case 106: name = AxisNames.GEODETIC_LATITUDE;
abrv = "φ";
unit = Units.DEGREE;
dir = AxisDirection.NORTH;
min = Latitude.MIN_VALUE;
max = Latitude.MAX_VALUE;
rm = RangeMeaning.EXACT;
break;
case 109: // Used in Ellipsoidal 3D.
case 107: name = AxisNames.GEODETIC_LONGITUDE;
abrv = "λ";
unit = Units.DEGREE;
dir = AxisDirection.EAST;
min = Longitude.MIN_VALUE;
max = Longitude.MAX_VALUE;
rm = RangeMeaning.WRAPAROUND;
break;
case 110: name = AxisNames.ELLIPSOIDAL_HEIGHT;
abrv = "h";
dir = AxisDirection.UP;
break;
case 114: name = AxisNames.GRAVITY_RELATED_HEIGHT;
abrv = "H";
dir = AxisDirection.UP;
break;
case 113: name = AxisNames.DEPTH;
abrv = "D";
dir = AxisDirection.DOWN;
break;
case 115: name = AxisNames.GEOCENTRIC_X;
abrv = "X";
dir = AxisDirection.GEOCENTRIC_X;
break;
case 116: name = AxisNames.GEOCENTRIC_Y;
abrv = "Y";
dir = AxisDirection.GEOCENTRIC_Y;
break;
case 117: name = AxisNames.GEOCENTRIC_Z;
abrv = "Z";
dir = AxisDirection.GEOCENTRIC_Z;
break;
case 1057: // Actually no axis allocated by EPSG here, but createCoordinateSystem(1027) needs this number.
case 1056: name = "Easting";
abrv = "E";
dir = CoordinateSystems.directionAlongMeridian(AxisDirection.NORTH, 90);
break;
case 1058: name = "Northing";
abrv = "N";
dir = CoordinateSystems.directionAlongMeridian(AxisDirection.NORTH, 0);
break;
case 1065: name = "Easting";
abrv = "E";
dir = CoordinateSystems.directionAlongMeridian(AxisDirection.SOUTH, 90);
break;
case 1066: name = "Northing";
abrv = "N";
dir = CoordinateSystems.directionAlongMeridian(AxisDirection.SOUTH, 180);
break;
default: if (!mandatory) return null;
throw new AssertionError(code);
}
final Map<String,Object> properties = properties(code, name, null, false);
properties.put(DefaultCoordinateSystemAxis.MINIMUM_VALUE_KEY, min);
properties.put(DefaultCoordinateSystemAxis.MAXIMUM_VALUE_KEY, max);
properties.put(DefaultCoordinateSystemAxis.RANGE_MEANING_KEY, rm);
return new DefaultCoordinateSystemAxis(properties, abrv, dir, unit);
}
}