Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id DBBD0200BD1 for ; Mon, 28 Nov 2016 11:52:32 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id DA4C6160B0D; Mon, 28 Nov 2016 10:52:32 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 180B6160B06 for ; Mon, 28 Nov 2016 11:52:30 +0100 (CET) Received: (qmail 5427 invoked by uid 500); 28 Nov 2016 10:52:30 -0000 Mailing-List: contact commits-help@sis.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: sis-dev@sis.apache.org Delivered-To: mailing list commits@sis.apache.org Received: (qmail 5412 invoked by uid 99); 28 Nov 2016 10:52:30 -0000 Received: from pnap-us-west-generic-nat.apache.org (HELO spamd1-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 28 Nov 2016 10:52:30 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd1-us-west.apache.org (ASF Mail Server at spamd1-us-west.apache.org) with ESMTP id A4108D5C35 for ; Mon, 28 Nov 2016 10:52:29 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd1-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: -1.198 X-Spam-Level: X-Spam-Status: No, score=-1.198 tagged_above=-999 required=6.31 tests=[KAM_ASCII_DIVIDERS=0.8, KAM_LAZY_DOMAIN_SECURITY=1, RP_MATCHES_RCVD=-2.999, URIBL_BLOCKED=0.001] autolearn=disabled Received: from mx1-lw-eu.apache.org ([10.40.0.8]) by localhost (spamd1-us-west.apache.org [10.40.0.7]) (amavisd-new, port 10024) with ESMTP id 3zpgWRr5NoOb for ; Mon, 28 Nov 2016 10:52:26 +0000 (UTC) Received: from mailrelay1-us-west.apache.org (mailrelay1-us-west.apache.org [209.188.14.139]) by mx1-lw-eu.apache.org (ASF Mail Server at mx1-lw-eu.apache.org) with ESMTP id 538625FB70 for ; Mon, 28 Nov 2016 10:52:25 +0000 (UTC) Received: from svn01-us-west.apache.org (svn.apache.org [10.41.0.6]) by mailrelay1-us-west.apache.org (ASF Mail Server at mailrelay1-us-west.apache.org) with ESMTP id 634E7E0480 for ; Mon, 28 Nov 2016 10:52:24 +0000 (UTC) Received: from svn01-us-west.apache.org (localhost [127.0.0.1]) by svn01-us-west.apache.org (ASF Mail Server at svn01-us-west.apache.org) with ESMTP id DB2A03A0254 for ; Mon, 28 Nov 2016 10:52:23 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: svn commit: r1771707 - in /sis/branches/JDK8: core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/ storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ Date: Mon, 28 Nov 2016 10:52:23 -0000 To: commits@sis.apache.org From: desruisseaux@apache.org X-Mailer: svnmailer-1.0.9 Message-Id: <20161128105223.DB2A03A0254@svn01-us-west.apache.org> archived-at: Mon, 28 Nov 2016 10:52:33 -0000 Author: desruisseaux Date: Mon Nov 28 10:52:22 2016 New Revision: 1771707 URL: http://svn.apache.org/viewvc?rev=1771707&view=rev Log: Cleanup the creation of GeodeticDatum in GeoTIFF files. Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGridSpatialRepresentation.java sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CRSBuilder.java sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoCodes.java sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoKeys.java sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGridSpatialRepresentation.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGridSpatialRepresentation.java?rev=1771707&r1=1771706&r2=1771707&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGridSpatialRepresentation.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGridSpatialRepresentation.java [UTF-8] Mon Nov 28 10:52:22 2016 @@ -32,7 +32,7 @@ import static org.apache.sis.internal.me /** - * Basic information required to uniquely identify a resource or resources. + * Method used to represent geographic information in the dataset. * *

Limitations:

*
    Modified: sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CRSBuilder.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CRSBuilder.java?rev=1771707&r1=1771706&r2=1771707&view=diff ============================================================================== --- sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CRSBuilder.java [UTF-8] (original) +++ sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CRSBuilder.java [UTF-8] Mon Nov 28 10:52:22 2016 @@ -16,14 +16,20 @@ */ package org.apache.sis.storage.geotiff; -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; import java.util.Map; +import java.util.HashMap; +import java.util.Collections; +import java.util.StringJoiner; import java.util.logging.Level; import java.util.logging.LogRecord; +import java.util.NoSuchElementException; +import java.lang.reflect.Array; import javax.measure.Unit; +import javax.measure.Quantity; +import javax.measure.quantity.Angle; +import javax.measure.quantity.Length; +import org.opengis.metadata.Identifier; import org.opengis.metadata.spatial.CellGeometry; import org.opengis.metadata.spatial.PixelOrientation; import org.opengis.parameter.ParameterValueGroup; @@ -48,9 +54,11 @@ import org.opengis.util.FactoryException import org.opengis.util.NoSuchIdentifierException; import org.apache.sis.internal.geotiff.Resources; +import org.apache.sis.internal.referencing.NilReferencingObject; import org.apache.sis.internal.storage.MetadataBuilder; import org.apache.sis.internal.system.DefaultFactories; import org.apache.sis.internal.util.Constants; +import org.apache.sis.internal.util.Utilities; import org.apache.sis.math.Vector; import org.apache.sis.measure.Units; import org.apache.sis.metadata.iso.citation.DefaultCitation; @@ -61,6 +69,9 @@ import org.apache.sis.referencing.cs.Coo import org.apache.sis.referencing.factory.GeodeticAuthorityFactory; import org.apache.sis.referencing.factory.GeodeticObjectFactory; import org.apache.sis.storage.DataStoreContentException; +import org.apache.sis.util.Characters; + +import static org.apache.sis.util.Utilities.equalsIgnoreMetadata; /** @@ -169,6 +180,13 @@ final class CRSBuilder { private CoordinateOperationFactory operationFactory; /** + * Name of the last object created. This is used by {@link #properties(String)} for reusing existing instance + * if possible. This is useful in GeoTIFF file since they do not use different names for geographic CRS, + * the datum and the ellipsoid. + */ + private Identifier lastName; + + /** * Creates a new builder of coordinate reference systems. * * @param reader where to report warnings if any. @@ -233,27 +251,55 @@ final class CRSBuilder { * Returns a map with the given name associated to the {@value org.opengis.referencing.IdentifiedObject#NAME_KEY} key. * This is an helper method for creating geodetic objects with {@link #objectFactory}. */ - private static Map name(final String name) { - return Collections.singletonMap(IdentifiedObject.NAME_KEY, name); + private Map properties(final String name) { + Object value = name; + if (name == null) { + value = NilReferencingObject.UNNAMED; + } else if (lastName != null && lastName.getCode().equals(name)) { + value = lastName; + } + return Collections.singletonMap(IdentifiedObject.NAME_KEY, value); + } + + /** + * Returns the value for the given key as a singleton (not an array). + * If the value was an array, a warning is reported and the first element is returned. + * + * @param key the GeoTIFF key for which to get a value. + * @return the singleton value for the given key, or {@code null} if none. + */ + private Object getSingleton(final short key) { + Object value = geoKeys.get(key); + if (value != null && value.getClass().isArray()) { + warning(Resources.Keys.UnexpectedListOfValues_2, GeoKeys.name(key), Array.getLength(value)); + value = Array.get(value, 0); // No need to verify length because we do not store empty arrays. + } + return value; } /** * Returns a {@link GeoKeys} value as a character string, or {@code null} if none. + * Value for the given key should be a sequence of characters. If it is one or more numbers instead, + * then this method formats those numbers in a comma-separated list. Such sequence of numbers would + * be unusual, but we do see strange GeoTIFF files in practice. * - * @param key the GeoTIFF key for which to get a value. - * @param mandatory whether a value is mandatory for the given key. + * @param key the GeoTIFF key for which to get a value. * @return a string representation of the value for the given key, or {@code null} if the key was not found. - * @throws DataStoreContentException if a value for the given key is mandatory but no value has been found. */ - private String getAsString(final short key, final boolean mandatory) throws DataStoreContentException { - final Object value = geoKeys.get(key); + private String getAsString(final short key) { + Object value = geoKeys.get(key); if (value != null) { + if (value.getClass().isArray()) { + final int length = Array.getLength(value); + final StringJoiner buffer = new StringJoiner(", "); + for (int i=0; iPre-requite:

    + *
      + *
    • {@link #build(Vector, Vector, String)} must have been invoked before this method.
    • + *
    • {@link ImageFileDirectory} must have filled its part of metadata before to invoke this method.
    • + *
    + * * @param metadata the helper class where to write metadata values. + * @throws NumberFormatException if a numeric value was stored as a string and can not be parsed. */ final void complete(final MetadataBuilder metadata) { /* * Whether the pixel value is thought of as filling the cell area or is considered as point measurements at * the vertices of the grid (not in the interior of a cell). This is determined by the value associated to - * GeoKeys.RasterType, which can be GeoCodes.RasterPixelIsArea or RasterPixelIsPoint. + * GeoKeys.RasterType, which can be GeoCodes.RasterPixelIsArea or GeoCodes.RasterPixelIsPoint. */ CellGeometry cg = null; PixelOrientation po = null; @@ -514,208 +599,210 @@ final class CRSBuilder { } metadata.setCellGeometry(cg); metadata.setPointInPixel(po); + /* + * ASCII reference to published documentation on the overall configuration of the GeoTIFF file. + */ + final String title = getAsString(GeoKeys.Citation); + if (title != null) { + if (!metadata.hasTitle()) { + metadata.addTitle(title); + } else { + metadata.setGridToCRS(title); + } + } } - //-------------------------- geodetic components --------------------------- + // -------------------------- geodetic components --------------------------- /** - * Creates units of measurement from the - * ProjLinearUnits and the - * ProjLinearUnitSize. The unit may either be - * specified as a standard EPSG recognized unit, or may be user defined. - * - * @param key - * @param userDefinedKey - * @param base - * @param def - * @return Unit object representative of the tags in the file. - * @throws IOException - * if theProjLinearUnits is not specified - * or if unit is user defined and - * ProjLinearUnitSize is either not defined - * or does not contain a number. + * Creates units of measurement for projected or geographic coordinate reference systems. + * The units may either be specified as a standard EPSG recognized unit, or may be user defined. + * If the first case (EPSG code), the {@code keyUser} is ignored. In the second case (user-defined), + * the unit of measurement is defined by a conversion factor from metre or radian base unit. + * + * @param keyEPSG the {@link GeoKeys} for a unit of measurement defined by an EPSG code. + * @param keyUser the {@link GeoKeys} for a unit of measurement defined by a scale applied on a base unit. + * @param quantity {@link Length} for a linear unit, or {@link Angle} for an angular unit. + * @param defaultValue the unit of measurement to return if no value is found in the GeoTIFF file. + * @return the unit of measurement associated to the given {@link GeoKeys}, or the default value. + * + * @throws NoSuchElementException if {@code keyEPSG} value is {@link GeoCodes#userDefined} and no value is associated to {@code keyUser}. + * @throws NumberFormatException if a numeric value was stored as a string and can not be parsed. + * @throws ClassCastException if the unit of measurement identified by the EPSG code is not of the expected quantity. */ - private Unit createUnit(final short key, final short userDefinedKey, final Unit base, final Unit def) - throws FactoryException, DataStoreContentException + private > Unit createUnit(final short keyEPSG, final short keyUser, + final Class quantity, final Unit defaultValue) throws FactoryException { - String unitCode = getAsString(key, false); - - // If not defined, return the default unit of measure. - if (unitCode == null) { - return def; - } - /* - * If specified, retrieve the appropriate unit code. There is two cases to take into account: - * First case is when the unit of measure has an EPSG code, second case is when it can be - * instantiated as a conversion from meter. - */ - if (unitCode.equals(GeoKeys.GTUserDefined_String)) { - return base.multiply(getAsDouble(userDefinedKey, true)); + final int epsg = getAsInteger(keyEPSG); + switch (epsg) { + case GeoCodes.undefined: { + return defaultValue; + } + case GeoCodes.userDefined: { + return defaultValue.getSystemUnit().multiply(getMandatoryDouble(keyUser)); + } + default: { + return epsgFactory().createUnit(String.valueOf(epsg)).asType(quantity); + } } - - //-- using epsg code for this unit - return epsgFactory().createUnit(unitCode); } /** - * Creating a Geodetic Datum for the {@link #createUserDefinedGCRS(javax.measure.Unit, javax.measure.Unit) } method - * we are creating at an higher level.
    - * As usual this method tries to follow the geotiff specification
    - * Needed tags are : + * Creates a geodetic datum from an EPSG code or from user-defined parameters. + * The GeoTIFF values used by this method are: + * *
      - *
    • a code definition given by {@link GeoKeys.GeogGeodeticDatumGeoKey} tag
    • - *
    • a name given by {@link GeoKeys.GeogCitationGeoKey}
    • - *
    • required prime meridian tiff tags
    • - *
    • required ellipsoid tiff tags
    • + *
    • a code given by {@link GeoKeys#GeogGeodeticDatum}
    • + *
    • a name given by {@link GeoKeys#GeogCitation}
    • + *
    • all values required by {@link #createPrimeMeridian(Unit)}
    • + *
    • all values required by {@link #createEllipsoid(Unit)}
    • *
    * - * @param unit to use for building this {@link GeodeticDatum}. - * @return a {@link GeodeticDatum}. - * @throws DataStoreContentException if datum code from relative tiff tag is missing ({@code null}). - * @throws FactoryException if factory problem during Datum creation. - * @see #createPrimeMeridian(javax.measure.Unit) - * @see #createEllipsoid(javax.measure.Unit) + * @param name the name to use if the geodetic datum is user-defined, or {@code null} if unnamed. + * @param angularUnit the angular unit of the longitude value relative to Greenwich. + * @param linearUnit the linear unit of the ellipsoid semi-axis lengths. + * + * @throws NoSuchElementException if a mandatory value is missing. + * @throws NumberFormatException if a numeric value was stored as a string and can not be parsed. + * @throws ClassCastException if an object defined by an EPSG code is not of the expected type. + * @throws FactoryException if an error occurred during objects creation with the factories. + * + * @see #createPrimeMeridian(Unit) + * @see #createEllipsoid(Unit) */ - private GeodeticDatum createGeodeticDatum(final Unit unit) - throws DataStoreContentException, FactoryException { - - // lookup the datum (w/o PrimeMeridian). - String datumCode = getAsString(GeoKeys.GeogGeodeticDatum, true); - - //-- Geodetic Datum define as an EPSG code. - if (!datumCode.equals(GeoKeys.GTUserDefined_String)) - return epsgFactory().createGeodeticDatum(String.valueOf(datumCode)); - - //-- USER DEFINE Geodetic Datum creation - { - //-- Datum name - assert datumCode.equals(GeoKeys.GTUserDefined_String); - String datumName = getAsString(GeoKeys.GeogCitation, false); - if (datumName == null) { - datumName = "Unamed User Defined Geodetic Datum"; + private GeodeticDatum createGeodeticDatum(String name, final Unit angularUnit, final Unit linearUnit) + throws FactoryException + { + final int epsg = getAsInteger(GeoKeys.GeogGeodeticDatum); + switch (epsg) { + case GeoCodes.undefined: { + throw new NoSuchElementException(missingValue(GeoKeys.GeogGeodeticDatum)); + } + default: { + // Geodetic Datum defined by an EPSG code. + return epsgFactory().createGeodeticDatum(String.valueOf(epsg)); + } + case GeoCodes.userDefined: { + /* + * Create the ellipsoid and the prime meridian, then assemble those components into a datum. + * The datum name however may not be appropriate, since GeoTIFF provides only a global name + * for the whole CRS. The name does not matter so much, except for datums where the name is + * taken in account when determining if two datums are equal. In order to get better names, + * after datum construction we will compare the datum with a few well-known cases defined in + * CommonCRS. We use the CRS name given in the GeoTIFF file for that purpose, exploiting the + * fact that it is often a name that can be mapped to a CommonCRS name like "WGS84". + */ + final Ellipsoid ellipsoid = createEllipsoid(name, linearUnit); + final PrimeMeridian meridian = createPrimeMeridian(angularUnit); + final GeodeticDatum datum = objectFactory().createGeodeticDatum(properties(name), ellipsoid, meridian); + name = Utilities.toUpperCase(name, Characters.Filter.LETTERS_AND_DIGITS); + lastName = datum.getName(); + try { + final GeodeticDatum predefined = CommonCRS.valueOf(name).datum(); + if (equalsIgnoreMetadata(predefined.getEllipsoid(), ellipsoid) && + equalsIgnoreMetadata(predefined.getPrimeMeridian(), meridian)) + { + return predefined; + } + } catch (IllegalArgumentException e) { + // Not a name that can be mapped to CommonCRS. Ignore. + } + return datum; } - - //-- particularity case - if (datumName.equalsIgnoreCase("WGS84")) return CommonCRS.WGS84.datum(); - - //-- ELLIPSOID creation - final Ellipsoid ellipsoid = createEllipsoid(unit); - - //-- PRIME MERIDIAN - final PrimeMeridian primeMeridian = createPrimeMeridian(unit); - - //-- factory Datum creation - return objectFactory().createGeodeticDatum(name(datumName), ellipsoid, primeMeridian); } } /** - * Creating a prime meridian for the {@link #createGeodeticDatum(javax.measure.Unit) } method - * we are creating at an higher level.
    - * As usual this method tries to follow the geotiff specification
    - * Needed tags are : + * Creates the prime meridian from an EPSG code or from user-defined parameters. + * The GeoTIFF values used by this method are: + * *
      - *
    • a code definition given by {@link GeoKeys.GeogPrimeMeridianGeoKey} tag
    • - *
    • a name given by {@link GeoKeys.GeogCitationGeoKey}
    • - *
    • a prime meridian value given by {@link GeoKeys.GeogPrimeMeridianLongGeoKey}
    • + *
    • a code given by {@link GeoKeys#GeogPrimeMeridian}
    • + *
    • a prime meridian value given by {@link GeoKeys#GeogPrimeMeridianLong}
    • *
    * - * @param linearUnit use for building this {@link PrimeMeridian}. - * @return a {@link PrimeMeridian} built using the provided {@link Unit} and - * the provided metadata. - * @throws FactoryException if problem during factory Prime Meridian creation. + * If no prime-meridian is defined, then the default is Greenwich as per GeoTIFF specification. + * + * @param unit the angular unit of the longitude value relative to Greenwich. + * @return a prime meridian created from the given {@link Unit} and the above-cited GeoTIFF keys. + * + * @throws NumberFormatException if a numeric value was stored as a string and can not be parsed. + * @throws FactoryException if an error occurred during objects creation with the factories. */ - private PrimeMeridian createPrimeMeridian(final Unit linearUnit) throws DataStoreContentException, FactoryException { - //-- prime meridian : - //-- could be an EPSG code - //-- or could be user defined - //-- or not defined = greenwich - String pmCode = getAsString(GeoKeys.GeogPrimeMeridian, false); - - //-- if Prime Meridian code not define, assume WGS84 - if (pmCode == null) return CommonCRS.WGS84.primeMeridian(); - - //-- if Prime Meridian define as an EPSG code. - if (!pmCode.equals(GeoKeys.GTUserDefined_String)) { - return epsgFactory().createPrimeMeridian(String.valueOf(pmCode)); - } - //-- user define Prime Meridian creation - { - assert pmCode.equals(GeoKeys.GTUserDefined_String); - - double pmValue = getAsDouble(GeoKeys.GeogPrimeMeridianLong, false); - if (Double.isNaN(pmValue)) { - missingValue(GeoKeys.GeogPrimeMeridianLong); - pmValue = 0; + private PrimeMeridian createPrimeMeridian(final Unit unit) throws FactoryException { + final int epsg = getAsInteger(GeoKeys.GeogPrimeMeridian); + switch (epsg) { + case GeoCodes.undefined: break; // If not specified, default to Greenwich. + default: { // Prime Meridian defined by an EPSG code. + return epsgFactory().createPrimeMeridian(String.valueOf(epsg)); + } + case GeoCodes.userDefined: { + final double longitude = getAsDouble(GeoKeys.GeogPrimeMeridianLong); + if (Double.isNaN(longitude)) { + missingValue(GeoKeys.GeogPrimeMeridianLong); + } else if (longitude != 0) { + /* + * If the prime meridian is not Greenwich, create that meridian without name. + * We do not use the name given by GeoKeys.GeogCitation because that name is + * for the CRS (e.g. "WGS84") while the prime meridian names are very different + * (e.g. "Paris", "Madrid", etc). + */ + return objectFactory().createPrimeMeridian(properties(null), longitude, unit); + } } - - //-- if user define prime meridian is not define, assume WGS84 - if (pmValue == 0) return CommonCRS.WGS84.primeMeridian(); - - final String name = getAsString(GeoKeys.GeogCitation, false); - return objectFactory().createPrimeMeridian( - name((name == null) ? "User Defined GEOTIFF Prime Meridian" : name), pmValue, linearUnit); } + return CommonCRS.WGS84.primeMeridian(); } /** - * Creating an {@link Ellipsoid} following the GeoTiff spec.
    - * Creating a Ellipsoid for the {@link #createGeodeticDatum(javax.measure.Unit) } method - * we are creating at an higher level.
    - * As usual this method tries to follow the geotiff specification
    - * Needed tags are : + * Creates the ellipsoid from an EPSG code or from user-defined parameters. + * The GeoTIFF values used by this method are: + * *
      - *
    • a code definition given by {@link GeoKeys.GeogEllipsoidGeoKey} tag
    • - *
    • a name given by {@link GeoKeys.GeogCitationGeoKey}
    • - *
    • a semi major axis value given by {@link GeoKeys.GeogSemiMajorAxisGeoKey}
    • - *
    • a semi major axis value given by {@link GeoKeys.GeogInvFlatteningGeoKey}
    • - *
    • a semi major axis value given by {@link GeoKeys.GeogSemiMinorAxisGeoKey}
    • + *
    • a code given by {@link GeoKeys#GeogEllipsoid} tag
    • + *
    • a name given by {@link GeoKeys#GeogCitation}
    • + *
    • a semi major axis value given by {@link GeoKeys#GeogSemiMajorAxis}
    • + *
    • a semi major axis value given by {@link GeoKeys#GeogInvFlattening}
    • + *
    • a semi major axis value given by {@link GeoKeys#GeogSemiMinorAxis}
    • *
    * - * @param unit use for building this {@link Ellipsoid}. - * @return a {@link Ellipsoid} built using the provided {@link Unit} and - * the provided metadata. - * @throws FactoryException if problem during factory Prime Meridian creation. - * @throws DataStoreContentException if missing needed geokeys. + * @param name the name to use if the ellipsoid is user-defined, or {@code null} if unnamed. + * @param unit the linear unit of the semi-axis lengths. + * @return an ellipsoid created from the given {@link Unit} and the above-cited GeoTIFF keys. + * @throws NoSuchElementException if a mandatory value is missing. + * @throws NumberFormatException if a numeric value was stored as a string and can not be parsed. + * @throws FactoryException if an error occurred during objects creation with the factories. */ - private Ellipsoid createEllipsoid(final Unit unit) - throws FactoryException, DataStoreContentException { - - //-- ellipsoid key - final String ellipsoidKey = getAsString(GeoKeys.GeogEllipsoid, false); - - //-- if ellipsoid key NOT "user define" decode EPSG code. - if (ellipsoidKey != null && !ellipsoidKey.equalsIgnoreCase(GeoKeys.GTUserDefined_String)) - return epsgFactory().createEllipsoid(ellipsoidKey); - - //-- User define Ellipsoid creation - { - String nameEllipsoid = getAsString(GeoKeys.GeogCitation, false); - if (nameEllipsoid == null) { - nameEllipsoid = "User define unamed Ellipsoid"; - } - //-- particularity case - if (nameEllipsoid.equalsIgnoreCase("WGS84")) - return CommonCRS.WGS84.ellipsoid(); - - //-- try to build ellipsoid from others parameters. - //-- get semi Major axis and, semi minor or invertflattening - - //-- semi Major - final double semiMajorAxis = getAsDouble(GeoKeys.GeogSemiMajorAxis, true); - - //-- try to get inverseFlattening - double inverseFlattening = getAsDouble(GeoKeys.GeogInvFlattening, false); - if (Double.isNaN(inverseFlattening)) { - //-- get semi minor axis to build missing inverseFlattening - final double semiMinSTR = getAsDouble(GeoKeys.GeogSemiMinorAxis, true); - inverseFlattening = semiMajorAxis / (semiMajorAxis - semiMinSTR); + private Ellipsoid createEllipsoid(final String name, final Unit unit) throws FactoryException { + final int epsg = getAsInteger(GeoKeys.GeogEllipsoid); + switch (epsg) { + case GeoCodes.undefined: { + throw new NoSuchElementException(missingValue(GeoKeys.GeogGeodeticDatum)); + } + default: { + // Ellipsoid defined by an EPSG code. + return epsgFactory().createEllipsoid(String.valueOf(epsg)); + } + case GeoCodes.userDefined: { + /* + * Try to build ellipsoid from others parameters. Those parameters are the + * semi-major axis and either semi-minor axis or inverse flattening factor. + */ + final Map properties = properties(name); + final double semiMajor = getMandatoryDouble(GeoKeys.GeogSemiMajorAxis); + double inverseFlattening = getAsDouble(GeoKeys.GeogInvFlattening); + if (!Double.isNaN(inverseFlattening)) { + return objectFactory().createFlattenedSphere(properties, semiMajor, inverseFlattening, unit); + } + /* + * If the inverse flattening factory was not defined, fallback on semi-major axis length. + * This is a less common way to define ellipsoid (the most common way uses flattening). + */ + final double semiMinor = getMandatoryDouble(GeoKeys.GeogSemiMinorAxis); + return objectFactory().createEllipsoid(properties, semiMajor, semiMinor, unit); } - - //-- ellipsoid creation - return objectFactory().createFlattenedSphere(name(nameEllipsoid), semiMajorAxis, inverseFlattening, unit); } } @@ -744,7 +831,7 @@ final class CRSBuilder { * @param fallBackUnit * @return */ - private CartesianCS retrieveCartesianCS(final short unitKey, final CartesianCS baseCS, final Unit fallBackUnit) + private CartesianCS retrieveCartesianCS(final short unitKey, final CartesianCS baseCS, final Unit fallBackUnit) throws DataStoreContentException, FactoryException { assert baseCS.getDimension() == 2; @@ -779,7 +866,7 @@ final class CRSBuilder { } //-- get the Unit epsg code if exist - String unitCode = getAsString(unitKey, false); + String unitCode = getAsString(unitKey); if (unitCode == null || unitCode.equalsIgnoreCase(GeoKeys.GTUserDefined_String)) { return (CartesianCS) CoordinateSystems.replaceLinearUnit(baseCS, fallBackUnit); } @@ -926,7 +1013,7 @@ final class CRSBuilder { * @param fallBackUnit * @return */ - private EllipsoidalCS retrieveEllipsoidalCS(final short unitKey, final EllipsoidalCS baseCS, final Unit fallBackUnit) + private EllipsoidalCS retrieveEllipsoidalCS(final short unitKey, final EllipsoidalCS baseCS, final Unit fallBackUnit) throws DataStoreContentException, FactoryException { assert baseCS.getDimension() == 2; @@ -961,7 +1048,7 @@ final class CRSBuilder { } //-- get the Unit epsg code if exist - String unitCode = getAsString(unitKey, false); + String unitCode = getAsString(unitKey); if (unitCode == null || unitCode.equalsIgnoreCase(GeoKeys.GTUserDefined_String)) { return (EllipsoidalCS) CoordinateSystems.replaceAngularUnit(baseCS, fallBackUnit); } @@ -1065,11 +1152,10 @@ final class CRSBuilder { private CoordinateReferenceSystem createProjectedCRS() throws FactoryException, DataStoreContentException { - final String projCode = getAsString(GeoKeys.ProjectedCSType, false); + final String projCode = getAsString(GeoKeys.ProjectedCSType); //-- getting the linear unit used by this coordinate reference system. - final Unit linearUnit = createUnit(GeoKeys.ProjLinearUnits, - GeoKeys.ProjLinearUnitSize, Units.METRE, Units.METRE); + final Unit linearUnit = createUnit(GeoKeys.ProjLinearUnits, GeoKeys.ProjLinearUnitSize, Length.class, Units.METRE); //--------------------------- USER DEFINE -----------------------------// //-- if it's user defined, we have to parse many informations and @@ -1091,10 +1177,11 @@ final class CRSBuilder { //-- if 'tiff defined unit' does not match with decoded Projected CRS, build another converted projected CRS. if (linearUnit != null && !linearUnit.equals(pcrs.getCoordinateSystem().getAxis(0).getUnit())) { //-- Creating a new projected CRS - pcrs = objectFactory().createProjectedCRS(name(IdentifiedObjects.getName(pcrs, new DefaultCitation("EPSG"))), - (GeographicCRS) pcrs.getBaseCRS(), - pcrs.getConversionFromBase(), - retrieveCartesianCS(GeoKeys.ProjLinearUnits, pcrs.getCoordinateSystem(), linearUnit)); + pcrs = objectFactory().createProjectedCRS(properties(IdentifiedObjects.getName(pcrs, new DefaultCitation("EPSG"))), + pcrs.getBaseCRS(), + pcrs.getConversionFromBase(), + retrieveCartesianCS(GeoKeys.ProjLinearUnits, pcrs.getCoordinateSystem(), linearUnit)); + lastName = pcrs.getName(); } return pcrs; } @@ -1115,10 +1202,10 @@ final class CRSBuilder { * @throws DataStoreContentException if missing needed geoKey. * @throws FactoryException if problem during projected CRS factory build. */ - private ProjectedCRS createUserDefinedProjectedCRS(final Unit linearUnit) + private ProjectedCRS createUserDefinedProjectedCRS(final Unit linearUnit) throws FactoryException, DataStoreContentException { //-- get projected CRS Name - String projectedCrsName = getAsString(GeoKeys.PCSCitation, false); + String projectedCrsName = getAsString(GeoKeys.PCSCitation); if (projectedCrsName == null) { projectedCrsName = "User Defined unnamed ProjectedCRS"; } @@ -1128,17 +1215,18 @@ final class CRSBuilder { final GeographicCRS gcs = createGeographicCRS(); //-- get the projection code if exist - final String projCode = getAsString(GeoKeys.Projection, false); + final String projCode = getAsString(GeoKeys.Projection); //-- is it user defined? final Conversion projection; if (projCode == null || projCode.equals(GeoKeys.GTUserDefined_String)) { //-- get Operation Method from proj key - final String coordTrans = getAsString(GeoKeys.ProjCoordTrans, true); + final String coordTrans = getMandatoryString(GeoKeys.ProjCoordTrans); final OperationMethod operationMethod = operationFactory().getOperationMethod(coordTrans); final ParameterValueGroup parameters = operationMethod.getParameters().createValue(); - projection = operationFactory().createDefiningConversion(name(projectedCrsName), operationMethod, parameters); + projection = operationFactory().createDefiningConversion(properties(projectedCrsName), operationMethod, parameters); + lastName = projection.getName(); } else { projection = (Conversion) epsgFactory().createCoordinateOperation(String.valueOf(projCode)); } @@ -1148,7 +1236,7 @@ final class CRSBuilder { if (linearUnit != null && !linearUnit.equals(Units.METRE)) predefineCartesianCS = retrieveCartesianCS(GeoKeys.ProjLinearUnits, predefineCartesianCS, linearUnit); - return objectFactory().createProjectedCRS(name(projectedCrsName), gcs, projection, predefineCartesianCS); + return objectFactory().createProjectedCRS(properties(projectedCrsName), gcs, projection, predefineCartesianCS); } @@ -1182,30 +1270,24 @@ final class CRSBuilder { throws FactoryException, DataStoreContentException { //-- Get the crs code - final String tempCode = getAsString(GeoKeys.GeographicType, false); + final String tempCode = getAsString(GeoKeys.GeographicType); //-- Angular units used in this geotiff image - Unit angularUnit = createUnit(GeoKeys.GeogAngularUnits, - GeoKeys.GeogAngularUnitSize, Units.RADIAN, - Units.DEGREE); + Unit angularUnit = createUnit(GeoKeys.GeogAngularUnits, GeoKeys.GeogAngularUnitSize, Angle.class, Units.DEGREE); //-- Geographic CRS is "UserDefine", we have to parse many informations from other geokeys. if (tempCode == null || tempCode.equals(GeoKeys.GTUserDefined_String)) { - //-- linear unit - final Unit linearUnit = createUnit(GeoKeys.GeogLinearUnits, - GeoKeys.GeogLinearUnitSize, Units.METRE, - Units.METRE); + final Unit linearUnit = createUnit(GeoKeys.GeogLinearUnits, GeoKeys.GeogLinearUnitSize, Length.class, Units.METRE); ///-- Geographic CRS given name from tiff tag (GeogCitation) - String name = getAsString(GeoKeys.GeogCitation, false); - if (name == null) name = "User Define Geographic CRS"; + String name = getAsString(GeoKeys.GeogCitation); + if (name == null) name = "User-defined Geographic CRS"; - final GeodeticDatum datum = createGeodeticDatum(linearUnit); + final GeodeticDatum datum = createGeodeticDatum(name, angularUnit, linearUnit); //-- make the user defined GCS from all the components... - return objectFactory().createGeographicCRS(name(name), - datum, - retrieveEllipsoidalCS(GeoKeys.GeogAngularUnits, - CommonCRS.defaultGeographic().getCoordinateSystem(), - angularUnit)); + return objectFactory().createGeographicCRS(properties(name), datum, + retrieveEllipsoidalCS(GeoKeys.GeogAngularUnits, + CommonCRS.defaultGeographic().getCoordinateSystem(), + angularUnit)); // (EllipsoidalCS) CoordinateSystems.replaceAngularUnit(CommonCRS.defaultGeographic().getCoordinateSystem(), // angularUnit)); } @@ -1227,19 +1309,21 @@ final class CRSBuilder { if (!(geoCRS instanceof GeographicCRS)) { warning(Resources.Keys.UnexpectedGeoCRS_1, reader.input.filename); - geoCRS = objectFactory().createGeographicCRS(name(IdentifiedObjects.getName(geoCRS, new DefaultCitation("EPSG"))), + geoCRS = objectFactory().createGeographicCRS(properties(IdentifiedObjects.getName(geoCRS, new DefaultCitation("EPSG"))), ((GeodeticCRS)geoCRS).getDatum(), CommonCRS.defaultGeographic().getCoordinateSystem()); + lastName = geoCRS.getName(); } //-- in case where tiff define unit does not match if (angularUnit != null && !angularUnit.equals(geoCRS.getCoordinateSystem().getAxis(0).getUnit())) { - geoCRS = objectFactory().createGeographicCRS(name(IdentifiedObjects.getName(geoCRS, new DefaultCitation("EPSG"))), - (GeodeticDatum) ((GeographicCRS)geoCRS).getDatum(), + geoCRS = objectFactory().createGeographicCRS(properties(IdentifiedObjects.getName(geoCRS, new DefaultCitation("EPSG"))), + ((GeographicCRS)geoCRS).getDatum(), retrieveEllipsoidalCS(GeoKeys.GeogAngularUnits, CommonCRS.defaultGeographic().getCoordinateSystem(), angularUnit)); // (EllipsoidalCS) CoordinateSystems.replaceAngularUnit(CommonCRS.defaultGeographic().getCoordinateSystem(), angularUnit)); + lastName = geoCRS.getName(); } return (GeographicCRS) geoCRS; } Modified: sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoCodes.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoCodes.java?rev=1771707&r1=1771706&r2=1771707&view=diff ============================================================================== --- sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoCodes.java [UTF-8] (original) +++ sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoCodes.java [UTF-8] Mon Nov 28 10:52:22 2016 @@ -39,6 +39,11 @@ final class GeoCodes { */ public static final short undefined = 0; + /** + * The code value for a property defined by the user instead than by an EPSG code. + */ + public static final short userDefined = 32767; + /* * 6.3.1.1 Model Type Codes * Modified: sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoKeys.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoKeys.java?rev=1771707&r1=1771706&r2=1771707&view=diff ============================================================================== --- sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoKeys.java [UTF-8] (original) +++ sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoKeys.java [UTF-8] Mon Nov 28 10:52:22 2016 @@ -44,8 +44,8 @@ final class GeoKeys { /** Section 6.3.1.2 Codes */ public static final short RasterType = 1025; /** Documentation */ public static final short Citation = 1026; - public static final short UserDefined = 32767; - static final String GTUserDefined_String = "32767"; + @Deprecated public static final short UserDefined = 32767; + @Deprecated static final String GTUserDefined_String = "32767"; // 6.2.2 Geographic CS Parameter Keys public static final short GeographicType = 2048; /* Section 6.3.2.1 Codes */ Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java?rev=1771707&r1=1771706&r2=1771707&view=diff ============================================================================== --- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java [UTF-8] (original) +++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java [UTF-8] Mon Nov 28 10:52:22 2016 @@ -855,6 +855,19 @@ public class MetadataBuilder { } /** + * Returns {@code true} if a title has been specified for the current identification information. + * This method is provided because titles are mandatory in ISO 19115 metadata, so data stores may + * want to provide a fallback if no title has been found. Titles are specified by calls to + * {@link #addTitle(CharSequence)} and needs to be specified again if {@link #newIdentification()} + * has been invoked. + * + * @return whether a title exists for the current identification information. + */ + public final boolean hasTitle() { + return (citation != null) && citation.getTitle() != null; + } + + /** * Adds a title or alternate title of the resource. * Storage location is: * @@ -1449,6 +1462,21 @@ parse: for (int i = 0; i < length;) } } + /** + * Sets a general description of the transformation form grid coordinates to "real world" coordinates. + * Storage location is: + * + *
    metadata/spatialRepresentationInfo/transformationDimensionDescription
    + * + * @param value a general description of the "grid to CRS" transformation, or {@code null} if unknown. + */ + public final void setGridToCRS(final CharSequence value) { + final InternationalString i18n = trim(value); + if (i18n != null) { + gridRepresentation().setTransformationDimensionDescription(i18n); + } + } + /** * Returns the axis at the given dimension index. All previous dimensions are created if needed. *