Merge from the JDK6 branch.
git-svn-id: https://svn.apache.org/repos/asf/sis/trunk@1753294 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/core/sis-build-helper/src/main/ant/prepare-release.xml b/core/sis-build-helper/src/main/ant/prepare-release.xml
index 08e3028..221cb78 100644
--- a/core/sis-build-helper/src/main/ant/prepare-release.xml
+++ b/core/sis-build-helper/src/main/ant/prepare-release.xml
@@ -32,7 +32,7 @@
<!-- Ensure that the "sis-build-helper" plugin used by the build is the released version. -->
<replaceregexp file = "${user.dir}/pom.xml"
match = "<sis\.plugin\.version>.+</sis\.plugin\.version>"
- replace = "<sis.plugin.version>${sis.version}-SNAPSHOT</sis.plugin.version>"/>
+ replace = "<sis.plugin.version>${branch.version}-SNAPSHOT</sis.plugin.version>"/>
<!-- The -SNAPSHOT part will be removed later, at tag creation. -->
<!-- Replace the version number in Java code. -->
@@ -44,9 +44,9 @@
<replace dir="${user.dir}" failOnNoReplacements="true">
<include name="**/pom.xml"/>
<replacefilter token="svn.apache.org/repos/asf/sis/trunk"
- value="svn.apache.org/repos/asf/sis/branches/${sis.version}"/>
+ value="svn.apache.org/repos/asf/sis/branches/${branch.version}"/>
<replacefilter token="svn.apache.org/viewvc/sis/trunk"
- value="svn.apache.org/viewvc/sis/branches/${sis.version}"/>
+ value="svn.apache.org/viewvc/sis/branches/${branch.version}"/>
</replace>
</target>
@@ -58,18 +58,18 @@
<!-- Replace URL to branch by URL to the branch on Subversion. -->
<replace dir="${user.dir}" failOnNoReplacements="true">
<include name="**/pom.xml"/>
- <replacefilter token="svn.apache.org/repos/asf/sis/branches/${sis.version}"
+ <replacefilter token="svn.apache.org/repos/asf/sis/branches/${branch.version}"
value="svn.apache.org/repos/asf/sis/tags/${sis.version}"/>
- <replacefilter token="svn.apache.org/viewvc/sis/branches/${sis.version}"
+ <replacefilter token="svn.apache.org/viewvc/sis/branches/${branch.version}"
value="svn.apache.org/viewvc/sis/tags/${sis.version}"/>
</replace>
<!-- Replace version numbers. Note that no snapshot other than SIS can exist at this point. -->
<replace dir="${user.dir}" failOnNoReplacements="true">
<include name="**/pom.xml"/>
- <replacefilter token="<version>${sis.version}-SNAPSHOT</version>"
+ <replacefilter token="<version>${branch.version}-SNAPSHOT</version>"
value="<version>${sis.version}</version>"/>
- <replacefilter token="<sis.plugin.version>${sis.version}-SNAPSHOT</sis.plugin.version>"
+ <replacefilter token="<sis.plugin.version>${branch.version}-SNAPSHOT</sis.plugin.version>"
value="<sis.plugin.version>${sis.version}</sis.plugin.version>"/>
</replace>
</target>
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java
index 2694e6a..ed0635f 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java
@@ -44,7 +44,8 @@
* @version 0.6
* @module
*
- * @see DefaultAssociationRole#newInstance()
+ * @see AbstractFeature
+ * @see DefaultAssociationRole
*/
public abstract class AbstractAssociation extends Field<AbstractFeature> implements Cloneable, Serializable {
/**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
index 57c9560..975ce99 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
@@ -69,7 +69,8 @@
* @version 0.6
* @module
*
- * @see DefaultAttributeType#newInstance()
+ * @see AbstractFeature
+ * @see DefaultAttributeType
*/
public abstract class AbstractAttribute<V> extends Field<V> implements Cloneable, Serializable {
/**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java
index 81038b7..ded0b53 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java
@@ -232,7 +232,7 @@
* Returns a natural language designator for the element.
* This can be used as an alternative to the {@linkplain #getName() name} in user interfaces.
*
- * @return Natural language designator for the element.
+ * @return Natural language designator for the element, or {@code null} if none.
*/
public InternationalString getDesignation() {
return designation;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractOperation.java b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractOperation.java
index e680d39..89a1b6b 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractOperation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractOperation.java
@@ -25,6 +25,7 @@
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.ParameterValueGroup;
import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.util.Classes;
import org.apache.sis.util.Debug;
// Branch-dependent imports
@@ -51,8 +52,10 @@
*
* @author Martin Desruisseaux (Geomatys)
* @since 0.6
- * @version 0.6
+ * @version 0.8
* @module
+ *
+ * @see DefaultFeatureType
*/
public abstract class AbstractOperation extends AbstractIdentifiedType {
/**
@@ -78,7 +81,11 @@
/**
* Returns a map that can be used for creating the {@link #getResult()} type.
- * This method can be invoked for subclass constructor.
+ * This method can be invoked for subclass constructor with the user-supplied map in argument.
+ * If the given map contains at least one key prefixed by {@value #RESULT_PREFIX}, then the values
+ * associated to those keys will be used.
+ *
+ * @param identification the map given by user to sub-class constructor.
*/
final Map<String,Object> resultIdentification(final Map<String,?> identification) {
final Map<String,Object> properties = new HashMap<String,Object>(6);
@@ -203,12 +210,12 @@
* Returns a string representation of this operation.
* The returned string is for debugging purpose and may change in any future SIS version.
*
- * @return A string representation of this operation for debugging purpose.
+ * @return a string representation of this operation for debugging purpose.
*/
@Debug
@Override
public String toString() {
- final StringBuilder buffer = new StringBuilder(40).append("Operation").append('[');
+ final StringBuilder buffer = new StringBuilder(40).append(Classes.getShortClassName(this)).append('[');
final GenericName name = getName();
if (name != null) {
buffer.append('“');
@@ -222,13 +229,31 @@
buffer.append(separator).append(IdentifiedObjects.toString(param.getName()));
separator = ", ";
}
- if (separator == ", ") { // Identity comparaison is okay here.
+ if (separator == ", ") { // Identity comparaison is okay here.
buffer.append(')');
}
final AbstractIdentifiedType result = getResult();
if (result != null) {
- buffer.append(" : ").append(result.getName());
+ final Object type;
+ if (result instanceof DefaultAttributeType<?>) {
+ type = Classes.getShortName(((DefaultAttributeType<?>) result).getValueClass());
+ } else {
+ type = result.getName();
+ }
+ buffer.append(" : ").append(type);
}
- return buffer.append(']').toString();
+ formatResultFormula(buffer.append(']'));
+ return buffer.toString();
+ }
+
+ /**
+ * Appends a string representation of the "formula" used for computing the result.
+ * The "formula" may be for example a link to another property.
+ *
+ * @param buffer where to format the "formula".
+ * @return {@code true} if this method has formatted a formula, or {@code false} otherwise.
+ */
+ boolean formatResultFormula(Appendable buffer) {
+ return false;
}
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java b/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java
index 1a4fc7e..8857973 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java
@@ -50,6 +50,7 @@
* @version 0.5
* @module
*
+ * @see DefaultFeatureType
* @see AbstractAssociation
*/
public class DefaultAssociationRole extends FieldType {
@@ -113,6 +114,8 @@
* @param minimumOccurs The minimum number of occurrences of the association within its containing entity.
* @param maximumOccurs The maximum number of occurrences of the association within its containing entity,
* or {@link Integer#MAX_VALUE} if there is no restriction.
+ *
+ * @see org.apache.sis.feature.builder.AssociationRoleBuilder
*/
public DefaultAssociationRole(final Map<String,?> identification, final DefaultFeatureType valueType,
final int minimumOccurs, final int maximumOccurs)
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java b/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java
index 29f1ce3..0a408bb 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java
@@ -105,6 +105,7 @@
* @version 0.5
* @module
*
+ * @see DefaultFeatureType
* @see AbstractAttribute
*/
public class DefaultAttributeType<V> extends FieldType {
@@ -179,6 +180,8 @@
* For example if this new {@code DefaultAttributeType} describes a measurement,
* then {@code characterizedBy} could holds the measurement accuracy.
* See <cite>"Attribute characterization"</cite> in class Javadoc for more information.
+ *
+ * @see org.apache.sis.feature.builder.AttributeTypeBuilder
*/
public DefaultAttributeType(final Map<String,?> identification, final Class<V> valueClass,
final int minimumOccurs, final int maximumOccurs, final V defaultValue,
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java b/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
index c7b685a..996c16f 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
@@ -79,6 +79,12 @@
* which are implicitly <cite>covariant</cite> (i.e. {@code String[]} can be casted to {@code CharSequence[]}, which
* is safe for read operations but not for write operations — the later may throw {@link ArrayStoreException}).</div>
*
+ * <div class="section">Instantiation</div>
+ * {@code DefaultFeatureType} can be instantiated directly by a call to its {@linkplain #DefaultFeatureType constructor}.
+ * But a more convenient approach may be to use the {@link org.apache.sis.feature.builder.FeatureTypeBuilder} instead,
+ * which provides shortcuts for frequently-used operations like creating various {@link org.opengis.util.GenericName}
+ * instances sharing the same namespace.
+ *
* <div class="section">Immutability and thread safety</div>
* Instances of this class are immutable if all properties ({@link GenericName} and {@link InternationalString}
* instances) and all arguments ({@link AttributeType} instances) given to the constructor are also immutable.
@@ -90,6 +96,8 @@
* @version 0.6
* @module
*
+ * @see DefaultAttributeType
+ * @see DefaultAssociationRole
* @see AbstractFeature
*/
public class DefaultFeatureType extends AbstractIdentifiedType implements FeatureType {
@@ -236,6 +244,8 @@
* @param superTypes The parents of this feature type, or {@code null} or empty if none.
* @param properties Any feature operation, any feature attribute type and any feature
* association role that carries characteristics of a feature type.
+ *
+ * @see org.apache.sis.feature.builder.FeatureTypeBuilder
*/
@SuppressWarnings("ThisEscapedInObjectConstruction")
public DefaultFeatureType(final Map<String,?> identification, final boolean isAbstract,
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java b/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java
index cfc9334..b068b5c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java
@@ -136,7 +136,7 @@
final GenericName name = property.getName();
final String attributeName = (property instanceof LinkOperation)
? ((LinkOperation) property).referentName : name.toString();
- final boolean isDefault = AttributeConvention.DEFAULT_GEOMETRY_PROPERTY.equals(name.tip());
+ final boolean isDefault = AttributeConvention.GEOMETRY_PROPERTY.equals(name.tip());
if (isDefault) {
defaultGeometry = attributeName;
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
index 68ab3d6..1d3f8b1 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
@@ -32,6 +32,7 @@
import org.opengis.util.GenericName;
import org.apache.sis.io.TableAppender;
import org.apache.sis.io.TabularFormat;
+import org.apache.sis.util.CharSequences;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.resources.Vocabulary;
@@ -202,7 +203,7 @@
final StringBuffer buffer = new StringBuffer();
final FieldPosition dummyFP = new FieldPosition(-1);
for (final AbstractIdentifiedType propertyType : featureType.getProperties(true)) {
- Object value;
+ Object value = null;
if (feature != null) {
value = feature.getPropertyValue(propertyType.getName().toString());
if (value == null) {
@@ -212,8 +213,11 @@
}
} else if (propertyType instanceof DefaultAttributeType<?>) {
value = ((DefaultAttributeType<?>) propertyType).getDefaultValue();
- } else {
- value = null;
+ } else if (propertyType instanceof AbstractOperation) {
+ if (((AbstractOperation) propertyType).formatResultFormula(buffer)) {
+ value = CharSequences.trimWhitespaces(buffer).toString();
+ buffer.setLength(0);
+ }
}
/*
* Column 0 - Name.
@@ -223,31 +227,31 @@
/*
* Column 1 and 2 - Type and cardinality.
*/
- final String valueType;
- final Class<?> valueClass;
- final int minimumOccurs, maximumOccurs;
- if (propertyType instanceof DefaultAttributeType<?>) {
- final DefaultAttributeType<?> pt = (DefaultAttributeType<?>) propertyType;
+ final String valueType; // The value to write in the type column.
+ final Class<?> valueClass; // AttributeType.getValueClass() if applicable.
+ final int minimumOccurs, maximumOccurs; // Negative values mean no cardinality.
+ final AbstractIdentifiedType resultType; // Result of operation if applicable.
+ if (propertyType instanceof AbstractOperation) {
+ resultType = ((AbstractOperation) propertyType).getResult();
+ } else {
+ resultType = propertyType;
+ }
+ if (resultType instanceof DefaultAttributeType<?>) {
+ final DefaultAttributeType<?> pt = (DefaultAttributeType<?>) resultType;
minimumOccurs = pt.getMinimumOccurs();
maximumOccurs = pt.getMaximumOccurs();
valueClass = pt.getValueClass();
valueType = getFormat(Class.class).format(valueClass, buffer, dummyFP).toString();
buffer.setLength(0);
- } else if (propertyType instanceof DefaultAssociationRole) {
- final DefaultAssociationRole pt = (DefaultAssociationRole) propertyType;
+ } else if (resultType instanceof DefaultAssociationRole) {
+ final DefaultAssociationRole pt = (DefaultAssociationRole) resultType;
minimumOccurs = pt.getMinimumOccurs();
maximumOccurs = pt.getMaximumOccurs();
valueType = toString(DefaultAssociationRole.getValueTypeName(pt));
valueClass = AbstractFeature.class;
- } else if (propertyType instanceof AbstractOperation) {
- final AbstractIdentifiedType resultType = ((AbstractOperation) propertyType).getResult();
- valueType = toString(resultType.getName());
- valueClass = null;
- minimumOccurs = -1;
- maximumOccurs = -1;
} else {
- valueType = "";
- valueClass = null;
+ valueType = toString(resultType.getName());
+ valueClass = null;
minimumOccurs = -1;
maximumOccurs = -1;
}
@@ -305,7 +309,7 @@
Object c = attribute.getDefaultValue();
if (feature != null) {
final Object p = feature.getProperty(propertyType.getName().toString());
- if (p instanceof AbstractAttribute<?>) { // Should always be true, but we are paranoiac.
+ if (p instanceof AbstractAttribute<?>) { // Should always be true, but we are paranoiac.
c = ((AbstractAttribute<?>) p).characteristics().get(attribute.getName().toString());
}
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
index b0e8c66..ed308d3 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
@@ -129,7 +129,7 @@
* For example features of type <b>Country</b> may have identifiers named “ISO country code”
* while features of type <b>Car</b> may have identifiers named “license plate number”.
* In order to simplify identifier usages regardless of their name,
- * an application could choose to add in all features a virtual property named {@code "@id"}
+ * an application could choose to add in all features a virtual property named {@code "identifier"}
* which links to whatever property is used as an identifier in an arbitrary feature.
* So the definition of the <b>Car</b> feature could contain the following code:
*
@@ -137,7 +137,7 @@
* AttributeType licensePlateNumber = ...; // Attribute creation omitted for brevity
* FeatureType car = new DefaultFeatureType(..., // Arguments omitted for brevity
* licensePlateNumber, model, owner,
- * FeatureOperations.link(singletonMap(NAME_KEY, "@id"), licensePlateNumber);
+ * FeatureOperations.link(singletonMap(NAME_KEY, "identifier"), licensePlateNumber);
* }
* </div>
*
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/LinkOperation.java b/core/sis-feature/src/main/java/org/apache/sis/feature/LinkOperation.java
index 0e08c75..d6e75f8 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/LinkOperation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/LinkOperation.java
@@ -20,6 +20,7 @@
import java.util.Map;
import java.util.HashMap;
import java.util.Collections;
+import java.io.IOException;
import org.opengis.metadata.Identifier;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.parameter.ParameterDescriptor;
@@ -28,6 +29,9 @@
import org.apache.sis.metadata.iso.citation.Citations;
import org.apache.sis.util.ArgumentChecks;
+// Branch-dependent imports
+import org.apache.sis.internal.jdk8.UncheckedIOException;
+
/**
* A link operation, which is like a redirection or an alias.
@@ -145,4 +149,20 @@
// 'this.result' is compared (indirectly) by the super class.
return super.equals(obj) && referentName.equals(((LinkOperation) obj).referentName);
}
+
+ /**
+ * Appends a string representation of the "formula" used for computing the result.
+ *
+ * @param buffer where to format the "formula".
+ * @return {@code true} since this method has formatted a formula.
+ */
+ @Override
+ boolean formatResultFormula(final Appendable buffer) {
+ try {
+ buffer.append(" → ").append(referentName);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ return true;
+ }
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/StringJoinOperation.java b/core/sis-feature/src/main/java/org/apache/sis/feature/StringJoinOperation.java
index 7c17135..1767956 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/StringJoinOperation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/StringJoinOperation.java
@@ -19,6 +19,7 @@
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
+import java.io.IOException;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.util.GenericName;
@@ -33,6 +34,7 @@
// Branch-dependent imports
import org.apache.sis.internal.jdk7.Objects;
+import org.apache.sis.internal.jdk8.UncheckedIOException;
/**
@@ -414,4 +416,26 @@
}
return false;
}
+
+ /**
+ * Appends a string representation of the "formula" used for computing the result.
+ *
+ * @param buffer where to format the "formula".
+ * @return {@code true} since this method has formatted a formula.
+ */
+ @Override
+ boolean formatResultFormula(final Appendable buffer) {
+ try {
+ buffer.append(" → ");
+ String separator = "(";
+ for (final String element : attributeNames) {
+ buffer.append(separator).append(element);
+ separator = ", ";
+ }
+ buffer.append(')');
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ return true;
+ }
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AssociationRoleBuilder.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AssociationRoleBuilder.java
new file mode 100644
index 0000000..ad855a7
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AssociationRoleBuilder.java
@@ -0,0 +1,176 @@
+/*
+ * 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.feature.builder;
+
+import org.opengis.util.GenericName;
+import org.apache.sis.feature.DefaultAssociationRole;
+
+// Branch-dependent imports
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultFeatureType;
+
+
+/**
+ * Describes one association from the {@code FeatureType} to be built by an {@code FeatureTypeBuilder} to another
+ * {@code FeatureType}. A different instance of {@code AssociationRoleBuilder} exists for each feature association
+ * to describe. Those instances are created preferably by {@link FeatureTypeBuilder#addAssociation(FeatureType)},
+ * or in case of cyclic reference by {@link FeatureTypeBuilder#addAssociation(GenericName)}.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.8
+ * @version 0.8
+ * @module
+ *
+ * @see org.apache.sis.feature.DefaultAssociationRole
+ * @see FeatureTypeBuilder#addAssociation(FeatureType)
+ * @see FeatureTypeBuilder#addAssociation(GenericName)
+ */
+public final class AssociationRoleBuilder extends PropertyTypeBuilder {
+ /**
+ * The target feature type, or {@code null} if unknown.
+ */
+ private final DefaultFeatureType type;
+
+ /**
+ * Name of the target feature type (never null).
+ */
+ private final GenericName typeName;
+
+ /**
+ * Creates a new {@code AssociationRole} builder for values of the given type.
+ * The {@code type} argument can be null if unknown, but {@code typeName} is mandatory.
+ *
+ * @param owner the builder of the {@code FeatureType} for which to add this property.
+ */
+ AssociationRoleBuilder(final FeatureTypeBuilder owner, final DefaultFeatureType type, final GenericName typeName) {
+ super(owner, null);
+ this.type = type;
+ this.typeName = typeName;
+ }
+
+ /**
+ * Creates a new {@code FeatureAssociationRole} builder initialized to the values of an existing association.
+ *
+ * @param owner the builder of the {@code FeatureType} for which to add this property.
+ */
+ AssociationRoleBuilder(final FeatureTypeBuilder owner, final DefaultAssociationRole template) {
+ super(owner, template);
+ minimumOccurs = template.getMinimumOccurs();
+ maximumOccurs = template.getMaximumOccurs();
+ type = template.getValueType();
+ typeName = type.getName();
+ }
+
+ /**
+ * Appends a text inside the value returned by {@link #toString()}, before the closing bracket.
+ */
+ @Override
+ final void toStringInternal(final StringBuilder buffer) {
+ buffer.append(" → ").append(typeName);
+ }
+
+ /**
+ * Returns a default name to use if the user did not specified a name. The first letter will be changed to
+ * lower case (unless the name looks like an acronym) for compliance with Java convention on property names.
+ */
+ @Override
+ final String getDefaultName() {
+ return typeName.tip().toString();
+ }
+
+ /**
+ * Sets the {@code FeatureAssociationRole} name as a generic name.
+ * If another name was defined before this method call, that previous value will be discarded.
+ *
+ * @return {@code this} for allowing method calls chaining.
+ */
+ @Override
+ public AssociationRoleBuilder setName(final GenericName name) {
+ super.setName(name);
+ return this;
+ }
+
+ /**
+ * Sets the {@code FeatureAssociationRole} name as a simple string with the default scope.
+ * The default scope is the value specified by the last call to
+ * {@link FeatureTypeBuilder#setDefaultScope(String)}.
+ * The name will be a {@linkplain org.apache.sis.util.iso.DefaultLocalName local name} if no default scope
+ * has been specified, or a {@linkplain org.apache.sis.util.iso.DefaultScopedName scoped name} otherwise.
+ *
+ * @return {@code this} for allowing method calls chaining.
+ */
+ @Override
+ public AssociationRoleBuilder setName(final String localPart) {
+ super.setName(localPart);
+ return this;
+ }
+
+ /**
+ * Sets the {@code FeatureAssociationRole} name as a string in the given scope.
+ * The name will be a {@linkplain org.apache.sis.util.iso.DefaultLocalName local name} if the given scope is
+ * {@code null} or empty, or a {@linkplain org.apache.sis.util.iso.DefaultScopedName scoped name} otherwise.
+ * If a {@linkplain FeatureTypeBuilder#setDefaultScope(String) default scope} has been specified, then the
+ * {@code scope} argument overrides it.
+ *
+ * @return {@code this} for allowing method calls chaining.
+ */
+ @Override
+ public AssociationRoleBuilder setName(final String scope, final String localPart) {
+ super.setName(scope, localPart);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AssociationRoleBuilder setDefinition(final CharSequence definition) {
+ super.setDefinition(definition);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AssociationRoleBuilder setDesignation(final CharSequence designation) {
+ super.setDesignation(designation);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AssociationRoleBuilder setDescription(final CharSequence description) {
+ super.setDescription(description);
+ return this;
+ }
+
+ /**
+ * Creates a new property type from the current setting.
+ */
+ @Override
+ final AbstractIdentifiedType create() {
+ if (type != null) {
+ return new DefaultAssociationRole(identification(), type, minimumOccurs, maximumOccurs);
+ } else {
+ return new DefaultAssociationRole(identification(), typeName, minimumOccurs, maximumOccurs);
+ }
+ }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AttributeRole.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AttributeRole.java
new file mode 100644
index 0000000..b9ed1a0
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AttributeRole.java
@@ -0,0 +1,70 @@
+/*
+ * 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.feature.builder;
+
+import org.apache.sis.feature.FeatureOperations;
+
+
+/**
+ * Roles that can be associated to some attributes for instructing {@code FeatureTypeBuilder}
+ * how to generate pre-defined operations. Those pre-defined operations are:
+ *
+ * <ul>
+ * <li>A {@linkplain FeatureOperations#compound compound operation} for generating a unique identifier
+ * from an arbitrary amount of attribute values.</li>
+ * <li>A {@linkplain FeatureOperations#link link operation} for referencing a geometry to be used as the
+ * <em>default</em> geometry.</li>
+ * <li>An {@linkplain FeatureOperations#envelope operation} for computing the bounding box of all geometries
+ * found in the feature. This operation is automatically added if the feature contains a default geometry.</li>
+ * </ul>
+ *
+ * This enumeration allows user code to specify which feature attribute to use for creating those operations.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.8
+ * @version 0.8
+ * @module
+ *
+ * @see Attribute#addRole(AttributeRole)
+ */
+public enum AttributeRole {
+ /**
+ * Attribute value will be part of a unique identifier for the feature instance.
+ * An arbitrary amount of attributes can be flagged as identifier components:
+ *
+ * <ul>
+ * <li>If no attribute has this role, then no attribute is marked as feature identifier.</li>
+ * <li>If exactly one attribute has this role, then a synthetic attribute named {@code "@identifier"}
+ * will be created as a {@linkplain FeatureOperations#link link} to the flagged attribute.</li>
+ * <li>If more than one attribute have this role, then a synthetic attribute named {@code "@identifier"}
+ * will be created as a {@linkplain FeatureOperations#compound compound key} made of all flagged
+ * attributes. The separator character can be modified by a call to
+ * {@link FeatureTypeBuilder#setIdentifierDelimiters(String, String, String)}</li>
+ * </ul>
+ *
+ * @see FeatureTypeBuilder#setIdentifierDelimiters(String, String, String)
+ */
+ IDENTIFIER_COMPONENT,
+
+ /**
+ * Attribute value will be flagged as the <em>default</em> geometry.
+ * Feature can have an arbitrary amount of geometry attributes,
+ * but only one can be flagged as the default geometry.
+ */
+ DEFAULT_GEOMETRY
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AttributeTypeBuilder.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AttributeTypeBuilder.java
new file mode 100644
index 0000000..0a90c48
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AttributeTypeBuilder.java
@@ -0,0 +1,389 @@
+/*
+ * 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.feature.builder;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import org.opengis.util.GenericName;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.FeatureOperations;
+import org.apache.sis.internal.util.CollectionsExt;
+import org.apache.sis.internal.feature.AttributeConvention;
+import org.apache.sis.internal.feature.Geometries;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.Classes;
+
+// Branch-dependent imports
+import java.util.Objects;
+import org.apache.sis.feature.AbstractIdentifiedType;
+
+
+/**
+ * Describes one attribute of the {@code FeatureType} to be built by the enclosing {@code FeatureTypeBuilder}.
+ * A different instance of {@code AttributeTypeBuilder} exists for each feature attribute to describe.
+ * Those instances are created by {@link FeatureTypeBuilder#addAttribute(Class)}.
+ *
+ * @param <V> the class of property values.
+ *
+ * @see org.apache.sis.feature.DefaultAttributeType
+ * @see FeatureTypeBuilder#addAttribute(Class)
+ */
+public final class AttributeTypeBuilder<V> extends PropertyTypeBuilder {
+ /**
+ * The class of property values. Can not be changed after construction
+ * because this value determines the parameterized type {@code <V>}.
+ */
+ private final Class<V> valueClass;
+
+ /**
+ * The default value for the property, or {@code null} if none.
+ */
+ private V defaultValue;
+
+ /**
+ * Whether this attribute will be used in a {@linkplain FeatureOperations#compound compound key} named
+ * {@code "@identifier"}. If only one attribute has this flag and {@link FeatureTypeBuilder#idPrefix} and
+ * {@code isSuffix} are null, then {@code "@identifier"} will be a {@linkplain FeatureOperations#link link}
+ * to {@code idAttributes[0]}.
+ *
+ * @see #addRole(AttributeRole)
+ */
+ private boolean isIdentifier;
+
+ /**
+ * Builders for the characteristics associated to the attribute.
+ */
+ private final List<CharacteristicTypeBuilder<?>> characteristics = new ArrayList<CharacteristicTypeBuilder<?>>();
+
+ /**
+ * Creates a new {@code AttributeType} builder for values of the given class.
+ *
+ * @param owner the builder of the {@code FeatureType} for which to add this property.
+ * @param valueClass the class of property values.
+ */
+ AttributeTypeBuilder(final FeatureTypeBuilder owner, final Class<V> valueClass) {
+ super(owner, null);
+ this.valueClass = valueClass;
+ }
+
+ /**
+ * Creates a new {@code AttributeType} builder initialized to the values of an existing attribute.
+ *
+ * @param owner the builder of the {@code FeatureType} for which to add this property.
+ */
+ AttributeTypeBuilder(final FeatureTypeBuilder owner, final DefaultAttributeType<V> template) {
+ super(owner, template);
+ minimumOccurs = template.getMinimumOccurs();
+ maximumOccurs = template.getMaximumOccurs();
+ valueClass = template.getValueClass();
+ defaultValue = template.getDefaultValue();
+ for (final DefaultAttributeType<?> c : template.characteristics().values()) {
+ characteristics.add(new CharacteristicTypeBuilder(this, c));
+ }
+ }
+
+ /**
+ * Returns a default name to use if the user did not specified a name. The first letter will be changed to
+ * lower case (unless the name looks like an acronym) for compliance with Java convention on property names.
+ */
+ @Override
+ final String getDefaultName() {
+ return Classes.getShortName(valueClass);
+ }
+
+ /**
+ * Sets the {@code AttributeType} name as a generic name.
+ * If another name was defined before this method call, that previous value will be discarded.
+ *
+ * @return {@code this} for allowing method calls chaining.
+ */
+ @Override
+ public AttributeTypeBuilder<V> setName(final GenericName name) {
+ super.setName(name);
+ return this;
+ }
+
+ /**
+ * Sets the {@code AttributeType} name as a simple string with the default scope.
+ * The default scope is the value specified by the last call to
+ * {@link FeatureTypeBuilder#setDefaultScope(String)}.
+ * The name will be a {@linkplain org.apache.sis.util.iso.DefaultLocalName local name} if no default scope
+ * has been specified, or a {@linkplain org.apache.sis.util.iso.DefaultScopedName scoped name} otherwise.
+ *
+ * @return {@code this} for allowing method calls chaining.
+ */
+ @Override
+ public AttributeTypeBuilder<V> setName(final String localPart) {
+ super.setName(localPart);
+ return this;
+ }
+
+ /**
+ * Sets the {@code AttributeType} name as a string in the given scope.
+ * The name will be a {@linkplain org.apache.sis.util.iso.DefaultLocalName local name} if the given scope is
+ * {@code null} or empty, or a {@linkplain org.apache.sis.util.iso.DefaultScopedName scoped name} otherwise.
+ * If a {@linkplain FeatureTypeBuilder#setDefaultScope(String) default scope} has been specified, then the
+ * {@code scope} argument overrides it.
+ *
+ * @return {@code this} for allowing method calls chaining.
+ */
+ @Override
+ public AttributeTypeBuilder<V> setName(final String scope, final String localPart) {
+ super.setName(scope, localPart);
+ return this;
+ }
+
+ /**
+ * Sets the default value for the property.
+ *
+ * @param value default property value, or {@code null} if none.
+ * @return {@code this} for allowing method calls chaining.
+ */
+ public AttributeTypeBuilder<V> setDefaultValue(final V value) {
+ if (!Objects.equals(defaultValue, value)) {
+ defaultValue = value;
+ clearCache();
+ }
+ return this;
+ }
+
+ /**
+ * Sets an enumeration of valid values for this attribute.
+ *
+ * <p>This is a convenience method for {@link #addCharacteristic(Class)} with a value
+ * of type {@link Set} and a conventional name.</p>
+ *
+ * @param values valid values.
+ * @return {@code this} for allowing method calls chaining.
+ * @throws UnsupportedOperationException if this property does not support characteristics.
+ *
+ * @see #characteristics()
+ * @see AttributeConvention#VALID_VALUES_CHARACTERISTIC
+ */
+ @SafeVarargs
+ public final AttributeTypeBuilder<V> setValidValues(final V... values) {
+ return setCharacteristic(AttributeConvention.VALID_VALUES_CHARACTERISTIC,
+ Set.class, CollectionsExt.immutableSet(false, values));
+ }
+
+ /**
+ * Sets the maximal length that characterizes the {@link CharSequence} values of this attribute.
+ * While this characteristic can be applied to any kind of attribute, it is meaningful only with
+ * character sequences.
+ *
+ * <p>This is a convenience method for {@link #addCharacteristic(Class)} with a value
+ * of type {@link Integer} and a conventional name.</p>
+ *
+ * @param length maximal length of {@link CharSequence} attribute values, or {@code null}.
+ * @return {@code this} for allowing method calls chaining.
+ * @throws UnsupportedOperationException if this property does not support length characteristics.
+ *
+ * @see #characteristics()
+ * @see AttributeConvention#MAXIMAL_LENGTH_CHARACTERISTIC
+ */
+ public AttributeTypeBuilder<V> setMaximalLength(final Integer length) {
+ return setCharacteristic(AttributeConvention.MAXIMAL_LENGTH_CHARACTERISTIC, Integer.class, length);
+ }
+
+ /**
+ * Sets the coordinate reference system that characterizes the values of this attribute.
+ * While this characteristic can be applied to any kind of attribute, it is meaningful
+ * only with georeferenced values like geometries or coverages.
+ *
+ * <p>This is a convenience method for {@link #addCharacteristic(Class)} with a value
+ * of type {@link CoordinateReferenceSystem} and a conventional name.</p>
+ *
+ * @param crs coordinate reference system associated to attribute values, or {@code null}.
+ * @return {@code this} for allowing method calls chaining.
+ * @throws UnsupportedOperationException if this property does not support CRS characteristics.
+ *
+ * @see #characteristics()
+ * @see AttributeConvention#CRS_CHARACTERISTIC
+ */
+ public AttributeTypeBuilder<V> setCRS(final CoordinateReferenceSystem crs) {
+ return setCharacteristic(AttributeConvention.CRS_CHARACTERISTIC, CoordinateReferenceSystem.class, crs);
+ }
+
+ /**
+ * Implementation of all setter methods for characteristics.
+ */
+ private <C> AttributeTypeBuilder<V> setCharacteristic(final GenericName name, final Class<C> type, final C value) {
+ for (final CharacteristicTypeBuilder<?> characteristic : characteristics) {
+ if (name.equals(characteristic.getName())) {
+ characteristic.set(value);
+ return this;
+ }
+ }
+ addCharacteristic(type).setDefaultValue(value).setName(name);
+ return this;
+ }
+
+ /**
+ * Adds another attribute type that describes this attribute type.
+ * See <cite>"Attribute characterization"</cite> in {@link DefaultAttributeType} Javadoc for more information.
+ *
+ * <p>Usage example:</p>
+ * {@preformat java
+ * attribute.addCharacteristic(Unit.class).setName("Unit of measurement").setDefaultValue(SI.CELSIUS);
+ * }
+ *
+ * The default characteristic name is the name of the given type, but callers should invoke one
+ * of the {@code CharacteristicTypeBuilder.setName(…)} methods on the returned instance with a better name.
+ *
+ * @param <C> the compile-time type of {@code type} argument.
+ * @param type the class of characteristic values.
+ * @return a builder for a characteristic of this attribute.
+ *
+ * @see #characteristics()
+ */
+ public <C> CharacteristicTypeBuilder<C> addCharacteristic(final Class<C> type) {
+ ensureNonNull("type", type);
+ final CharacteristicTypeBuilder<C> characteristic = new CharacteristicTypeBuilder<C>(this, type);
+ characteristics.add(characteristic);
+ clearCache();
+ return characteristic;
+ }
+
+ /**
+ * Adds another attribute type that describes this attribute type, using an existing one as a template.
+ * See <cite>"Attribute characterization"</cite> in {@link DefaultAttributeType} Javadoc for more information.
+ *
+ * <div class="warning"><b>Warning:</b>
+ * The {@code template} argument type will be changed to {@code AttributeType} if and when such interface
+ * will be defined in GeoAPI.</div>
+ *
+ * @param <C> the compile-time type of values in the {@code template} argument.
+ * @param template an existing attribute type to use as a template.
+ * @return a builder for a characteristic of this attribute, initialized with the values of the given template.
+ *
+ * @see #characteristics()
+ */
+ public <C> CharacteristicTypeBuilder<C> addCharacteristic(final DefaultAttributeType<C> template) {
+ ensureNonNull("template", template);
+ final CharacteristicTypeBuilder<C> characteristic = new CharacteristicTypeBuilder<C>(this, template);
+ characteristics.add(characteristic);
+ clearCache();
+ return characteristic;
+ }
+
+ /**
+ * Flags this attribute as an input of one of the pre-defined operations managed by {@code FeatureTypeBuilder}.
+ *
+ * @param role the role to add to this attribute (shall not be null).
+ */
+ public void addRole(final AttributeRole role) {
+ ensureNonNull("role", role);
+ switch (role) {
+ case IDENTIFIER_COMPONENT: {
+ if (!isIdentifier) {
+ isIdentifier = true;
+ owner.identifierCount++;
+ owner.clearCache(); // The change does not impact this attribute itself.
+ }
+ break;
+ }
+ case DEFAULT_GEOMETRY: {
+ if (owner.defaultGeometry != this) {
+ if (!Geometries.isKnownType(valueClass)) {
+ throw new IllegalStateException(errors().getString(Errors.Keys.UnsupportedImplementation_1, valueClass));
+ }
+ if (owner.defaultGeometry != null) {
+ throw new IllegalStateException(errors().getString(Errors.Keys.PropertyAlreadyExists_2,
+ owner.getDisplayName(), AttributeConvention.GEOMETRY_PROPERTY));
+ }
+ owner.defaultGeometry = this;
+ owner.clearCache(); // The change does not impact this attribute itself.
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * Returns {@code true} if {@link AttributeRole#IDENTIFIER_COMPONENT} has been associated to this attribute.
+ */
+ @Override
+ boolean isIdentifier() {
+ return isIdentifier;
+ }
+
+ /**
+ * Returns a view of all characteristics added to the {@code AttributeType} to build.
+ * The returned list is <cite>live</cite>: changes in this builder are reflected in that list and conversely.
+ * However the returned list allows only {@linkplain List#remove(Object) remove} operations;
+ * new characteristics can be added only by calls to one of the {@code set/addCharacteristic(…)} methods.
+ *
+ * @return a live list over the characteristics declared to this builder.
+ *
+ * @see #addCharacteristic(Class)
+ * @see #addCharacteristic(AttributeType)
+ * @see #setValidValues(Object...)
+ * @see #setCRS(CoordinateReferenceSystem)
+ */
+ public List<CharacteristicTypeBuilder<?>> characteristics() {
+ return new RemoveOnlyList<CharacteristicTypeBuilder<?>>(characteristics);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AttributeTypeBuilder<V> setDefinition(final CharSequence definition) {
+ super.setDefinition(definition);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AttributeTypeBuilder<V> setDesignation(final CharSequence designation) {
+ super.setDesignation(designation);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AttributeTypeBuilder<V> setDescription(final CharSequence description) {
+ super.setDescription(description);
+ return this;
+ }
+
+ /**
+ * Appends a text inside the value returned by {@link #toString()}, before the closing bracket.
+ */
+ @Override
+ final void toStringInternal(final StringBuilder buffer) {
+ buffer.append(" : ").append(Classes.getShortName(valueClass));
+ }
+
+ /**
+ * Creates a new property type from the current setting.
+ */
+ @Override
+ final AbstractIdentifiedType create() {
+ final DefaultAttributeType<?>[] chrts = new DefaultAttributeType<?>[characteristics.size()];
+ for (int i=0; i<chrts.length; i++) {
+ chrts[i] = characteristics.get(i).build();
+ }
+ return new DefaultAttributeType<V>(identification(), valueClass, minimumOccurs, maximumOccurs, defaultValue, chrts);
+ }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/CharacteristicTypeBuilder.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/CharacteristicTypeBuilder.java
new file mode 100644
index 0000000..4eb2824
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/CharacteristicTypeBuilder.java
@@ -0,0 +1,218 @@
+/*
+ * 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.feature.builder;
+
+import org.opengis.util.GenericName;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.util.Classes;
+
+// Branch-dependent imports
+import java.util.Objects;
+
+
+/**
+ * Describes one characteristic of an {@code AttributeType} to be built by the enclosing {@code FeatureTypeBuilder}.
+ * A different instance of {@code CharacteristicTypeBuilder} exists for each characteristic to describe.
+ * Those instances are created by:
+ *
+ * <ul>
+ * <li>{@link AttributeTypeBuilder#addCharacteristic(Class)}</li>
+ * </ul>
+ *
+ * @param <V> the class of characteristic values.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.8
+ * @version 0.8
+ * @module
+ */
+public final class CharacteristicTypeBuilder<V> extends TypeBuilder {
+ /**
+ * The attribute type builder instance that created this {@code CharacteristicTypeBuilder} builder.
+ */
+ private final AttributeTypeBuilder<?> owner;
+
+ /**
+ * The class of attribute values. Can not be changed after construction
+ * because this value determines the parameterized type {@code <V>}.
+ */
+ private final Class<V> valueClass;
+
+ /**
+ * The default value for the attribute, or {@code null} if none.
+ */
+ private V defaultValue;
+
+ /**
+ * The characteristic created by this builder, or {@code null} if not yet created.
+ * This field must be cleared every time that a setter method is invoked on this builder.
+ */
+ private transient DefaultAttributeType<V> characteristic;
+
+ /**
+ * Creates a new characteristic builder for values of the given class.
+ *
+ * @param owner the builder of the {@code AttributeType} for which to add this property.
+ * @param valueClass the class of characteristic values.
+ */
+ CharacteristicTypeBuilder(final AttributeTypeBuilder<?> owner, final Class<V> valueClass) {
+ super(null, owner.getLocale());
+ this.owner = owner;
+ this.valueClass = valueClass;
+ }
+
+ /**
+ * Creates a new characteristic builder initialized to the values of an existing attribute.
+ *
+ * @param owner the builder of the {@code AttributeType} for which to add this property.
+ */
+ CharacteristicTypeBuilder(final AttributeTypeBuilder<?> owner, final DefaultAttributeType<V> template) {
+ super(template, owner.getLocale());
+ this.owner = owner;
+ valueClass = template.getValueClass();
+ defaultValue = template.getDefaultValue();
+ characteristic = template;
+ }
+
+ /**
+ * If the {@code AttributeType<V>} created by the last call to {@link #build()} has been cached,
+ * clears that cache. This method must be invoked every time that a setter method is invoked.
+ */
+ @Override
+ final void clearCache() {
+ characteristic = null;
+ owner.clearCache();
+ }
+
+ /**
+ * Returns a default name to use if the user did not specified a name. The first letter will be changed to
+ * lower case (unless the name looks like an acronym) for compliance with Java convention on property names.
+ */
+ @Override
+ final String getDefaultName() {
+ return Classes.getShortName(valueClass);
+ }
+
+ /**
+ * Sets the characteristic name as a generic name.
+ * If another name was defined before this method call, that previous value will be discarded.
+ *
+ * @return {@code this} for allowing method calls chaining.
+ */
+ @Override
+ public CharacteristicTypeBuilder<V> setName(final GenericName name) {
+ super.setName(name);
+ return this;
+ }
+
+ /**
+ * Sets the characteristic name as a simple string with the default scope.
+ * The default scope is the value specified by the last call to
+ * {@link FeatureTypeBuilder#setDefaultScope(String)}.
+ * The name will be a {@linkplain org.apache.sis.util.iso.DefaultLocalName local name} if no default scope
+ * has been specified, or a {@linkplain org.apache.sis.util.iso.DefaultScopedName scoped name} otherwise.
+ *
+ * @return {@code this} for allowing method calls chaining.
+ */
+ @Override
+ public CharacteristicTypeBuilder<V> setName(final String localPart) {
+ super.setName(localPart);
+ return this;
+ }
+
+ /**
+ * Sets the characteristic name as a string in the given scope.
+ * The name will be a {@linkplain org.apache.sis.util.iso.DefaultLocalName local name} if the given scope is
+ * {@code null} or empty, or a {@linkplain org.apache.sis.util.iso.DefaultScopedName scoped name} otherwise.
+ * If a {@linkplain FeatureTypeBuilder#setDefaultScope(String) default scope} has been specified, then the
+ * {@code scope} argument overrides it.
+ *
+ * @return {@code this} for allowing method calls chaining.
+ */
+ @Override
+ public CharacteristicTypeBuilder<V> setName(final String scope, final String localPart) {
+ super.setName(scope, localPart);
+ return this;
+ }
+
+ /**
+ * Delegates the creation of a new name to the enclosing builder.
+ */
+ @Override
+ final GenericName name(final String scope, final String localPart) {
+ return owner.name(scope, localPart);
+ }
+
+ /**
+ * Sets the default value with check of the value class.
+ */
+ final void set(final Object value) {
+ setDefaultValue(valueClass.cast(value));
+ }
+
+ /**
+ * Sets the default value for the characteristic.
+ *
+ * @param value characteristic default value, or {@code null} if none.
+ * @return {@code this} for allowing method calls chaining.
+ */
+ public CharacteristicTypeBuilder<V> setDefaultValue(final V value) {
+ if (!Objects.equals(defaultValue, value)) {
+ defaultValue = value;
+ clearCache();
+ }
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public CharacteristicTypeBuilder<V> setDefinition(final CharSequence definition) {
+ super.setDefinition(definition);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public CharacteristicTypeBuilder<V> setDesignation(final CharSequence designation) {
+ super.setDesignation(designation);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public CharacteristicTypeBuilder<V> setDescription(final CharSequence description) {
+ super.setDescription(description);
+ return this;
+ }
+
+ /**
+ * Creates a new characteristic from the current setting.
+ */
+ final DefaultAttributeType<V> build() {
+ if (characteristic == null) {
+ characteristic = new DefaultAttributeType<V>(identification(), valueClass, 0, 1, defaultValue);
+ }
+ return characteristic;
+ }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java
new file mode 100644
index 0000000..31b7f46
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java
@@ -0,0 +1,716 @@
+/*
+ * 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.feature.builder;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Locale;
+import org.opengis.util.GenericName;
+import org.opengis.util.NameFactory;
+import org.opengis.util.FactoryException;
+import org.apache.sis.feature.AbstractOperation;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.FeatureOperations;
+import org.apache.sis.internal.system.DefaultFactories;
+import org.apache.sis.internal.feature.AttributeConvention;
+import org.apache.sis.util.CorruptedObjectException;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.ArraysExt;
+
+// Branch-dependent imports
+import java.util.Objects;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAssociationRole;
+import org.apache.sis.feature.DefaultAttributeType;
+
+
+/**
+ * Helper class for the creation of {@link FeatureType} instances.
+ * This builder can create the arguments to be given to the
+ * {@linkplain DefaultFeatureType#DefaultFeatureType feature type constructor}
+ * from simpler parameters given to this builder.
+ *
+ * <p>{@code FeatureTypeBuilder} should be short lived.
+ * After the {@code FeatureType} has been created, the builder should be discarded.</p>
+ *
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.8
+ * @version 0.8
+ * @module
+ *
+ * @see org.apache.sis.parameter.ParameterBuilder
+ */
+public class FeatureTypeBuilder extends TypeBuilder {
+ /**
+ * The factory to use for creating names.
+ */
+ private final NameFactory nameFactory;
+
+ /**
+ * Builders for the properties (attributes, associations or operations) of this feature.
+ */
+ private final List<PropertyTypeBuilder> properties;
+
+ /**
+ * The parent of the feature to create. By default, new features have no parent.
+ */
+ private final List<DefaultFeatureType> superTypes;
+
+ /**
+ * Whether the feature type is abstract. The default value is {@code false}.
+ *
+ * @see #isAbstract()
+ * @see #setAbstract(boolean)
+ */
+ private boolean isAbstract;
+
+ /**
+ * The default scope to use when {@link #name(String, String)} is invoked with a null scope.
+ *
+ * @see #getDefaultScope()
+ * @see #setDefaultScope(String)
+ */
+ private String defaultScope;
+
+ /**
+ * The default minimum number of property values.
+ *
+ * @see #setDefaultCardinality(int, int)
+ */
+ int defaultMinimumOccurs;
+
+ /**
+ * The default maximum number of property values.
+ *
+ * @see #setDefaultCardinality(int, int)
+ */
+ int defaultMaximumOccurs;
+
+ /**
+ * An optional prefix or suffix to insert before or after the {@linkplain FeatureOperations#compound compound key}
+ * named {@code "@identifier"}.
+ */
+ private String idPrefix, idSuffix;
+
+ /**
+ * The separator to insert between each single component in a {@linkplain FeatureOperations#compound compound key}
+ * named {@code "@identifier"}. This is ignored if {@link #identifierCount} is zero.
+ */
+ private String idDelimiter;
+
+ /**
+ * Number of attribute that have been flagged as an identifier component.
+ *
+ * @see AttributeRole#IDENTIFIER_COMPONENT
+ * @see AttributeConvention#IDENTIFIER_PROPERTY
+ */
+ int identifierCount;
+
+ /**
+ * The default geometry attribute, or {@code null} if none.
+ *
+ * @see AttributeRole#DEFAULT_GEOMETRY
+ * @see AttributeConvention#GEOMETRY_PROPERTY
+ */
+ AttributeTypeBuilder<?> defaultGeometry;
+
+ /**
+ * The object created by this builder, or {@code null} if not yet created.
+ * This field must be cleared every time that a setter method is invoked on this builder.
+ */
+ private transient DefaultFeatureType feature;
+
+ /**
+ * Creates a new builder instance using the default name factory.
+ */
+ public FeatureTypeBuilder() {
+ this(null, null, null);
+ }
+
+ /**
+ * Creates a new builder instance using the given feature type as a template.
+ *
+ * <div class="warning"><b>Warning:</b>
+ * The {@code template} argument type will be changed to {@code FeatureType} if and when such interface
+ * will be defined in GeoAPI.</div>
+ *
+ * @param template an existing feature type to use as a template, or {@code null} if none.
+ */
+ public FeatureTypeBuilder(final DefaultFeatureType template) {
+ this(template, null, null);
+ }
+
+ /**
+ * Creates a new builder instance using the given name factory, template and locale for formatting error messages.
+ *
+ * <div class="warning"><b>Warning:</b>
+ * The {@code template} argument type will be changed to {@code FeatureType} if and when such interface
+ * will be defined in GeoAPI.</div>
+ *
+ * @param template an existing feature type to use as a template, or {@code null} if none.
+ * @param factory the factory to use for creating names, or {@code null} for the default factory.
+ * @param locale the locale to use for formatting error messages, or {@code null} for the default locale.
+ */
+ public FeatureTypeBuilder(final DefaultFeatureType template, NameFactory factory, final Locale locale) {
+ super(template, locale);
+ if (factory == null) {
+ factory = DefaultFactories.forBuildin(NameFactory.class);
+ }
+ nameFactory = factory;
+ properties = new ArrayList<PropertyTypeBuilder>();
+ superTypes = new ArrayList<DefaultFeatureType>();
+ idDelimiter = ":";
+ defaultMinimumOccurs = 1;
+ defaultMaximumOccurs = 1;
+ if (template != null) {
+ feature = template;
+ isAbstract = template.isAbstract();
+ superTypes.addAll(template.getSuperTypes());
+ for (final AbstractIdentifiedType p : template.getProperties(false)) {
+ final PropertyTypeBuilder builder;
+ if (p instanceof DefaultAttributeType<?>) {
+ builder = new AttributeTypeBuilder(this, (DefaultAttributeType<?>) p);
+ } else if (p instanceof DefaultAssociationRole) {
+ builder = new AssociationRoleBuilder(this, (DefaultAssociationRole) p);
+ } else {
+ continue; // Skip unknown types.
+ }
+ properties.add(builder);
+ }
+ }
+ }
+
+ /**
+ * If the {@code FeatureType} created by the last call to {@link #build()} has been cached,
+ * clears that cache. This method must be invoked every time that a setter method is invoked.
+ */
+ @Override
+ final void clearCache() {
+ feature = null;
+ }
+
+ /**
+ * Returns {@code true} if the feature type to create will act as an abstract super-type.
+ * Abstract types can not be {@linkplain DefaultFeatureType#newInstance() instantiated}.
+ *
+ * @return {@code true} if the feature type to create will act as an abstract super-type.
+ *
+ * @see DefaultFeatureType#isAbstract()
+ */
+ public boolean isAbstract() {
+ return isAbstract;
+ }
+
+ /**
+ * Sets whether the feature type to create will be abstract.
+ * If this method is not invoked, then the default value is {@code false}.
+ *
+ * @param isAbstract whether the feature type will be abstract.
+ * @return {@code this} for allowing method calls chaining.
+ */
+ public FeatureTypeBuilder setAbstract(final boolean isAbstract) {
+ if (this.isAbstract != isAbstract) {
+ this.isAbstract = isAbstract;
+ clearCache();
+ }
+ return this;
+ }
+
+ /**
+ * Returns the direct parents of the feature type to create.
+ *
+ * <div class="warning"><b>Warning:</b>
+ * The return type will be changed to {@code FeatureType[]} if and when such interface
+ * will be defined in GeoAPI.</div>
+ *
+ * @return the parents of the feature type to create, or an empty array if none.
+ *
+ * @see DefaultFeatureType#getSuperTypes()
+ */
+ public DefaultFeatureType[] getSuperTypes() {
+ return superTypes.toArray(new DefaultFeatureType[superTypes.size()]);
+ }
+
+ /**
+ * Sets the parent types (or super-type) from which to inherit properties.
+ * If this method is not invoked, then the default value is to have no parent.
+ *
+ * <div class="warning"><b>Warning:</b>
+ * The {@code parents} argument type will be changed to {@code FeatureType...} if and when such interface
+ * will be defined in GeoAPI.</div>
+ *
+ * @param parents the parent types from which to inherit properties, or an empty array if none.
+ * @return {@code this} for allowing method calls chaining.
+ */
+ public FeatureTypeBuilder setSuperTypes(final DefaultFeatureType... parents) {
+ ensureNonNull("parents", parents);
+ final List<DefaultFeatureType> asList = Arrays.asList(parents);
+ if (!superTypes.equals(asList)) {
+ superTypes.clear();
+ superTypes.addAll(asList);
+ clearCache();
+ }
+ return this;
+ }
+
+ /**
+ * Sets the {@code FeatureType} name as a generic name.
+ * If another name was defined before this method call, that previous value will be discarded.
+ *
+ * <div class="note"><b>Note for subclasses:</b>
+ * all {@code setName(…)} convenience methods in this builder delegate to this method.
+ * Consequently this method can be used as a central place where to control the creation of all names.</div>
+ *
+ * @return {@code this} for allowing method calls chaining.
+ */
+ @Override
+ public FeatureTypeBuilder setName(final GenericName name) {
+ super.setName(name);
+ return this;
+ }
+
+ /**
+ * Sets the {@code FeatureType} name as a simple string with the default scope.
+ * The default scope is the value specified by the last call to {@link #setDefaultScope(String)}.
+ * The name will be a {@linkplain org.apache.sis.util.iso.DefaultLocalName local name} if no default scope
+ * has been specified, or a {@linkplain org.apache.sis.util.iso.DefaultScopedName scoped name} otherwise.
+ *
+ * <p>This convenience method creates a {@link GenericName} instance,
+ * then delegates to {@link #setName(GenericName)}.</p>
+ *
+ * @return {@code this} for allowing method calls chaining.
+ */
+ @Override
+ public FeatureTypeBuilder setName(final String localPart) {
+ super.setName(localPart);
+ return this;
+ }
+
+ /**
+ * Sets the {@code FeatureType} name as a string in the given scope.
+ * The name will be a {@linkplain org.apache.sis.util.iso.DefaultLocalName local name} if the given scope is
+ * {@code null} or empty, or a {@linkplain org.apache.sis.util.iso.DefaultScopedName scoped name} otherwise.
+ * If a {@linkplain #setDefaultScope(String) default scope} has been specified, then the
+ * {@code scope} argument overrides it.
+ *
+ * <p>This convenience method creates a {@link GenericName} instance,
+ * then delegates to {@link #setName(GenericName)}.</p>
+ *
+ * @return {@code this} for allowing method calls chaining.
+ */
+ @Override
+ public FeatureTypeBuilder setName(final String scope, final String localPart) {
+ super.setName(scope, localPart);
+ return this;
+ }
+
+ /**
+ * Invoked by {@link TypeBuilder} for creating new {@code LocalName} or {@code GenericName} instances.
+ */
+ @Override
+ final GenericName name(String scope, final String localPart) {
+ if (scope == null) {
+ scope = getDefaultScope();
+ }
+ if (scope == null || scope.isEmpty()) {
+ return nameFactory.createLocalName(null, localPart);
+ } else {
+ return nameFactory.createGenericName(null, scope, localPart);
+ }
+ }
+
+ /**
+ * Returns the scope of the names created by {@code setName(String)} method calls.
+ *
+ * @return the scope to use by default when {@link #setName(String)} is invoked.
+ */
+ public String getDefaultScope() {
+ return defaultScope;
+ }
+
+ /**
+ * Sets the scope of the next names created by {@code setName(String)} method calls.
+ * This method applies only to the next calls to {@code setName(String)};
+ * the result of all previous calls stay unmodified.
+ *
+ * <p>There is different conventions about the use of name scopes. ISO 19109 suggests that the scope of all
+ * {@code AttributeType} names is the name of the enclosing {@code FeatureType}, but this is not mandatory.
+ * Users who want to apply this convention can invoke {@code setDefaultScope(featureName)} after
+ * <code>{@linkplain #setName(String) FeatureTypeBuilder.setName}(featureName)</code> but before
+ * <code>{@linkplain AttributeTypeBuilder#setName(String) AttributeTypeBuilder.setName}(attributeName)</code>.</p>
+ *
+ * @param scope the new default scope, or {@code null} if none.
+ * @return {@code this} for allowing method calls chaining.
+ */
+ public FeatureTypeBuilder setDefaultScope(final String scope) {
+ defaultScope = scope;
+ // No need to clear the cache because this change affects
+ // only the next names to be created, not the existing ones.
+ return this;
+ }
+
+ /**
+ * Sets the default minimum and maximum number of next attributes and associations to add.
+ * Those defaults will applied to newly created attributes or associations,
+ * for example in next calls to {@link #addAttribute(Class)}.
+ * Attributes and associations added before this method call are not modified.
+ *
+ * <p>If this method is not invoked, then the default cardinality is [1 … 1].</p>
+ *
+ * @param minimumOccurs new default minimum number of property values.
+ * @param maximumOccurs new default maximum number of property values.
+ * @return {@code this} for allowing method calls chaining.
+ *
+ * @see AttributeTypeBuilder#setCardinality(int, int)
+ */
+ public FeatureTypeBuilder setDefaultCardinality(final int minimumOccurs, final int maximumOccurs) {
+ if (minimumOccurs < 0 || maximumOccurs < minimumOccurs) {
+ throw new IllegalArgumentException(errors().getString(Errors.Keys.IllegalRange_2, minimumOccurs, maximumOccurs));
+ }
+ defaultMinimumOccurs = minimumOccurs;
+ defaultMaximumOccurs = maximumOccurs;
+ // No need to clear the cache because this change affects only
+ // the next properties to be created, not the existing ones.
+ return this;
+ }
+
+ /**
+ * Sets the prefix, suffix and delimiter to use when formatting a compound identifier made of two or more attributes.
+ * The delimiter will be used only if at least two attributes have the {@linkplain AttributeRole#IDENTIFIER_COMPONENT
+ * identifier component role}.
+ *
+ * <p>If this method is not invoked, then the default values are the {@code ":"} delimiter and no prefix or suffix.</p>
+ *
+ * @param delimiter the characters to use as delimiter between each single property value.
+ * @param prefix characters to use at the beginning of the concatenated string, or {@code null} if none.
+ * @param suffix characters to use at the end of the concatenated string, or {@code null} if none.
+ * @return {@code this} for allowing method calls chaining.
+ *
+ * @see AttributeRole#IDENTIFIER_COMPONENT
+ * @see FeatureOperations#compound(Map, String, String, String, PropertyType...)
+ */
+ public FeatureTypeBuilder setIdentifierDelimiters(final String delimiter, final String prefix, final String suffix) {
+ ensureNonEmpty("delimiter", delimiter);
+ if (!delimiter.equals(idDelimiter) || !Objects.equals(prefix, idPrefix) || !Objects.equals(suffix, idSuffix)) {
+ idDelimiter = delimiter;
+ idPrefix = prefix;
+ idSuffix = suffix;
+ clearCache();
+ }
+ return this;
+ }
+
+ /**
+ * Returns a view of all attributes and associations added to the {@code FeatureType} to build.
+ * The returned list is <cite>live</cite>: changes in this builder are reflected in that list and conversely.
+ * However the returned list allows only {@linkplain List#remove(Object) remove} operations;
+ * new attributes or associations can be added only by calls to one of the {@code addAttribute(…)}
+ * or {@code addAssociation(…)} methods.
+ *
+ * @return a live list over the properties declared to this builder.
+ *
+ * @see #addAttribute(Class)
+ * @see #addAttribute(AttributeType)
+ * @see #addAssociation(FeatureType)
+ * @see #addAssociation(GenericName)
+ * @see #addAssociation(FeatureAssociationRole)
+ */
+ public List<PropertyTypeBuilder> properties() {
+ return new RemoveOnlyList<PropertyTypeBuilder>(properties);
+ }
+
+ /**
+ * Creates a new {@code AttributeType} builder for values of the given class.
+ * The default attribute name is the name of the given type, but callers should invoke one
+ * of the {@code AttributeTypeBuilder.setName(…)} methods on the returned instance with a better name.
+ *
+ * <p>Usage example:</p>
+ * {@preformat java
+ * builder.addAttribute(String.class).setName("City").setDefaultValue("Metropolis");
+ * }
+ *
+ * The value class can not be {@code Feature.class} since features shall be handled
+ * as {@linkplain #addAssociation(FeatureType) associations} instead than attributes.
+ *
+ * @param <V> the compile-time value of {@code valueClass} argument.
+ * @param valueClass the class of attribute values (can not be {@code Feature.class}).
+ * @return a builder for an {@code AttributeType}.
+ *
+ * @see #properties()
+ */
+ public <V> AttributeTypeBuilder<V> addAttribute(final Class<V> valueClass) {
+ ensureNonNull("valueClass", valueClass);
+ if (AbstractFeature.class.isAssignableFrom(valueClass)) {
+ // We disallow Feature.class because that type shall be handled as association instead than attribute.
+ throw new IllegalArgumentException(errors().getString(Errors.Keys.IllegalArgumentValue_2, "valueClass", valueClass));
+ }
+ final AttributeTypeBuilder<V> property = new AttributeTypeBuilder<V>(this, valueClass);
+ properties.add(property);
+ clearCache();
+ return property;
+ }
+
+ /**
+ * Creates a new {@code AttributeType} builder initialized to the same characteristics than the given template.
+ *
+ * <div class="warning"><b>Warning:</b>
+ * The {@code template} argument type will be changed to {@code AttributeType} if and when such interface
+ * will be defined in GeoAPI.</div>
+ *
+ * @param <V> the compile-time type of values in the {@code template} argument.
+ * @param template an existing attribute type to use as a template.
+ * @return a builder for an {@code AttributeType}, initialized with the values of the given template.
+ *
+ * @see #properties()
+ */
+ public <V> AttributeTypeBuilder<V> addAttribute(final DefaultAttributeType<V> template) {
+ ensureNonNull("template", template);
+ final AttributeTypeBuilder<V> property = new AttributeTypeBuilder<V>(this, template);
+ properties.add(property);
+ clearCache();
+ return property;
+ }
+
+ /**
+ * Creates a new {@code FeatureAssociationRole} builder for features of the given type.
+ * The default association name is the name of the given type, but callers should invoke one
+ * of the {@code AssociationRoleBuilder.setName(…)} methods on the returned instance with a better name.
+ *
+ * <div class="warning"><b>Warning:</b>
+ * The {@code type} argument type will be changed to {@code FeatureType} if and when such interface
+ * will be defined in GeoAPI.</div>
+ *
+ * @param type the type of feature values.
+ * @return a builder for a {@code FeatureAssociationRole}.
+ *
+ * @see #properties()
+ */
+ public AssociationRoleBuilder addAssociation(final DefaultFeatureType type) {
+ ensureNonNull("type", type);
+ final AssociationRoleBuilder property = new AssociationRoleBuilder(this, type, type.getName());
+ properties.add(property);
+ clearCache();
+ return property;
+ }
+
+ /**
+ * Creates a new {@code FeatureAssociationRole} builder for features of a type of the given name.
+ * This method can be invoked as an alternative to {@link #addAssociation(FeatureType)} when the
+ * {@code FeatureType} instance is not yet available because of cyclic dependency.
+ *
+ * @param type the name of the type of feature values.
+ * @return a builder for a {@code FeatureAssociationRole}.
+ *
+ * @see #properties()
+ */
+ public AssociationRoleBuilder addAssociation(final GenericName type) {
+ ensureNonNull("type", type);
+ final AssociationRoleBuilder property = new AssociationRoleBuilder(this, null, type);
+ properties.add(property);
+ clearCache();
+ return property;
+ }
+
+ /**
+ * Creates a new {@code FeatureAssociationRole} builder initialized to the same characteristics
+ * than the given template.
+ *
+ * <div class="warning"><b>Warning:</b>
+ * The {@code template} argument type will be changed to {@code FeatureAssociationRole} if and when such interface
+ * will be defined in GeoAPI.</div>
+ *
+ * @param template an existing feature association to use as a template.
+ * @return a builder for an {@code FeatureAssociationRole}, initialized with the values of the given template.
+ *
+ * @see #properties()
+ */
+ public AssociationRoleBuilder addAssociation(final DefaultAssociationRole template) {
+ ensureNonNull("template", template);
+ final AssociationRoleBuilder property = new AssociationRoleBuilder(this, template);
+ properties.add(property);
+ clearCache();
+ return property;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public FeatureTypeBuilder setDefinition(final CharSequence definition) {
+ super.setDefinition(definition);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public FeatureTypeBuilder setDesignation(final CharSequence designation) {
+ super.setDesignation(designation);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public FeatureTypeBuilder setDescription(final CharSequence description) {
+ super.setDescription(description);
+ return this;
+ }
+
+ /**
+ * Builds the feature type from the information and properties specified to this builder.
+ * One of the {@code setName(…)} methods must have been invoked before this {@code build()} method (mandatory).
+ * All other methods are optional, but some calls to a {@code add} method are usually needed.
+ *
+ * <div class="warning"><b>Warning:</b>
+ * The return type will be changed to {@code FeatureType} if and when such interface
+ * will be defined in GeoAPI.</div>
+ *
+ * @return the new feature type.
+ * @throws IllegalStateException if the feature type contains incompatible
+ * {@linkplain AttributeTypeBuilder#setCRS CRS characteristics}.
+ */
+ public DefaultFeatureType build() throws IllegalStateException {
+ if (feature == null) {
+ /*
+ * Creates an initial array of property types with up to 3 slots reserved for @identifier, @geometry
+ * and @envelope operations. At first we presume that there is always an identifier. The identifier
+ * slot will be removed later if there is none.
+ */
+ final int numSpecified = properties.size(); // Number of explicitely specified properties.
+ int numSynthetic; // Number of synthetic properties that may be generated.
+ int envelopeIndex = -1;
+ int geometryIndex = -1;
+ final AbstractIdentifiedType[] identifierTypes;
+ if (identifierCount == 0) {
+ numSynthetic = 0;
+ identifierTypes = null;
+ } else {
+ numSynthetic = 1;
+ identifierTypes = new AbstractIdentifiedType[identifierCount];
+ }
+ if (defaultGeometry != null) {
+ envelopeIndex = numSynthetic;
+ geometryIndex = numSynthetic + 1;
+ numSynthetic += 2;
+ }
+ final AbstractIdentifiedType[] propertyTypes = new AbstractIdentifiedType[numSynthetic + numSpecified];
+ int propertyCursor = numSynthetic;
+ int identifierCursor = 0;
+ for (int i=0; i<numSpecified; i++) {
+ final PropertyTypeBuilder builder = properties.get(i);
+ final AbstractIdentifiedType instance = builder.build();
+ propertyTypes[propertyCursor] = instance;
+ /*
+ * Collect the attributes to use as identifier components while we loop over all properties.
+ * A NullPointerException or an ArrayIndexOutOfBoundsException in this block would mean that
+ * identifierCount field has not been updated correctly by an addRole(AttributeRole) method.
+ */
+ if (builder.isIdentifier()) {
+ identifierTypes[identifierCursor++] = instance;
+ }
+ /*
+ * If there is a default geometry, add a link named "@geometry" to that geometry.
+ * It may happen that the property created by the user is already named "@geometry",
+ * in which case we will avoid to duplicate the property.
+ */
+ if (builder == defaultGeometry) {
+ if (propertyTypes[geometryIndex] != null) {
+ // Assuming that there is no bug in our implementation, this error could happen if the user
+ // has modified this FeatureTypeBuilder in another thread during this build() execution.
+ throw new CorruptedObjectException();
+ }
+ if (AttributeConvention.GEOMETRY_PROPERTY.equals(instance.getName())) {
+ System.arraycopy(propertyTypes, geometryIndex, propertyTypes, geometryIndex-1, (numSynthetic - geometryIndex) + i);
+ geometryIndex = -1;
+ numSynthetic--;
+ continue; // Skip the increment of propertyCursor.
+ }
+ propertyTypes[geometryIndex] = FeatureOperations.link(name(AttributeConvention.GEOMETRY_PROPERTY), instance);
+ }
+ propertyCursor++;
+ }
+ /*
+ * Create the "envelope" operation only after we created all other properties.
+ * Actually it is okay if the 'propertyTypes' array still contains null elements not needed for envelope calculation
+ * like "@identifier", since FeatureOperations.envelope(…) constructor ignores any property which is not for a value.
+ */
+ if (envelopeIndex >= 0) try {
+ propertyTypes[envelopeIndex] = FeatureOperations.envelope(name(AttributeConvention.ENVELOPE_PROPERTY), null, propertyTypes);
+ } catch (FactoryException e) {
+ throw new IllegalStateException(e);
+ }
+ /*
+ * If a synthetic identifier need to be created, create it now as the first property.
+ * It may happen that the user provided a single identifier component already named
+ * "@identifier", in which case we avoid to duplicate the property.
+ */
+ if (identifierTypes != null) {
+ if (identifierCursor != identifierTypes.length) {
+ // Assuming that there is no bug in our implementation, this error could happen if the user
+ // has modified this FeatureTypeBuilder in another thread during this build() execution.
+ throw new CorruptedObjectException();
+ }
+ if (identifierCursor == 1 && AttributeConvention.IDENTIFIER_PROPERTY.equals(identifierTypes[0].getName())) {
+ System.arraycopy(propertyTypes, 1, propertyTypes, 0, --propertyCursor);
+ } else {
+ propertyTypes[0] = FeatureOperations.compound(name(AttributeConvention.IDENTIFIER_PROPERTY),
+ idDelimiter, idPrefix, idSuffix, identifierTypes);
+ }
+ }
+ feature = new DefaultFeatureType(identification(), isAbstract(),
+ superTypes.toArray(new DefaultFeatureType[superTypes.size()]),
+ ArraysExt.resize(propertyTypes, propertyCursor));
+ }
+ return feature;
+ }
+
+ /**
+ * Helper method for creating identification info of synthetic attributes.
+ */
+ static Map<String,?> name(final GenericName name) {
+ return Collections.singletonMap(AbstractOperation.NAME_KEY, name);
+ }
+
+ /**
+ * Formats a string representation of this builder for debugging purpose.
+ */
+ @Override
+ final void toStringInternal(final StringBuilder buffer) {
+ if (isAbstract()) {
+ buffer.insert(buffer.indexOf("[") + 1, "abstract ");
+ }
+ String separator = " : ";
+ for (final DefaultFeatureType parent : superTypes) {
+ buffer.append(separator).append('“').append(parent.getName()).append('”');
+ separator = ", ";
+ }
+ buffer.append(" {");
+ separator = System.lineSeparator();
+ for (final PropertyTypeBuilder p : properties) {
+ p.toString(buffer.append(separator).append(" ").append(p.getClass().getSimpleName()));
+ }
+ buffer.append(separator).append('}');
+ }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/PropertyTypeBuilder.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/PropertyTypeBuilder.java
new file mode 100644
index 0000000..42bdfc6
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/PropertyTypeBuilder.java
@@ -0,0 +1,151 @@
+/*
+ * 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.feature.builder;
+
+import org.opengis.util.GenericName;
+import org.apache.sis.util.resources.Errors;
+
+// Branch-dependent imports
+import org.apache.sis.feature.AbstractIdentifiedType;
+
+
+/**
+ * Describes one property of the {@code FeatureType} to be built by an {@code FeatureTypeBuilder}.
+ * A different instance of {@code PropertyTypeBuilder} exists for each property to describe.
+ * Those instances can be created by:
+ *
+ * <ul>
+ * <li>{@link FeatureTypeBuilder#addAttribute(Class)}</li>
+ * <li>{@link FeatureTypeBuilder#addAttribute(AttributeType)} for using an existing attribute as a template</li>
+ * <li>{@link FeatureTypeBuilder#addAssociation(FeatureType)}</li>
+ * <li>{@link FeatureTypeBuilder#addAssociation(GenericName)}</li>
+ * <li>{@link FeatureTypeBuilder#addAssociation(FeatureAssociationRole)} for using an existing association as a template</li>
+ * </ul>
+ *
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.8
+ * @version 0.8
+ * @module
+ */
+public abstract class PropertyTypeBuilder extends TypeBuilder {
+ /**
+ * The feature type builder instance that created this {@code PropertyTypeBuilder}.
+ */
+ final FeatureTypeBuilder owner;
+
+ /**
+ * The minimum number of property values.
+ * The default value is 1, unless otherwise specified by {@link #setDefaultCardinality(int, int)}.
+ *
+ * @see #setCardinality(int, int)
+ */
+ int minimumOccurs;
+
+ /**
+ * The maximum number of property values.
+ * The default value is 1, unless otherwise specified by {@link #setDefaultCardinality(int, int)}.
+ *
+ * @see #setCardinality(int, int)
+ */
+ int maximumOccurs;
+
+ /**
+ * The attribute or association created by this builder, or {@code null} if not yet created.
+ * This field must be cleared every time that a setter method is invoked on this builder.
+ */
+ private transient AbstractIdentifiedType property;
+
+ /**
+ * Creates a new {@code PropertyType} builder initialized to the values of an existing property.
+ *
+ * @param owner the builder of the {@code FeatureType} for which to add this property.
+ * @param template an existing property to use as a template, or {@code null} if none.
+ */
+ PropertyTypeBuilder(final FeatureTypeBuilder owner, final AbstractIdentifiedType template) {
+ super(template, owner.getLocale());
+ this.owner = owner;
+ minimumOccurs = owner.defaultMinimumOccurs;
+ maximumOccurs = owner.defaultMaximumOccurs;
+ property = template;
+ }
+
+ /**
+ * Sets the minimum and maximum number of property values. Those numbers must be equal or greater than zero.
+ *
+ * <p>If this method is not invoked, then the default values are the cardinality specified by the last call
+ * to {@link FeatureTypeBuilder#setDefaultCardinality(int, int)} at the time this instance has been created.
+ * If the later method has not been invoked, then the default cardinality is [1 … 1].</p>
+ *
+ * @param minimumOccurs new minimum number of property values.
+ * @param maximumOccurs new maximum number of property values.
+ * @return {@code this} for allowing method calls chaining.
+ */
+ @SuppressWarnings("unchecked")
+ public PropertyTypeBuilder setCardinality(final int minimumOccurs, final int maximumOccurs) {
+ if (this.minimumOccurs != minimumOccurs || this.maximumOccurs != maximumOccurs) {
+ if (minimumOccurs < 0 || maximumOccurs < minimumOccurs) {
+ throw new IllegalArgumentException(errors().getString(Errors.Keys.IllegalRange_2, minimumOccurs, maximumOccurs));
+ }
+ this.minimumOccurs = minimumOccurs;
+ this.maximumOccurs = maximumOccurs;
+ clearCache();
+ }
+ return this;
+ }
+
+ /**
+ * Returns {@code true} if {@link AttributeRole#IDENTIFIER_COMPONENT} has been associated to this property.
+ */
+ boolean isIdentifier() {
+ return false;
+ }
+
+ /**
+ * Delegates the creation of a new name to the enclosing builder.
+ */
+ @Override
+ final GenericName name(final String scope, final String localPart) {
+ return owner.name(scope, localPart);
+ }
+
+ /**
+ * If the {@code PropertyType} created by the last call to {@link #build()} has been cached,
+ * clears that cache. This method must be invoked every time that a setter method is invoked.
+ */
+ @Override
+ final void clearCache() {
+ property = null;
+ owner.clearCache();
+ }
+
+ /**
+ * Returns the property type from the current setting.
+ * This method may return an existing property if it was already created.
+ */
+ final AbstractIdentifiedType build() {
+ if (property == null) {
+ property = create();
+ }
+ return property;
+ }
+
+ /**
+ * Creates a new property type from the current setting.
+ */
+ abstract AbstractIdentifiedType create();
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/RemoveOnlyList.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/RemoveOnlyList.java
new file mode 100644
index 0000000..327f7ed
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/RemoveOnlyList.java
@@ -0,0 +1,51 @@
+/*
+ * 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.feature.builder;
+
+import java.util.AbstractList;
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * Wraps another list in a new list allowing only read and remove operations.
+ * Addition of new values are not allowed through this {@code RemoveOnlyList}.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.8
+ * @version 0.8
+ * @module
+ */
+final class RemoveOnlyList<E> extends AbstractList<E> {
+ /**
+ * The original list to wrap.
+ */
+ private final List<E> elements;
+
+ /**
+ * Creates a new list wrapping the given list.
+ */
+ RemoveOnlyList(final List<E> elements) {
+ this.elements = elements;
+ }
+
+ @Override public void clear() { elements.clear();}
+ @Override public int size() {return elements.size();}
+ @Override public E get(int index) {return elements.get(index);}
+ @Override public E remove(int index) {return elements.get(index);}
+ @Override public Iterator<E> iterator() {return elements.iterator();}
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/TypeBuilder.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/TypeBuilder.java
new file mode 100644
index 0000000..895a369
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/TypeBuilder.java
@@ -0,0 +1,386 @@
+/*
+ * 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.feature.builder;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Locale;
+import org.opengis.util.GenericName;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.NullArgumentException;
+import org.apache.sis.util.Localized;
+import org.apache.sis.util.Classes;
+import org.apache.sis.util.Debug;
+
+// Branch-dependent imports
+import java.util.Objects;
+
+
+/**
+ * Properties common to all kind of types (feature, association, characteristics).
+ * Those properties are:
+ *
+ * <ul>
+ * <li>the name — a unique name which can be defined within a scope (or namespace).</li>
+ * <li>the definition — a concise definition of the element.</li>
+ * <li>the designation — a natural language designator for the element for user interfaces.</li>
+ * <li>the description — information beyond that required for concise definition of the element.</li>
+ * </ul>
+ *
+ * In many cases, the names of all {@code AttributeType}s and {@code AssociationRole}s to create
+ * within a {@code FeatureType} share the same namespace.
+ * For making name creations more convenient, a default namespace can be
+ * {@linkplain FeatureTypeBuilder#setDefaultScope specified once} and applied automatically
+ * to all names created by the {@link #setName(String)} method.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.8
+ * @version 0.8
+ * @module
+ */
+public abstract class TypeBuilder implements Localized {
+ /**
+ * The feature name, definition, designation and description.
+ * The name is mandatory; all other information are optional.
+ */
+ private final Map<String,Object> identification = new HashMap<String,Object>(4);
+
+ /**
+ * Creates a new builder initialized to the values of an existing type.
+ */
+ TypeBuilder(final AbstractIdentifiedType template, final Locale locale) {
+ putIfNonNull(Errors.LOCALE_KEY, locale);
+ if (template != null) {
+ putIfNonNull(AbstractIdentifiedType.NAME_KEY, template.getName());
+ putIfNonNull(AbstractIdentifiedType.DEFINITION_KEY, template.getDefinition());
+ putIfNonNull(AbstractIdentifiedType.DESIGNATION_KEY, template.getDesignation());
+ putIfNonNull(AbstractIdentifiedType.DESCRIPTION_KEY, template.getDescription());
+ }
+ }
+
+ /**
+ * Puts the given value in the {@link #identification} map if the value is non-null.
+ * This method should be invoked only when the {@link #identification} map is known
+ * to not contain any value for the given key.
+ */
+ private void putIfNonNull(final String key, final Object value) {
+ if (value != null) {
+ identification.put(key, value);
+ }
+ }
+
+ /**
+ * Returns the map of properties to give to the {@code FeatureType} or {@code PropertyType} constructor.
+ * If the map does not contains a name, a default name may be generated.
+ */
+ @SuppressWarnings("ReturnOfCollectionOrArrayField")
+ final Map<String,Object> identification() {
+ if (identification.get(AbstractIdentifiedType.NAME_KEY) == null) {
+ String name = getDefaultName();
+ if (name != null) {
+ final int length = name.length();
+ if (length != 0) {
+ final int c = name.codePointAt(0);
+ final int lc = Character.toLowerCase(c);
+ if (c != lc) {
+ final int n = Character.charCount(c);
+ if (n >= length || Character.isLowerCase(name.codePointAt(n))) {
+ final StringBuilder buffer = new StringBuilder(length);
+ name = buffer.appendCodePoint(lc).append(name, n, length).toString();
+ }
+ }
+ identification.put(AbstractIdentifiedType.NAME_KEY, name(null, name));
+ }
+ }
+ }
+ return identification;
+ }
+
+ /**
+ * If the object created by the last call to {@code build()} has been cached, clears that cache.
+ */
+ abstract void clearCache();
+
+ /**
+ * Creates a generic name from the given scope and local part.
+ * An empty scope means no scope. A {@code null} scope means the
+ * {@linkplain FeatureTypeBuilder#setDefaultScope(String) default scope}.
+ *
+ * @param scope the scope of the name to create, or {@code null} if the name is local.
+ * @param localPart the local part of the generic name (can not be {@code null}).
+ */
+ abstract GenericName name(String scope, String localPart);
+
+ /**
+ * Returns the name of the {@code IdentifiedType} to create, or {@code null} if undefined.
+ * This method returns the value built from the last call to a {@code setName(…)} method,
+ * or a default name or {@code null} if no name has been explicitely specified.
+ *
+ * @return the name of the {@code IdentifiedType} to create (may be a default name or {@code null}).
+ *
+ * @see AbstractIdentifiedType#getName()
+ */
+ public GenericName getName() {
+ return (GenericName) identification().get(AbstractIdentifiedType.NAME_KEY);
+ }
+
+ /**
+ * Returns a default name to use if the user did not specified a name. The first letter will be changed to
+ * lower case (unless the name looks like an acronym) for compliance with Java convention on property names.
+ */
+ String getDefaultName() {
+ return null;
+ }
+
+ /**
+ * Returns the name to use for displaying error messages.
+ */
+ final String getDisplayName() {
+ final GenericName name = getName();
+ return (name != null) ? name.toString() : Vocabulary.getResources(identification).getString(Vocabulary.Keys.Unnamed);
+ }
+
+ /**
+ * Sets the {@code IdentifiedType} name as a generic name.
+ * If another name was defined before this method call, that previous value will be discarded.
+ *
+ * <div class="note"><b>Note for subclasses:</b>
+ * all {@code setName(…)} convenience methods in this builder delegate to this method.
+ * Consequently this method can be used as a central place where to control the creation of all names.</div>
+ *
+ * @param name the generic name (can not be {@code null}).
+ * @return {@code this} for allowing method calls chaining.
+ *
+ * @see #getName()
+ * @see AbstractIdentifiedType#NAME_KEY
+ */
+ public TypeBuilder setName(final GenericName name) {
+ ensureNonNull("name", name);
+ if (!name.equals(identification.put(AbstractIdentifiedType.NAME_KEY, name))) {
+ clearCache();
+ }
+ return this;
+ }
+
+ /**
+ * Sets the {@code IdentifiedType} name as a simple string with the default scope.
+ * The default scope is the value specified by the last call to {@link FeatureTypeBuilder#setDefaultScope(String)}.
+ * The name will be a {@linkplain org.apache.sis.util.iso.DefaultLocalName local name} if no default scope
+ * has been specified, or a {@linkplain org.apache.sis.util.iso.DefaultScopedName scoped name} otherwise.
+ *
+ * <p>This convenience method creates a {@link GenericName} instance,
+ * then delegates to {@link #setName(GenericName)}.</p>
+ *
+ * @param localPart the local part of the generic name (can not be {@code null}).
+ * @return {@code this} for allowing method calls chaining.
+ *
+ * @see #getName()
+ */
+ public TypeBuilder setName(final String localPart) {
+ ensureNonEmpty("localPart", localPart);
+ return setName(name(null, localPart));
+ }
+
+ /**
+ * Sets the {@code IdentifiedType} name as a string in the given scope.
+ * The name will be a {@linkplain org.apache.sis.util.iso.DefaultLocalName local name} if the given scope is
+ * {@code null} or empty, or a {@linkplain org.apache.sis.util.iso.DefaultScopedName scoped name} otherwise.
+ * If a {@linkplain FeatureTypeBuilder#setDefaultScope(String) default scope} has been specified, then the
+ * {@code scope} argument overrides it.
+ *
+ * <p>This convenience method creates a {@link GenericName} instance,
+ * then delegates to {@link #setName(GenericName)}.</p>
+ *
+ * @param scope the scope of the name to create, or {@code null} if the name is local.
+ * @param localPart the local part of the generic name (can not be {@code null}).
+ * @return {@code this} for allowing method calls chaining.
+ *
+ * @see #getName()
+ */
+ public TypeBuilder setName(String scope, final String localPart) {
+ ensureNonEmpty("localPart", localPart);
+ if (scope == null) {
+ scope = ""; // For preventing the use of default scope.
+ }
+ return setName(name(scope, localPart));
+ }
+
+ /**
+ * Returns a concise definition of the element.
+ *
+ * @return concise definition of the element, or {@code null} if none.
+ *
+ * @see AbstractIdentifiedType#getDefinition()
+ */
+ public CharSequence getDefinition() {
+ return (CharSequence) identification.get(AbstractIdentifiedType.DEFINITION_KEY);
+ }
+
+ /**
+ * Sets a concise definition of the element.
+ *
+ * @param definition a concise definition of the element, or {@code null} if none.
+ * @return {@code this} for allowing method calls chaining.
+ *
+ * @see #getDefinition()
+ * @see AbstractIdentifiedType#DEFINITION_KEY
+ */
+ public TypeBuilder setDefinition(final CharSequence definition) {
+ if (!Objects.equals(definition, identification.put(AbstractIdentifiedType.DEFINITION_KEY, definition))) {
+ clearCache();
+ }
+ return this;
+ }
+
+ /**
+ * Returns a natural language designator for the element.
+ * This can be used as an alternative to the {@linkplain #getName() name} in user interfaces.
+ *
+ * @return natural language designator for the element, or {@code null} if none.
+ *
+ * @see AbstractIdentifiedType#getDesignation()
+ */
+ public CharSequence getDesignation() {
+ return (CharSequence) identification.get(AbstractIdentifiedType.DESIGNATION_KEY);
+ }
+
+ /**
+ * Sets a natural language designator for the element.
+ * This can be used as an alternative to the {@linkplain #getName() name} in user interfaces.
+ *
+ * @param designation a natural language designator for the element, or {@code null} if none.
+ * @return {@code this} for allowing method calls chaining.
+ *
+ * @see #getDesignation()
+ * @see AbstractIdentifiedType#DESIGNATION_KEY
+ */
+ public TypeBuilder setDesignation(final CharSequence designation) {
+ if (!Objects.equals(designation, identification.put(AbstractIdentifiedType.DESIGNATION_KEY, designation))) {
+ clearCache();
+ }
+ return this;
+ }
+
+ /**
+ * Returns optional information beyond that required for concise definition of the element.
+ * The description may assist in understanding the element scope and application.
+ *
+ * @return information beyond that required for concise definition of the element, or {@code null} if none.
+ *
+ * @see AbstractIdentifiedType#getDescription()
+ */
+ public CharSequence getDescription() {
+ return (CharSequence) identification.get(AbstractIdentifiedType.DESCRIPTION_KEY);
+ }
+
+ /**
+ * Sets optional information beyond that required for concise definition of the element.
+ * The description may assist in understanding the feature scope and application.
+ *
+ * @param description information beyond that required for concise definition of the element, or {@code null} if none.
+ * @return {@code this} for allowing method calls chaining.
+ *
+ * @see #getDescription()
+ * @see AbstractIdentifiedType#DESCRIPTION_KEY
+ */
+ public TypeBuilder setDescription(final CharSequence description) {
+ if (!Objects.equals(description, identification.put(AbstractIdentifiedType.DESCRIPTION_KEY, description))) {
+ clearCache();
+ }
+ return this;
+ }
+
+ /**
+ * Returns the locale used for formatting error messages, or {@code null} if unspecified.
+ * If unspecified, the system default locale will be used.
+ *
+ * @return the locale used for formatting error messages, or {@code null} if unspecified.
+ */
+ @Override
+ public Locale getLocale() {
+ return (Locale) identification.get(Errors.LOCALE_KEY);
+ }
+
+ /**
+ * Returns the resources for error messages.
+ */
+ final Errors errors() {
+ return Errors.getResources(identification);
+ }
+
+ /**
+ * Same as {@link org.apache.sis.util.ArgumentChecks#ensureNonNull(String, Object)},
+ * but uses the current locale in case of error.
+ *
+ * @param name the name of the argument to be checked. Used only if an exception is thrown.
+ * @param object the user argument to check against null value.
+ * @throws NullArgumentException if {@code object} is null.
+ */
+ final void ensureNonNull(final String name, final Object value) {
+ if (value == null) {
+ throw new NullArgumentException(errors().getString(Errors.Keys.NullArgument_1, name));
+ }
+ }
+
+ /**
+ * Same as {@link org.apache.sis.util.ArgumentChecks#ensureNonEmpty(String, CharSequence)},
+ * but uses the current locale in case of error.
+ *
+ * @param name the name of the argument to be checked. Used only if an exception is thrown.
+ * @param text the user argument to check against null value and empty sequences.
+ * @throws NullArgumentException if {@code text} is null.
+ * @throws IllegalArgumentException if {@code text} is empty.
+ */
+ final void ensureNonEmpty(final String name, final String text) {
+ if (text == null) {
+ throw new NullArgumentException(errors().getString(Errors.Keys.NullArgument_1, name));
+ }
+ if (text.length() == 0) {
+ throw new IllegalArgumentException(errors().getString(Errors.Keys.EmptyArgument_1, name));
+ }
+ }
+
+ /**
+ * Returns a string representation of this object.
+ * The returned string is for debugging purpose only and may change in any future SIS version.
+ *
+ * @return a string representation of this object for debugging purpose.
+ */
+ @Debug
+ @Override
+ public String toString() {
+ return toString(new StringBuilder(Classes.getShortClassName(this))).toString();
+ }
+
+ /**
+ * Partial implementation of {@link #toString()}. This method assumes that the class name
+ * has already been written in the buffer.
+ */
+ final StringBuilder toString(final StringBuilder buffer) {
+ toStringInternal(buffer.append("[“").append(getDisplayName()).append('”'));
+ return buffer.append(']');
+ }
+
+ /**
+ * Appends a text inside the value returned by {@link #toString()}, before the closing bracket.
+ */
+ void toStringInternal(StringBuilder buffer) {
+ }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/package-info.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/package-info.java
new file mode 100644
index 0000000..58850f8
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/package-info.java
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+/**
+ * Helper classes for creating {@code FeatureType} instances. Usage of this package is not mandatory,
+ * but make easier to create {@link org.apache.sis.feature.DefaultFeatureType} instances together with
+ * their attributes and associations.
+ *
+ * <p>The starting point is {@link org.apache.sis.feature.builder.FeatureTypeBuilder}.
+ * Example:</p>
+ *
+ * {@preformat java
+ * // Create a feature type for a city, which contains a name and a population.
+ * FeatureTypeBuilder builder = new FeatureTypeBuilder().setName("City");
+ * builder.addAttribute(String.class).setName("name");
+ * builder.addAttribute(Integer.class).setName("population");
+ * FeatureType city = builder.build();
+ *
+ * // Create a subclass for a city which is also a capital.
+ * builder = new FeatureTypeBuilder().setName("Capital").setSuperTypes(city);
+ * builder.addAttribute(String.class).setName("parliament");
+ * FeatureType capital = builder.build();
+ * }
+ *
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.8
+ * @version 0.8
+ * @module
+ *
+ * @see org.apache.sis.feature.DefaultFeatureType
+ */
+package org.apache.sis.feature.builder;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/package-info.java b/core/sis-feature/src/main/java/org/apache/sis/feature/package-info.java
index 7506afd..40332cb 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/package-info.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/package-info.java
@@ -72,17 +72,29 @@
* {@code ├─} {@linkplain org.apache.sis.feature.DefaultAssociationRole Feature association role}<br>
* {@code └─} {@linkplain org.apache.sis.feature.AbstractOperation Operation}<br>
* </td><td class="sep" style="width: 50%; white-space: nowrap">
- * {@linkplain org.apache.sis.feature.AbstractFeature Feature} (<cite>sparse</cite> or <cite>dense</cite>)<br>
- * Property<br>
- * {@code ├─} {@linkplain org.apache.sis.feature.AbstractAttribute Attribute} (<cite>singleton</cite> or <cite>multi-valued</cite>)<br>
- * {@code └─} {@linkplain org.apache.sis.feature.AbstractAssociation Feature association} (<cite>singleton</cite> or <cite>multi-valued</cite>)<br>
+ * Object<br>
+ * {@code ├─} {@linkplain org.apache.sis.feature.AbstractFeature Feature} (<cite>sparse</cite> or <cite>dense</cite>)<br>
+ * {@code └─} Property<br>
+ * {@code ├─} {@linkplain org.apache.sis.feature.AbstractAttribute Attribute} (<cite>singleton</cite> or <cite>multi-valued</cite>)<br>
+ * {@code └─} {@linkplain org.apache.sis.feature.AbstractAssociation Feature association} (<cite>singleton</cite> or <cite>multi-valued</cite>)<br>
* </td></tr></table>
*
+ * <div class="section">Instantiation</div>
+ * Classes defined in this package are rarely instantiated directly (by a {@code new} statement).
+ * Instead those classes are instantiated indirectly by invoking a method on a parent container,
+ * or by using a builder. The starting point is {@code FeatureType}, which may be created by a
+ * {@link org.apache.sis.feature.builder.FeatureTypeBuilder} or may be provided by a
+ * {@link org.apache.sis.storage.DataStore} reading a data file.
+ * Once a {@code FeatureType} has been obtained, {@code Feature}s can be instantiated by calls to the
+ * {@link org.apache.sis.feature.DefaultFeatureType#newInstance() FeatureType.newInstance()} method.
+ * Once a {@code Feature} instance has been obtained, {@code Attribute}s can be instantiated indirectly
+ * by calls to the {@link org.apache.sis.feature.AbstractFeature#setPropertyValue Feature.setPropertyValue(…)} method.
+ *
* @author Travis L. Pinney
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
* @since 0.5
- * @version 0.5
+ * @version 0.8
* @module
*/
package org.apache.sis.feature;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeConvention.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeConvention.java
index f6cb733..ce210d6 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeConvention.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeConvention.java
@@ -40,10 +40,10 @@
* appropriate "real" property in the feature.
*
* <div class="note"><b>Example:</b>
- * one of the most frequently used synthetic property is {@code "@id"}, which contains a unique
+ * one of the most frequently used synthetic property is {@code "@identifier"}, which contains a unique
* identifier (or primary key) for the feature. This property is usually (but not necessarily)
* a {@linkplain org.apache.sis.feature.FeatureOperations#link link to an existing attribute}.
- * By using the {@code "@id"} alias, users do not need to know the name of the "real" attribute.
+ * By using the {@code "@identifier"} alias, users do not need to know the name of the "real" attribute.
* </div>
*
* This class defines names for two kinds of usage:
@@ -60,7 +60,7 @@
* but we may refactor this class in future SIS versions if there is a need to support different conventions.
*
* <p>In order to reduce the risk of name collision with properties in user-defined features
- * (e.g. the user may already have an attribute named {@code "id"} for his own purpose),
+ * (e.g. the user may already have an attribute named {@code "identifier"} for his own purpose),
* all names defined in this class begin with the {@code "@"} character.</p>
*
* @author Johann Sorel (Geomatys)
@@ -89,7 +89,7 @@
* <p>The {@linkplain org.apache.sis.feature.DefaultAttributeType#getValueClass() value class} is usually
* {@link String}, {@link Integer}, {@link java.util.UUID} or other types commonly used as identifiers.</p>
*/
- public static final LocalName ID_PROPERTY;
+ public static final LocalName IDENTIFIER_PROPERTY;
/**
* Conventional name for a property containing the geometric object to use by default.
@@ -106,11 +106,11 @@
*
* @see #isGeometryAttribute(IdentifiedType)
*/
- public static final LocalName DEFAULT_GEOMETRY_PROPERTY;
+ public static final LocalName GEOMETRY_PROPERTY;
/**
* Conventional name for fetching the envelope encompassing all geometries in a feature. Most {@code FeatureType}s
- * have at most one geometry, which is also the {@linkplain #DEFAULT_GEOMETRY_PROPERTY default geometry}.
+ * have at most one geometry, which is also the {@link #GEOMETRY_PROPERTY default geometry}.
* But if several geometries exist, then the value for this synthetic property is the union of all geometries.
*
* <p>Properties of this name are usually
@@ -125,10 +125,10 @@
* Conventional name for fetching the Coordinate Reference System (CRS) of a geometry or a coverage.
* This characteristic is typically an entry in the map returned by a call to the
* {@link org.apache.sis.feature.DefaultAttributeType#characteristics()} method
- * on the attribute referenced by {@link #DEFAULT_GEOMETRY_PROPERTY}.
+ * on the attribute referenced by {@link #GEOMETRY_PROPERTY}.
*
* <p>While it is technically possible to have different CRS for different feature instances,
- * in most cases the CRS is the same for all geometries found in {@code DEFAULT_GEOMETRY_PROPERTY}.
+ * in most cases the CRS is the same for all geometries found in {@code GEOMETRY_PROPERTY}.
* In such cases, the CRS can be specified only once as the
* {@linkplain org.apache.sis.feature.DefaultAttributeType#getDefaultValue() default value}
* of this {@code CRS_CHARACTERISTIC}.</p>
@@ -167,8 +167,8 @@
final NameFactory factory = DefaultFactories.forBuildin(NameFactory.class);
NAMESPACE = factory.createGenericName(null, "Apache", Constants.SIS);
NameSpace ns = factory.createNameSpace(NAMESPACE, null);
- ID_PROPERTY = factory.createLocalName(ns, "@identifier");
- DEFAULT_GEOMETRY_PROPERTY = factory.createLocalName(ns, "@geometry");
+ IDENTIFIER_PROPERTY = factory.createLocalName(ns, "@identifier");
+ GEOMETRY_PROPERTY = factory.createLocalName(ns, "@geometry");
ENVELOPE_PROPERTY = factory.createLocalName(ns, "@envelope");
CRS_CHARACTERISTIC = factory.createLocalName(ns, "@crs");
MAXIMAL_LENGTH_CHARACTERISTIC = factory.createLocalName(ns, "@maximalLength");
@@ -222,7 +222,7 @@
* @return {@code true} if the given type is (directly or indirectly) an attribute type
* for one of the recognized geometry types.
*
- * @see #DEFAULT_GEOMETRY_PROPERTY
+ * @see #GEOMETRY_PROPERTY
*/
public static boolean isGeometryAttribute(AbstractIdentifiedType type) {
while (type instanceof AbstractOperation) {
@@ -252,7 +252,7 @@
* @throws ClassCastException if {@link #CRS_CHARACTERISTIC} has been found but is associated
* to an object which is not a {@link CoordinateReferenceSystem} instance.
*
- * @see org.apache.sis.internal.feature.FeatureTypeBuilder.Property#setCRSCharacteristic(CoordinateReferenceSystem)
+ * @see org.apache.sis.feature.builder.AttributeTypeBuilder#setCRS(CoordinateReferenceSystem)
*/
public static CoordinateReferenceSystem getCRSCharacteristic(final Object attribute) {
return (CoordinateReferenceSystem) getCharacteristic(attribute, CRS_CHARACTERISTIC.toString());
@@ -279,7 +279,7 @@
* @throws ClassCastException if {@link #MAXIMAL_LENGTH_CHARACTERISTIC} has been found but is associated
* to an object which is not an {@link Integer} instance.
*
- * @see org.apache.sis.internal.feature.FeatureTypeBuilder.Property#setMaximalLengthCharacteristic(Integer)
+ * @see org.apache.sis.feature.builder.AttributeTypeBuilder#setMaximalLength(Integer)
*/
public static Integer getMaximalLengthCharacteristic(final Object attribute) {
return (Integer) getCharacteristic(attribute, MAXIMAL_LENGTH_CHARACTERISTIC.toString());
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Builder.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Builder.java
deleted file mode 100644
index 6f519e0..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Builder.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * 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.feature;
-
-import java.util.HashMap;
-import java.util.Map;
-import org.opengis.util.GenericName;
-import org.apache.sis.feature.AbstractIdentifiedType;
-import org.apache.sis.util.resources.Vocabulary;
-import org.apache.sis.util.ArgumentChecks;
-
-
-/**
- * Base class of feature and attribute builders.
- * This base class provide the method needed for filling the {@code identification} map.
- *
- * @param <T> the builder subclass. It is subclass responsibility to ensure that {@code this}
- * is assignable to {@code <T>}; this {@code Builder} class can not verify that.
- *
- * @author Johann Sorel (Geomatys)
- * @author Martin Desruisseaux (Geomatys)
- * @since 0.7
- * @version 0.7
- * @module
- */
-abstract class Builder<T extends Builder<T>> {
- /**
- * The feature name, definition, designation and description.
- * The name is mandatory; all other information are optional.
- */
- final Map<String,Object> identification = new HashMap<String,Object>(4);
-
- /**
- * Creates a new builder instance.
- */
- Builder() {
- }
-
- /**
- * Resets this builder to its initial state. After invocation of this method,
- * this builder is in the same state than after construction.
- *
- * @return {@code this} for allowing method calls chaining.
- */
- @SuppressWarnings("unchecked")
- public T clear() {
- identification.clear();
- return (T) this;
- }
-
- /**
- * Creates a generic name from the given scope and local part.
- * An empty scope means no scope. A {@code null} scope means the
- * {@linkplain FeatureTypeBuilder#setDefaultScope(String) default scope}.
- *
- * @param scope the scope of the name to create, or {@code null} if the name is local.
- * @param localPart the local part of the generic name (can not be {@code null}).
- */
- abstract GenericName name(String scope, String localPart);
-
- /**
- * Sets the feature type name as a simple string with the default scope.
- * The default scope is the value specified by the last call to
- * {@link FeatureTypeBuilder#setDefaultScope(String)}.
- *
- * <p>The name will be an instance of {@link org.opengis.util.LocalName} if no default scope
- * has been specified, or an instance of {@link org.opengis.util.ScopedName} otherwise.</p>
- *
- * <p>This convenience method creates a {@link GenericName} instance,
- * then delegates to {@link #setName(GenericName)}.</p>
- *
- * @param localPart the local part of the generic name (can not be {@code null}).
- * @return {@code this} for allowing method calls chaining.
- */
- public T setName(String localPart) {
- ArgumentChecks.ensureNonEmpty("localPart", localPart);
- return setName(name(null, localPart));
- }
-
- /**
- * Sets the feature type name as a string in the given scope.
- * If a {@linkplain FeatureTypeBuilder#setDefaultScope(String) default scope} was specified,
- * this method override it.
- *
- * <p>The name will be an instance of {@link org.opengis.util.LocalName} if the given scope
- * is {@code null} or empty, or an instance of {@link org.opengis.util.ScopedName} otherwise.</p>
- *
- * <p>This convenience method creates a {@link GenericName} instance,
- * then delegates to {@link #setName(GenericName)}.</p>
- *
- * @param scope the scope of the name to create, or {@code null} if the name is local.
- * @param localPart the local part of the generic name (can not be {@code null}).
- * @return {@code this} for allowing method calls chaining.
- */
- public T setName(String scope, String localPart) {
- ArgumentChecks.ensureNonEmpty("localPart", localPart);
- if (scope == null) {
- scope = ""; // For preventing the use of default scope.
- }
- return setName(name(scope, localPart));
- }
-
- /**
- * Sets the feature type name as a generic name.
- * If another name was defined before this method call, that previous value will be discarded.
- *
- * <div class="note"><b>Note for subclasses:</b>
- * all {@code setName(…)} convenience methods in this builder delegate to this method.
- * Consequently this method can be used as a central place where to control the creation of all names.</div>
- *
- * @param name the generic name (can not be {@code null}).
- * @return {@code this} for allowing method calls chaining.
- *
- * @see AbstractIdentifiedType#NAME_KEY
- */
- @SuppressWarnings("unchecked")
- public T setName(GenericName name) {
- ArgumentChecks.ensureNonNull("name", name);
- identification.put(AbstractIdentifiedType.NAME_KEY, name);
- return (T) this;
- }
-
- /**
- * Returns the current {@code FeatureType} name, or {@code null} if undefined.
- * This method returns the value built from the last call to a {@code setName(…)} method.
- *
- * @return the current {@code FeatureType} name, or {@code null} if the name has not yet been specified.
- */
- public GenericName getName() {
- return (GenericName) identification.get(AbstractIdentifiedType.NAME_KEY);
- }
-
- /**
- * Returns the name to use for displaying error messages.
- */
- final String getDisplayName() {
- final GenericName name = getName();
- return (name != null) ? name.toString() : Vocabulary.getResources(identification).getString(Vocabulary.Keys.Unnamed);
- }
-
- /**
- * Sets a concise definition of the element.
- *
- * @param definition a concise definition of the element, or {@code null} if none.
- * @return {@code this} for allowing method calls chaining.
- *
- * @see AbstractIdentifiedType#DEFINITION_KEY
- */
- @SuppressWarnings("unchecked")
- public T setDefinition(CharSequence definition) {
- identification.put(AbstractIdentifiedType.DEFINITION_KEY, definition);
- return (T) this;
- }
-
- /**
- * Sets a natural language designator for the element.
- * This can be used as an alternative to the {@linkplain #getName() name} in user interfaces.
- *
- * @param designation a natural language designator for the element, or {@code null} if none.
- * @return {@code this} for allowing method calls chaining.
- *
- * @see AbstractIdentifiedType#DESIGNATION_KEY
- */
- @SuppressWarnings("unchecked")
- public T setDesignation(CharSequence designation) {
- identification.put(AbstractIdentifiedType.DESIGNATION_KEY, designation);
- return (T) this;
- }
-
- /**
- * Sets optional information beyond that required for concise definition of the element.
- * The description may assist in understanding the feature scope and application.
- *
- * @param description information beyond that required for concise definition of the element, or {@code null} if none.
- * @return {@code this} for allowing method calls chaining.
- *
- * @see AbstractIdentifiedType#DESCRIPTION_KEY
- */
- @SuppressWarnings("unchecked")
- public T setDescription(CharSequence description) {
- identification.put(AbstractIdentifiedType.DESCRIPTION_KEY, description);
- return (T) this;
- }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureTypeBuilder.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureTypeBuilder.java
deleted file mode 100644
index 2da4c2e..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureTypeBuilder.java
+++ /dev/null
@@ -1,765 +0,0 @@
-/*
- * 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.feature;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import org.opengis.util.GenericName;
-import org.opengis.util.NameFactory;
-import org.opengis.util.FactoryException;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.apache.sis.feature.AbstractOperation;
-import org.apache.sis.feature.DefaultAssociationRole;
-import org.apache.sis.feature.DefaultAttributeType;
-import org.apache.sis.feature.DefaultFeatureType;
-import org.apache.sis.feature.FeatureOperations;
-import org.apache.sis.internal.system.DefaultFactories;
-import org.apache.sis.internal.util.CollectionsExt;
-import org.apache.sis.util.NullArgumentException;
-import org.apache.sis.util.resources.Errors;
-import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.ArraysExt;
-
-// Branch-dependent imports
-import org.apache.sis.feature.AbstractFeature;
-import org.apache.sis.feature.AbstractIdentifiedType;
-
-
-/**
- * Helper class for the creation of {@link FeatureType} instances.
- * This builder can create the parameters to be given to the {@linkplain DefaultFeatureType#DefaultFeatureType
- * feature type constructor} from simpler parameters given to this builder.
- *
- * <p>{@code FeatureTypeBuilder} should be short lived.
- * After the {@code FeatureType} has been created, the builder should be discarded.</p>
- *
- * @author Johann Sorel (Geomatys)
- * @author Martin Desruisseaux (Geomatys)
- * @since 0.7
- * @version 0.7
- * @module
- *
- * @see org.apache.sis.parameter.ParameterBuilder
- */
-public class FeatureTypeBuilder extends Builder<FeatureTypeBuilder> {
- /**
- * The factory to use for creating names.
- */
- private final NameFactory nameFactory;
-
- /**
- * Builders for the properties of this feature.
- */
- private final List<Property<?>> properties;
-
- /**
- * The parent of the feature to create. By default, new features have no parent.
- */
- private final List<DefaultFeatureType> superTypes;
-
- /**
- * Whether the feature type is abstract. The default value is {@code false}.
- */
- private boolean isAbstract;
-
- /**
- * The default scope to use when {@link #name(String, String)} is invoked with a null scope.
- *
- * @see #setDefaultScope(String)
- */
- private String defaultScope;
-
- /**
- * The default minimum number of property values.
- *
- * @see #setDefaultCardinality(int, int)
- */
- private int defaultMinimumOccurs;
-
- /**
- * The default maximum number of property values.
- *
- * @see #setDefaultCardinality(int, int)
- */
- private int defaultMaximumOccurs;
-
- /**
- * If {@link #idAttributes} is non-null, an optional prefix or suffix to insert before
- * or after the {@linkplain FeatureOperations#compound compound key} named {@code "@id"}.
- */
- private String idPrefix, idSuffix;
-
- /**
- * If {@link #idAttributes} is non-null and contains more than one value, the separator to insert between
- * each single component in a {@linkplain FeatureOperations#compound compound key} named {@code "@id"}.
- */
- private String idDelimiter;
-
- /**
- * The attributes to use in a {@linkplain FeatureOperations#compound compound key} named {@code "@id"},
- * or {@code null} if none. If this array contains only one property and {@link #idPrefix} is null,
- * then {@code "@id"} will be a {@linkplain FeatureOperations#link link} to {@code idAttributes[0]}.
- */
- private final List<Property<?>> idAttributes;
-
- /**
- * The default geometry attribute, or {@code null} if none.
- *
- * @see AttributeConvention#DEFAULT_GEOMETRY_PROPERTY
- */
- private Property<?> defaultGeometry;
-
- /**
- * Creates a new builder instance using the default name factory.
- */
- public FeatureTypeBuilder() {
- this(DefaultFactories.forBuildin(NameFactory.class));
- }
-
- /**
- * Creates a new builder instance using the given name factory.
- *
- * @param factory the factory to use for creating names.
- */
- public FeatureTypeBuilder(final NameFactory factory) {
- nameFactory = factory;
- properties = new ArrayList<Property<?>>();
- superTypes = new ArrayList<DefaultFeatureType>();
- idAttributes = new ArrayList<Property<?>>();
- idDelimiter = ":";
- defaultMinimumOccurs = 1;
- defaultMaximumOccurs = 1;
- }
-
- /**
- * Resets this builder to its initial state. After invocation of this method,
- * this builder is in the same state than after construction.
- *
- * @return {@code this} for allowing method calls chaining.
- */
- @Override
- public FeatureTypeBuilder clear() {
- super.clear();
- properties .clear();
- superTypes .clear();
- idAttributes.clear();
- idDelimiter = ":";
- idPrefix = null;
- idSuffix = null;
- isAbstract = false;
- defaultGeometry = null;
- defaultMinimumOccurs = 1;
- defaultMaximumOccurs = 1;
- return this;
- }
-
- /**
- * Sets whether the feature type is abstract.
- * If this method is not invoked, then the default value is {@code false}.
- *
- * @param isAbstract whether the feature type is abstract.
- * @return {@code this} for allowing method calls chaining.
- */
- public FeatureTypeBuilder setAbstract(final boolean isAbstract) {
- this.isAbstract = isAbstract;
- return this;
- }
-
- /**
- * Sets the parent types (or super-type) from which to inherit properties.
- * If this method is not invoked, then the default value is to have no parent.
- *
- * @param parents the parent types from which to inherit properties, or an empty array if none.
- * @return {@code this} for allowing method calls chaining.
- */
- public FeatureTypeBuilder setSuperTypes(final DefaultFeatureType... parents) {
- ArgumentChecks.ensureNonNull("parents", parents);
- superTypes.clear();
- superTypes.addAll(Arrays.asList(parents));
- return this;
- }
-
- /**
- * Sets the scope to use by default when {@link #setName(String)} is invoked.
- *
- * @param scope the new default scope, or {@code null} if none.
- * @return {@code this} for allowing method calls chaining.
- */
- public FeatureTypeBuilder setDefaultScope(final String scope) {
- defaultScope = scope;
- return this;
- }
-
- /**
- * Sets the default minimum and maximum number of property values.
- * Those defaults will applied to newly created attributes or associations,
- * for example in next calls to {@link #addAttribute(Class)}.
- *
- * <p>If this method is not invoked, then the default cardinality is [1 … 1].</p>
- *
- * @param minimumOccurs new default minimum number of property values.
- * @param maximumOccurs new default maximum number of property values.
- * @return {@code this} for allowing method calls chaining.
- *
- * @see Property#setCardinality(int, int)
- */
- public FeatureTypeBuilder setDefaultCardinality(final int minimumOccurs, final int maximumOccurs) {
- if (minimumOccurs < 0 || maximumOccurs < minimumOccurs) {
- throw new IllegalArgumentException(errors().getString(Errors.Keys.IllegalRange_2, minimumOccurs, maximumOccurs));
- }
- defaultMinimumOccurs = minimumOccurs;
- defaultMaximumOccurs = maximumOccurs;
- return this;
- }
-
- /**
- * Sets the prefix, suffix and delimiter to use when formatting a compound identifier made of two or more attributes.
- * The strings specified to this method will be used only if {@link #addIdentifier(Class)} is invoked more than once.
- *
- * <p>If this method is not invoked, then the default values are the {@code ":"} delimiter and no prefix or suffix.</p>
- *
- * @param delimiter the characters to use as delimiter between each single property value.
- * @param prefix characters to use at the beginning of the concatenated string, or {@code null} if none.
- * @param suffix characters to use at the end of the concatenated string, or {@code null} if none.
- * @return {@code this} for allowing method calls chaining.
- *
- * @see java.util.StringJoiner
- * @see FeatureOperations#compound(Map, String, String, String, PropertyType...)
- */
- public FeatureTypeBuilder setIdentifierDelimiters(final String delimiter, final String prefix, final String suffix) {
- ArgumentChecks.ensureNonEmpty("delimiter", delimiter);
- idDelimiter = delimiter;
- idPrefix = prefix;
- idSuffix = suffix;
- return this;
- }
-
- /**
- * Creates a new {@code AttributeType} builder for values of the given class which will be used as identifiers.
- * An arbitrary amount of attributes can be specified as identifiers:
- *
- * <ul>
- * <li>If this method is never invoked, no attribute is marked as feature identifier.</li>
- * <li>If this method is invoked exactly once, then a new attribute is created in the same way than
- * {@link #addAttribute(Class)} and a synthetic attribute named {@code "@id"} will be created as
- * a {@linkplain FeatureOperations#link link} to the new attribute.</li>
- * <li>If this method is invoked more than once, then new attributes are created in the same way than
- * {@link #addAttribute(Class)} and a synthetic attribute named {@code "@id"} will be created as
- * a {@linkplain FeatureOperations#compound compound key} made of all identifiers.</li>
- * </ul>
- *
- * Callers shall invoke at least one of the {@code Property.setName(…)} methods on the returned instance.
- * All other methods are optional.
- *
- * @param <V> the compile-time value of {@code valueClass} argument.
- * @param valueClass the class of attribute values.
- * @return a builder for an {@code AttributeType}.
- */
- public <V> Property<V> addIdentifier(final Class<V> valueClass) {
- ensureAttributeType(valueClass);
- final Property<V> property = new Property<V>(valueClass);
- idAttributes.add(property);
- properties.add(property);
- return property;
- }
-
- /**
- * Creates a new {@code AttributeType} builder for a geometry which will be flagged as the default geometry.
- * Callers shall invoke at least one of the {@code Property.setName(…)} methods on the returned instance.
- * All other methods are optional.
- *
- * @param <V> the compile-time value of {@code valueClass} argument.
- * @param valueClass the geometry class of attribute values.
- * @return a builder for an {@code AttributeType} which will contain the default geometry.
- * @throws IllegalArgumentException if the given type is not a supported geometry type.
- * @throws IllegalStateException if a default geometry has already been specified to this builder.
- */
- public <V> Property<V> addDefaultGeometry(final Class<V> valueClass) {
- ensureAttributeType(valueClass);
- if (!Geometries.isKnownType(valueClass)) {
- throw new IllegalArgumentException(errors().getString(Errors.Keys.UnsupportedImplementation_1, valueClass));
- }
- if (defaultGeometry != null) {
- throw new IllegalStateException(errors().getString(Errors.Keys.PropertyAlreadyExists_2,
- getDisplayName(), AttributeConvention.DEFAULT_GEOMETRY_PROPERTY));
- }
- final Property<V> property = new Property<V>(valueClass);
- defaultGeometry = property;
- properties.add(property);
- return property;
- }
-
- /**
- * Creates a new {@code AttributeType} builder for values of the given class.
- * Callers shall invoke at least one of the {@code Property.setName(…)} methods on the returned instance.
- * All other methods are optional.
- *
- * <p>Usage example:</p>
- * {@preformat java
- * builder.addAttribute(String.class).setName("City");
- * }
- *
- * @param <V> the compile-time value of {@code valueClass} argument.
- * @param valueClass the class of attribute values.
- * @return a builder for an {@code AttributeType}.
- */
- public <V> Property<V> addAttribute(final Class<V> valueClass) {
- ensureAttributeType(valueClass);
- final Property<V> property = new Property<V>(valueClass);
- properties.add(property);
- return property;
- }
-
- /**
- * Ensures that the given value class is not null and not assignable to {@code Feature}.
- * We disallow {@code Feature.class} because those type shall be handled as associations
- * instead than attributes.
- */
- private void ensureAttributeType(final Class<?> valueClass) {
- if (valueClass == null) {
- throw new NullArgumentException(errors().getString(Errors.Keys.NullArgument_1, "valueClass"));
- }
- if (AbstractFeature.class.isAssignableFrom(valueClass)) {
- throw new IllegalArgumentException(errors().getString(Errors.Keys.IllegalArgumentValue_2, "valueClass", valueClass));
- }
- }
-
- /**
- * Creates a new {@code FeatureAssociationRole} builder for features of the given type.
- * Callers shall invoke at least one of the {@code Property.setName(…)} methods on the returned instance.
- * All other methods are optional.
- *
- * @param type the type of feature values.
- * @return a builder for a {@code FeatureAssociationRole}.
- */
- public Property<AbstractFeature> addAssociation(final DefaultFeatureType type) {
- ArgumentChecks.ensureNonNull("type", type);
- final Property<AbstractFeature> property = new Property<AbstractFeature>(AbstractFeature.class, DefaultFeatureType.class, type);
- properties.add(property);
- return property;
- }
-
- /**
- * Creates a new {@code FeatureAssociationRole} builder for features of a type of the given name.
- * This method can be invoked as an alternative to {@link #addAssociation(FeatureType)} when the
- * {@code FeatureType} instance is not yet available because of cyclic dependency.
- *
- * @param type the name of the type of feature values.
- * @return a builder for a {@code FeatureAssociationRole}.
- */
- public Property<AbstractFeature> addAssociation(final GenericName type) {
- ArgumentChecks.ensureNonNull("type", type);
- final Property<AbstractFeature> property = new Property<AbstractFeature>(AbstractFeature.class, GenericName.class, type);
- properties.add(property);
- return property;
- }
-
- /**
- * Describes one property of the {@code FeatureType} to be built by the enclosing {@code FeatureTypeBuilder}.
- * A different instance of {@code Property} exists for each property to describe. Those instances are created by:
- *
- * <ul>
- * <li>{@link FeatureTypeBuilder#addIdentifier(Class)}</li>
- * <li>{@link FeatureTypeBuilder#addDefaultGeometry(Class)}</li>
- * <li>{@link FeatureTypeBuilder#addAttribute(Class)}</li>
- * <li>{@link FeatureTypeBuilder#addAssociation(FeatureType)}</li>
- * <li>{@link FeatureTypeBuilder#addAssociation(GenericName)}</li>
- * </ul>
- *
- * @param <V> the class of property values.
- */
- public final class Property<V> extends Builder<Property<V>> {
- /**
- * The class of property values. Can not be changed after construction
- * because this value determines the parameterized type {@code <V>}.
- */
- private final Class<V> valueClass;
-
- /**
- * The default value for the property, or {@code null} if none.
- */
- private V defaultValue;
-
- /**
- * The minimum number of property values.
- * The default value is 1, unless otherwise specified by {@link #setDefaultCardinality(int, int)}.
- *
- * @see #setCardinality(int, int)
- */
- private int minimumOccurs;
-
- /**
- * The maximum number of property values. The default value is 1.
- * The default value is 1, unless otherwise specified by {@link #setDefaultCardinality(int, int)}.
- *
- * @see #setCardinality(int, int)
- */
- private int maximumOccurs;
-
- /**
- * Builders for the characteristics associated to the attribute.
- */
- private final List<Characteristic<?>> characteristics;
-
- /**
- * Creates a new {@code AttributeType} or {@code Operation} builder for values of the given class.
- *
- * @param valueClass the class of property values.
- */
- Property(final Class<V> valueClass) {
- this.valueClass = valueClass;
- minimumOccurs = defaultMinimumOccurs;
- maximumOccurs = defaultMaximumOccurs;
- characteristics = new ArrayList<Characteristic<?>>();
- }
-
- /**
- * Creates a new {@code AssociationRole} builder for values of the given type.
- * This constructor arbitrarily stores the feature type as an unnamed characteristic of this property.
- *
- * @param valueClass shall be {@code Feature.class}.
- * @param typeClass shall be either {@code FeatureType.class} or {@code GenericName.class}.
- * @param type the type of associated features.
- */
- <C> Property(final Class<V> valueClass, final Class<C> typeClass, final C type) {
- this.valueClass = valueClass;
- minimumOccurs = defaultMinimumOccurs;
- maximumOccurs = defaultMaximumOccurs;
- characteristics = Collections.<Characteristic<?>>singletonList(new Characteristic<C>(typeClass).setDefaultValue(type));
- }
-
- /**
- * Delegates the creation of a new name to the enclosing builder.
- */
- @Override
- final GenericName name(final String scope, final String localPart) {
- return FeatureTypeBuilder.this.name(scope, localPart);
- }
-
- /**
- * Sets the minimum and maximum number of property values. Those numbers must be equal or greater than zero.
- *
- * <p>If this method is not invoked, then the default values are the cardinality specified by the last call
- * to {@link #setDefaultCardinality(int, int)} at the time this {@code Property} instance has been created.
- * If the later method has not invoked neither, then the default cardinality is [1 … 1].</p>
- *
- * @param minimumOccurs new minimum number of property values.
- * @param maximumOccurs new maximum number of property values.
- * @return {@code this} for allowing method calls chaining.
- */
- public Property<V> setCardinality(final int minimumOccurs, final int maximumOccurs) {
- if (minimumOccurs < 0 || maximumOccurs < minimumOccurs) {
- throw new IllegalArgumentException(errors().getString(Errors.Keys.IllegalRange_2, minimumOccurs, maximumOccurs));
- }
- this.minimumOccurs = minimumOccurs;
- this.maximumOccurs = maximumOccurs;
- return this;
- }
-
- /**
- * Sets the default value for the property.
- *
- * @param defaultValue default property value, or {@code null} if none.
- * @return {@code this} for allowing method calls chaining.
- */
- public Property<V> setDefaultValue(final V defaultValue) {
- this.defaultValue = defaultValue;
- return this;
- }
-
- /**
- * Sets an enumeration of valid values for this attribute.
- *
- * <p>This is a convenience method for {@link #addCharacteristic(Class)} with a value
- * of type {@link Set} and a conventional name.</p>
- *
- * @param values valid values.
- * @return {@code this} for allowing method calls chaining.
- * @throws UnsupportedOperationException if this property does not support characteristics.
- *
- * @see AttributeConvention#VALID_VALUES_CHARACTERISTIC
- */
- public final Property<V> setValidValues(final V... values) {
- return setCharacteristic(AttributeConvention.VALID_VALUES_CHARACTERISTIC,
- Set.class, CollectionsExt.immutableSet(false, values));
- }
-
- /**
- * Sets the maximal length that characterizes the {@link CharSequence} values of this attribute.
- * While this characteristic can be applied to any kind of attribute, it is meaningful only with
- * character sequences.
- *
- * <p>This is a convenience method for {@link #addCharacteristic(Class)} with a value
- * of type {@link Integer} and a conventional name.</p>
- *
- * @param length maximal length of {@link CharSequence} attribute values, or {@code null}.
- * @return {@code this} for allowing method calls chaining.
- * @throws UnsupportedOperationException if this property does not support length characteristics.
- *
- * @see AttributeConvention#MAXIMAL_LENGTH_CHARACTERISTIC
- */
- public Property<V> setMaximalLengthCharacteristic(final Integer length) {
- return setCharacteristic(AttributeConvention.MAXIMAL_LENGTH_CHARACTERISTIC, Integer.class, length);
- }
-
- /**
- * Sets the coordinate reference system that characterizes the values of this attribute.
- * While this characteristic can be applied to any kind of attribute, it is meaningful
- * only with georeferenced values like geometries or coverages.
- *
- * <p>This is a convenience method for {@link #addCharacteristic(Class)} with a value
- * of type {@link CoordinateReferenceSystem} and a conventional name.</p>
- *
- * @param crs coordinate reference system associated to attribute values, or {@code null}.
- * @return {@code this} for allowing method calls chaining.
- * @throws UnsupportedOperationException if this property does not support CRS characteristics.
- *
- * @see AttributeConvention#CRS_CHARACTERISTIC
- */
- public Property<V> setCRSCharacteristic(final CoordinateReferenceSystem crs) {
- return setCharacteristic(AttributeConvention.CRS_CHARACTERISTIC, CoordinateReferenceSystem.class, crs);
- }
-
- /**
- * Implementation of all setter methods for characteristics.
- *
- * @throws UnsupportedOperationException if this property does not support characteristics.
- */
- private <C> Property<V> setCharacteristic(final GenericName name, final Class<C> type, final C value) {
- for (final Characteristic<?> characteristic : characteristics) {
- if (name.equals(characteristic.identification.get(DefaultAttributeType.NAME_KEY))) {
- characteristic.set(value);
- return this;
- }
- }
- addCharacteristic(type).setDefaultValue(value).setName(name);
- return this;
- }
-
- /**
- * Adds another attribute type that describes this attribute type.
- * See <cite>"Attribute characterization"</cite> in {@link DefaultAttributeType} Javadoc for more information.
- *
- * <p>Usage example:</p>
- * {@preformat java
- * attribute.addCharacteristic(Unit.class).setName("Unit of measurement").setDefaultValue(SI.CELSIUS);
- * }
- *
- * Callers shall invoke at least one of the {@code Characteristic.setName(…)} methods on the returned instance.
- * All other methods are optional.
- *
- * @param <C> the compile-time type of {@code type} argument.
- * @param type the class of characteristic values.
- * @return a builder for a characteristic of this attribute.
- * @throws UnsupportedOperationException if this property does not support characteristics.
- */
- public <C> Characteristic<C> addCharacteristic(final Class<C> type) {
- if (valueClass == AbstractFeature.class) {
- throw new UnsupportedOperationException(errors().getString(Errors.Keys.IllegalOperationForValueClass_1, valueClass));
- }
- ArgumentChecks.ensureNonNull("type", type);
- final Characteristic<C> characteristic = new Characteristic<C>(type);
- characteristics.add(characteristic);
- return characteristic;
- }
-
- /**
- * Creates a new property type from the current setting.
- */
- final AbstractIdentifiedType build() {
- final AbstractIdentifiedType property;
- if (valueClass == AbstractFeature.class) {
- final Object type = CollectionsExt.first(characteristics).defaultValue;
- if (type instanceof DefaultFeatureType) {
- property = new DefaultAssociationRole(identification, (DefaultFeatureType) type, minimumOccurs, maximumOccurs);
- } else {
- property = new DefaultAssociationRole(identification, (GenericName) type, minimumOccurs, maximumOccurs);
- }
- } else {
- final DefaultAttributeType<?>[] chrts = new DefaultAttributeType<?>[characteristics.size()];
- for (int i=0; i<chrts.length; i++) {
- chrts[i] = characteristics.get(i).build();
- }
- property = new DefaultAttributeType<V>(identification, valueClass, minimumOccurs, maximumOccurs, defaultValue, chrts);
- }
- return property;
- }
- }
-
- /**
- * Describes one characteristic of the {@code AttributeType} to be built by the enclosing {@code FeatureTypeBuilder}.
- * A different instance of {@code Characteristic} exists for each characteristic to describe.
- * Those instances are created by:
- *
- * <ul>
- * <li>{@link Property#addCharacteristic(Class)}</li>
- * </ul>
- *
- * @param <V> the class of characteristic values.
- */
- public final class Characteristic<V> extends Builder<Characteristic<V>> {
- /**
- * The class of attribute values. Can not be changed after construction
- * because this value determines the parameterized type {@code <V>}.
- */
- private final Class<V> valueClass;
-
- /**
- * The default value for the attribute, or {@code null} if none.
- */
- V defaultValue;
-
- /**
- * Creates a new characteristic builder for values of the given class.
- *
- * @param valueClass the class of characteristic values.
- */
- Characteristic(final Class<V> valueClass) {
- this.valueClass = valueClass;
- }
-
- /**
- * Delegates the creation of a new name to the enclosing builder.
- */
- @Override
- final GenericName name(final String scope, final String localPart) {
- return FeatureTypeBuilder.this.name(scope, localPart);
- }
-
- /**
- * Sets the default value with check of the value class.
- */
- final void set(final Object value) {
- setDefaultValue(valueClass.cast(value));
- }
-
- /**
- * Sets the default value for the characteristic.
- *
- * @param value characteristic default value, or {@code null} if none.
- * @return {@code this} for allowing method calls chaining.
- */
- public Characteristic<V> setDefaultValue(final V value) {
- defaultValue = value;
- return this;
- }
-
- /**
- * Creates a new characteristic from the current setting.
- */
- final DefaultAttributeType<V> build() {
- return new DefaultAttributeType<V>(identification, valueClass, 0, 1, defaultValue);
- }
- }
-
- /**
- * Builds the feature type from the information and properties specified to this builder.
- * One of the {@code setName(…)} methods must have been invoked before this {@code build()} method (mandatory).
- * All other methods are optional, but some calls to a {@code add} method are usually needed.
- *
- * @return the new feature type.
- * @throws IllegalStateException if the feature type contains incompatible
- * {@linkplain Property#setCRSCharacteristic CRS characteristics}.
- */
- public DefaultFeatureType build() throws IllegalStateException {
- int numSynthetic; // Number of synthetic properties to be generated.
- int numSpecified = properties.size(); // Number of explicitely specified properties.
- final AbstractIdentifiedType[] identifierTypes;
- if (idAttributes.isEmpty()) {
- identifierTypes = null;
- numSynthetic = 0;
- } else {
- identifierTypes = new AbstractIdentifiedType[idAttributes.size()];
- numSynthetic = 1; // Reserve one slot for the synthetic property "@id".
- }
- if (defaultGeometry != null) {
- numSynthetic += 2; // One slot for "@defaultGeometry" and one for "@envelope".
- }
- AbstractIdentifiedType[] propertyTypes = new AbstractIdentifiedType[numSynthetic + numSpecified];
- for (int i=0,j=numSynthetic; i<numSpecified; i++, j++) {
- final Property<?> builder = properties.get(i);
- final AbstractIdentifiedType instance = builder.build();
- propertyTypes[j] = instance;
- final int id = idAttributes.indexOf(builder);
- if (id >= 0) {
- identifierTypes[id] = instance;
- }
- /*
- * If there is a default geometry, add a link named "@geometry" to that geometry. It may happen
- * that the property created by the user is already named "@geometry", in which case will will
- * avoid to duplicate the property by removing the second occurrence.
- */
- if (builder == defaultGeometry) {
- final AbstractIdentifiedType geom;
- if (AttributeConvention.DEFAULT_GEOMETRY_PROPERTY.equals(instance.getName())) {
- propertyTypes = ArraysExt.remove(propertyTypes, j--, 1);
- geom = instance;
- } else {
- geom = FeatureOperations.link(name(AttributeConvention.DEFAULT_GEOMETRY_PROPERTY), instance);
- }
- propertyTypes[numSynthetic - 1] = geom;
- }
- }
- /*
- * Create the "envelope" operation only after we created all other properties, except "@id" which is not needed
- * for envelope. It is okay if the 'propertyTypes' array still contains null elements like the "@id" one, since
- * FeatureOperations.envelope(…) constructor will ignore any property which is not for a value.
- */
- if (defaultGeometry != null) try {
- propertyTypes[numSynthetic - 2] = FeatureOperations.envelope(name(AttributeConvention.ENVELOPE_PROPERTY), null, propertyTypes);
- } catch (FactoryException e) {
- throw new IllegalStateException(e);
- }
- if (identifierTypes != null) {
- propertyTypes[0] = FeatureOperations.compound(name(AttributeConvention.ID_PROPERTY), idDelimiter, idPrefix, idSuffix, identifierTypes);
- }
- return new DefaultFeatureType(identification, isAbstract, superTypes.toArray(new DefaultFeatureType[superTypes.size()]), propertyTypes);
- }
-
- /**
- * Helper method for creating identification info of synthetic attributes.
- */
- private static Map<String,?> name(final GenericName name) {
- return Collections.singletonMap(AbstractOperation.NAME_KEY, name);
- }
-
- /**
- * Invoked by {@link Builder} for creating new {@code LocalName} or {@code GenericName} instances.
- */
- @Override
- final GenericName name(String scope, final String localPart) {
- if (scope == null) {
- scope = defaultScope;
- }
- if (scope == null || scope.isEmpty()) {
- return nameFactory.createLocalName(null, localPart);
- } else {
- return nameFactory.createGenericName(null, scope, localPart);
- }
- }
-
- /**
- * Returns the resources for error messages.
- */
- final Errors errors() {
- return Errors.getResources(identification);
- }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java
index 84cd269..e66a385 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java
@@ -16,10 +16,15 @@
*/
package org.apache.sis.internal.feature;
+import java.util.logging.Level;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import com.esri.core.geometry.Geometry;
import com.esri.core.geometry.Envelope2D;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.util.Static;
+import org.apache.sis.util.logging.Logging;
+import org.apache.sis.internal.system.Loggers;
/**
@@ -32,11 +37,52 @@
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
* @since 0.7
- * @version 0.7
+ * @version 0.8
* @module
*/
public final class Geometries extends Static {
/**
+ * The geometry object from Java Topology Suite (JTS),
+ * or {@code null} if the JTS library is not on the classpath.
+ */
+ private static final Class<?> JTS;
+
+ /**
+ * Getter methods on JTS envelopes, or {@code null} if the JTS library is not on the classpath.
+ * Each methods take no argument and return a {@code double} value.
+ */
+ private static final Method INTERNAL, MIN_X, MIN_Y, MAX_X, MAX_Y;
+
+ static {
+ Class<?> type;
+ Method genv, xmin, ymin, xmax, ymax;
+ try {
+ final Class<?> envt;
+ type = Class.forName("com.vividsolutions.jts.geom.Geometry");
+ genv = type.getMethod("getEnvelopeInternal", (Class[]) null);
+ envt = genv.getReturnType();
+ xmin = envt.getMethod("getMinX", (Class[]) null);
+ ymin = envt.getMethod("getMinY", (Class[]) null);
+ xmax = envt.getMethod("getMaxX", (Class[]) null);
+ ymax = envt.getMethod("getMaxY", (Class[]) null);
+ } catch (Exception e) { // (ClassNotFoundException | NoSuchMethodException) on the JDK7 branch.
+ Logging.getLogger(Loggers.GEOMETRY).log(Level.CONFIG, e.toString());
+ type = null;
+ genv = null;
+ xmin = null;
+ xmax = null;
+ ymin = null;
+ ymax = null;
+ }
+ JTS = type;
+ INTERNAL = genv;
+ MIN_X = xmin;
+ MIN_Y = ymin;
+ MAX_X = xmax;
+ MAX_Y = ymax;
+ }
+
+ /**
* Do not allow instantiation of this class.
*/
private Geometries() {
@@ -49,7 +95,7 @@
* @return {@code true} if the given type is one of the geometry type known to SIS.
*/
public static boolean isKnownType(final Class<?> type) {
- return Geometry.class.isAssignableFrom(type);
+ return Geometry.class.isAssignableFrom(type) || (JTS != null && JTS.isAssignableFrom(type));
}
/**
@@ -61,16 +107,43 @@
* a recognized geometry or its envelope is empty.
*/
public static GeneralEnvelope getEnvelope(final Object geometry) {
+ final double xmin, ymin, xmax, ymax;
if (geometry instanceof Geometry) {
final Envelope2D bounds = new Envelope2D();
((Geometry) geometry).queryEnvelope2D(bounds);
- if (!bounds.isEmpty()) { // Test if there is NaN values.
- final GeneralEnvelope env = new GeneralEnvelope(2);
- env.setRange(0, bounds.xmin, bounds.xmax);
- env.setRange(1, bounds.ymin, bounds.ymax);
- return env;
+ if (bounds.isEmpty()) { // Test if there is NaN values.
+ return null;
}
+ xmin = bounds.xmin;
+ ymin = bounds.ymin;
+ xmax = bounds.xmax;
+ ymax = bounds.ymax;
+ } else if (JTS != null && JTS.isInstance(geometry)) {
+ try {
+ final Object env = INTERNAL.invoke(geometry, (Object[]) null);
+ xmin = (Double) MIN_X.invoke(env, (Object[]) null);
+ ymin = (Double) MIN_Y.invoke(env, (Object[]) null);
+ xmax = (Double) MAX_X.invoke(env, (Object[]) null);
+ ymax = (Double) MAX_Y.invoke(env, (Object[]) null);
+ } catch (ReflectiveOperationException e) {
+ if (e instanceof InvocationTargetException) {
+ final Throwable cause = e.getCause();
+ if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ }
+ if (cause instanceof Error) {
+ throw (Error) cause;
+ }
+ }
+ // Should never happen unless JTS's API changed.
+ throw (Error) new IncompatibleClassChangeError(e.toString()).initCause(e);
+ }
+ } else {
+ return null;
}
- return null;
+ final GeneralEnvelope env = new GeneralEnvelope(2);
+ env.setRange(0, xmin, xmax);
+ env.setRange(1, ymin, ymax);
+ return env;
}
}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/AbstractOperationTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/AbstractOperationTest.java
index 85c60b6..e12bbb9 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/AbstractOperationTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/AbstractOperationTest.java
@@ -31,7 +31,7 @@
*
* @author Martin Desruisseaux (Geomatys)
* @since 0.6
- * @version 0.6
+ * @version 0.8
* @module
*/
@DependsOn(SingletonAttributeTest.class)
@@ -62,6 +62,6 @@
*/
@Test
public void testToString() {
- assertEquals("Operation[“found city” (founder) : city]", foundCity().toString());
+ assertEquals("NoOperation[“found city” (founder) : String]", foundCity().toString());
}
}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/EnvelopeOperationTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/EnvelopeOperationTest.java
index 00942b3..3c9b4ff 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/EnvelopeOperationTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/EnvelopeOperationTest.java
@@ -78,7 +78,7 @@
null,
null
};
- attributes[4] = FeatureOperations.link(name(AttributeConvention.DEFAULT_GEOMETRY_PROPERTY), attributes[defaultGeometry]);
+ attributes[4] = FeatureOperations.link(name(AttributeConvention.GEOMETRY_PROPERTY), attributes[defaultGeometry]);
attributes[5] = FeatureOperations.envelope(name("bounds"), null, attributes);
return new DefaultFeatureType(name("school"), false, null, attributes);
}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/FeatureTypeBuilderTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/builder/FeatureTypeBuilderTest.java
similarity index 80%
rename from core/sis-feature/src/test/java/org/apache/sis/internal/feature/FeatureTypeBuilderTest.java
rename to core/sis-feature/src/test/java/org/apache/sis/feature/builder/FeatureTypeBuilderTest.java
index 28052fa..6874c8b 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/FeatureTypeBuilderTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/builder/FeatureTypeBuilderTest.java
@@ -14,13 +14,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.sis.internal.feature;
+package org.apache.sis.feature.builder;
import java.util.Iterator;
import com.esri.core.geometry.Geometry;
import com.esri.core.geometry.Point;
+import org.apache.sis.internal.feature.AttributeConvention;
+import org.apache.sis.feature.DefaultFeatureTypeTest;
import org.apache.sis.referencing.crs.HardCodedCRS;
import org.apache.sis.test.DependsOnMethod;
+import org.apache.sis.test.TestUtilities;
import org.apache.sis.test.TestCase;
import org.junit.Test;
@@ -37,8 +40,8 @@
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @since 0.7
- * @version 0.7
+ * @since 0.8
+ * @version 0.8
* @module
*/
public final strictfp class FeatureTypeBuilderTest extends TestCase {
@@ -47,25 +50,20 @@
*/
@Test
public void testEmptyProperty() {
- final FeatureTypeBuilder.Property<String> builder = new FeatureTypeBuilder().addAttribute(String.class);
- try {
- builder.build();
- fail("Builder should have failed if there is not at least a name set.");
- } catch (IllegalArgumentException ex) {
- final String message = ex.getMessage();
- assertTrue(message, message.contains("name"));
- }
+ final AttributeTypeBuilder<String> builder = new FeatureTypeBuilder().addAttribute(String.class);
+ assertEquals("default name", "string", builder.getName().toString());
+
builder.setName("myScope", "myName");
final DefaultAttributeType<?> att = (DefaultAttributeType<?>) builder.build();
- assertEquals("name", "myScope:myName", att.getName().toString());
- assertEquals("valueClass", String.class, att.getValueClass());
- assertNull ("defaultValue", att.getDefaultValue());
- assertNull ("definition", att.getDefinition());
- assertNull ("description", att.getDescription());
- assertNull ("designation", att.getDesignation());
- assertEquals("minimumOccurs", 1, att.getMinimumOccurs());
- assertEquals("maximumOccurs", 1, att.getMaximumOccurs());
+ assertEquals("name", "myScope:myName", att.getName().toString());
+ assertEquals("valueClass", String.class, att.getValueClass());
+ assertNull ("defaultValue", att.getDefaultValue());
+ assertNull ("definition", att.getDefinition());
+ assertNull ("description", att.getDescription());
+ assertNull ("designation", att.getDesignation());
+ assertEquals("minimumOccurs", 1, att.getMinimumOccurs());
+ assertEquals("maximumOccurs", 1, att.getMaximumOccurs());
}
/**
@@ -104,14 +102,14 @@
@Test
@DependsOnMethod("testEmptyProperty")
public void testPropertyBuild() {
- final FeatureTypeBuilder.Property<String> builder = new FeatureTypeBuilder().addAttribute(String.class);
+ final AttributeTypeBuilder<String> builder = new FeatureTypeBuilder().addAttribute(String.class);
builder.setName ("myScope", "myName");
builder.setDefinition ("test definition");
builder.setDesignation ("test designation");
builder.setDescription ("test description");
builder.setDefaultValue("test default value.");
builder.setCardinality(10, 60);
- builder.setMaximalLengthCharacteristic(80);
+ builder.setMaximalLength(80);
final DefaultAttributeType<?> att = (DefaultAttributeType<?>) builder.build();
assertEquals("name", "myScope:myName", att.getName().toString());
@@ -147,8 +145,8 @@
builder.setAbstract(true);
builder.addAttribute(String .class).setName("name");
builder.addAttribute(Integer.class).setName("age");
- builder.addAttribute(Point .class).setName("location").setCRSCharacteristic(HardCodedCRS.WGS84);
- builder.addAttribute(Double .class).setName("score").setCardinality(5, 50).setDefaultValue(10.0);
+ builder.addAttribute(Point .class).setName("location").setCRS(HardCodedCRS.WGS84);
+ builder.addAttribute(Double .class).setName("score").setDefaultValue(10.0).setCardinality(5, 50);
final DefaultFeatureType type = builder.build();
assertEquals("name", "myScope:myName", type.getName().toString());
@@ -196,18 +194,6 @@
}
/**
- * Test {@link FeatureTypeBuilder#clear()}.
- */
- @Test
- @DependsOnMethod({"testEmptyFeature", "testAddProperties"})
- public void testClear() {
- final FeatureTypeBuilder builder = new FeatureTypeBuilder();
- testAddProperties(builder);
- builder.clear();
- testEmptyFeature(builder);
- }
-
- /**
* Tests {@link FeatureTypeBuilder#addIdentifier(Class)}.
*/
@Test
@@ -216,8 +202,11 @@
final FeatureTypeBuilder builder = new FeatureTypeBuilder();
builder.setName("scope", "test");
builder.setIdentifierDelimiters("-", "pref.", null);
- builder.addIdentifier(String.class).setName("name");
- builder.addDefaultGeometry(Geometry.class).setName("shape").setCRSCharacteristic(HardCodedCRS.WGS84);
+ builder.addAttribute(String.class).setName("name")
+ .addRole(AttributeRole.IDENTIFIER_COMPONENT);
+ builder.addAttribute(Geometry.class).setName("shape")
+ .setCRS(HardCodedCRS.WGS84)
+ .addRole(AttributeRole.DEFAULT_GEOMETRY);
final DefaultFeatureType type = builder.build();
assertEquals("name", "scope:test", type.getName().toString());
@@ -231,10 +220,22 @@
final AbstractIdentifiedType a4 = it.next();
assertFalse("properties count", it.hasNext());
- assertEquals("name", AttributeConvention.ID_PROPERTY, a0.getName());
- assertEquals("name", AttributeConvention.ENVELOPE_PROPERTY, a1.getName());
- assertEquals("name", AttributeConvention.DEFAULT_GEOMETRY_PROPERTY, a2.getName());
- assertEquals("name", "name", a3.getName().toString());
- assertEquals("name", "shape", a4.getName().toString());
+ assertEquals("name", AttributeConvention.IDENTIFIER_PROPERTY, a0.getName());
+ assertEquals("name", AttributeConvention.ENVELOPE_PROPERTY, a1.getName());
+ assertEquals("name", AttributeConvention.GEOMETRY_PROPERTY, a2.getName());
+ assertEquals("name", "name", a3.getName().toString());
+ assertEquals("name", "shape", a4.getName().toString());
+ }
+
+ /**
+ * Tests creation of a builder from an existing feature type.
+ * This method also acts as a test of {@code FeatureTypeBuilder} getter methods.
+ */
+ @Test
+ public void testCreateFromTemplate() {
+ final FeatureTypeBuilder builder = new FeatureTypeBuilder(DefaultFeatureTypeTest.capital());
+ assertEquals("name", "Capital", builder.getName().toString());
+ assertEquals("superTypes", "City", TestUtilities.getSingleton(builder.getSuperTypes()).getName().toString());
+ assertFalse ("isAbstract", builder.isAbstract());
}
}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
index 784e74d..9f4a3f1 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
@@ -49,7 +49,7 @@
org.apache.sis.feature.FeatureFormatTest.class,
org.apache.sis.feature.FeaturesTest.class,
org.apache.sis.internal.feature.AttributeConventionTest.class,
- org.apache.sis.internal.feature.FeatureTypeBuilderTest.class
+ org.apache.sis.feature.builder.FeatureTypeBuilderTest.class
})
public final strictfp class FeatureTestSuite extends TestSuite {
/**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
index 890a759..b732218 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
@@ -375,6 +375,11 @@
/**
* Returns a unique identifier for this metadata record.
*
+ * <div class="note"><b>Note:</b>
+ * OGC 07-045 (Catalog Service Specification — ISO metadata application profile) recommends usage
+ * of a UUID (Universal Unique Identifier) as specified by <a href="http://www.ietf.org">IETF</a>
+ * to ensure identifier’s uniqueness.</div>
+ *
* @return Unique identifier for this metadata record, or {@code null}.
*
* @since 0.5
@@ -611,7 +616,7 @@
/**
* Returns an identification of the parent metadata record.
- * This is non-null if this metadata is a subset (child) of another metadata.
+ * This is non-null if this metadata is a subset (child) of another metadata that is described elsewhere.
*
* @return Identification of the parent metadata record, or {@code null} if none.
*
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AbstractStereographic.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AbstractStereographic.java
index 5f7a86b..6eebeb1 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AbstractStereographic.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AbstractStereographic.java
@@ -31,7 +31,7 @@
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @since 0.6
- * @version 0.6
+ * @version 0.8
* @module
*/
@XmlTransient
@@ -42,12 +42,6 @@
private static final long serialVersionUID = -8797654778436582119L;
/**
- * The operation parameter descriptor for the <cite>Scale factor at natural origin</cite> (k₀) parameter value.
- * Valid values range is (0 … ∞) and default value is 1.
- */
- public static final ParameterDescriptor<Double> SCALE_FACTOR = Mercator1SP.SCALE_FACTOR;
-
- /**
* The operation parameter descriptor for the <cite>False easting</cite> (FE) parameter value.
* Valid values range is unrestricted and default value is 0 metre.
*/
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
index ab78a5f..9be7867 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
@@ -128,13 +128,13 @@
static final ParameterDescriptor<Double> DS;
static {
final ParameterBuilder builder = builder();
- TX = createShift(builder.addName("X-axis translation").addName(Citations.OGC, "dx"));
- TY = createShift(builder.addName("Y-axis translation").addName(Citations.OGC, "dy"));
- TZ = createShift(builder.addName("Z-axis translation").addName(Citations.OGC, "dz"));
- RX = createRotation(builder, "X-axis rotation", "ex");
- RY = createRotation(builder, "Y-axis rotation", "ey");
- RZ = createRotation(builder, "Z-axis rotation", "ez");
- DS = builder.addName("Scale difference").addName(Citations.OGC, "ppm").create(1, Units.PPM);
+ TX = createShift(builder.addIdentifier("8605").addName("X-axis translation").addName(Citations.OGC, "dx"));
+ TY = createShift(builder.addIdentifier("8606").addName("Y-axis translation").addName(Citations.OGC, "dy"));
+ TZ = createShift(builder.addIdentifier("8607").addName("Z-axis translation").addName(Citations.OGC, "dz"));
+ RX = createRotation(builder.addIdentifier("8608"), "X-axis rotation", "ex");
+ RY = createRotation(builder.addIdentifier("8609"), "Y-axis rotation", "ey");
+ RZ = createRotation(builder.addIdentifier("8610"), "Z-axis rotation", "ez");
+ DS = builder.addIdentifier("8611").addName("Scale difference").addName(Citations.OGC, "ppm").create(1, Units.PPM);
}
/**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/LambertConformal2SP.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/LambertConformal2SP.java
index 2c16bc8..5d3a5b8 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/LambertConformal2SP.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/LambertConformal2SP.java
@@ -107,9 +107,8 @@
* NetCDF: longitude_of_central_meridian
* GeoTIFF: FalseOriginLong
*/
- LONGITUDE_OF_FALSE_ORIGIN = createLongitude(exceptEPSG(LambertConformal1SP.LONGITUDE_OF_ORIGIN, builder
- .addIdentifier("8822")
- .addName("Longitude of false origin"))
+ LONGITUDE_OF_FALSE_ORIGIN = createLongitude(
+ rename(LambertConformal1SP.LONGITUDE_OF_ORIGIN, "8822", "Longitude of false origin", builder)
.rename(Citations.NETCDF, "longitude_of_central_meridian")
.rename(Citations.GEOTIFF, "FalseOriginLong"));
/*
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/LambertCylindricalEqualArea.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/LambertCylindricalEqualArea.java
new file mode 100644
index 0000000..1d70b43
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/LambertCylindricalEqualArea.java
@@ -0,0 +1,117 @@
+/*
+ * 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 javax.xml.bind.annotation.XmlTransient;
+import org.opengis.parameter.ParameterDescriptor;
+import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.referencing.operation.CylindricalProjection;
+import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.parameter.Parameters;
+import org.apache.sis.referencing.operation.projection.NormalizedProjection;
+import org.apache.sis.referencing.operation.projection.CylindricalEqualArea;
+
+
+/**
+ * The provider for <cite>"Lambert Cylindrical Equal Area"</cite> projection (EPSG:9835).
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.8
+ * @version 0.8
+ * @module
+ *
+ * @see <a href="http://www.remotesensing.org/geotiff/proj_list/cylindrical_equal_area.html">Cylindrical Equal Area on RemoteSensing.org</a>
+ */
+@XmlTransient
+public final class LambertCylindricalEqualArea extends MapProjection {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = -672278344635217838L;
+
+ /**
+ * The operation parameter descriptor for the <cite>Latitude of 1st standard parallel</cite> (φ₁) parameter value.
+ * Valid values range is (-90 … 90)° and default value is 0°.
+ */
+ public static final ParameterDescriptor<Double> STANDARD_PARALLEL = Equirectangular.STANDARD_PARALLEL;
+
+ /**
+ * The operation parameter descriptor for the <cite>Longitude of natural origin</cite> (λ₀) parameter value.
+ * Valid values range is [-180 … 180]° and default value is 0°.
+ */
+ public static final ParameterDescriptor<Double> LONGITUDE_OF_ORIGIN = Mercator1SP.LONGITUDE_OF_ORIGIN;
+
+ /**
+ * The operation parameter descriptor for the <cite>False easting</cite> (FE) parameter value.
+ * Valid values range is unrestricted and default value is 0 metre.
+ */
+ public static final ParameterDescriptor<Double> FALSE_EASTING = Equirectangular.FALSE_EASTING;
+
+ /**
+ * The operation parameter descriptor for the <cite>False northing</cite> (FN) parameter value.
+ * Valid values range is unrestricted and default value is 0 metre.
+ */
+ public static final ParameterDescriptor<Double> FALSE_NORTHING = Equirectangular.FALSE_NORTHING;
+
+ /**
+ * The group of all parameters expected by this coordinate operation.
+ */
+ static final ParameterDescriptorGroup PARAMETERS;
+ static {
+ PARAMETERS = builder()
+ .addIdentifier( "9835")
+ .addName( "Lambert Cylindrical Equal Area")
+ .addName(Citations.OGC, "Cylindrical_Equal_Area")
+ .addName(Citations.ESRI, "Cylindrical_Equal_Area")
+ .addName(Citations.GEOTIFF, "CT_CylindricalEqualArea")
+ .addName(Citations.PROJ4, "cea")
+ .addIdentifier(Citations.GEOTIFF, "28")
+ .createGroupForMapProjection(
+ STANDARD_PARALLEL,
+ LONGITUDE_OF_ORIGIN,
+ Mercator2SP.SCALE_FACTOR, // Not formally a Cylindrical Equal Area parameter.
+ FALSE_EASTING,
+ FALSE_NORTHING);
+ }
+
+ /**
+ * Constructs a new provider.
+ */
+ public LambertCylindricalEqualArea() {
+ super(PARAMETERS);
+ }
+
+ /**
+ * Returns the operation type for this map projection.
+ *
+ * @return {@code CylindricalProjection.class}
+ */
+ @Override
+ public final Class<CylindricalProjection> getOperationType() {
+ return CylindricalProjection.class;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return The map projection created from the given parameter values.
+ */
+ @Override
+ protected NormalizedProjection createProjection(final Parameters parameters) {
+ return new CylindricalEqualArea(this, parameters);
+ }
+}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/LambertCylindricalEqualAreaSpherical.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/LambertCylindricalEqualAreaSpherical.java
new file mode 100644
index 0000000..4151836
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/LambertCylindricalEqualAreaSpherical.java
@@ -0,0 +1,89 @@
+/*
+ * 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 javax.xml.bind.annotation.XmlTransient;
+import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.referencing.operation.CylindricalProjection;
+import org.apache.sis.parameter.Parameters;
+import org.apache.sis.referencing.operation.projection.NormalizedProjection;
+import org.apache.sis.referencing.operation.projection.CylindricalEqualArea;
+
+
+/**
+ * The provider for <cite>"Lambert Cylindrical Equal Area (Spherical)"</cite> projection (EPSG:9834).
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.8
+ * @version 0.8
+ * @module
+ */
+@XmlTransient
+public final class LambertCylindricalEqualAreaSpherical extends MapProjection {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = 1456941129750586197L;
+
+ /**
+ * The EPSG identifier, to be preferred to the name when available.
+ */
+ public static final String IDENTIFIER = "9834";
+
+ /**
+ * The group of all parameters expected by this coordinate operation.
+ */
+ static final ParameterDescriptorGroup PARAMETERS;
+ static {
+ PARAMETERS = builder()
+ .addIdentifier(IDENTIFIER)
+ .addName("Lambert Cylindrical Equal Area (Spherical)")
+ .createGroupForMapProjection(
+ LambertCylindricalEqualArea.STANDARD_PARALLEL,
+ LambertCylindricalEqualArea.LONGITUDE_OF_ORIGIN,
+ Mercator2SP.SCALE_FACTOR, // Not formally a Cylindrical Equal Area parameter.
+ LambertCylindricalEqualArea.FALSE_EASTING,
+ LambertCylindricalEqualArea.FALSE_NORTHING);
+ }
+
+ /**
+ * Constructs a new provider.
+ */
+ public LambertCylindricalEqualAreaSpherical() {
+ super(PARAMETERS);
+ }
+
+ /**
+ * Returns the operation type for this map projection.
+ *
+ * @return {@code CylindricalProjection.class}
+ */
+ @Override
+ public final Class<CylindricalProjection> getOperationType() {
+ return CylindricalProjection.class;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return The map projection created from the given parameter values.
+ */
+ @Override
+ protected NormalizedProjection createProjection(final Parameters parameters) {
+ return new CylindricalEqualArea(this, parameters);
+ }
+}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MapProjection.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MapProjection.java
index 44d9432..d076a44 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MapProjection.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MapProjection.java
@@ -58,7 +58,7 @@
*
* @author Martin Desruisseaux (Geomatys)
* @since 0.6
- * @version 0.7
+ * @version 0.8
* @module
*/
@XmlTransient
@@ -245,47 +245,105 @@
}
/**
- * Copies all aliases and identifiers except the ones for the given authority.
- * If the given replacement is non-null, then it will be used instead of the
- * first occurrence of the omitted name.
+ * Rename the primary name and identifier of the given descriptor. Aliases are copied as-is.
*
- * <p>This method does not copy the primary name. It is caller's responsibility to add it first.</p>
+ * @param template the parameter from which to copy the aliases.
+ * @param identifier the new EPSG identifier.
+ * @param name the new EPSG name.
+ * @param builder an initially clean builder where to add the names.
+ * @return the given {@code builder}, for method call chaining.
*
- * @param source The parameter from which to copy the names.
- * @param except The authority of the name to omit. Can not be EPSG.
- * @param replacement The name to use instead of the omitted one, or {@code null} if none.
- * @param builder Where to add the names.
- * @return The given {@code builder}, for method call chaining.
- *
- * @since 0.7
+ * @since 0.8
*/
- static ParameterBuilder except(final ParameterDescriptor<Double> source, final Citation except,
- GenericName replacement, final ParameterBuilder builder)
+ static ParameterBuilder rename(final ParameterDescriptor<?> template, final String identifier, final String name,
+ final ParameterBuilder builder)
{
- for (GenericName alias : source.getAlias()) {
- if (((Identifier) alias).getAuthority() == except) {
- if (replacement == null) continue;
- alias = replacement;
- replacement = null;
- }
- builder.addName(alias);
- }
- for (final ReferenceIdentifier id : source.getIdentifiers()) {
+ return exceptEPSG(template, builder.addIdentifier(identifier).addName(name));
+ }
+
+ /**
+ * Copies name, aliases and identifiers of the given {@code template}, except the alias of the given authority
+ * which is replaced by the alias 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)
+ {
+ copyAliases(template, toRename, sameNameAs(toRename, replacement), builder.addName(template.getName()));
+ for (final ReferenceIdentifier id : template.getIdentifiers()) {
builder.addIdentifier(id);
}
return builder;
}
/**
- * Copies all names except the EPSG one from the given parameter into the builder.
- * The EPSG name is presumed the first name and identifier (this is not verified).
+ * Copies all aliases except the ones for the given authority. If the given replacement is non-null,
+ * then it will be used instead of the first occurrence of the omitted name.
*
- * @param source The parameter from which to copy the names.
- * @param builder Where to add the names.
- * @return The given {@code builder}, for method call chaining.
+ * <p>This method does not copy the primary name. It is caller's responsibility to add it first.</p>
+ *
+ * @param template the parameter from which to copy the aliases.
+ * @param exclude the authority of the alias to omit. Can not be EPSG.
+ * @param replacement the alias to use instead of the omitted one, or {@code null} if none.
+ * @param builder where to add the aliases.
+ * @return the given {@code builder}, for method call chaining.
*/
- static ParameterBuilder exceptEPSG(final ParameterDescriptor<?> source, final ParameterBuilder builder) {
- for (final GenericName alias : source.getAlias()) {
+ private static ParameterBuilder copyAliases(final ParameterDescriptor<Double> template, final Citation exclude,
+ GenericName replacement, final ParameterBuilder builder)
+ {
+ for (GenericName alias : template.getAlias()) {
+ if (((Identifier) alias).getAuthority() == exclude) {
+ if (replacement == null) continue;
+ alias = replacement;
+ replacement = null;
+ }
+ builder.addName(alias);
+ }
+ return builder;
+ }
+
+ /**
+ * Copies all aliases and identifiers, but using the alias specified by the given authority as the primary name.
+ * The old primary name (usually the EPSG name) is discarded. Identifier are <strong>not</strong> copied, which
+ * usually implies that only the EPSG identifier is ignored (because it is usually the only parameter identifier).
+ *
+ * <p>This is a convenience method for defining the parameters of an ESRI-specific (or any other authority)
+ * projection using the EPSG parameters as template. Note that in the particular case where the desired
+ * authority is OGC, {@link #exceptEPSG(ParameterDescriptor, ParameterBuilder)} is more efficient.</p>
+ *
+ * @param template the parameter from which to copy the names.
+ * @param authority the authority to use for the primary name.
+ * @param builder an initially clean builder where to add the names.
+ * @return the given {@code builder}, for method call chaining.
+ *
+ * @since 0.8
+ */
+ static ParameterBuilder alternativeAuthority(final ParameterDescriptor<Double> template,
+ final Citation authority, final ParameterBuilder builder)
+ {
+ return copyAliases(template, authority, null, builder.addName(sameNameAs(authority, template)));
+ }
+
+ /**
+ * Copies all names except the EPSG one from the given parameter into the builder.
+ * The EPSG information are presumed to be the primary name and the only identifier (this is not verified).
+ *
+ * <p>If this method is invoking with a "clean" builder, then the result is to promote the first alias as
+ * the primary name. The first alias is usually the OGC name.</p>
+ *
+ * @param template the parameter from which to copy the names.
+ * @param builder where to add the names.
+ * @return the given {@code builder}, for method call chaining.
+ */
+ static ParameterBuilder exceptEPSG(final ParameterDescriptor<?> template, final ParameterBuilder builder) {
+ for (final GenericName alias : template.getAlias()) {
builder.addName(alias);
}
return builder;
@@ -294,8 +352,8 @@
/**
* 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}.
+ * @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 Messages.formatInternational(Messages.Keys.NotFormalProjectionParameter_1, origin);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator2SP.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator2SP.java
index d921cdc..2ea5148 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator2SP.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator2SP.java
@@ -105,9 +105,9 @@
.addIdentifier(Citations.S57, "8")
.createGroupForMapProjection(
STANDARD_PARALLEL,
- latitudeOfOrigin, // Not formally a Mercator2SP parameter.
+ latitudeOfOrigin, // Not formally a Mercator2SP parameter.
Mercator1SP.LONGITUDE_OF_ORIGIN,
- SCALE_FACTOR, // Not formally a Mercator2SP parameter.
+ SCALE_FACTOR, // Not formally a Mercator2SP parameter.
FALSE_EASTING,
FALSE_NORTHING);
}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Molodensky.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Molodensky.java
index b0c3e6d..eeb1b69 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Molodensky.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Molodensky.java
@@ -92,8 +92,8 @@
public static final ParameterDescriptorGroup PARAMETERS;
static {
final ParameterBuilder builder = builder();
- AXIS_LENGTH_DIFFERENCE = builder.addName("Semi-major axis length difference").create(Double.NaN, SI.METRE);
- FLATTENING_DIFFERENCE = builder.addName("Flattening difference").createBounded(-1, +1, Double.NaN, Unit.ONE);
+ AXIS_LENGTH_DIFFERENCE = builder.addIdentifier("8654").addName("Semi-major axis length difference").create(Double.NaN, SI.METRE);
+ FLATTENING_DIFFERENCE = builder.addIdentifier("8655").addName("Flattening difference").createBounded(-1, +1, Double.NaN, Unit.ONE);
PARAMETERS = builder.setRequired(true)
.addIdentifier("9604")
.addName("Molodensky")
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ObliqueStereographic.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ObliqueStereographic.java
index 600ec4c..0d97a3b 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ObliqueStereographic.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ObliqueStereographic.java
@@ -31,7 +31,7 @@
* @author Rueben Schulz (UBC)
* @author Martin Desruisseaux (Geomatys)
* @since 0.6
- * @version 0.7
+ * @version 0.8
* @module
*
* @see <a href="http://www.remotesensing.org/geotiff/proj_list/oblique_stereographic.html">Oblique Stereographic on RemoteSensing.org</a>
@@ -56,6 +56,12 @@
public static final ParameterDescriptor<Double> LONGITUDE_OF_ORIGIN = Mercator1SP.LONGITUDE_OF_ORIGIN;
/**
+ * The operation parameter descriptor for the <cite>Scale factor at natural origin</cite> (k₀) parameter value.
+ * Valid values range is (0 … ∞) and default value is 1.
+ */
+ public static final ParameterDescriptor<Double> SCALE_FACTOR = Mercator1SP.SCALE_FACTOR; // Same as PolarStereographicA.
+
+ /**
* The group of all parameters expected by this coordinate operation.
*/
private static final ParameterDescriptorGroup PARAMETERS;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicA.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicA.java
index c3f12d7..e278d30 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicA.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicA.java
@@ -29,7 +29,7 @@
* @author Rueben Schulz (UBC)
* @author Martin Desruisseaux (Geomatys)
* @since 0.6
- * @version 0.7
+ * @version 0.8
* @module
*
* @see <a href="http://www.remotesensing.org/geotiff/proj_list/polar_stereographic.html">Polar Stereographic on RemoteSensing.org</a>
@@ -64,6 +64,12 @@
public static final ParameterDescriptor<Double> LONGITUDE_OF_ORIGIN;
/**
+ * The operation parameter descriptor for the <cite>Scale factor at natural origin</cite> (k₀) parameter value.
+ * Valid values range is (0 … ∞) and default value is 1.
+ */
+ public static final ParameterDescriptor<Double> SCALE_FACTOR = Mercator1SP.SCALE_FACTOR;
+
+ /**
* The group of all parameters expected by this coordinate operation.
*/
private static final ParameterDescriptorGroup PARAMETERS;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicB.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicB.java
index f9e225b..99e8ea3 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicB.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicB.java
@@ -31,7 +31,7 @@
* @author Rueben Schulz (UBC)
* @author Martin Desruisseaux (Geomatys)
* @since 0.6
- * @version 0.6
+ * @version 0.8
* @module
*/
@XmlTransient
@@ -67,7 +67,6 @@
* because it is sometime used in Well Known Text (WKT). However it shall be interpreted as a
* <cite>Scale factor at the standard parallel</cite> rather than at the natural origin.</p>
*/
- @SuppressWarnings("FieldNameHidesFieldInSuperclass")
static final ParameterDescriptor<Double> SCALE_FACTOR;
/**
@@ -77,8 +76,7 @@
static {
final ParameterBuilder builder = builder();
LONGITUDE_OF_ORIGIN = createLongitude(
- exceptEPSG(PolarStereographicA.LONGITUDE_OF_ORIGIN,
- builder.addIdentifier("8833").addName("Longitude of origin")));
+ rename(PolarStereographicA.LONGITUDE_OF_ORIGIN, "8833", "Longitude of origin", builder));
STANDARD_PARALLEL = createMandatoryLatitude(builder
.addIdentifier("8832").addName("Latitude of standard parallel")
@@ -98,7 +96,7 @@
.createGroupForMapProjection(
STANDARD_PARALLEL,
LONGITUDE_OF_ORIGIN,
- SCALE_FACTOR, // Not formally a parameter of this projection.
+ SCALE_FACTOR, // Not formally a parameter of this projection.
FALSE_EASTING,
FALSE_NORTHING);
}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicSouth.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicSouth.java
index 12c8284..6359602 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicSouth.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicSouth.java
@@ -44,22 +44,18 @@
private static final long serialVersionUID = -6173635411676914083L;
/**
- * Copies all names and identifiers, but using the ESRI authority as the primary name.
- * This is a convenience method for defining the parameters of an ESRI-specific projection
- * using the EPSG parameters as template.
- */
- private static ParameterBuilder addNamesAndIdentifiers(final ParameterDescriptor<Double> source, final ParameterBuilder builder) {
- return except(source, Citations.ESRI, null, builder.addName(sameNameAs(Citations.ESRI, source)).addName(source.getName()));
- }
-
- /**
- * Returns the same parameter than the given one, except that the primary name is the ESRI name
- * instead than the EPSG one.
+ * Returns the same parameter than the given one, except that the alias of the ESRI authority
+ * is promoted as the primary name. The old primary name and identifiers (which are usually the
+ * EPSG ones) are discarded.
+ *
+ * @param template the parameter from which to copy the names and identifiers.
+ * @param builder an initially clean builder where to add the names.
+ * @return the given {@code builder}, for method call chaining.
*/
@SuppressWarnings("unchecked")
- private static ParameterDescriptor<Double> forESRI(final ParameterDescriptor<Double> source, final ParameterBuilder builder) {
- return addNamesAndIdentifiers(source, builder).createBounded((MeasurementRange<Double>)
- ((DefaultParameterDescriptor<Double>) source).getValueDomain(), source.getDefaultValue());
+ private static ParameterDescriptor<Double> forESRI(final ParameterDescriptor<Double> template, final ParameterBuilder builder) {
+ return alternativeAuthority(template, Citations.ESRI, builder).createBounded((MeasurementRange<Double>)
+ ((DefaultParameterDescriptor<Double>) template).getValueDomain(), template.getDefaultValue());
}
/**
@@ -69,13 +65,13 @@
static {
final ParameterBuilder builder = builder();
final ParameterDescriptor<?>[] parameters = {
- addNamesAndIdentifiers(PolarStereographicB.STANDARD_PARALLEL, builder)
+ alternativeAuthority(PolarStereographicB.STANDARD_PARALLEL, Citations.ESRI, builder)
.createBounded(Latitude.MIN_VALUE, 0, Latitude.MIN_VALUE, NonSI.DEGREE_ANGLE),
forESRI(PolarStereographicB.LONGITUDE_OF_ORIGIN, builder),
- forESRI(PolarStereographicB.SCALE_FACTOR, builder),
- forESRI(PolarStereographicB.FALSE_EASTING, builder),
- forESRI(PolarStereographicB.FALSE_NORTHING, builder)
+ PolarStereographicB.SCALE_FACTOR, // Not formally a parameter of this projection.
+ forESRI(LambertCylindricalEqualArea.FALSE_EASTING, builder),
+ forESRI(LambertCylindricalEqualArea.FALSE_NORTHING, builder)
};
PARAMETERS = builder
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/RegionalMercator.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/RegionalMercator.java
index 6894502..ee9271b 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/RegionalMercator.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/RegionalMercator.java
@@ -74,19 +74,16 @@
static {
final ParameterBuilder builder = builder();
- LATITUDE_OF_FALSE_ORIGIN = createLatitude(exceptEPSG(Mercator1SP.LATITUDE_OF_ORIGIN, builder
- .addIdentifier("8821")
- .addName("Latitude of false origin"))
+ LATITUDE_OF_FALSE_ORIGIN = createLatitude(
+ rename(Mercator1SP.LATITUDE_OF_ORIGIN, "8821", "Latitude of false origin", builder)
.rename(Citations.GEOTIFF, "FalseOriginLat"), false);
- EASTING_AT_FALSE_ORIGIN = createShift(exceptEPSG(FALSE_EASTING, builder
- .addIdentifier("8826")
- .addName("Easting at false origin"))
+ EASTING_AT_FALSE_ORIGIN = createShift(
+ rename(FALSE_EASTING, "8826", "Easting at false origin", builder)
.rename(Citations.GEOTIFF, "FalseOriginEasting"));
- NORTHING_AT_FALSE_ORIGIN = createShift(exceptEPSG(FALSE_NORTHING, builder
- .addIdentifier("8827")
- .addName("Northing at false origin"))
+ NORTHING_AT_FALSE_ORIGIN = createShift(
+ rename(FALSE_NORTHING, "8827", "Northing at false origin", builder)
.rename(Citations.GEOTIFF, "FalseOriginNorthing"));
PARAMETERS = builder
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/TransverseMercator.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/TransverseMercator.java
index 5c6df3d..1849ce9 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/TransverseMercator.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/TransverseMercator.java
@@ -90,9 +90,8 @@
LATITUDE_OF_ORIGIN = createLatitude(builder
.addNamesAndIdentifiers(Mercator1SP.LATITUDE_OF_ORIGIN), true);
- builder.addName(Mercator1SP.LONGITUDE_OF_ORIGIN.getName());
- LONGITUDE_OF_ORIGIN = createLongitude(except(Mercator1SP.LONGITUDE_OF_ORIGIN, Citations.NETCDF,
- sameNameAs(Citations.NETCDF, LambertConformal2SP.LONGITUDE_OF_FALSE_ORIGIN), builder));
+ LONGITUDE_OF_ORIGIN = createLongitude(renameAlias(Mercator1SP.LONGITUDE_OF_ORIGIN,
+ Citations.NETCDF, LambertConformal2SP.LONGITUDE_OF_FALSE_ORIGIN, builder));
SCALE_FACTOR = createScale(builder
.addNamesAndIdentifiers(Mercator1SP.SCALE_FACTOR)
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/package-info.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/package-info.java
index 77ea846..d71ee76 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/package-info.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/package-info.java
@@ -22,7 +22,7 @@
*
* @author Martin Desruisseaux (Geomatys)
* @since 0.6
- * @version 0.7
+ * @version 0.8
* @module
*
* @see org.apache.sis.referencing.operation.transform.MathTransformProvider
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterBuilder.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterBuilder.java
index 514b046..0481f40 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterBuilder.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterBuilder.java
@@ -125,7 +125,7 @@
/**
* Creates a new builder initialized to properties of the given object.
*
- * @param descriptor The descriptor from which to inherit properties, or {@code null}.
+ * @param descriptor the descriptor from which to inherit properties, or {@code null}.
*
* @since 0.6
*/
@@ -165,10 +165,10 @@
/**
* Creates a descriptor for values of the given type without domain restriction.
*
- * @param <T> The compile-time type of the {@code valueClass} argument.
- * @param valueClass The class that describe the type of the parameter values.
- * @param defaultValue The default value for the parameter, or {@code null} if none.
- * @return The parameter descriptor for the given default value and unit.
+ * @param <T> the compile-time type of the {@code valueClass} argument.
+ * @param valueClass the class that describe the type of the parameter values.
+ * @param defaultValue the default value for the parameter, or {@code null} if none.
+ * @return the parameter descriptor for the given default value and unit.
*/
public <T> ParameterDescriptor<T> create(final Class<T> valueClass, final T defaultValue) {
return create(valueClass, null, null, defaultValue);
@@ -178,9 +178,9 @@
* Creates a descriptor for floating point values without domain restriction.
* All {@code double} values are considered valid.
*
- * @param defaultValue The default value for the parameter, or {@link Double#NaN} if none.
- * @param unit The default unit, or {@code null} if none.
- * @return The parameter descriptor for the given default value and unit.
+ * @param defaultValue the default value for the parameter, or {@link Double#NaN} if none.
+ * @param unit the default unit, or {@code null} if none.
+ * @return the parameter descriptor for the given default value and unit.
*/
public ParameterDescriptor<Double> create(final double defaultValue, final Unit<?> unit) {
final Range<Double> valueDomain;
@@ -196,9 +196,9 @@
* Creates a descriptor for floating point values greater than zero.
* The zero value is not considered valid. There is no maximal value.
*
- * @param defaultValue The default value for the parameter, or {@link Double#NaN} if none.
- * @param unit The default unit, or {@code null} if none.
- * @return The parameter descriptor for the given default value and unit.
+ * @param defaultValue the default value for the parameter, or {@link Double#NaN} if none.
+ * @param unit the default unit, or {@code null} if none.
+ * @return the parameter descriptor for the given default value and unit.
*/
public ParameterDescriptor<Double> createStrictlyPositive(final double defaultValue, final Unit<?> unit) {
final Range<Double> valueDomain;
@@ -213,11 +213,11 @@
/**
* Creates a descriptor for floating point values restricted to the given domain.
*
- * @param minimumValue The minimum parameter value (inclusive), or {@link Double#NEGATIVE_INFINITY} if none.
- * @param maximumValue The maximum parameter value (inclusive), or {@link Double#POSITIVE_INFINITY} if none.
- * @param defaultValue The default value for the parameter, or {@link Double#NaN} if none.
- * @param unit The unit for default, minimum and maximum values, or {@code null} if none.
- * @return The parameter descriptor for the given domain of values.
+ * @param minimumValue the minimum parameter value (inclusive), or {@link Double#NEGATIVE_INFINITY} if none.
+ * @param maximumValue the maximum parameter value (inclusive), or {@link Double#POSITIVE_INFINITY} if none.
+ * @param defaultValue the default value for the parameter, or {@link Double#NaN} if none.
+ * @param unit the unit for default, minimum and maximum values, or {@code null} if none.
+ * @return the parameter descriptor for the given domain of values.
*/
public ParameterDescriptor<Double> createBounded(final double minimumValue, final double maximumValue,
final double defaultValue, final Unit<?> unit)
@@ -236,10 +236,10 @@
/**
* Creates a descriptor for integer values restricted to the given domain.
*
- * @param minimumValue The minimum parameter value (inclusive).
- * @param maximumValue The maximum parameter value (inclusive).
- * @param defaultValue The default value for the parameter.
- * @return The parameter descriptor for the given domain of values.
+ * @param minimumValue the minimum parameter value (inclusive).
+ * @param maximumValue the maximum parameter value (inclusive).
+ * @param defaultValue the default value for the parameter.
+ * @return the parameter descriptor for the given domain of values.
*/
public ParameterDescriptor<Integer> createBounded(final int minimumValue, final int maximumValue,
final int defaultValue)
@@ -250,12 +250,12 @@
/**
* Creates a descriptor for values of the given type restricted to the given domain.
*
- * @param <T> The compile-time type of the {@code valueClass} argument.
- * @param valueClass The class that describe the type of the parameter values.
- * @param minimumValue The minimum parameter value (inclusive), or {@code null} if none.
- * @param maximumValue The maximum parameter value (inclusive), or {@code null} if none.
- * @param defaultValue The default value for the parameter, or {@code null} if none.
- * @return The parameter descriptor for the given domain of values.
+ * @param <T> the compile-time type of the {@code valueClass} argument.
+ * @param valueClass the class that describe the type of the parameter values.
+ * @param minimumValue the minimum parameter value (inclusive), or {@code null} if none.
+ * @param maximumValue the maximum parameter value (inclusive), or {@code null} if none.
+ * @param defaultValue the default value for the parameter, or {@code null} if none.
+ * @return the parameter descriptor for the given domain of values.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public <T extends Comparable<? super T>> ParameterDescriptor<T> createBounded(final Class<T> valueClass,
@@ -277,10 +277,10 @@
* Creates a descriptor for values in the domain represented by the given {@code Range} object.
* This method allows to specify whether the minimum and maximum values are inclusive or not.
*
- * @param <T> The type of the parameter values.
- * @param valueDomain The minimum value, maximum value and unit of measurement.
- * @param defaultValue The default value for the parameter, or {@code null} if none.
- * @return The parameter descriptor for the given domain of values.
+ * @param <T> the type of the parameter values.
+ * @param valueDomain the minimum value, maximum value and unit of measurement.
+ * @param defaultValue the default value for the parameter, or {@code null} if none.
+ * @return the parameter descriptor for the given domain of values.
*/
public <T extends Comparable<? super T>> ParameterDescriptor<T> createBounded(
final Range<T> valueDomain, final T defaultValue)
@@ -297,12 +297,12 @@
* a {@linkplain org.opengis.util.CodeList code list} or {@linkplain Enum enumeration} subset.
* It is not necessary to provide this property when all values from the code list or enumeration are valid.</p>
*
- * @param <T> The compile-time type of the {@code valueClass} argument.
- * @param valueClass The class that describe the type of the parameter values.
- * @param validValues A finite set of valid values (usually from a code list or enumeration)
- * or {@code null} if it doesn't apply.
- * @param defaultValue The default value for the parameter, or {@code null} if none.
- * @return The parameter descriptor for the given set of valid values.
+ * @param <T> the compile-time type of the {@code valueClass} argument.
+ * @param valueClass the class that describe the type of the parameter values.
+ * @param validValues a finite set of valid values (usually from a code list or enumeration)
+ * or {@code null} if it doesn't apply.
+ * @param defaultValue the default value for the parameter, or {@code null} if none.
+ * @return the parameter descriptor for the given set of valid values.
*/
public <T> ParameterDescriptor<T> createEnumerated(final Class<T> valueClass, final T[] validValues, final T defaultValue) {
ensureNonNull("valueClass", valueClass);
@@ -330,13 +330,13 @@
/**
* Creates a descriptor group for the given cardinality and parameters.
*
- * @param minimumOccurs The {@linkplain DefaultParameterDescriptorGroup#getMinimumOccurs() minimum}
- * number of times that values for this parameter group are required.
- * @param maximumOccurs The {@linkplain DefaultParameterDescriptorGroup#getMaximumOccurs() maximum}
- * number of times that values for this parameter group are required.
- * @param parameters The {@linkplain DefaultParameterDescriptorGroup#descriptors() parameter descriptors}
- * for the group to create.
- * @return The parameter descriptor group.
+ * @param minimumOccurs the {@linkplain DefaultParameterDescriptorGroup#getMinimumOccurs() minimum}
+ * number of times that values for this parameter group are required.
+ * @param maximumOccurs the {@linkplain DefaultParameterDescriptorGroup#getMaximumOccurs() maximum}
+ * number of times that values for this parameter group are required.
+ * @param parameters the {@linkplain DefaultParameterDescriptorGroup#descriptors() parameter descriptors}
+ * for the group to create.
+ * @return the parameter descriptor group.
*/
public ParameterDescriptorGroup createGroup(final int minimumOccurs, final int maximumOccurs,
final GeneralParameterDescriptor... parameters)
@@ -356,9 +356,9 @@
* {@link #createGroup(int, int, GeneralParameterDescriptor[])} with a cardinality of [0 … 1]
* or [1 … 1] depending on the value given to the last call to {@link #setRequired(boolean)}.
*
- * @param parameters The {@linkplain DefaultParameterDescriptorGroup#descriptors() parameter descriptors}
+ * @param parameters the {@linkplain DefaultParameterDescriptorGroup#descriptors() parameter descriptors}
* for the group to create.
- * @return The parameter descriptor group.
+ * @return the parameter descriptor group.
*/
public ParameterDescriptorGroup createGroup(final GeneralParameterDescriptor... parameters) {
return createGroup(required ? 1 : 0, 1, parameters);
@@ -373,8 +373,8 @@
* expect the same parameters than their <cite>"Position Vector transformation"</cite> counterpart
* (EPSG codes 1033, 1037 and 9606) but perform the rotation in the opposite direction.</div>
*
- * @param parameters The existing group from which to copy the parameters.
- * @return The parameter descriptor group.
+ * @param parameters the existing group from which to copy the parameters.
+ * @return the parameter descriptor group.
*
* @since 0.7
*/
@@ -421,9 +421,9 @@
* minimum} and {@linkplain DefaultParameterDescriptorGroup#getMaximumOccurs() maximum occurrence} of 1,
* regardless the value given to {@link #setRequired(boolean)}.
*
- * @param parameters The {@linkplain DefaultParameterDescriptorGroup#descriptors() parameter descriptors}
+ * @param parameters the {@linkplain DefaultParameterDescriptorGroup#descriptors() parameter descriptors}
* for the group to create.
- * @return The parameter descriptor group for a map projection.
+ * @return the parameter descriptor group for a map projection.
*
* @since 0.6
*/
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
index f9c0f05..f2fc6fb 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
@@ -567,7 +567,7 @@
* from ISO 19111 since 'SingleOperation' is conceptually abstract. But we do that as a way to said that
* we are missing this important piece of information but still go ahead.
*
- * It is unconvenient to guarantee that the created operation is an instance of 'baseType' since the user
+ * It is inconvenient to guarantee that the created operation is an instance of 'baseType' since the user
* could have specified an implementation class or a custom sub-interface. We will perform the type check
* only after object creation.
*/
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java
index 2441617..374ef16 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java
@@ -167,10 +167,10 @@
* For each line below, add the smallest values first in order to reduce rounding errors.
* The smallest values are the one using the eccentricity raised to the highest power.
*/
- ci2 = 13/ 360.* e8 + 1/ 12.* e6 + 5/24.* e4 + e2/2;
- ci4 = 811/ 11520.* e8 + 29/240.* e6 + 7/48.* e4;
- ci6 = 81/ 1120.* e8 + 7/120.* e6;
- ci8 = 4279/161280.* e8;
+ ci2 = 13/ 360. * e8 + 1/ 12. * e6 + 5/24. * e4 + e2/2;
+ ci4 = 811/ 11520. * e8 + 29/240. * e6 + 7/48. * e4;
+ ci6 = 81/ 1120. * e8 + 7/120. * e6;
+ ci8 = 4279/161280. * e8;
/*
* When rewriting equations using trigonometric identities, some constants appear.
* For example sin(2θ) = 2⋅sinθ⋅cosθ, so we can factor out the 2 constant into the
@@ -257,7 +257,7 @@
+ ci2 * sin(2*φ);
} else {
/*
- * Same formula than above, be rewriten using trigonometric identities in order to have only two
+ * Same formula than above, but rewriten using trigonometric identities in order to have only two
* calls to Math.sin/cos instead than 5. The performance gain is twice faster on tested machine.
*/
final double sin_2φ = sin(2*φ);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CylindricalEqualArea.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CylindricalEqualArea.java
new file mode 100644
index 0000000..ef5c624
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CylindricalEqualArea.java
@@ -0,0 +1,375 @@
+/*
+ * 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.operation.projection;
+
+import java.util.EnumMap;
+import org.opengis.parameter.ParameterDescriptor;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.MathTransformFactory;
+import org.opengis.referencing.operation.Matrix;
+import org.opengis.referencing.operation.OperationMethod;
+import org.opengis.referencing.operation.TransformException;
+import org.opengis.util.FactoryException;
+import org.apache.sis.internal.referencing.provider.Mercator1SP;
+import org.apache.sis.internal.referencing.provider.LambertCylindricalEqualAreaSpherical;
+import org.apache.sis.internal.util.DoubleDouble;
+import org.apache.sis.parameter.Parameters;
+import org.apache.sis.referencing.operation.matrix.Matrix2;
+import org.apache.sis.referencing.operation.matrix.MatrixSIS;
+import org.apache.sis.referencing.operation.transform.ContextualParameters;
+import org.apache.sis.util.Workaround;
+
+import static java.lang.Math.*;
+import static org.apache.sis.internal.util.DoubleDouble.verbatim;
+import static org.apache.sis.internal.referencing.provider.LambertCylindricalEqualArea.*;
+
+
+/**
+ * <cite>Cylindrical Equal Area</cite> projection (EPSG codes 9834, 9835).
+ * This is the simplest equal-area projection.
+ * This projection has various names depending on its standard parallel:
+ *
+ * <table class="sis">
+ * <caption>Non-exhaustive list of variants</caption>
+ * <tr><th>Name</th> <th>Standard parallel</th></tr>
+ * <tr><td>Lambert cylindrical equal-area</td> <td>0°</td></tr>
+ * <tr><td>Behrmann cylindrical equal-area</td> <td>30°</td></tr>
+ * <tr><td>Gall orthographic</td> <td>45°</td></tr>
+ * <tr><td>Balthasart</td> <td>50°</td></tr>
+ * </table>
+ *
+ * <div class="section">Description</div>
+ * The parallels and the meridians are straight lines and cross at right angles.
+ * The scale is true along standard parallels, but distortion increase greatly at other locations.
+ * Distortions are so great that there is little use of this projection for world mapping purposes.
+ * However this projection may be useful for computing areas.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.8
+ * @version 0.8
+ * @module
+ */
+public class CylindricalEqualArea extends EqualAreaProjection {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = 8840395516658904421L;
+
+ /**
+ * Returns the variant of the projection based on the name and identifier of the given operation method.
+ * See {@link #variant} for the list of possible values.
+ */
+ private static byte getVariant(final OperationMethod method) {
+ if (identMatch(method, "(?i).*\\bSpherical\\b.*", LambertCylindricalEqualAreaSpherical.IDENTIFIER)) {
+ return Initializer.AUTHALIC_RADIUS;
+ }
+ return 0;
+ }
+
+ /**
+ * The type of Cylindrical Equal Area projection. Possible values are:
+ *
+ * <ul>
+ * <li>0 if this projection is a default variant.</li>
+ * <li>{@link Initializer#AUTHALIC_RADIUS} if this projection is the "Lambert Cylindrical Equal Area (Spherical)"
+ * case, in which case the semi-major and semi-minor axis lengths should be replaced by the authalic radius
+ * (this replacement is performed by the {@link Initializer} constructor).</li>
+ * </ul>
+ *
+ * Other cases may be added in the future.
+ *
+ * @see #getVariant(OperationMethod)
+ */
+ private final byte variant;
+
+ /**
+ * Value of {@link #qm(double)} function (part of Snyder equation (3-12)) at pole (sinφ = 1).
+ *
+ * @see #computeCoefficients()
+ */
+ private transient double qmPolar;
+
+ /**
+ * Creates a Cylindrical Equal Area projection from the given parameters.
+ *
+ * @param method Description of the projection parameters.
+ * @param parameters The parameter values of the projection to create.
+ */
+ public CylindricalEqualArea(final OperationMethod method, final Parameters parameters) {
+ this(initializer(method, parameters));
+ }
+
+ /**
+ * Work around for RFE #4093999 in Sun's bug database
+ * ("Relax constraint on placement of this()/super() call in constructors").
+ */
+ @SuppressWarnings("fallthrough")
+ @Workaround(library="JDK", version="1.7")
+ private static Initializer initializer(final OperationMethod method, final Parameters parameters) {
+ final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles =
+ new EnumMap<ParameterRole, ParameterDescriptor<Double>>(ParameterRole.class);
+ /*
+ * "Longitude of origin" and "scale factor" are intentionally omitted from this map because they will
+ * be handled in a special way. See comments in Mercator.initializer(…) method for more details.
+ */
+ roles.put(ParameterRole.FALSE_EASTING, FALSE_EASTING);
+ roles.put(ParameterRole.FALSE_NORTHING, FALSE_NORTHING);
+ return new Initializer(method, parameters, roles, getVariant(method));
+ }
+
+ /**
+ * Work around for RFE #4093999 in Sun's bug database
+ * ("Relax constraint on placement of this()/super() call in constructors").
+ */
+ @Workaround(library="JDK", version="1.7")
+ private CylindricalEqualArea(final Initializer initializer) {
+ super(initializer);
+ variant = initializer.variant;
+ final MatrixSIS denormalize = context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION);
+ /*
+ * The longitude of origin is normally subtracted in the 'normalize' matrix. But in the particular of case
+ * of this map projection we can apply -λ₀ on any matrix. So we apply that operation on 'denormalize' for
+ * consistency with the Mercator projection and for increasing the chances to have cancellation when
+ * multiplying matrices together.
+ */
+ final double λ0 = initializer.getAndStore(LONGITUDE_OF_ORIGIN);
+ if (λ0 != 0) {
+ final DoubleDouble offset = DoubleDouble.createDegreesToRadians();
+ offset.multiply(-λ0);
+ denormalize.convertBefore(0, null, offset);
+ }
+ /*
+ * Compute the scale factor as k₀ = cosφ₁/√(1 - ℯ²⋅sin²φ₁), multiplied by user-specified scale factor if any.
+ * Explicit scale factor is not formally a Cylindrical Equal Area parameter (it is rather computed from φ₁),
+ * but we nevertheless support it.
+ */
+ final double φ1 = toRadians(initializer.getAndStore(STANDARD_PARALLEL));
+ final DoubleDouble k0 = verbatim(initializer.scaleAtφ(sin(φ1), cos(φ1)));
+ k0.multiply(initializer.getAndStore(Mercator1SP.SCALE_FACTOR));
+ /*
+ * In most Apache SIS map projection implementations, the scale factor is handled by the super-class by
+ * specifying a ParameterRole.SCALE_FACTOR. However in the case of this CylindricalEqualArea we rather
+ * handle the scale factor ourselves, because we do not perform the same multiplication on both axes:
+ *
+ * x shall be multiplied by k₀
+ * y shall be divided by k₀
+ *
+ * Furthermore we also multiply y by (1-ℯ²)/2 for avoiding the need to recompute this constant during
+ * the projection of every point.
+ */
+ final DoubleDouble ik = new DoubleDouble(1, 0);
+ ik.subtract(initializer.eccentricitySquared);
+ ik.multiply(0.5, 0); // This line need to be cancelled when using spherical formulas.
+ ik.divide(k0);
+ denormalize.convertAfter(0, k0, null);
+ denormalize.convertAfter(1, ik, null);
+ computeCoefficients();
+ }
+
+ /**
+ * Invoked at construction time or on deserialization for computing the transient fields.
+ */
+ @Override
+ final void computeCoefficients() {
+ super.computeCoefficients();
+ qmPolar = qm(1);
+ }
+
+ /**
+ * Creates a new projection initialized to the same parameters than the given one.
+ */
+ CylindricalEqualArea(final CylindricalEqualArea other) {
+ super(other);
+ variant = other.variant;
+ qmPolar = other.qmPolar;
+ }
+
+ /**
+ * Returns the sequence of <cite>normalization</cite> → {@code this} → <cite>denormalization</cite> transforms
+ * as a whole. The transform returned by this method expects (<var>longitude</var>, <var>latitude</var>)
+ * coordinates in <em>degrees</em> and returns (<var>x</var>,<var>y</var>) coordinates in <em>metres</em>.
+ *
+ * <p>The non-linear part of the returned transform will be {@code this} transform, except if the ellipsoid
+ * is spherical. In the later case, {@code this} transform will be replaced by a simplified implementation.</p>
+ *
+ * @param factory The factory to use for creating the transform.
+ * @return The map projection from (λ,φ) to (<var>x</var>,<var>y</var>) coordinates.
+ * @throws FactoryException if an error occurred while creating a transform.
+ */
+ @Override
+ public MathTransform createMapProjection(final MathTransformFactory factory) throws FactoryException {
+ CylindricalEqualArea kernel = this;
+ if (variant == Initializer.AUTHALIC_RADIUS || eccentricity == 0) {
+ kernel = new Spherical(this);
+ }
+ return context.completeTransform(factory, kernel);
+ }
+
+ /**
+ * Converts the specified (λ,φ) coordinate (units in radians) and stores the result in {@code dstPts}
+ * (linear distance on a unit sphere). In addition, opportunistically computes the projection derivative
+ * if {@code derivate} is {@code true}.
+ *
+ * @return The matrix of the projection derivative at the given source position,
+ * or {@code null} if the {@code derivate} argument is {@code false}.
+ * @throws ProjectionException if the coordinate can not be converted.
+ */
+ @Override
+ public Matrix transform(final double[] srcPts, final int srcOff,
+ final double[] dstPts, final int dstOff,
+ final boolean derivate) throws ProjectionException
+ {
+ final double φ = srcPts[srcOff+1];
+ final double sinφ = sin(φ);
+ if (dstPts != null) {
+ dstPts[dstOff ] = srcPts[srcOff]; // Multiplication by k₀ will be applied by the denormalization matrix.
+ dstPts[dstOff+1] = qm(sinφ); // Multiplication by (1-ℯ²)/(2k₀) will be applied by the denormalization matrix.
+ }
+ /*
+ * End of map projection. Now compute the derivative, if requested.
+ */
+ return derivate ? new Matrix2(1, 0, 0, dqm_dφ(sinφ, cos(φ))) : null;
+ }
+
+ /**
+ * Converts a list of coordinate point ordinal values.
+ *
+ * <div class="note"><b>Note:</b>
+ * We override the super-class method only as an optimization in the special case where the target coordinates
+ * are written at the same locations than the source coordinates. In such case, we can take advantage of the
+ * fact that the λ values are not modified by the normalized Cylindrical Equal Area projection.</div>
+ *
+ * @throws TransformException if a point can not be converted.
+ */
+ @Override
+ public void transform(final double[] srcPts, int srcOff,
+ final double[] dstPts, int dstOff, int numPts)
+ throws TransformException
+ {
+ if (srcPts != dstPts || srcOff != dstOff) {
+ super.transform(srcPts, srcOff, dstPts, dstOff, numPts);
+ } else {
+ dstOff--;
+ while (--numPts >= 0) {
+ final double φ = dstPts[dstOff += 2]; // Same as srcPts[srcOff + 1].
+ dstPts[dstOff] = qm(sin(φ)); // Part of Synder equation (10-15)
+ }
+ }
+ }
+
+ /**
+ * Converts the specified (<var>x</var>,<var>y</var>) coordinates
+ * and stores the result in {@code dstPts} (angles in radians).
+ *
+ * @throws ProjectionException if the point can not be converted.
+ */
+ @Override
+ protected void inverseTransform(final double[] srcPts, final int srcOff,
+ final double[] dstPts, final int dstOff)
+ throws ProjectionException
+ {
+ final double y = srcPts[srcOff+1]; // Must be before writing x.
+ dstPts[dstOff ] = srcPts[srcOff ]; // Must be before writing y.
+ dstPts[dstOff+1] = φ(y / qmPolar);
+ /*
+ * Equation 10-26 of Synder gives β = asin(2y⋅k₀/(a⋅qPolar)).
+ * In our case it simplifies to sinβ = (y/qmPolar) because:
+ *
+ * - y is already multiplied by 2k₀/a because of the denormalization matrix
+ * - the missing (1-ℯ²) term in qmPolar (compared to qPolar) is in the denormalization matrix.
+ * - taking the arc sine of β is left to φ(double) function.
+ */
+ }
+
+
+ /**
+ * Provides the transform equations for the spherical case of the Cylindrical Equal Area projection.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.8
+ * @version 0.8
+ * @module
+ */
+ static final class Spherical extends CylindricalEqualArea {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = 1063449347697947732L;
+
+ /**
+ * Constructs a new map projection from the parameters of the given projection.
+ *
+ * @param other The other projection (usually ellipsoidal) from which to copy the parameters.
+ */
+ Spherical(final CylindricalEqualArea other) {
+ super(other);
+ context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION).convertAfter(1, 2, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Matrix transform(final double[] srcPts, final int srcOff,
+ final double[] dstPts, final int dstOff,
+ final boolean derivate) throws ProjectionException
+ {
+ final double φ = srcPts[srcOff+1];
+ if (dstPts != null) {
+ dstPts[dstOff ] = srcPts[srcOff];
+ dstPts[dstOff+1] = sin(φ);
+ }
+ return derivate ? new Matrix2(1, 0, 0, cos(φ)) : null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <div class="note"><b>Note:</b>
+ * This method must be overridden because the {@link Mercator} class overrides the {@link NormalizedProjection}
+ * default implementation.</div>
+ */
+ @Override
+ public void transform(final double[] srcPts, int srcOff,
+ final double[] dstPts, int dstOff, int numPts)
+ throws TransformException
+ {
+ if (srcPts != dstPts || srcOff != dstOff) {
+ super.transform(srcPts, srcOff, dstPts, dstOff, numPts);
+ } else {
+ dstOff--;
+ while (--numPts >= 0) {
+ final double φ = dstPts[dstOff += 2]; // Same as srcPts[srcOff + 1].
+ dstPts[dstOff] = sin(φ);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void inverseTransform(final double[] srcPts, final int srcOff,
+ final double[] dstPts, final int dstOff)
+ throws ProjectionException
+ {
+ final double y = srcPts[srcOff+1]; // Must be before writing x.
+ dstPts[dstOff ] = srcPts[srcOff]; // Must be before writing y.
+ dstPts[dstOff+1] = asin(y);
+ }
+ }
+}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/EqualAreaProjection.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/EqualAreaProjection.java
new file mode 100644
index 0000000..d6a101c
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/EqualAreaProjection.java
@@ -0,0 +1,209 @@
+/*
+ * 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.operation.projection;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+
+import static java.lang.Math.*;
+import static org.apache.sis.math.MathFunctions.atanh;
+
+
+/**
+ * Provides formulas common to Equal Area projections.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.8
+ * @version 0.8
+ * @module
+ */
+abstract class EqualAreaProjection extends NormalizedProjection {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = -6175270149094989517L;
+
+ /**
+ * {@code false} for using the original formulas as published by Synder, or {@code true} for using formulas
+ * modified using trigonometric identities. The use of trigonometric identities is for reducing the amount
+ * of calls to the {@link Math#sin(double)} and similar methods. Some identities used are:
+ *
+ * <ul>
+ * <li>sin(2β) = 2⋅sinβ⋅cosβ</li>
+ * <li>sin(4β) = (2 - 4⋅sin²β)⋅sin(2β)</li>
+ * <li>sin(8β) = 4⋅sin(2β)⋅(cos²β - sin²β)⋅(8⋅cos⁴β - 8⋅cos²β + 1)</li>
+ * </ul>
+ *
+ * Note that since this boolean is static final, the compiler should exclude the code in the branch that is never
+ * executed (no need to comment-out that code).
+ */
+ private static final boolean ALLOW_TRIGONOMETRIC_IDENTITIES = false;
+
+ /**
+ * Coefficients in the series expansion of the inverse projection,
+ * depending only on {@linkplain #eccentricity eccentricity} value.
+ * The series expansion is of the following form:
+ *
+ * <blockquote>φ = ci₂⋅sin(2β) + ci₄⋅sin(4β) + ci₈⋅sin(8β)</blockquote>
+ *
+ * This {@code EqualAreaProjection} class uses those coefficients in {@link #φ(double)}.
+ *
+ * <p><strong>Consider those fields as final!</strong> They are not final only for sub-class
+ * constructors convenience and for the purpose of {@link #readObject(ObjectInputStream)}.</p>
+ *
+ * @see #computeCoefficients()
+ */
+ private transient double ci2, ci4, ci8;
+
+ /**
+ * Creates a new normalized projection from the parameters computed by the given initializer.
+ *
+ * @param initializer The initializer for computing map projection internal parameters.
+ */
+ EqualAreaProjection(final Initializer initializer) {
+ super(initializer);
+ }
+
+ /**
+ * Computes the coefficients in the series expansions from the {@link #eccentricitySquared} value.
+ * This method shall be invoked after {@code EqualAreaProjection} construction or deserialization.
+ */
+ void computeCoefficients() {
+ final double e2 = eccentricitySquared;
+ final double e4 = e2 * e2;
+ final double e6 = e2 * e4;
+ ci2 = 517/5040. * e6 + 31/180. * e4 + 1/3. * e2;
+ ci4 = 251/3780. * e6 + 23/360. * e4;
+ ci8 = 761/45360. * e6;
+ /*
+ * When rewriting equations using trigonometric identities, some constants appear.
+ * For example sin(2β) = 2⋅sinβ⋅cosβ, so we can factor out the 2 constant into the
+ * into the corresponding 'c' field.
+ */
+ if (ALLOW_TRIGONOMETRIC_IDENTITIES) {
+ // Multiplication by powers of 2 does not bring any additional rounding error.
+ ci2 *= 2;
+ ci4 *= 8;
+ ci8 *= 64;
+ }
+ }
+
+ /**
+ * Creates a new projection initialized to the values of the given one. This constructor may be invoked after
+ * we determined that the default implementation can be replaced by an other one, for example using spherical
+ * formulas instead than the ellipsoidal ones. This constructor allows to transfer all parameters to the new
+ * instance without recomputing them.
+ */
+ EqualAreaProjection(final EqualAreaProjection other) {
+ super(other);
+ ci2 = other.ci2;
+ ci4 = other.ci4;
+ ci8 = other.ci8;
+ }
+
+ /**
+ * Calculates <strong>part</strong> of <var>q</var> from Snyder equation (3-12).
+ * In order to get the <var>q</var> function, this method output must be multiplied
+ * by <code>(1 - {@linkplain #eccentricitySquared})</code>.
+ *
+ * <p>This equation has the following properties:</p>
+ *
+ * <ul>
+ * <li>Input in the [-1 … +1] range</li>
+ * <li>Output multiplied by {@code (1 - ℯ²)} in the [-2 … +2] range</li>
+ * <li>Output of the same sign than input</li>
+ * <li>q(-sinφ) = -q(sinφ)</li>
+ * <li>q(0) = 0</li>
+ * </ul>
+ *
+ * In the spherical case, <var>q</var> = 2⋅sinφ. It is caller responsibility to ensure that this
+ * method is not invoked in the spherical case, since this implementation does not work in such case.
+ *
+ * @param sinφ sine of the latitude <var>q</var> is calculated for.
+ * @return <var>q</var> from Snyder equation (3-12).
+ */
+ final double qm(final double sinφ) {
+ final double ℯsinφ = eccentricity * sinφ;
+ return sinφ / (1 - ℯsinφ*ℯsinφ) + atanh(ℯsinφ) / eccentricity;
+ }
+
+ /**
+ * Gets the derivative of the {@link #qm(double)} method.
+ * Callers must multiply the returned value by <code>(1 - {@linkplain #eccentricitySquared})</code>
+ * in order to get the derivative of Snyder equation (3-12).
+ *
+ * @param sinφ the sine of latitude.
+ * @param cosφ the cosines of latitude.
+ * @return the {@code qm} derivative at the specified latitude.
+ */
+ final double dqm_dφ(final double sinφ, final double cosφ) {
+ final double ℯsinφ2 = eccentricitySquared * (sinφ*sinφ);
+ return (cosφ / (1 - ℯsinφ2)) * (1 + ((1 + ℯsinφ2) / (1 - ℯsinφ2)));
+ }
+
+ /**
+ * Computes the latitude using equation 3-18 from Synder.
+ *
+ * @param sinβ see Synder equation 10-26.
+ * @return the latitude in radians.
+ */
+ final double φ(final double sinβ) {
+ final double β = asin(sinβ);
+ if (!ALLOW_TRIGONOMETRIC_IDENTITIES) {
+ return ci8 * sin(8*β)
+ + ci4 * sin(4*β)
+ + ci2 * sin(2*β)
+ + β; // Synder 3-18
+ } else {
+ /*
+ * Same formula than above, but rewriten using trigonometric identities in order to avoid
+ * multiple calls to sin(double) method. The cost is only one sqrt(double) method call.
+ */
+ final double sin2_β = sinβ*sinβ; // = sin²β
+ final double cos2_β = 1 - sin2_β; // = cos²β
+ final double t2β = sinβ * sqrt(cos2_β); // = sin(2β) / 2
+ final double t4β = 0.5 - sin2_β; // = sin(4β) / ( 4⋅sin(2β))
+ final double t8β = (cos2_β - sin2_β)*(cos2_β*cos2_β - cos2_β + 1./8); // = sin(8β) / (32⋅sin(2β))
+
+ assert identityEquals(t2β, sin(2*β) / ( 2 ));
+ assert identityEquals(t4β, sin(4*β) / ( 8 * t2β));
+ assert identityEquals(t8β, sin(8*β) / (64 * t2β));
+
+ return (ci8*t8β + ci4*t4β + ci2) * t2β + β;
+ }
+ }
+
+ /**
+ * Verifies if a trigonometric identity produced the expected value. This method is used in assertions only.
+ * The tolerance threshold is approximatively 1.5E-12 (note that it still about 7000 time greater than
+ * {@code Math.ulp(1.0)}).
+ *
+ * @see #ALLOW_TRIGONOMETRIC_IDENTITIES
+ */
+ private static boolean identityEquals(final double actual, final double expected) {
+ // Use !(a > b) instead of (a <= b) in order to tolerate NaN.
+ return !(abs(actual - expected) > (ANGULAR_TOLERANCE / 1000));
+ }
+
+ /**
+ * Restores transient fields after deserialization.
+ */
+ private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ computeCoefficients();
+ }
+}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Initializer.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Initializer.java
index b9862a0..237afba 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Initializer.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Initializer.java
@@ -20,6 +20,7 @@
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterNotFoundException;
import org.opengis.referencing.operation.OperationMethod;
+import org.apache.sis.internal.referencing.Formulas;
import org.apache.sis.internal.referencing.provider.MapProjection;
import org.apache.sis.internal.util.Constants;
import org.apache.sis.parameter.Parameters;
@@ -88,19 +89,33 @@
final DoubleDouble eccentricitySquared;
/**
- * Map projection variant. This is a convenience field left at
- * the discretion of {@link NormalizedProjection} subclasses.
+ * Map projection variant.
+ * Values from 0 to 127 inclusive are convenience values at the discretion of {@link NormalizedProjection} subclasses.
+ * Values from 128 to 255 inclusive are values handled in a special way by {@link Initializer} constructor.
*/
final byte variant;
/**
+ * A {@link #variant} value telling the constructor to computing the authalic radius instead than using
+ * the semi-major and semi-minor axis lengths directly.
+ *
+ * <p>Note that this value is not necessarily equivalent to the {@code SPHERICAL} value defined in some
+ * map projection, because EPSG guidance notes recommend different approaches for spherical implementations.
+ * For example the Mercator projection will use the radius of conformal sphere instead than the authalic radius.</p>
+ */
+ static final byte AUTHALIC_RADIUS = (byte) 128;
+
+ /**
* Creates a new initializer. The parameters are described in
* {@link NormalizedProjection#NormalizedProjection(OperationMethod, Parameters, Map)}.
*
- * @param method Description of the map projection parameters.
- * @param parameters The parameters of the projection to be created.
- * @param roles Parameters to look for <cite>central meridian</cite>, <cite>scale factor</cite>,
- * <cite>false easting</cite>, <cite>false northing</cite> and other values.
+ * @param method description of the map projection parameters.
+ * @param parameters the parameters of the projection to be created.
+ * @param roles parameters to look for <cite>central meridian</cite>, <cite>scale factor</cite>,
+ * <cite>false easting</cite>, <cite>false northing</cite> and other values.
+ * @param variant convenience field left at the discretion of {@link NormalizedProjection} subclasses.
+ * Values equal to greater than 128 are special values recognized by this constructor
+ * (see {@link #AUTHALIC_RADIUS}).
*/
Initializer(final OperationMethod method, final Parameters parameters,
final Map<ParameterRole, ? extends ParameterDescriptor<? extends Number>> roles,
@@ -114,8 +129,7 @@
this.variant = variant;
/*
* Note: we do not use Map.getOrDefault(K,V) below because the user could have explicitly associated
- * a null value to keys (we are paranoiac...) and because it conflicts with the "? extends" part of
- * in this constructor signature.
+ * a null value to keys (we are paranoiac...) and because it conflicts with the "? extends" parts.
*/
ParameterDescriptor<? extends Number> semiMajor = roles.get(ParameterRole.SEMI_MAJOR);
ParameterDescriptor<? extends Number> semiMinor = roles.get(ParameterRole.SEMI_MINOR);
@@ -133,65 +147,70 @@
eccentricitySquared = new DoubleDouble();
DoubleDouble k = new DoubleDouble(a); // The value by which to multiply all results of normalized projection.
if (a != b) {
- /*
- * (1) Using axis lengths: ℯ² = 1 - (b/a)²
- * (2) Using flattening; ℯ² = 2f - f² where f is the (NOT inverse) flattening factor.
- *
- * If the inverse flattening factor is the definitive factor for the ellipsoid, we use (2).
- * Otherwise use (1). With double-double arithmetic, this makes a difference in the 3 last
- * digits for the WGS84 ellipsoid.
- */
- boolean isIvfDefinitive;
- try {
- isIvfDefinitive = parameters.parameter(Constants.IS_IVF_DEFINITIVE).booleanValue();
- } catch (ParameterNotFoundException e) {
- /*
- * Should never happen with Apache SIS implementation, but may happen if the given parameters come
- * from another implementation. We can safely abandon our attempt to get the inverse flattening value,
- * since it was redundant with semi-minor axis length.
- */
- isIvfDefinitive = false;
- }
- /*
- * The ellipsoid parameters (a, b or ivf) are assumed accurate in base 10 rather than in base 2,
- * because they are defined by authorities. For example the semi-major axis length of the WGS84
- * ellipsoid is equal to exactly 6378137 metres by definition of that ellipsoid. The DoubleDouble
- * constructor applies corrections for making those values more accurate in base 10 rather than 2.
- */
- if (isIvfDefinitive) {
- final DoubleDouble f = new DoubleDouble(parameters.parameter(Constants.INVERSE_FLATTENING).doubleValue());
- f.inverseDivide(1,0);
- eccentricitySquared.setFrom(f);
- eccentricitySquared.multiply(2,0);
- f.square();
- eccentricitySquared.subtract(f);
+ if (variant == AUTHALIC_RADIUS) {
+ k.value = Formulas.getAuthalicRadius(a, b);
+ k.error = 0;
} else {
- final DoubleDouble rs = new DoubleDouble(b);
- rs.divide(k); // rs = b/a
- rs.square();
- eccentricitySquared.value = 1;
- eccentricitySquared.subtract(rs);
- }
- final ParameterDescriptor<? extends Number> radius = roles.get(ParameterRole.LATITUDE_OF_CONFORMAL_SPHERE_RADIUS);
- if (radius != null) {
/*
- * EPSG said: R is the radius of the sphere and will normally be one of the CRS parameters.
- * If the figure of the earth used is an ellipsoid rather than a sphere then R should be calculated
- * as the radius of the conformal sphere at the projection origin at latitude φ₀ using the formula
- * for Rc given in section 1.2, table 3.
+ * (1) Using axis lengths: ℯ² = 1 - (b/a)²
+ * (2) Using flattening; ℯ² = 2f - f² where f is the (NOT inverse) flattening factor.
*
- * Table 3 gives:
- * Radius of conformal sphere Rc = a √(1 – ℯ²) / (1 – ℯ²⋅sin²φ)
- *
- * Using √(1 – ℯ²) = b/a we rewrite as: Rc = b / (1 – ℯ²⋅sin²φ)
- *
- * Equivalent Java code:
- *
- * final double sinφ = sin(toRadians(parameters.doubleValue(radius)));
- * k = b / (1 - eccentricitySquared * (sinφ*sinφ));
+ * If the inverse flattening factor is the definitive factor for the ellipsoid, we use (2).
+ * Otherwise use (1). With double-double arithmetic, this makes a difference in the 3 last
+ * digits for the WGS84 ellipsoid.
*/
- k = rν2(sin(toRadians(parameters.doubleValue(radius))));
- k.inverseDivide(b, 0);
+ boolean isIvfDefinitive;
+ try {
+ isIvfDefinitive = parameters.parameter(Constants.IS_IVF_DEFINITIVE).booleanValue();
+ } catch (ParameterNotFoundException e) {
+ /*
+ * Should never happen with Apache SIS implementation, but may happen if the given parameters come
+ * from another implementation. We can safely abandon our attempt to get the inverse flattening value,
+ * since it was redundant with semi-minor axis length.
+ */
+ isIvfDefinitive = false;
+ }
+ /*
+ * The ellipsoid parameters (a, b or ivf) are assumed accurate in base 10 rather than in base 2,
+ * because they are defined by authorities. For example the semi-major axis length of the WGS84
+ * ellipsoid is equal to exactly 6378137 metres by definition of that ellipsoid. The DoubleDouble
+ * constructor applies corrections for making those values more accurate in base 10 rather than 2.
+ */
+ if (isIvfDefinitive) {
+ final DoubleDouble f = new DoubleDouble(parameters.parameter(Constants.INVERSE_FLATTENING).doubleValue());
+ f.inverseDivide(1,0);
+ eccentricitySquared.setFrom(f);
+ eccentricitySquared.multiply(2,0);
+ f.square();
+ eccentricitySquared.subtract(f);
+ } else {
+ final DoubleDouble rs = new DoubleDouble(b);
+ rs.divide(k); // rs = b/a
+ rs.square();
+ eccentricitySquared.value = 1;
+ eccentricitySquared.subtract(rs);
+ }
+ final ParameterDescriptor<? extends Number> radius = roles.get(ParameterRole.LATITUDE_OF_CONFORMAL_SPHERE_RADIUS);
+ if (radius != null) {
+ /*
+ * EPSG said: R is the radius of the sphere and will normally be one of the CRS parameters.
+ * If the figure of the earth used is an ellipsoid rather than a sphere then R should be calculated
+ * as the radius of the conformal sphere at the projection origin at latitude φ₀ using the formula
+ * for Rc given in section 1.2, table 3.
+ *
+ * Table 3 gives:
+ * Radius of conformal sphere Rc = a √(1 – ℯ²) / (1 – ℯ²⋅sin²φ)
+ *
+ * Using √(1 – ℯ²) = b/a we rewrite as: Rc = b / (1 – ℯ²⋅sin²φ)
+ *
+ * Equivalent Java code:
+ *
+ * final double sinφ = sin(toRadians(parameters.doubleValue(radius)));
+ * k = b / (1 - eccentricitySquared * (sinφ*sinφ));
+ */
+ k = rν2(sin(toRadians(parameters.doubleValue(radius))));
+ k.inverseDivide(b, 0);
+ }
}
}
/*
@@ -239,7 +258,7 @@
final Number defaultValue = descriptor.getDefaultValue();
if (defaultValue == null || !defaultValue.equals(value)) {
MapProjection.validate(descriptor, value);
- context.parameter(descriptor.getName().getCode()).setValue(value);
+ context.getOrCreate(descriptor).setValue(value);
}
return value;
}
@@ -255,7 +274,7 @@
return defaultValue;
}
MapProjection.validate(descriptor, value);
- context.parameter(descriptor.getName().getCode()).setValue(value);
+ context.getOrCreate(descriptor).setValue(value);
return value;
}
@@ -291,8 +310,8 @@
* (otherwise we get {@link Double#NaN}).</li>
* </ul>
*
- * @param sinφ The sine of the φ latitude.
- * @return Reciprocal squared of the radius of curvature of the ellipsoid
+ * @param sinφ the sine of the φ latitude.
+ * @return reciprocal squared of the radius of curvature of the ellipsoid
* perpendicular to the meridian at latitude φ.
*/
private DoubleDouble rν2(final double sinφ) {
@@ -321,8 +340,8 @@
* the use of φ₀ (or φ₁ as relevant to method) for φ is suggested, except if the projection is
* equal area when the radius of authalic sphere should be used.
*
- * @param sinφ The sine of the φ latitude.
- * @return Radius of the conformal sphere at latitude φ.
+ * @param sinφ the sine of the φ latitude.
+ * @return radius of the conformal sphere at latitude φ.
*/
final double radiusOfConformalSphere(final double sinφ) {
final DoubleDouble Rc = verbatim(1);
@@ -340,9 +359,9 @@
* The result is returned as a {@code double} because the limited precision of {@code sinφ} and {@code cosφ}
* makes the error term meaningless. We use double-double arithmetic only for intermediate calculation.
*
- * @param sinφ The sine of the φ latitude.
- * @param cosφ The cosine of the φ latitude.
- * @return Scale factor at latitude φ.
+ * @param sinφ the sine of the φ latitude.
+ * @param cosφ the cosine of the φ latitude.
+ * @return scale factor at latitude φ.
*/
final double scaleAtφ(final double sinφ, final double cosφ) {
final DoubleDouble s = rν2(sinφ);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java
index f806c7d..768e3dd 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java
@@ -95,8 +95,8 @@
*
* @see #getVariant(OperationMethod)
*/
- private static final byte SPHERICAL = 1, PSEUDO = 3, // Must be odd and SPHERICAL must be 1.
- REGIONAL = 2, MILLER = 4; // Must be even.
+ private static final byte SPHERICAL = 1, PSEUDO = 3, // Must be odd and SPHERICAL must be 1.
+ REGIONAL = 2, MILLER = 4; // Must be even.
/**
* Returns the variant of the projection based on the name and identifier of the given operation method.
@@ -291,7 +291,7 @@
if (φ0 == 0 && isPositive(φ1 != 0 ? φ1 : φ0)) {
final Number reverseSign = verbatim(-1);
normalize .convertBefore(1, reverseSign, null);
- denormalize.convertBefore(1, reverseSign, null); // Must be before false easting/northing.
+ denormalize.convertBefore(1, reverseSign, null); // Must be before false easting/northing.
}
}
@@ -354,7 +354,7 @@
// about why we perform explicit checks for the pole cases.
final double a = abs(φ);
if (a < PI/2) {
- y = log(expOfNorthing(φ, eccentricity * sinφ)); // Snyder (7-7)
+ y = log(expOfNorthing(φ, eccentricity * sinφ)); // Snyder (7-7)
} else if (a <= (PI/2 + ANGULAR_TOLERANCE)) {
y = copySign(POSITIVE_INFINITY, φ);
} else {
@@ -390,7 +390,7 @@
} else {
dstOff--;
while (--numPts >= 0) {
- final double φ = dstPts[dstOff += 2]; // Same as srcPts[srcOff + 1].
+ final double φ = dstPts[dstOff += 2]; // Same as srcPts[srcOff + 1].
if (φ != 0) {
// See the javadoc of the Spherical inner class for a note
// about why we perform explicit checks for the pole cases.
@@ -420,8 +420,8 @@
final double[] dstPts, final int dstOff)
throws ProjectionException
{
- final double y = srcPts[srcOff+1]; // Must be before writing x.
- dstPts[dstOff ] = srcPts[srcOff ]; // Must be before writing y.
+ final double y = srcPts[srcOff+1]; // Must be before writing x.
+ dstPts[dstOff ] = srcPts[srcOff ]; // Must be before writing y.
dstPts[dstOff+1] = φ(exp(-y));
}
@@ -488,7 +488,7 @@
// See class javadoc for a note about explicit check for poles.
final double a = abs(φ);
if (a < PI/2) {
- y = log(tan(PI/4 + 0.5*φ)); // Part of Snyder (7-2)
+ y = log(tan(PI/4 + 0.5*φ)); // Part of Snyder (7-2)
} else if (a <= (PI/2 + ANGULAR_TOLERANCE)) {
y = copySign(POSITIVE_INFINITY, φ);
} else {
@@ -518,13 +518,13 @@
} else {
dstOff--;
while (--numPts >= 0) {
- final double φ = dstPts[dstOff += 2]; // Same as srcPts[srcOff + 1].
+ final double φ = dstPts[dstOff += 2]; // Same as srcPts[srcOff + 1].
if (φ != 0) {
// See class javadoc for a note about explicit check for poles.
final double a = abs(φ);
final double y;
if (a < PI/2) {
- y = log(tan(PI/4 + 0.5*φ)); // Part of Snyder (7-2)
+ y = log(tan(PI/4 + 0.5*φ)); // Part of Snyder (7-2)
} else if (a <= (PI/2 + ANGULAR_TOLERANCE)) {
y = copySign(POSITIVE_INFINITY, φ);
} else {
@@ -544,9 +544,9 @@
final double[] dstPts, final int dstOff)
throws ProjectionException
{
- final double y = srcPts[srcOff+1]; // Must be before writing x.
- dstPts[dstOff ] = srcPts[srcOff]; // Must be before writing y.
- dstPts[dstOff+1] = PI/2 - 2*atan(exp(-y)); // Part of Snyder (7-4);
+ final double y = srcPts[srcOff+1]; // Must be before writing x.
+ dstPts[dstOff ] = srcPts[srcOff]; // Must be before writing y.
+ dstPts[dstOff+1] = PI/2 - 2*atan(exp(-y)); // Part of Snyder (7-4);
}
}
}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java
index 49910c0..c191d17 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java
@@ -113,7 +113,6 @@
* Work around for RFE #4093999 in Sun's bug database
* ("Relax constraint on placement of this()/super() call in constructors").
*/
- @SuppressWarnings("fallthrough")
@Workaround(library="JDK", version="1.7")
private static Initializer initializer(final OperationMethod method, final Parameters parameters) {
final byte variant = getVariant(method);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/package-info.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/package-info.java
index 980fe2b..fd05f2a 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/package-info.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/package-info.java
@@ -160,7 +160,7 @@
* @author Rémi Maréchal (Geomatys)
* @author Adrian Custer (Geomatys)
* @since 0.6
- * @version 0.7
+ * @version 0.8
* @module
*
* @see <a href="http://www.remotesensing.org/geotiff/proj_list">Projections list on RemoteSensing.org</a>
diff --git a/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod b/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod
index b1af88b..e93d3d7 100644
--- a/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod
+++ b/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod
@@ -27,6 +27,8 @@
org.apache.sis.internal.referencing.provider.PseudoMercator
org.apache.sis.internal.referencing.provider.RegionalMercator
org.apache.sis.internal.referencing.provider.MillerCylindrical
+org.apache.sis.internal.referencing.provider.LambertCylindricalEqualArea
+org.apache.sis.internal.referencing.provider.LambertCylindricalEqualAreaSpherical
org.apache.sis.internal.referencing.provider.LambertConformal1SP
org.apache.sis.internal.referencing.provider.LambertConformal2SP
org.apache.sis.internal.referencing.provider.LambertConformalWest
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java
index bea1cd3..762e2ac 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java
@@ -37,7 +37,7 @@
*
* @author Martin Desruisseaux (Geomatys)
* @since 0.6
- * @version 0.7
+ * @version 0.8
* @module
*/
@DependsOn({
@@ -79,6 +79,8 @@
PseudoMercator.class,
RegionalMercator.class,
MillerCylindrical.class,
+ LambertCylindricalEqualArea.class,
+ LambertCylindricalEqualAreaSpherical.class,
LambertConformal1SP.class,
LambertConformal2SP.class,
LambertConformalWest.class,
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/CylindricalEqualAreaTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/CylindricalEqualAreaTest.java
new file mode 100644
index 0000000..ee2f29d
--- /dev/null
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/CylindricalEqualAreaTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.operation.projection;
+
+import org.opengis.util.FactoryException;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.internal.referencing.Formulas;
+import org.apache.sis.internal.referencing.provider.LambertCylindricalEqualArea;
+import org.apache.sis.internal.referencing.provider.LambertCylindricalEqualAreaSpherical;
+import org.apache.sis.test.DependsOnMethod;
+import org.junit.Test;
+
+import static java.lang.StrictMath.*;
+
+
+/**
+ * Tests the {@link CylindricalEqualArea} class.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.8
+ * @version 0.8
+ * @module
+ */
+public final strictfp class CylindricalEqualAreaTest extends MapProjectionTestCase {
+ /**
+ * Creates a map projection.
+ */
+ private void createCompleteProjection(final boolean ellipse,
+ final double centralMeridian, final double standardParallel) throws FactoryException
+ {
+ createCompleteProjection(new LambertCylindricalEqualArea(),
+ ellipse, centralMeridian, 0, standardParallel, 1, 0, 0);
+ }
+
+ /**
+ * Tests the derivatives at a few points. This method compares the derivatives computed by
+ * the projection with an estimation of derivatives computed by the finite differences method.
+ *
+ * @throws TransformException if an error occurred while projecting a point.
+ */
+ private void testDerivative() throws TransformException {
+ final double delta = toRadians(100.0 / 60) / 1852; // Approximatively 100 metres.
+ derivativeDeltas = new double[] {delta, delta};
+ tolerance = 1E-6; // More severe than Formulas.LINEAR_TOLERANCE.
+ verifyDerivative(toRadians(15), toRadians( 30));
+ verifyDerivative(toRadians(10), toRadians(-60));
+ }
+
+ /**
+ * Tests <cite>Lambert Cylindrical Equal Area</cite> projection of a point in the in ellipsoidal case.
+ *
+ * @throws FactoryException if an error occurred while creating the map projection.
+ * @throws TransformException if an error occurred while projecting a point.
+ */
+ @Test
+ public void testEllipsoidal() throws FactoryException, TransformException {
+ createCompleteProjection(true, 0, 0);
+ tolerance = Formulas.LINEAR_TOLERANCE;
+ final double λ = 2;
+ final double φ = 1;
+ final double x = 222638.98; // Test point from Proj.4.
+ final double y = 110568.81;
+ verifyTransform(new double[] {λ, φ, -λ, φ, λ, -φ, -λ, -φ},
+ new double[] {x, y, -x, y, x, -y, -x, -y});
+ testDerivative();
+ }
+
+ /**
+ * Tests <cite>Lambert Cylindrical Equal Area</cite> projection of a point in the in spherical case.
+ *
+ * @throws FactoryException if an error occurred while creating the map projection.
+ * @throws TransformException if an error occurred while projecting a point.
+ */
+ @Test
+ @DependsOnMethod("testEllipsoidal")
+ public void testSpherical() throws FactoryException, TransformException {
+ createCompleteProjection(false, 0, 0);
+ tolerance = Formulas.LINEAR_TOLERANCE;
+ final double λ = 2;
+ final double φ = 1;
+ final double x = 222390.10; // Anti-regression values (not from an external source).
+ final double y = 111189.40;
+ verifyTransform(new double[] {λ, φ, -λ, φ, λ, -φ, -λ, -φ},
+ new double[] {x, y, -x, y, x, -y, -x, -y});
+ testDerivative();
+ }
+
+ /**
+ * Tests <cite>Lambert Cylindrical Equal Area (Spherical)</cite> projection.
+ * The difference between this test and {@link #testSpherical()} is that this case shall
+ * compute the radius of the conformal sphere instead than using the semi-major axis length.
+ * The result near the equator are almost the same however.
+ *
+ * @throws FactoryException if an error occurred while creating the map projection.
+ * @throws TransformException if an error occurred while projecting a point.
+ */
+ @Test
+ @DependsOnMethod("testSpherical")
+ public void testSphericalWithConformalSphereRadius() throws FactoryException, TransformException {
+ createCompleteProjection(new LambertCylindricalEqualAreaSpherical(), true, 0, 0, 0, 1, 0, 0);
+ tolerance = Formulas.LINEAR_TOLERANCE;
+ final double λ = 2;
+ final double φ = 1;
+ final double x = 222390.10; // Anti-regression values (not from an external source).
+ final double y = 111189.40;
+ verifyTransform(new double[] {λ, φ, -λ, φ, λ, -φ, -λ, -φ},
+ new double[] {x, y, -x, y, x, -y, -x, -y});
+ testDerivative();
+ }
+}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java
index 4c0382c..6132f6c 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java
@@ -127,7 +127,7 @@
/**
* Tests the projection at some special latitudes (0, ±π/2, NaN).
*
- * @throws ProjectionException Should never happen.
+ * @throws ProjectionException if an error occurred while projecting a point.
*/
@Test
public void testSpecialLatitudes() throws ProjectionException {
@@ -157,7 +157,7 @@
* Tests the derivatives at a few points. This method compares the derivatives computed by
* the projection with an estimation of derivatives computed by the finite differences method.
*
- * @throws TransformException Should never happen.
+ * @throws TransformException if an error occurred while projecting a point.
*/
@Test
@DependsOnMethod("testSpecialLatitudes")
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransformTest.java
index 517ba92..bbb0cdb 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransformTest.java
@@ -115,8 +115,8 @@
" Param_MT[“Molodensky inverse interpolation (radians domain)”,\n" +
" Parameter[“src_semi_major”, 6378249.2],\n" +
" Parameter[“src_semi_minor”, 6356515.0],\n" +
- " Parameter[“Semi-major axis length difference”, -112.2],\n" +
- " Parameter[“Flattening difference”, -5.4738838833299144E-5],\n" +
+ " Parameter[“Semi-major axis length difference”, -112.2, Id[“EPSG”, 8654]],\n" +
+ " Parameter[“Flattening difference”, -5.4738838833299144E-5, Id[“EPSG”, 8655]],\n" +
" ParameterFile[“Geocentric translation file”, “\\E.*\\W\\Q" +
FranceGeocentricInterpolationTest.TEST_FILE + "”, Id[“EPSG”, 8727],\n" +
" Remark[“\\E.*\\Q”]],\n" +
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
index 53f5fe1..bcff6a2 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
@@ -181,7 +181,7 @@
final float[] asFloats = Numerics.copyAsFloats(coordinates);
final float[] result = verifyConsistency(asFloats);
for (int i=0; i<coordinates.length; i++) {
- assertEquals("Detected change in source coordinates.", (float) coordinates[i], asFloats[i], 0f); // Paranoiac check.
+ assertEquals("Detected change in source coordinates.", (float) coordinates[i], asFloats[i], 0f); // Paranoiac check.
}
/*
* The comparison below needs a higher tolerance threshold, because we converted the source
@@ -195,7 +195,7 @@
for (int i=0; i<expected.length; i++) {
final double e = expected[i];
double tol = 1E-6 * abs(e);
- if (!(tol > tolerance)) { // Use '!' for replacing NaN by 'tolerance'.
+ if (!(tol > tolerance)) { // Use '!' for replacing NaN by 'tolerance'.
tol = tolerance;
}
assertEquals(e, result[i], tol);
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MolodenskyTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MolodenskyTransformTest.java
index 08512aa..84cd22a 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MolodenskyTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MolodenskyTransformTest.java
@@ -302,11 +302,11 @@
" Param_MT[“Molodensky (radians domain)”,\n" +
" Parameter[“src_semi_major”, 6378137.0],\n" +
" Parameter[“src_semi_minor”, 6356752.314245179],\n" +
- " Parameter[“Semi-major axis length difference”, 251.0],\n" +
- " Parameter[“Flattening difference”, 1.4192702255886284E-5],\n" +
- " Parameter[“X-axis translation”, 84.87],\n" +
- " Parameter[“Y-axis translation”, 96.49],\n" +
- " Parameter[“Z-axis translation”, 116.95],\n" +
+ " Parameter[“Semi-major axis length difference”, 251.0, Id[“EPSG”, 8654]],\n" +
+ " Parameter[“Flattening difference”, 1.4192702255886284E-5, Id[“EPSG”, 8655]],\n" +
+ " Parameter[“X-axis translation”, 84.87, Id[“EPSG”, 8605]],\n" +
+ " Parameter[“Y-axis translation”, 96.49, Id[“EPSG”, 8606]],\n" +
+ " Parameter[“Z-axis translation”, 116.95, Id[“EPSG”, 8607]],\n" +
" Parameter[“abridged”, TRUE],\n" +
" Parameter[“dim”, 3]],\n" +
" Param_MT[“Affine”,\n" +
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/CoordinateOperationMethods.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/CoordinateOperationMethods.java
index 4595812..e4b8f43 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/CoordinateOperationMethods.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/CoordinateOperationMethods.java
@@ -66,7 +66,7 @@
*
* @author Martin Desruisseaux (Geomatys)
* @since 0.6
- * @version 0.7
+ * @version 0.8
* @module
*/
public strictfp class CoordinateOperationMethods extends HTMLGenerator {
@@ -165,7 +165,7 @@
* @throws IOException if an error occurred while writing to the file.
*/
public CoordinateOperationMethods() throws IOException {
- super("CoordinateOperationMethods.html", "Apache SIS Coordinate Operation Methods");
+ super("CoordinateOperationMethods.html", "Apache SIS Coordinate Operation Methods", "authority-codes.css");
domainOfValidity = Collections.emptyMap(); // TODO: not yet available.
rangeFormat = new RangeFormat(LOCALE);
final int header = openTag("header");
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/HTMLGenerator.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/HTMLGenerator.java
index 995a8a1..853088f 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/HTMLGenerator.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/HTMLGenerator.java
@@ -83,11 +83,12 @@
* Creates a new instance which will write in the given file.
* This constructor immediately writes the HTML header up to the {@code <body>} line, inclusive.
*
- * @param filename The name of the file where to write.
- * @param title The document title.
+ * @param filename the name of the file where to write.
+ * @param title the document title.
+ * @param path path to the CSS file.
* @throws IOException if the file can not be created (e.g. because it already exists).
*/
- HTMLGenerator(final String filename, final String title) throws IOException {
+ HTMLGenerator(final String filename, final String title, final String css) throws IOException {
final File file = new File(filename);
if (file.exists()) {
throw new IOException("File " + file.getAbsolutePath() + " already exists.");
@@ -112,7 +113,11 @@
out.newLine();
println("title", title);
openTag("style type=\"text/css\" media=\"all\"");
- println("@import url(\"../tables.css\");");
+ out.write(margin);
+ out.write("@import url(\"");
+ out.write(css);
+ out.write("\");");
+ out.newLine();
closeTags(head);
openTag("body");
}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/package-info.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/package-info.java
index 56f7baa..7a3b7b2 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/package-info.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/package-info.java
@@ -23,7 +23,7 @@
*
* @author Martin Desruisseaux (Geomatys)
* @since 0.7
- * @version 0.7
+ * @version 0.8
* @module
*/
package org.apache.sis.referencing.report;
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java b/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
index 03170ad..804991c 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
@@ -164,6 +164,7 @@
org.apache.sis.referencing.operation.projection.TransverseMercatorTest.class,
org.apache.sis.referencing.operation.projection.PolarStereographicTest.class,
org.apache.sis.referencing.operation.projection.ObliqueStereographicTest.class,
+ org.apache.sis.referencing.operation.projection.CylindricalEqualAreaTest.class,
// Coordinate operation and derived Coordinate Reference Systems (cyclic dependency).
org.apache.sis.referencing.operation.DefaultTransformationTest.class,
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/jdk8/UncheckedIOException.java b/core/sis-utility/src/main/java/org/apache/sis/internal/jdk8/UncheckedIOException.java
new file mode 100644
index 0000000..8f078e0
--- /dev/null
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/jdk8/UncheckedIOException.java
@@ -0,0 +1,45 @@
+/*
+ * 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.jdk8;
+
+import java.io.IOException;
+
+
+/**
+ * Placeholder for the {@link java.io.UncheckedIOException}.
+ */
+@SuppressWarnings("serial")
+public class UncheckedIOException extends RuntimeException {
+ /**
+ * Wraps the given I/O exception.
+ *
+ * @param e the I/O exception to wrap.
+ */
+ public UncheckedIOException(IOException e) {
+ super(e);
+ }
+
+ /**
+ * Returns the I/O exception.
+ *
+ * @return the I/O exception specified at construction time.
+ */
+ @Override
+ public IOException getCause() {
+ return (IOException) super.getCause();
+ }
+}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java b/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java
index 728fba3..b4b9298 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java
@@ -364,6 +364,8 @@
/*
* The classical formulas is log((1+x)/(1-x))/2, but the following is more
* accurate if the (1+x)/(1-x) ratio is close to 1, i.e. if x is close to 0.
+ * This is often the case in Apache SIS since x is often a value close to the
+ * Earth excentricity, which is a small value (0 would be a perfect sphere).
*/
return 0.5 * Math.log1p(2*x / (1-x));
}
diff --git a/ide-project/NetBeans/build.xml b/ide-project/NetBeans/build.xml
index e8c1300..aafab68 100644
--- a/ide-project/NetBeans/build.xml
+++ b/ide-project/NetBeans/build.xml
@@ -191,16 +191,12 @@
<include name="**/*.laa"/>
<include name="**/*.loa"/>
</fileset>
- </copy>
- <copy todir="${build.test.classes.dir}">
<fileset dir="${project.root}/storage/sis-shapefile/src/test/resources">
<include name="**/*.dbf"/>
<include name="**/*.prj"/>
<include name="**/*.shp"/>
<include name="**/*.shx"/>
</fileset>
- </copy>
- <copy todir="${build.test.classes.dir}">
<fileset dir="${project.root}/profiles/sis-french-profile/src/test/resources">
<include name="**/*.xml"/>
</fileset>