You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2015/06/09 19:06:32 UTC
svn commit: r1684479 - in /sis/branches/JDK7: ./
core/sis-metadata/src/main/java/org/apache/sis/io/wkt/
core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/
core/sis-referencing/src/main/java/org/apache/sis/parameter/
core/sis-refere...
Author: desruisseaux
Date: Tue Jun 9 17:06:31 2015
New Revision: 1684479
URL: http://svn.apache.org/r1684479
Log:
Merged WKT bug fixes from the JDK8 branch.
Modified:
sis/branches/JDK7/ (props changed)
sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java
sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/parameter/AbstractParameterDescriptor.java
sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java
sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java
sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java
sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java
sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultGeographicCRSTest.java
sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultProjectedCRSTest.java
Propchange: sis/branches/JDK7/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Tue Jun 9 17:06:31 2015
@@ -1,4 +1,4 @@
/sis/branches/Android:1430670-1480699
/sis/branches/JDK6:1394913-1508480
-/sis/branches/JDK8:1584960-1684220
+/sis/branches/JDK8:1584960-1684478
/sis/trunk:1394364-1508466,1519089-1519674
Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java?rev=1684479&r1=1684478&r2=1684479&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java [UTF-8] Tue Jun 9 17:06:31 2015
@@ -1306,7 +1306,7 @@ public class Formatter implements Locali
/**
* Adds a unit to use for the next measurements of the quantity {@code Q}. The given unit will apply to
* all WKT elements containing a value of quantity {@code Q} without their own {@code UNIT[…]} element,
- * until the {@link #removeContextualUnit(Unit)} method is invoked with a unit of the same quantity.
+ * until the {@link #restoreContextualUnit(Unit, Unit)} method is invoked.
*
* <p>If the given unit is null, then this method does nothing and returns {@code null}.</p>
*
@@ -1328,11 +1328,55 @@ public class Formatter implements Locali
}
/**
+ * Restores the contextual unit to its previous state before the call to {@link #addContextualUnit(Unit)}.
+ * This method is used in the following pattern:
+ *
+ * {@preformat java
+ * final Unit<?> previous = formatter.addContextualUnit(unit);
+ * // ... format some WKT elements here.
+ * formatter.restoreContextualUnit(unit, previous);
+ * }
+ *
+ * @param unit The value given in argument to {@code addContextualUnit(unit)} (can be {@code null}).
+ * @param previous The value returned by {@code addContextualUnit(unit)} (can be {@code null}).
+ * @throws IllegalStateException if this method has not been invoked in the pattern documented above.
+ *
+ * @since 0.6
+ */
+ public void restoreContextualUnit(final Unit<?> unit, final Unit<?> previous) {
+ if (previous == null) {
+ if (unit != null && units.remove(unit.toSI()) != unit) {
+ /*
+ * The unit that we removed was not the expected one. Probably the user has invoked
+ * addContextualUnit(…) again without a matching call to restoreContextualUnit(…).
+ * However this check does not work in Convention.WKT1_COMMON_UNITS mode, since the
+ * map is always empty in that mode.
+ */
+ if (!convention.usesCommonUnits()) {
+ throw new IllegalStateException();
+ }
+ }
+ } else if (units.put(previous.toSI(), previous) != unit) {
+ /*
+ * The unit that we replaced was not the expected one. Probably the user has invoked
+ * addContextualUnit(…) again without a matching call to restoreContextualUnit(…).
+ * Note that this case should never happen in Convention.WKT1_COMMON_UNITS mode,
+ * since 'previous' should never be non-null in that mode (if the user followed
+ * the documented pattern).
+ */
+ throw new IllegalStateException();
+ }
+ }
+
+ /**
* Removes the unit previously added by a call to {@code addContextualUnit(unit)}.
* If the given unit is null, then this method does nothing.
*
* @param unit The contextual unit to remove, or {@code null} if none.
+ *
+ * @deprecated Replaced by {@link #restoreContextualUnit(Unit, Unit)}.
*/
+ @Deprecated
public void removeContextualUnit(final Unit<?> unit) {
if (unit != null) {
units.remove(unit.toSI());
@@ -1340,14 +1384,14 @@ public class Formatter implements Locali
}
/**
- * Returns the unit to use instead than the given one, or {@code null} if there is no replacement
- * for {@code unit}. This method searches for a unit specified by {@link #addContextualUnit(Unit)}
+ * Returns the unit to use instead than the given one, or {@code unit} if there is no replacement.
+ * This method searches for a unit specified by {@link #addContextualUnit(Unit)}
* which {@linkplain Unit#isCompatible(Unit) is compatible} with the given unit.
*
* @param <Q> The quantity of the unit.
* @param unit The unit to replace by the contextual unit, or {@code null}.
* @return A contextual unit compatible with the given unit, or {@code unit}
- * if no contextual unit has been found.
+ * (which may be null) if no contextual unit has been found.
*/
public <Q extends Quantity> Unit<Q> toContextualUnit(final Unit<Q> unit) {
if (unit != null) {
@@ -1357,7 +1401,7 @@ public class Formatter implements Locali
return candidate;
}
}
- return null;
+ return unit;
}
/**
Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java?rev=1684479&r1=1684478&r2=1684479&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java [UTF-8] Tue Jun 9 17:06:31 2015
@@ -875,9 +875,10 @@ final class GeodeticObjectParser extends
final String name = element.pullString("name");
final GeographicCRS geoCRS = parseGeoGCS(element);
final Unit<Length> linearUnit = parseUnit(element, SI.METRE);
- final Conversion conversion = parseProjection(element, linearUnit,
- (convention == Convention.WKT1_COMMON_UNITS) ? NonSI.DEGREE_ANGLE :
- geoCRS.getCoordinateSystem().getAxis(0).getUnit().asType(Angle.class));
+ final boolean usesCommonUnits = convention.usesCommonUnits();
+ final Conversion conversion = parseProjection(element,
+ usesCommonUnits ? SI.METRE : linearUnit,
+ usesCommonUnits ? NonSI.DEGREE_ANGLE : geoCRS.getCoordinateSystem().getAxis(0).getUnit().asType(Angle.class));
CoordinateSystemAxis axis0 = parseAxis(element, false, linearUnit, false);
CoordinateSystemAxis axis1 = null;
try {
Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java?rev=1684479&r1=1684478&r2=1684479&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java [UTF-8] Tue Jun 9 17:06:31 2015
@@ -19,7 +19,6 @@ package org.apache.sis.internal.referenc
import java.util.Collection;
import java.util.logging.Logger;
import javax.measure.unit.Unit;
-import javax.measure.unit.NonSI;
import javax.measure.quantity.Angle;
import org.opengis.annotation.UML;
import org.opengis.annotation.Specification;
@@ -112,12 +111,12 @@ public final class ReferencingUtilities
* The preference will be given to the longitude axis, if found.
*
* @param cs The coordinate system from which to get the angular unit, or {@code null}.
- * @return The angular unit, of {@link NonSI#DEGREE_ANGLE} if no angular unit was found.
+ * @return The angular unit, of {@code null} if no angular unit was found.
*
* @since 0.6
*/
public static Unit<Angle> getAngularUnit(final CoordinateSystem cs) {
- Unit<Angle> unit = NonSI.DEGREE_ANGLE;
+ Unit<Angle> unit = null;
if (cs != null) {
for (int i = cs.getDimension(); --i>=0;) {
final CoordinateSystemAxis axis = cs.getAxis(i);
Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/parameter/AbstractParameterDescriptor.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/parameter/AbstractParameterDescriptor.java?rev=1684479&r1=1684478&r2=1684479&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/parameter/AbstractParameterDescriptor.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/parameter/AbstractParameterDescriptor.java [UTF-8] Tue Jun 9 17:06:31 2015
@@ -17,7 +17,6 @@
package org.apache.sis.parameter;
import java.util.Map;
-import javax.measure.unit.Unit;
import org.opengis.util.InternationalString;
import org.opengis.parameter.ParameterDirection;
import org.opengis.parameter.ParameterDescriptor;
@@ -344,12 +343,7 @@ public abstract class AbstractParameterD
if (defaultValue != null) {
formatter.appendAny(defaultValue);
}
- final Unit<?> unit = ((ParameterDescriptor<?>) this).getUnit();
- if (unit != null) {
- if (!formatter.getConvention().isSimplified() || !unit.equals(formatter.toContextualUnit(unit))) {
- formatter.append(unit);
- }
- }
+ formatter.append(((ParameterDescriptor<?>) this).getUnit());
}
return WKTKeywords.Parameter;
}
Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java?rev=1684479&r1=1684478&r2=1684479&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java [UTF-8] Tue Jun 9 17:06:31 2015
@@ -481,6 +481,13 @@ public class DefaultParameterValue<T> ex
}
/**
+ * Returns {@code true} if the given value is an instance of one of the types documented in {@link #valueFile()}.
+ */
+ private static boolean isFile(final Object value) {
+ return (value instanceof URI || value instanceof URL || value instanceof File || value instanceof Path);
+ }
+
+ /**
* Returns the exception to throw when an incompatible method is invoked for the value type.
*/
private IllegalStateException incompatibleValue(final Object value) {
@@ -819,9 +826,47 @@ public class DefaultParameterValue<T> ex
* Parameter["False easting", 0.0, LengthUnit["metre", 1]]
* }
*
- * <div class="note"><b>Compatibility note:</b>
- * Version 1 of WKT format did not specified the parameter unit explicitely.
- * Instead, the unit was inherited from the enclosing element.</div>
+ * <div class="section">Unit of measurement</div>
+ * The units of measurement were never specified in WKT 1 format, and are optional in WKT 2 format.
+ * If the units are not specified, then they are inferred from the context.
+ * Typically, parameter values that are lengths are given in the unit for the projected CRS axes
+ * while parameter values that are angles are given in the unit for the base geographic CRS.
+ *
+ * <div class="note"><b>Example:</b>
+ * The snippet below show WKT representations of the map projection parameters of a projected CRS
+ * (most other elements are omitted). The map projection uses a <cite>"Latitude of natural origin"</cite>
+ * parameters which is set to 52 <strong>grads</strong>, as defined in the {@code UNIT[…]} element of the
+ * enclosing CRS. A similar rule applies to <cite>“False easting”</cite> and <cite>“False northing”</cite>
+ * parameters, which are in kilometres in this example.
+ *
+ * <p><b>WKT 1:</b></p>
+ * {@preformat wkt
+ * PROJCS[…,
+ * GEOGCS[…,
+ * UNIT[“grad”, 0.015707963267948967]], // Unit for all angles
+ * PROJECTION[“Lambert_Conformal_Conic_1SP”]
+ * PARAMETER[“latitude_of_origin”, 52.0], // In grads
+ * PARAMETER[“scale_factor”, 0.99987742],
+ * PARAMETER[“false_easting”, 600.0], // In kilometres
+ * PARAMETER[“false_northing”, 2200.0], // In kilometres
+ * UNIT[“km”, 1000]] // Unit for all lengths
+ * }
+ *
+ * <p><b>WKT 2:</b></p>
+ * {@preformat wkt
+ * ProjectedCRS[…
+ * BaseGeodCRS[…
+ * AngleUnit[“grad”, 0.015707963267948967]],
+ * Conversion["Lambert zone II",
+ * Method["Lambert Conic Conformal (1SP)"],
+ * Parameter["Latitude of natural origin", 52.0],
+ * Parameter["Scale factor at natural origin", 0.99987742],
+ * Parameter["False easting", 600.0],
+ * Parameter["False northing", 2200.0]],
+ * CS["Cartesian", 2],
+ * LengthUnit["km", 1000]]
+ * }
+ * </div>
*
* @param formatter The formatter where to format the inner content of this WKT element.
* @return {@code "Parameter"} or {@code "ParameterFile"}.
@@ -840,38 +885,55 @@ public class DefaultParameterValue<T> ex
* If this parameter value does not use the same unit, then we must convert it.
* Otherwise we can write the value as-is.
*/
- final Unit<?> unit = getUnit(); // Gives to users a chance to override this property.
- if (isWKT1 && targetUnit != null) {
- double convertedValue;
- try {
- convertedValue = doubleValue(targetUnit);
- } catch (IllegalStateException exception) {
- // May happen if a parameter is mandatory (e.g. "semi-major")
- // but no value has been set for this parameter.
- formatter.setInvalidWKT(descriptor, exception);
- convertedValue = Double.NaN;
- }
+ final T value = getValue(); // Gives to users a chance to override this property.
+ final Unit<?> unit = getUnit(); // Gives to users a chance to override this property.
+ double convertedValue = Double.NaN;
+ boolean sameUnit = Objects.equals(unit, targetUnit);
+ if (!sameUnit) try {
+ convertedValue = doubleValue(targetUnit);
+ sameUnit = (value instanceof Number) && ((Number) value).doubleValue() == convertedValue;
+ } catch (IllegalStateException exception) {
+ // May happen if a parameter is mandatory (e.g. "semi-major")
+ // but no value has been set for this parameter.
+ formatter.setInvalidWKT(descriptor, exception);
+ }
+ if (isWKT1 && !sameUnit) {
formatter.append(convertedValue);
+ } else if (!isWKT1 && (unit == null) && isFile(value)) {
+ formatter.append(value.toString(), null);
+ return WKTKeywords.ParameterFile;
} else {
- final T value = getValue(); // Gives to users a chance to override this property.
- if (!isWKT1 && (unit == null) && (value instanceof URI || value instanceof URL
- || value instanceof File || value instanceof Path))
- {
- formatter.append(value.toString(), null);
- return WKTKeywords.ParameterFile;
- }
formatter.appendAny(value);
}
/*
* In the WKT 2 specification, the unit and the identifier are optional but recommended.
* We follow that recommendation in strict WKT2 mode, but omit them in non-strict modes.
* The only exception is when the parameter unit is not the same than the contextual unit,
- * in which case we have no choice: we must format that unit.
+ * in which case we have no choice: we must format that unit, unless the numerical value
+ * is identical in both units (typically the 0 value).
*/
- if (unit != null && !isWKT1 && (!convention.isSimplified() || !unit.equals(targetUnit))) {
+ if (!isWKT1 && (!sameUnit || !convention.isSimplified() || !hasContextualUnit(formatter))) {
formatter.append(unit);
// ID will be added by the Formatter itself.
}
return WKTKeywords.Parameter;
}
+
+ /**
+ * Returns {@code true} if the given formatter has contextual units, in which case the WKT format can omit
+ * the unit of measurement. The contextual units may be defined either in the direct parent or in the parent
+ * of the parent, depending if we are formatting WKT1 or WKT2 respectively. This is because WKT2 wraps the
+ * parameters in an additional {@code CONVERSION[…]} element which is not present in WKT1.
+ *
+ * <p>Taking the example documented in {@link #formatTo(Formatter)}:</p>
+ * <ul>
+ * <li>in WKT 1, {@code PROJCS[…]} is the immediate parent of {@code PARAMETER[…]}, but</li>
+ * <li>in WKT 2, {@code ProjectedCRS[…]} is the parent of {@code Conversion[…]},
+ * which is the parent of {@code Parameter[…]}.</li>
+ * </ul>
+ */
+ private static boolean hasContextualUnit(final Formatter formatter) {
+ return formatter.hasContextualUnit(1) || // In WKT1
+ formatter.hasContextualUnit(2); // In WKT2
+ }
}
Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java?rev=1684479&r1=1684478&r2=1684479&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java [UTF-8] Tue Jun 9 17:06:31 2015
@@ -41,7 +41,6 @@ import org.apache.sis.io.wkt.Formatter;
import static org.apache.sis.util.Utilities.deepEquals;
import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
import static org.apache.sis.internal.referencing.WKTUtilities.toFormattable;
-import static org.apache.sis.internal.referencing.ReferencingUtilities.canSetProperty;
// Branch-dependent imports
import java.util.Objects;
@@ -323,7 +322,9 @@ public class AbstractCRS extends Abstrac
* @throws IllegalStateException If the coordinate system has already been set.
*/
final void setCoordinateSystem(final String name, final CoordinateSystem cs) {
- if (cs != null && canSetProperty(AbstractCRS.class, "setCoordinateSystem", name, coordinateSystem != null)) {
+ if (cs != null && ReferencingUtilities.canSetProperty(AbstractCRS.class,
+ "setCoordinateSystem", name, coordinateSystem != null))
+ {
coordinateSystem = cs;
}
}
@@ -468,13 +469,14 @@ public class AbstractCRS extends Abstrac
formatter.newLine();
final boolean isWKT1 = formatter.getConvention().majorVersion() == 1;
if (isWKT1 || !isBaseCRS(formatter)) {
- formatCS(formatter, getCoordinateSystem(), isWKT1);
+ final CoordinateSystem cs = getCoordinateSystem();
+ formatCS(formatter, cs, ReferencingUtilities.getUnit(cs), isWKT1);
}
return keyword;
}
/**
- * Returns {@code true} if the given formatter is in the process of formatting the base CRS of a
+ * Returns {@code true} if the given formatter is in the process of formatting the base CRS of an
* {@link AbstractDerivedCRS}. In such case, the coordinate system axes shall not be formatted.
*
* <p>This method should return {@code true} when {@code this} CRS is the value returned by
@@ -495,15 +497,19 @@ public class AbstractCRS extends Abstrac
* because ISO 19162 excludes the coordinate system definition in base CRS. Note however that WKT 1 includes the
* coordinate systems.</p>
*
+ * <div class="note"><b>Note:</b> the {@code unit} and {@code isWKT1} arguments could be computed by this method,
+ * but are requested in order to avoid computing them twice, because the caller usually have them anyway.</div>
+ *
* @param formatter The formatter where to append the coordinate system.
* @param cs The coordinate system to append.
+ * @param unit The value of {@code ReferencingUtilities.getUnit(cs)}.
* @param isWKT1 {@code true} if formatting WKT 1, or {@code false} for WKT 2.
*/
- final void formatCS(final Formatter formatter, final CoordinateSystem cs, final boolean isWKT1) {
+ final void formatCS(final Formatter formatter, final CoordinateSystem cs, final Unit<?> unit, final boolean isWKT1) {
+ assert unit == ReferencingUtilities.getUnit(cs) : unit;
assert (formatter.getConvention().majorVersion() == 1) == isWKT1 : isWKT1;
assert isWKT1 || !isBaseCRS(formatter) : isWKT1; // Condition documented in javadoc.
- final Unit<?> unit = ReferencingUtilities.getUnit(cs);
final Unit<?> oldUnit = formatter.addContextualUnit(unit);
if (isWKT1) { // WKT 1 writes unit before axes, while WKT 2 writes them after axes.
formatter.append(unit);
@@ -524,8 +530,7 @@ public class AbstractCRS extends Abstrac
formatter.append(unit);
formatter.indent(-1);
}
- formatter.removeContextualUnit(unit);
- formatter.addContextualUnit(oldUnit);
+ formatter.restoreContextualUnit(unit, oldUnit);
formatter.newLine(); // For writing the ID[…] element on its own line.
}
}
Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultDerivedCRS.java?rev=1684479&r1=1684478&r2=1684479&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultDerivedCRS.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultDerivedCRS.java [UTF-8] Tue Jun 9 17:06:31 2015
@@ -45,6 +45,7 @@ import org.apache.sis.referencing.Abstra
import org.apache.sis.referencing.operation.DefaultConversion;
import org.apache.sis.referencing.operation.DefaultOperationMethod;
import org.apache.sis.referencing.cs.AxesConvention;
+import org.apache.sis.internal.referencing.ReferencingUtilities;
import org.apache.sis.internal.referencing.WKTUtilities;
import org.apache.sis.internal.metadata.WKTKeywords;
import org.apache.sis.io.wkt.FormattableObject;
@@ -537,7 +538,8 @@ public class DefaultDerivedCRS extends A
}
});
if (!isBaseCRS(formatter)) {
- formatCS(formatter, getCoordinateSystem(), isWKT1);
+ final CoordinateSystem cs = getCoordinateSystem();
+ formatCS(formatter, cs, ReferencingUtilities.getUnit(cs), isWKT1);
}
return keyword();
}
Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java?rev=1684479&r1=1684478&r2=1684479&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java [UTF-8] Tue Jun 9 17:06:31 2015
@@ -17,7 +17,9 @@
package org.apache.sis.referencing.crs;
import java.util.Map;
+import javax.measure.unit.Unit;
import javax.measure.unit.NonSI;
+import javax.measure.quantity.Angle;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@@ -178,6 +180,7 @@ class DefaultGeodeticCRS extends Abstrac
WKTUtilities.appendName(this, formatter, null);
final Convention convention = formatter.getConvention();
final boolean isWKT1 = (convention.majorVersion() == 1);
+ CoordinateSystem cs = getCoordinateSystem();
/*
* Unconditionally format the datum element, followed by the prime meridian.
* The prime meridian is part of datum according ISO 19111, but is formatted
@@ -188,13 +191,16 @@ class DefaultGeodeticCRS extends Abstrac
formatter.append(toFormattable(datum));
formatter.newLine();
final PrimeMeridian pm = datum.getPrimeMeridian();
+ final Unit<Angle> angularUnit = ReferencingUtilities.getAngularUnit(cs);
if (convention != Convention.WKT2_SIMPLIFIED ||
ReferencingUtilities.getGreenwichLongitude(pm, NonSI.DEGREE_ANGLE) != 0)
{
+ final Unit<Angle> oldUnit = formatter.addContextualUnit(angularUnit);
formatter.indent(1);
formatter.append(toFormattable(pm));
formatter.indent(-1);
formatter.newLine();
+ formatter.restoreContextualUnit(angularUnit, oldUnit);
}
/*
* Get the coordinate system to format. This will also determine the units to write and the keyword to
@@ -205,7 +211,6 @@ class DefaultGeodeticCRS extends Abstrac
* NOT create an instance of a subclass (because the distinction between geographic and geocentric CRS
* is not anymore in ISO 19111:2007).
*/
- CoordinateSystem cs = getCoordinateSystem();
final boolean isBaseCRS;
if (isWKT1) {
if (!(cs instanceof EllipsoidalCS)) { // Tested first because this is the most common case.
@@ -228,12 +233,13 @@ class DefaultGeodeticCRS extends Abstrac
* Note that even if we do not format the CS, we may still write the units if we are formatting in "simplified"
* mode (as opposed to the more verbose mode). This looks like the opposite of what we would expect, but this is
* because formatting the unit here allow us to avoid repeating the unit in projection parameters when this CRS
- * is part of a ProjectedCRS.
+ * is part of a ProjectedCRS. Note however that in such case, the units to format are the angular units because
+ * the linear units will be formatted in the enclosing PROJCS[…] element.
*/
if (!isBaseCRS) {
- formatCS(formatter, cs, isWKT1); // Will also format the axis unit.
+ formatCS(formatter, cs, ReferencingUtilities.getUnit(cs), isWKT1); // Will also format the axes unit.
} else if (convention.isSimplified()) {
- formatter.append(formatter.toContextualUnit(ReferencingUtilities.getAngularUnit(cs)));
+ formatter.append(formatter.toContextualUnit(angularUnit));
}
/*
* For WKT 1, the keyword depends on the subclass: "GeogCS" for GeographicCRS or "GeocCS" for GeocentricCRS.
Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultProjectedCRS.java?rev=1684479&r1=1684478&r2=1684479&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultProjectedCRS.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultProjectedCRS.java [UTF-8] Tue Jun 9 17:06:31 2015
@@ -44,7 +44,6 @@ import org.apache.sis.internal.referenci
import org.apache.sis.internal.util.Constants;
import org.apache.sis.io.wkt.FormattableObject;
import org.apache.sis.io.wkt.Formatter;
-import org.apache.sis.io.wkt.Convention;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.logging.Logging;
@@ -383,11 +382,13 @@ public class DefaultProjectedCRS extends
return super.formatTo(formatter);
}
WKTUtilities.appendName(this, formatter, null);
- final Convention convention = formatter.getConvention();
- final boolean isWKT1 = (convention.majorVersion() == 1);
- final GeographicCRS baseCRS = getBaseCRS();
- final Unit<Angle> unit = ReferencingUtilities.getAngularUnit(baseCRS.getCoordinateSystem());
- final Unit<Angle> oldUnit = formatter.addContextualUnit(unit);
+ final boolean isWKT1 = (formatter.getConvention().majorVersion() == 1);
+ final CartesianCS cs = getCoordinateSystem();
+ final GeographicCRS baseCRS = getBaseCRS();
+ final Unit<?> lengthUnit = ReferencingUtilities.getUnit(cs);
+ final Unit<Angle> angularUnit = ReferencingUtilities.getAngularUnit(baseCRS.getCoordinateSystem());
+ final Unit<Angle> oldAngle = formatter.addContextualUnit(angularUnit);
+ final Unit<?> oldLength = formatter.addContextualUnit(lengthUnit);
/*
* Format the enclosing base CRS. Note that WKT 1 formats a full GeographicCRS while WKT 2 formats only
* the datum with the prime meridian (no coordinate system) and uses a different keyword ("BaseGeodCRS"
@@ -411,10 +412,10 @@ public class DefaultProjectedCRS extends
* of another derived CRS.
*/
if (!isBaseCRS) {
- formatCS(formatter, getCoordinateSystem(), isWKT1);
+ formatCS(formatter, cs, lengthUnit, isWKT1);
}
- formatter.removeContextualUnit(unit);
- formatter.addContextualUnit(oldUnit);
+ formatter.restoreContextualUnit(lengthUnit, oldLength);
+ formatter.restoreContextualUnit(angularUnit, oldAngle);
return isWKT1 ? WKTKeywords.ProjCS : isBaseCRS ? WKTKeywords.BaseProjCRS : WKTKeywords.ProjectedCRS;
}
Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java?rev=1684479&r1=1684478&r2=1684479&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java [UTF-8] Tue Jun 9 17:06:31 2015
@@ -27,6 +27,7 @@ import org.opengis.util.GenericName;
import org.opengis.util.InternationalString;
import org.opengis.metadata.Identifier;
import org.opengis.referencing.datum.PrimeMeridian;
+import org.opengis.referencing.crs.GeneralDerivedCRS;
import org.apache.sis.referencing.AbstractIdentifiedObject;
import org.apache.sis.internal.metadata.WKTKeywords;
import org.apache.sis.internal.jaxb.gco.Measure;
@@ -81,7 +82,7 @@ import java.util.Objects;
* @author Martin Desruisseaux (IRD, Geomatys)
* @author Cédric Briançon (Geomatys)
* @since 0.4
- * @version 0.4
+ * @version 0.6
* @module
*
* @see org.apache.sis.referencing.CommonCRS#primeMeridian()
@@ -337,6 +338,57 @@ public class DefaultPrimeMeridian extend
}
/**
+ * Returns {@code true} if the given formatter is in the process of formatting the prime meridian of a base CRS
+ * of an {@link AbstractDerivedCRS}. In such case, base CRS coordinate system axes shall not be formatted, which
+ * has the consequence of bringing the {@code UNIT[…]} element right below the {@code PRIMEM[…]} one. Example:
+ *
+ * {@preformat wkt
+ * ProjectedCRS[“NTF (Paris) / Lambert zone II”,
+ * BaseGeodCRS[“NTF (Paris)”,
+ * Datum[“Nouvelle Triangulation Francaise”,
+ * Ellipsoid[“NTF”, 6378249.2, 293.4660212936269]],
+ * PrimeMeridian[“Paris”, 2.5969213],
+ * AngleUnit[“grade”, 0.015707963267948967]],
+ * Conversion[“Lambert zone II”,
+ * etc...
+ * }
+ *
+ * If we were not formatting a base CRS, we would have many lines between {@code PrimeMeridian[…]} and
+ * {@code AngleUnit[…]} in the above example, which would make less obvious that the angle unit applies
+ * also to the prime meridian. It does not bring any ambiguity from an ISO 19162 standard point of view,
+ * but historically some other softwares interpreted the {@code PRIMEM[…]} units wrongly, which is why
+ * we try to find a compromise between keeping the WKT simple and avoiding an historical ambiguity.
+ *
+ * @see org.apache.sis.referencing.crs.AbstractCRS#isBaseCRS(Formatter)
+ */
+ private static boolean isElementOfBaseCRS(final Formatter formatter) {
+ return formatter.getEnclosingElement(2) instanceof GeneralDerivedCRS;
+ }
+
+ /**
+ * Returns {@code true} if {@link #formatTo(Formatter)} should conservatively format the angular unit
+ * even if it would be legal to omit it.
+ *
+ * <div class="section">Rational</div>
+ * According the ISO 19162 standard, it is legal to omit the {@code PrimeMeridian} angular unit when
+ * that unit is the same than the unit of the axes of the enclosing {@code GeographicCRS}. However the
+ * relationship between the CRS axes and the prime meridian is less obvious in WKT2 than it was in WKT1,
+ * because the WKT2 {@code UNIT[…]} element is far from the {@code PRIMEM[…]} element while it was just
+ * below it in WKT1. Furthermore, the {@code PRIMEM[…]} unit is one source of incompatibility between
+ * various WKT1 parsers (i.e. some popular libraries are not conform to OGC 01-009 and ISO 19162).
+ * So we are safer to unconditionally format any unit other than degrees, even if we could legally
+ * omit them.
+ *
+ * <p>However in order to keep the WKT slightly simpler in {@link Convention#WKT2_SIMPLIFIED} mode,
+ * we make an exception to the above-cited safety if the {@code UNIT[…]} element is formatted right
+ * below the {@code PRIMEM[…]} one, which happen if we are inside a base CRS.
+ * See {@link #isElementOfBaseCRS(Formatter)} for more discussion.
+ */
+ private static boolean beConservative(final Formatter formatter, final Unit<Angle> targetUnit) {
+ return !targetUnit.equals(NonSI.DEGREE_ANGLE) && !isElementOfBaseCRS(formatter);
+ }
+
+ /**
* Formats this prime meridian as a <cite>Well Known Text</cite> {@code PrimeMeridian[…]} element.
*
* @return {@code "PrimeMeridian"} (WKT 2) or {@code "PrimeM"} (WKT 1).
@@ -348,16 +400,13 @@ public class DefaultPrimeMeridian extend
super.formatTo(formatter);
final Convention convention = formatter.getConvention();
final boolean isWKT1 = convention.majorVersion() == 1;
- Unit<Angle> targetUnit = formatter.toContextualUnit(NonSI.DEGREE_ANGLE);
- if (targetUnit == null) {
- targetUnit = NonSI.DEGREE_ANGLE;
- }
+ final Unit<Angle> targetUnit = formatter.toContextualUnit(NonSI.DEGREE_ANGLE);
formatter.append(isWKT1 ? getGreenwichLongitude(targetUnit) : getGreenwichLongitude());
if (isWKT1) {
return WKTKeywords.PrimeM;
}
final Unit<Angle> angularUnit = getAngularUnit(); // Gives to users a chance to override properties.
- if (!convention.isSimplified() || !targetUnit.equals(angularUnit)) {
+ if (!convention.isSimplified() || !targetUnit.equals(angularUnit) || beConservative(formatter, targetUnit)) {
formatter.append(angularUnit);
}
return WKTKeywords.PrimeMeridian;
Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java?rev=1684479&r1=1684478&r2=1684479&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java [UTF-8] Tue Jun 9 17:06:31 2015
@@ -23,6 +23,7 @@ import java.text.ParseException;
import javax.measure.unit.SI;
import javax.measure.unit.NonSI;
import javax.measure.unit.Unit;
+import javax.measure.quantity.Length;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.cs.*;
import org.opengis.referencing.crs.*;
@@ -128,12 +129,13 @@ public final strictfp class GeodeticObje
}
/**
- * Verifies that the axes of the coordinate system are those of a projected CRS, in metres.
+ * Verifies that the axes of the coordinate system are those of a projected CRS,
+ * with (East, North) axis directions.
*/
- private static void verifyProjectedCS(final CartesianCS cs) {
+ private static void verifyProjectedCS(final CartesianCS cs, final Unit<Length> unit) {
assertEquals("dimension", 2, cs.getDimension());
- assertUnboundedAxisEquals("Easting", "E", AxisDirection.EAST, SI.METRE, cs.getAxis(0));
- assertUnboundedAxisEquals("Northing", "N", AxisDirection.NORTH, SI.METRE, cs.getAxis(1));
+ assertUnboundedAxisEquals("Easting", "E", AxisDirection.EAST, unit, cs.getAxis(0));
+ assertUnboundedAxisEquals("Northing", "N", AxisDirection.NORTH, unit, cs.getAxis(1));
}
/**
@@ -199,7 +201,7 @@ public final strictfp class GeodeticObje
*/
@Test
@DependsOnMethod("testGeographicCRS")
- public void testAxisSwapping() throws ParseException {
+ public void testWithAxisSwapping() throws ParseException {
verifyGeographicCRS(1, parse(GeographicCRS.class,
" GEOGCS[“WGS 84”,\n" +
" DATUM[“World Geodetic System 1984”,\n" +
@@ -215,11 +217,11 @@ public final strictfp class GeodeticObje
*
* @throws ParseException if the parsing failed.
*
- * @see #testImplicitAxesInSeconds()
+ * @see #testWithImplicitAxesInSeconds()
*/
@Test
@DependsOnMethod("testGeographicCRS")
- public void testImplicitAxes() throws ParseException {
+ public void testWithImplicitAxes() throws ParseException {
verifyGeographicCRS(0, parse(GeographicCRS.class,
" GEOGCS[“WGS 84”,\n" +
" DATUM[“World Geodetic System 1984”,\n" +
@@ -229,7 +231,7 @@ public final strictfp class GeodeticObje
}
/**
- * Implementation of {@link #testGeographicCRS()} and {@link #testAxisSwapping()}.
+ * Implementation of {@link #testGeographicCRS()} and {@link #testWithAxisSwapping()}.
* This test expects no {@code AUTHORITY} element on any component.
*
* @param swap 1 if axes are expected to be swapped, or 0 otherwise.
@@ -279,7 +281,7 @@ public final strictfp class GeodeticObje
" AXIS[“Northing”, NORTH]]");
assertNameAndIdentifierEqual("Mercator test", 0, crs);
- verifyProjectedCS(crs.getCoordinateSystem());
+ verifyProjectedCS(crs.getCoordinateSystem(), SI.METRE);
verifyGeographicCRS(0, crs.getBaseCRS());
final GeodeticDatum datum = crs.getDatum();
@@ -293,12 +295,12 @@ public final strictfp class GeodeticObje
assertEquals("Mercator (variant A)", crs.getConversionFromBase().getMethod().getName().getCode());
final ParameterValueGroup param = crs.getConversionFromBase().getParameterValues();
- assertEquals("semi_major", 6378137.0, param.parameter("semi_major" ).doubleValue(), STRICT);
- assertEquals("semi_minor", 6356752.3, param.parameter("semi_minor" ).doubleValue(), 0.1);
- assertEquals("central_meridian", -20.0, param.parameter("central_meridian").doubleValue(), STRICT);
- assertEquals("scale_factor", 1.0, param.parameter("scale_factor" ).doubleValue(), STRICT);
- assertEquals("false_easting", 500000.0, param.parameter("false_easting" ).doubleValue(), STRICT);
- assertEquals("false_northing", 0.0, param.parameter("false_northing" ).doubleValue(), STRICT);
+ assertEquals("semi_major", 6378137.0, param.parameter("semi_major" ).doubleValue(SI .METRE), STRICT);
+ assertEquals("semi_minor", 6356752.3, param.parameter("semi_minor" ).doubleValue(SI .METRE), 0.1);
+ assertEquals("central_meridian", -20.0, param.parameter("central_meridian").doubleValue(NonSI.DEGREE_ANGLE), STRICT);
+ assertEquals("scale_factor", 1.0, param.parameter("scale_factor" ).doubleValue(Unit .ONE), STRICT);
+ assertEquals("false_easting", 500000.0, param.parameter("false_easting" ).doubleValue(SI .METRE), STRICT);
+ assertEquals("false_northing", 0.0, param.parameter("false_northing" ).doubleValue(SI .METRE), STRICT);
}
/**
@@ -309,7 +311,7 @@ public final strictfp class GeodeticObje
*/
@Test
@DependsOnMethod("testGeographicCRS")
- public void testNonGreenwichMeridian() throws ParseException {
+ public void testWithNonGreenwichMeridian() throws ParseException {
String wkt = "GEOGCS[“NTF (Paris)”,\n" +
" DATUM[“Nouvelle Triangulation Française (Paris)”,\n" +
" SPHEROID[“Clarke 1880 (IGN)”, 6378249.2, 293.4660212936269]],\n" +
@@ -352,14 +354,14 @@ public final strictfp class GeodeticObje
}
/**
- * Tests the parsing of a projected CRS using parameters in grades instead than degrees.
- * The grad units are also used for the prime meridian.
+ * Tests the parsing of a projected CRS using angular values in grades instead than degrees
+ * and in lengths in kilometres instead than metres.
*
* @throws ParseException if the parsing failed.
*/
@Test
- @DependsOnMethod({"testNonGreenwichMeridian", "testProjectedCRS"})
- public void testGradUnitInParameters() throws ParseException {
+ @DependsOnMethod({"testWithNonGreenwichMeridian", "testProjectedCRS"})
+ public void testWithLessCommonUnits() throws ParseException {
String wkt = "PROJCS[“NTF (Paris) / Lambert zone II”," +
" GEOGCS[“NTF (Paris)”," +
" DATUM[“Nouvelle Triangulation Française (Paris)”," +
@@ -370,13 +372,13 @@ public final strictfp class GeodeticObje
" PROJECTION[“Lambert Conformal Conic (1SP)”]," + // Intentional swapping of "Conformal" and "Conic".
" PARAMETER[“latitude_of_origin”, 52.0]," + // In grads.
" PARAMETER[“scale_factor”, 0.99987742]," +
- " PARAMETER[“false_easting”, 600000]," +
- " PARAMETER[“false_northing”, 2200000]," +
- " UNIT[“metre”,1]]";
+ " PARAMETER[“false_easting”, 600.0]," +
+ " PARAMETER[“false_northing”, 2200.0]," +
+ " UNIT[“metre”,1000]]";
ProjectedCRS crs = parse(ProjectedCRS.class, wkt);
assertNameAndIdentifierEqual("NTF (Paris) / Lambert zone II", 0, crs);
- verifyProjectedCS(crs.getCoordinateSystem());
+ verifyProjectedCS(crs.getCoordinateSystem(), SI.KILOMETRE);
PrimeMeridian pm = verifyNTF(crs.getDatum());
assertEquals("angularUnit", NonSI.GRADE, pm.getAngularUnit());
assertEquals("greenwichLongitude", 2.5969213, pm.getGreenwichLongitude(), STRICT);
@@ -385,16 +387,18 @@ public final strictfp class GeodeticObje
assertEquals("latitude_of_origin", 52.0, param.doubleValue(), STRICT);
/*
* Parse again using Convention.WKT1_COMMON_UNITS and ignoring AXIS[…] elements.
- * See the comment in testNonGreenwichMeridian for a discussion. The new aspect
- * tested by this method is that the unit should be ignored for the parameters
- * in addition to the prime meridian.
+ * See the comment in 'testWithNonGreenwichMeridian()' method for a discussion.
+ * The new aspect tested by this method is that the unit should be ignored
+ * for the parameters in addition to the prime meridian.
*/
- wkt = wkt.replace("2.5969213", "2.33722917"); // Convert unit in prime meridian.
- wkt = wkt.replace("52.0", "46.8"); // Convert unit in “latitude_of_origin” parameter.
+ wkt = wkt.replace("2.5969213", "2.33722917"); // Convert unit in prime meridian.
+ wkt = wkt.replace("52.0", "46.8"); // Convert unit in “latitude_of_origin” parameter.
+ wkt = wkt.replace("600.0", "600000"); // Convert unit in “false_easting” parameter.
+ wkt = wkt.replace("2200.0", "2200000"); // Convert unit in “false_northing” parameter.
setConvention(Convention.WKT1_COMMON_UNITS, true);
crs = parse(ProjectedCRS.class, wkt);
assertNameAndIdentifierEqual("NTF (Paris) / Lambert zone II", 0, crs);
- verifyProjectedCS(crs.getCoordinateSystem());
+ verifyProjectedCS(crs.getCoordinateSystem(), SI.KILOMETRE);
pm = verifyNTF(crs.getDatum());
assertEquals("angularUnit", NonSI.DEGREE_ANGLE, pm.getAngularUnit());
assertEquals("greenwichLongitude", 2.33722917, pm.getGreenwichLongitude(), STRICT);
@@ -431,12 +435,12 @@ public final strictfp class GeodeticObje
*/
private static ParameterValue<?> verifyNTF(final ParameterValueGroup param) {
assertEquals("Lambert Conic Conformal (1SP)", param.getDescriptor().getName().getCode());
- assertEquals("semi_major", 6378249.2, param.parameter("semi_major" ).doubleValue(), STRICT);
- assertEquals("semi_minor", 6356515.0, param.parameter("semi_minor" ).doubleValue(), 1E-12);
- assertEquals("central_meridian", 0.0, param.parameter("central_meridian").doubleValue(), STRICT);
- assertEquals("scale_factor", 0.99987742, param.parameter("scale_factor" ).doubleValue(), STRICT);
- assertEquals("false_easting", 600000.0, param.parameter("false_easting" ).doubleValue(), STRICT);
- assertEquals("false_northing", 2200000.0, param.parameter("false_northing" ).doubleValue(), STRICT);
+ assertEquals("semi_major", 6378249.2, param.parameter("semi_major" ).doubleValue(SI .METRE), STRICT);
+ assertEquals("semi_minor", 6356515.0, param.parameter("semi_minor" ).doubleValue(SI .METRE), 1E-12);
+ assertEquals("central_meridian", 0.0, param.parameter("central_meridian").doubleValue(NonSI.DEGREE_ANGLE), STRICT);
+ assertEquals("scale_factor", 0.99987742, param.parameter("scale_factor" ).doubleValue(Unit .ONE), STRICT);
+ assertEquals("false_easting", 600000.0, param.parameter("false_easting" ).doubleValue(SI .METRE), STRICT);
+ assertEquals("false_northing", 2200000.0, param.parameter("false_northing" ).doubleValue(SI .METRE), STRICT);
return param.parameter("latitude_of_origin");
}
@@ -502,11 +506,11 @@ public final strictfp class GeodeticObje
*
* @throws ParseException if the parsing failed.
*
- * @see #testImplicitAxes()
+ * @see #testWithImplicitAxes()
*/
@Test
- @DependsOnMethod("testImplicitAxes")
- public void testImplicitAxesInSeconds() throws ParseException {
+ @DependsOnMethod("testWithImplicitAxes")
+ public void testWithImplicitAxesInSeconds() throws ParseException {
final GeographicCRS crs = parse(GeographicCRS.class,
"GEOGCS[“NAD83 / NFIS Seconds”," +
"DATUM[“North_American_Datum_1983”,\n" +
@@ -576,7 +580,7 @@ public final strictfp class GeodeticObje
"AXIS[“Easting”,East],AXIS[“Northing”,North]]");
assertNameAndIdentifierEqual("FRANCE/NTF/Lambert III", 0, crs);
- verifyProjectedCS(crs.getCoordinateSystem());
+ verifyProjectedCS(crs.getCoordinateSystem(), SI.METRE);
final GeographicCRS geoCRS = crs.getBaseCRS();
assertNameAndIdentifierEqual("NTF=GR3DF97A", 0, geoCRS); // Inherited the datum name.
@@ -596,12 +600,12 @@ public final strictfp class GeodeticObje
final ParameterValueGroup param = crs.getConversionFromBase().getParameterValues();
assertEquals("Lambert Conic Conformal (1SP)", param.getDescriptor().getName().getCode());
- assertEquals("semi_major", 6378249.2, param.parameter("semi_major" ).doubleValue(), STRICT);
- assertEquals("semi_minor", 6356515.0, param.parameter("semi_minor" ).doubleValue(), 1E-12);
- assertEquals("latitude_of_origin", 44.1, param.parameter("latitude_of_origin").doubleValue(), STRICT);
- assertEquals("central_meridian", 2.33722917, param.parameter("central_meridian" ).doubleValue(), STRICT);
- assertEquals("scale_factor", 0.999877499, param.parameter("scale_factor" ).doubleValue(), STRICT);
- assertEquals("false_easting", 600000.0, param.parameter("false_easting" ).doubleValue(), STRICT);
- assertEquals("false_northing", 200000.0, param.parameter("false_northing" ).doubleValue(), STRICT);
+ assertEquals("semi_major", 6378249.2, param.parameter("semi_major" ).doubleValue(SI .METRE), STRICT);
+ assertEquals("semi_minor", 6356515.0, param.parameter("semi_minor" ).doubleValue(SI .METRE), 1E-12);
+ assertEquals("latitude_of_origin", 44.1, param.parameter("latitude_of_origin").doubleValue(NonSI.DEGREE_ANGLE), STRICT);
+ assertEquals("central_meridian", 2.33722917, param.parameter("central_meridian" ).doubleValue(NonSI.DEGREE_ANGLE), STRICT);
+ assertEquals("scale_factor", 0.999877499, param.parameter("scale_factor" ).doubleValue(Unit .ONE), STRICT);
+ assertEquals("false_easting", 600000.0, param.parameter("false_easting" ).doubleValue(SI .METRE), STRICT);
+ assertEquals("false_northing", 200000.0, param.parameter("false_northing" ).doubleValue(SI .METRE), STRICT);
}
}
Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java?rev=1684479&r1=1684478&r2=1684479&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java [UTF-8] Tue Jun 9 17:06:31 2015
@@ -18,11 +18,12 @@ package org.apache.sis.io.wkt;
import java.text.ParseException;
import org.opengis.referencing.crs.VerticalCRS;
+import org.apache.sis.test.DependsOnMethod;
import org.apache.sis.test.DependsOn;
import org.apache.sis.test.TestCase;
import org.junit.Test;
-import static org.junit.Assert.*;
+import static org.apache.sis.test.Assert.*;
/**
@@ -36,6 +37,18 @@ import static org.junit.Assert.*;
@DependsOn(GeodeticObjectParserTest.class)
public final strictfp class WKTFormatTest extends TestCase {
/**
+ * The instance to use for the test, or {@code null} if none.
+ */
+ private WKTFormat format;
+
+ /**
+ * An instance used by {@link #testConsistency()} for parsing the WKT.
+ * May be a different instance than {@link #format} if the two instances
+ * do not use the same {@link Convention}.
+ */
+ private WKTFormat parser;
+
+ /**
* Verifies the condition documented in {@link WKTFormat#SHORT_DATE_PATTERN} javadoc.
*/
@Test
@@ -53,7 +66,7 @@ public final strictfp class WKTFormatTes
*/
@Test
public void testParse() throws ParseException {
- final WKTFormat format = new WKTFormat(null, null);
+ format = new WKTFormat(null, null);
final VerticalCRS crs = (VerticalCRS) format.parseObject(
"VERT_CS[“Gravity-related height”,\n" +
" VERT_DATUM[“Mean Sea Level”, 2005],\n" +
@@ -63,4 +76,102 @@ public final strictfp class WKTFormatTes
GeodeticObjectParserTest.assertNameAndIdentifierEqual("Gravity-related height", 0, crs);
GeodeticObjectParserTest.assertNameAndIdentifierEqual("Mean Sea Level", 0, crs.getDatum());
}
+
+ /**
+ * Tests consistency between the parser and the formatter when using the WKT 1 format.
+ * This test parses a WKT, formats it then parses again. We should obtain the same result.
+ *
+ * @throws ParseException if a parsing failed.
+ */
+ @Test
+ public void testConsistencyOfWKT1() throws ParseException {
+ format = new WKTFormat(null, null);
+ format.setConvention(Convention.WKT1);
+ parser = format;
+ testConsistency();
+ }
+
+ /**
+ * Tests consistency between the parser and the formatter when using the WKT 1 format.
+ * This test parses a WKT, formats it then parses again. We should obtain the same result.
+ *
+ * @throws ParseException if a parsing failed.
+ */
+ @Test
+ @DependsOnMethod("testConsistencyOfWKT1")
+ public void testConsistencyOfWKT1_WithCommonUnits() throws ParseException {
+ format = new WKTFormat(null, null);
+ format.setConvention(Convention.WKT1_COMMON_UNITS);
+ parser = new WKTFormat(null, null);
+ parser.setConvention(Convention.WKT1);
+ testConsistency();
+ }
+
+ /**
+ * Implementation of {@link #testConsistencyOfWKT1()} and variants.
+ *
+ * @throws ParseException if a parsing failed.
+ */
+ private void testConsistency() throws ParseException {
+ testConsistency(
+ "GEOGCS[“Tokyo”,"
+ + "DATUM[“Tokyo”,"
+ + "SPHEROID[“Bessel 1841”,6377397.155,299.1528128,AUTHORITY[“EPSG”,“7004”]],"
+ + "TOWGS84[-148,507,685,0,0,0,0],AUTHORITY[“EPSG”,“6301”]],"
+ + "PRIMEM[“Greenwich”,0,AUTHORITY[“EPSG”,“8901”]],"
+ + "UNIT[“DMSH”,0.0174532925199433,AUTHORITY[“EPSG”,“9108”]],"
+ + "AXIS[“Lat”,NORTH],"
+ + "AXIS[“Long”,EAST],"
+ + "AUTHORITY[“EPSG”,“4301”]]");
+
+ testConsistency(
+ "GEOGCS[“NTF (Paris)”,"
+ + "DATUM[“Nouvelle_Triangulation_Francaise”,"
+ + "SPHEROID[“Clarke 1880 (IGN)”,6378249.2,293.466021293627,AUTHORITY[“EPSG”,“7011”]],"
+ + "TOWGS84[-168,-60,320,0,0,0,0],AUTHORITY[“EPSG”,“6275”]],"
+ + "PRIMEM[“Paris”,2.5969213,AUTHORITY[“EPSG”,“8903”]],"
+ + "UNIT[“grad”,0.015707963267949,AUTHORITY[“EPSG”,“9105”]],"
+ + "AXIS[“Lat”,NORTH],"
+ + "AXIS[“Long”,EAST],"
+ + "AUTHORITY[“EPSG”,“4807”]]");
+
+ testConsistency(
+ "PROJCS[“NTF (Paris) / France I”,"
+ + "GEOGCS[“NTF (Paris)”,"
+ + "DATUM[“Nouvelle_Triangulation_Francaise”,"
+ + "SPHEROID[“Clarke 1880 (IGN)”,6378249.2,293.466021293627,AUTHORITY[“EPSG”,“7011”]],"
+ + "TOWGS84[-168,-60,320,0,0,0,0],"
+ + "AUTHORITY[“EPSG”,“6275”]],"
+ + "PRIMEM[“Paris”,2.5969213,AUTHORITY[“EPSG”,“8903”]],"
+ + "UNIT[“grad”,0.015707963267949,AUTHORITY[“EPSG”,“9105”]],"
+ + "AXIS[“Lat”,NORTH],"
+ + "AXIS[“Long”,EAST],"
+ + "AUTHORITY[“EPSG”,“4807”]],"
+ + "PROJECTION[“Lambert_Conformal_Conic_1SP”],"
+ + "PARAMETER[“latitude_of_origin”,55]," // 55 grads = 49.5 degrees
+ + "PARAMETER[“central_meridian”,0],"
+ + "PARAMETER[“scale_factor”,0.999877341],"
+ + "PARAMETER[“false_easting”,600],"
+ + "PARAMETER[“false_northing”,1200],"
+ + "UNIT[“km”,1000],"
+ + "AXIS[“X”,EAST],"
+ + "AXIS[“Y”,NORTH]]");
+
+ testConsistency(
+ "VERT_CS[“mean sea level depth”,"
+ + "VERT_DATUM[“Mean Sea Level”,2005,AUTHORITY[“EPSG”,“5100”]],"
+ + "UNIT[“kilometre”,1000],AXIS[“Z”,DOWN]]");
+ }
+
+ /**
+ * Implementation of {@link #testConsistency()} for a single WKT.
+ *
+ * @throws ParseException if the parsing failed.
+ */
+ private void testConsistency(final String wkt) throws ParseException {
+ final Object expected = parser.parseObject(wkt);
+ final String reformat = format.format(expected);
+ final Object reparsed = format.parseObject(reformat);
+ assertEqualsIgnoreMetadata(expected, reparsed);
+ }
}
Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultGeographicCRSTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultGeographicCRSTest.java?rev=1684479&r1=1684478&r2=1684479&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultGeographicCRSTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultGeographicCRSTest.java [UTF-8] Tue Jun 9 17:06:31 2015
@@ -228,6 +228,26 @@ public final strictfp class DefaultGeogr
}
/**
+ * Tests WKT 1 formatting on a CRS using a prime meridian other than Greenwich.
+ *
+ * <p>This CRS used in this test is equivalent to {@code EPSG:4807} except for axis order,
+ * since EPSG defines (<var>latitude</var>, <var>longitude</var>) in grades.</p>
+ */
+ @Test
+ @DependsOnMethod("testWKT2")
+ public void testWKT1_ForNonGreenwich() {
+ assertWktEquals(Convention.WKT1,
+ "GEOGCS[“NTF (Paris)”,\n" +
+ " DATUM[“Nouvelle Triangulation Francaise”,\n" + // Formatter should replace "ç" by "c".
+ " SPHEROID[“NTF”, 6378249.2, 293.4660212936269]],\n" +
+ " PRIMEM[“Paris”, 2.5969213],\n" +
+ " UNIT[“grade”, 0.015707963267948967],\n" +
+ " AXIS[“Longitude”, EAST],\n" +
+ " AXIS[“Latitude”, NORTH]]",
+ HardCodedCRS.NTF);
+ }
+
+ /**
* Tests WKT 1 formatting using {@link Convention#WKT1_COMMON_UNITS}. That convention ignores the unit of
* measurement in {@code PRIMEM} element, and rather unconditionally interpret the angle unit as degrees.
* This is a violation of OGC 01-009 and ISO 19162 standards, but is required for compatibility with GDAL.
Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultProjectedCRSTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultProjectedCRSTest.java?rev=1684479&r1=1684478&r2=1684479&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultProjectedCRSTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultProjectedCRSTest.java [UTF-8] Tue Jun 9 17:06:31 2015
@@ -94,7 +94,7 @@ public final strictfp class DefaultProje
" AXIS[“Longitude”, EAST],\n" +
" AXIS[“Latitude”, NORTH]],\n" +
" PROJECTION[“Lambert_Conformal_Conic_1SP”, AUTHORITY[“EPSG”, “9801”]],\n" +
- " PARAMETER[“latitude_of_origin”, 52.0],\n" +
+ " PARAMETER[“latitude_of_origin”, 52.0],\n" + // In grads
" PARAMETER[“central_meridian”, 0.0],\n" +
" PARAMETER[“scale_factor”, 0.99987742],\n" +
" PARAMETER[“false_easting”, 600000.0],\n" +
@@ -107,6 +107,37 @@ public final strictfp class DefaultProje
}
/**
+ * Tests WKT 1 formatting using {@link Convention#WKT1_COMMON_UNITS}.
+ *
+ * @throws FactoryException if the CRS creation failed.
+ */
+ @Test
+ @DependsOnMethod("testWKT1")
+ public void testWKT1_WithCommonUnits() throws FactoryException {
+ final ProjectedCRS crs = create(HardCodedCRS.NTF);
+ assertWktEquals(Convention.WKT1_COMMON_UNITS,
+ "PROJCS[“NTF (Paris) / Lambert zone II”,\n" +
+ " GEOGCS[“NTF (Paris)”,\n" +
+ " DATUM[“Nouvelle Triangulation Francaise”,\n" +
+ " SPHEROID[“NTF”, 6378249.2, 293.4660212936269]],\n" +
+ " PRIMEM[“Paris”, 2.33722917],\n" + // Note the conversion from 2.5969213 grads.
+ " UNIT[“grade”, 0.015707963267948967],\n" +
+ " AXIS[“Longitude”, EAST],\n" +
+ " AXIS[“Latitude”, NORTH]],\n" +
+ " PROJECTION[“Lambert_Conformal_Conic_1SP”, AUTHORITY[“EPSG”, “9801”]],\n" +
+ " PARAMETER[“latitude_of_origin”, 46.8],\n" + // Note the conversion from 52 grads.
+ " PARAMETER[“central_meridian”, 0.0],\n" +
+ " PARAMETER[“scale_factor”, 0.99987742],\n" +
+ " PARAMETER[“false_easting”, 600000.0],\n" +
+ " PARAMETER[“false_northing”, 2200000.0],\n" +
+ " UNIT[“meter”, 1],\n" +
+ " AXIS[“Easting”, EAST],\n" +
+ " AXIS[“Northing”, NORTH],\n" +
+ " AUTHORITY[“EPSG”, “27572”]]",
+ crs);
+ }
+
+ /**
* Tests WKT 1 formatting with a somewhat convolved case where the units of the prime meridian is not
* the same than the unit of axes. Since the axis units is what we write in the {@code UNIT[…]} element,
* the WKT formatter need to convert the unit of prime meridian and all parameter angular values.
@@ -114,7 +145,7 @@ public final strictfp class DefaultProje
* @throws FactoryException if the CRS creation failed.
*/
@Test
- @DependsOnMethod("testWKT1")
+ @DependsOnMethod("testWKT1_WithCommonUnits")
public void testWKT1_WithMixedUnits() throws FactoryException {
final ProjectedCRS crs = create(HardCodedCRS.NTF_NORMALIZED_AXES);
Validators.validate(crs); // Opportunist check.
@@ -123,12 +154,12 @@ public final strictfp class DefaultProje
" GEOGCS[“NTF (Paris)”,\n" +
" DATUM[“Nouvelle Triangulation Francaise”,\n" +
" SPHEROID[“NTF”, 6378249.2, 293.4660212936269]],\n" +
- " PRIMEM[“Paris”, 2.33722917],\n" + // Note the conversion from 2.5969213 grades.
+ " PRIMEM[“Paris”, 2.33722917],\n" + // Note the conversion from 2.5969213 grads.
" UNIT[“degree”, 0.017453292519943295],\n" +
" AXIS[“Longitude”, EAST],\n" +
" AXIS[“Latitude”, NORTH]],\n" +
" PROJECTION[“Lambert_Conformal_Conic_1SP”, AUTHORITY[“EPSG”, “9801”]],\n" +
- " PARAMETER[“latitude_of_origin”, 46.8],\n" + // Note the conversion from 52 grades.
+ " PARAMETER[“latitude_of_origin”, 46.8],\n" + // Note the conversion from 52 grads.
" PARAMETER[“central_meridian”, 0.0],\n" +
" PARAMETER[“scale_factor”, 0.99987742],\n" +
" PARAMETER[“false_easting”, 600000.0],\n" +
@@ -141,6 +172,62 @@ public final strictfp class DefaultProje
}
/**
+ * Tests WKT 2 formatting in simplified mode.
+ *
+ * @throws FactoryException if the CRS creation failed.
+ */
+ @Test
+ @DependsOnMethod("testWKT1")
+ public void testWKT2_Simplified() throws FactoryException {
+ ProjectedCRS crs = create(HardCodedCRS.NTF);
+ assertWktEquals(Convention.WKT2_SIMPLIFIED,
+ "ProjectedCRS[“NTF (Paris) / Lambert zone II”,\n" +
+ " BaseGeodCRS[“NTF (Paris)”,\n" +
+ " Datum[“Nouvelle Triangulation Francaise”,\n" +
+ " Ellipsoid[“NTF”, 6378249.2, 293.4660212936269]],\n" +
+ " PrimeMeridian[“Paris”, 2.5969213],\n" +
+ " Unit[“grade”, 0.015707963267948967]],\n" +
+ " Conversion[“Lambert zone II”,\n" +
+ " Method[“Lambert Conic Conformal (1SP)”],\n" +
+ " Parameter[“Latitude of natural origin”, 52.0],\n" +
+ " Parameter[“Longitude of natural origin”, 0.0],\n" +
+ " Parameter[“Scale factor at natural origin”, 0.99987742],\n" +
+ " Parameter[“False easting”, 600000.0],\n" +
+ " Parameter[“False northing”, 2200000.0]],\n" +
+ " CS[“Cartesian”, 2],\n" +
+ " Axis[“Easting (E)”, east],\n" +
+ " Axis[“Northing (N)”, north],\n" +
+ " Unit[“metre”, 1],\n" +
+ " Id[“EPSG”, 27572, URI[“urn:ogc:def:crs:EPSG::27572”]]]",
+ crs);
+ /*
+ * Try again, but with mixed units. It should force the formatter to add explicit
+ * unit declaration in PrimeMeridian[…] and some Parameter[…] elements.
+ */
+ crs = create(HardCodedCRS.NTF_NORMALIZED_AXES);
+ assertWktEquals(Convention.WKT2_SIMPLIFIED,
+ "ProjectedCRS[“NTF (Paris) / Lambert zone II”,\n" +
+ " BaseGeodCRS[“NTF (Paris)”,\n" +
+ " Datum[“Nouvelle Triangulation Francaise”,\n" +
+ " Ellipsoid[“NTF”, 6378249.2, 293.4660212936269]],\n" +
+ " PrimeMeridian[“Paris”, 2.5969213, Unit[“grade”, 0.015707963267948967]],\n" +
+ " Unit[“degree”, 0.017453292519943295]],\n" +
+ " Conversion[“Lambert zone II”,\n" +
+ " Method[“Lambert Conic Conformal (1SP)”],\n" +
+ " Parameter[“Latitude of natural origin”, 52.0, Unit[“grade”, 0.015707963267948967]],\n" +
+ " Parameter[“Longitude of natural origin”, 0.0],\n" +
+ " Parameter[“Scale factor at natural origin”, 0.99987742],\n" +
+ " Parameter[“False easting”, 600000.0],\n" +
+ " Parameter[“False northing”, 2200000.0]],\n" +
+ " CS[“Cartesian”, 2],\n" +
+ " Axis[“Easting (E)”, east],\n" +
+ " Axis[“Northing (N)”, north],\n" +
+ " Unit[“metre”, 1],\n" +
+ " Id[“EPSG”, 27572, URI[“urn:ogc:def:crs:EPSG::27572”]]]",
+ crs);
+ }
+
+ /**
* Tests WKT 2 formatting. Contrarily to the WKT 1 formatting, in this case it does not matter
* if we mix the units of measurement because the unit is declared for each parameter and axis.
*
@@ -148,7 +235,7 @@ public final strictfp class DefaultProje
*/
@Test
@DependsOnMethod("testWKT1")
- public void testWKT2() throws FactoryException {
+ public void testWKT2_WithMixedUnits() throws FactoryException {
final ProjectedCRS crs = create(HardCodedCRS.NTF_NORMALIZED_AXES);
assertWktEquals(Convention.WKT2,
"ProjectedCRS[“NTF (Paris) / Lambert zone II”,\n" +
@@ -221,7 +308,7 @@ public final strictfp class DefaultProje
* @throws FactoryException if the CRS creation failed.
*/
@Test
- @DependsOnMethod("testWKT2")
+ @DependsOnMethod("testWKT2_Simplified")
public void testWKT2_ForEquirectangular() throws FactoryException {
final ProjectedCRS crs = new GeodeticObjectBuilder()
.setConversionMethod("Equirectangular")
@@ -241,8 +328,8 @@ public final strictfp class DefaultProje
" Method[“Equidistant Cylindrical (Spherical)”],\n" +
" Parameter[“Latitude of 1st standard parallel”, 0.0],\n" +
" Parameter[“Longitude of natural origin”, 0.0],\n" +
- " Parameter[“False easting”, 1000.0, Unit[“metre”, 1]],\n" +
- " Parameter[“False northing”, 2000.0, Unit[“metre”, 1]]],\n" +
+ " Parameter[“False easting”, 1000.0],\n" +
+ " Parameter[“False northing”, 2000.0]],\n" +
" CS[“Cartesian”, 2],\n" +
" Axis[“Easting (E)”, east],\n" +
" Axis[“Northing (N)”, north],\n" +