This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 19abdd5 Improve the support of coordinate display when the image
shown on screen does not have an affine transform to a known geospatial CRS.
This is the case of netCDF files when the coordinates of each pixel is
specified in data arrays (localization grid). This work required the addition
of CommonCRS.Engineering enumeration with DISPLAY and GRID values, for making
easier to detect when we have such grid CRS. For now we use the
EngineeringDatum of CommonCRS.Engineering.GRID as [...]
19abdd5 is described below
commit 19abdd5a5caefeaf25dc212569ff664ab46118e3
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Mon May 4 01:44:32 2020 +0200
Improve the support of coordinate display when the image shown on screen
does not have an affine transform to a known geospatial CRS.
This is the case of netCDF files when the coordinates of each pixel is
specified in data arrays (localization grid).
This work required the addition of CommonCRS.Engineering enumeration with
DISPLAY and GRID values, for making easier to detect when we have such grid CRS.
For now we use the EngineeringDatum of CommonCRS.Engineering.GRID as a
sentinel value for identifying grid CRS.
---
.../org/apache/sis/gui/coverage/ImageRequest.java | 5 +-
.../java/org/apache/sis/gui/map/MapCanvas.java | 31 +--
.../org/apache/sis/gui/map/OperationFinder.java | 238 +++++++++++++++++++++
.../java/org/apache/sis/gui/map/StatusBar.java | 109 ++++++----
.../apache/sis/coverage/grid/GridExtentCRS.java | 11 +-
.../org/apache/sis/portrayal/PlanarCanvas.java | 25 +--
.../java/org/apache/sis/referencing/CommonCRS.java | 142 +++++++++++-
.../factory/CommonAuthorityFactory.java | 38 +---
.../java/org/apache/sis/internal/jdk9/JDK9.java | 13 ++
9 files changed, 483 insertions(+), 129 deletions(-)
diff --git
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageRequest.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageRequest.java
index 17df35b..3ee0826 100644
---
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageRequest.java
+++
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageRequest.java
@@ -218,8 +218,9 @@ public class ImageRequest {
/*
* By `GridCoverage.render(GridExtent)` contract, the `RenderedImage`
pixel coordinates are relative
* to the requested `GridExtent`. Consequently we need to translate
the image coordinates so that it
- * become the coordinates of the original `GridGeometry` before to
apply `gridToCRS`. It is okay to
- * modify `StatusBar.localToObjectiveCRS` because we do not associate
it to a `MapCanvas`.
+ * become the coordinates of the original `GridGeometry` before to
apply `gridToCRS`. It is okay to
+ * modify `StatusBar.localToObjectiveCRS` because we do not associate
it to a `MapCanvas`, so it will
+ * not be overwritten by gesture events (zoom, pan, etc).
*/
if (request != null) {
final double[] origin = new double[request.getDimension()];
diff --git
a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java
index db377f0..e3ed2b0 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java
@@ -43,7 +43,7 @@ import javafx.scene.transform.NonInvertibleTransformException;
import org.opengis.geometry.Envelope;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.operation.NoninvertibleTransformException;
+import org.opengis.referencing.operation.TransformException;
import org.apache.sis.referencing.operation.matrix.Matrices;
import org.apache.sis.referencing.operation.matrix.MatrixSIS;
import org.apache.sis.referencing.operation.transform.MathTransforms;
@@ -666,29 +666,30 @@ public abstract class MapCanvas extends PlanarCanvas {
*/
if (invalidObjectiveToDisplay) {
invalidObjectiveToDisplay = false;
- final LinearTransform tr;
- CoordinateReferenceSystem crs;
final Envelope2D target = getDisplayBounds();
+ final GridExtent extent = new GridExtent(null,
+ new long[] {Math.round(target.getMinX()),
Math.round(target.getMinY())},
+ new long[] {Math.round(target.getMaxX()),
Math.round(target.getMaxY())}, false);
+
+ final LinearTransform crsToDisplay;
+ CoordinateReferenceSystem crs;
if (objectiveBounds != null) {
- crs = objectiveBounds.getCoordinateReferenceSystem();
final MatrixSIS m =
Matrices.createTransform(objectiveBounds, target);
Matrices.forceUniformScale(m, 0, new double[]
{target.width / 2, target.height / 2});
- tr = MathTransforms.linear(m);
+ crsToDisplay = MathTransforms.linear(m);
+ crs = objectiveBounds.getCoordinateReferenceSystem();
+ if (crs == null) {
+ crs =
extent.toEnvelope(crsToDisplay.inverse()).getCoordinateReferenceSystem();
+ // CRS computed above should not be null.
+ }
} else {
- tr = MathTransforms.identity(BIDIMENSIONAL);
- crs = null;
- }
- if (crs == null) {
- // TODO: build an EngineeringCRS reflecting better the
data.
+ crsToDisplay = MathTransforms.identity(BIDIMENSIONAL);
crs = getDisplayCRS();
}
- final GridExtent extent = new GridExtent(null,
- new long[] {Math.round(target.getMinX()),
Math.round(target.getMinY())},
- new long[] {Math.round(target.getMaxX()),
Math.round(target.getMaxY())}, false);
- setGridGeometry(new GridGeometry(extent,
PixelInCell.CELL_CORNER, tr.inverse(), crs));
+ setGridGeometry(new GridGeometry(extent,
PixelInCell.CELL_CORNER, crsToDisplay.inverse(), crs));
transform.setToIdentity();
}
- } catch (NoninvertibleTransformException | RenderException ex) {
+ } catch (TransformException | RenderException ex) {
floatingPane.setCursor(Cursor.CROSSHAIR);
errorOccurred(ex);
return;
diff --git
a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/OperationFinder.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/OperationFinder.java
new file mode 100644
index 0000000..95e6552
--- /dev/null
+++
b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/OperationFinder.java
@@ -0,0 +1,238 @@
+/*
+ * 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.gui.map;
+
+import java.util.function.Predicate;
+import javafx.beans.property.ReadOnlyProperty;
+import javafx.concurrent.Task;
+import org.opengis.geometry.Envelope;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.crs.SingleCRS;
+import org.opengis.referencing.datum.PixelInCell;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.CoordinateOperation;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.gui.coverage.CoverageCanvas;
+import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.util.logging.Logging;
+import org.apache.sis.internal.system.Modules;
+
+
+/**
+ * Finds a coordinate operation between two CRS in the context of a {@link
MapCanvas}.
+ * The operation may depend on the region visible in the canvas and the
resolution.
+ * Computing the coordinate operation may be costly, and for this reason
should be
+ * done in a background thread.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+abstract class OperationFinder extends Task<MathTransform> {
+ /**
+ * The grid geometry of data, or {@code null} if none or unknown. This is
used for getting the operation between
+ * two CRSs when one of the source or target CRS is {@link
org.apache.sis.referencing.CommonCRS.Engineering#GRID}.
+ * Because the relationship from {@code GRID} CRS to a geospatial CRS is
unknown to {@code CRS.findOperation(…)},
+ * the operation can not be found without the help of this {@code
dataGeometry} field.
+ *
+ * Actually this information is rarely needed. It is needed only if there
is no known affine transform from grid
+ * coordinates to some geospatial coordinates that we can use as a
starting point (before to apply map projection
+ * to other CRSs if desired). For example some netCDF files provides the
coordinates of each pixel in data arrays.
+ * Those data arrays can be stored (indirectly) in this {@code
dataGeometry} object.
+ */
+ private final GridGeometry dataGeometry;
+
+ /**
+ * Region visible in the map canvas, or {@code null} if none. May be in
any CRS.
+ */
+ private final Envelope areaOfInterest;
+
+ /**
+ * The source and target CRS requested by the user. This is usually the
{@link #operation} source
+ * and target CRS, unless a CRS was a {@linkplain
#isGridCRS(CoordinateReferenceSystem) grid CRS}.
+ */
+ private final CoordinateReferenceSystem sourceCRS, targetCRS;
+
+ /**
+ * {@code true} if {@link #sourceCRS} or {@link #targetCRS} is a
+ * {@linkplain #isGridCRS(CoordinateReferenceSystem) grid CRS}.
+ */
+ private boolean sourceIsGrid, targetIsGrid;
+
+ /**
+ * The coordinate operation from {@link #sourceCRS} to {@link #targetCRS},
computed in background thread.
+ * The {@link CoordinateOperation#getMathTransform()} value may not be the
complete transform returned by
+ * {@link #getValue()} because the later may include transform from/to
{@linkplain #isGridCRS grid CRS}.
+ */
+ private CoordinateOperation operation;
+
+ /**
+ * Creates a new task for finding the coordinate operation between two CRS.
+ *
+ * @param canvas the canvas for which we are searching a
coordinate operation, or {@code null}.
+ * @param areaOfInterest region visible in the map canvas, or {@code
null}. May be in any CRS.
+ * @param sourceCRS source CRS of the transform to compute.
+ * @param targetCRS target CRS of the transform to compute.
+ */
+ protected OperationFinder(final MapCanvas canvas,
+ final Envelope areaOfInterest,
+ final CoordinateReferenceSystem sourceCRS,
+ final CoordinateReferenceSystem targetCRS)
+ {
+ this.dataGeometry = dataGeometry(canvas);
+ this.sourceCRS = sourceCRS;
+ this.targetCRS = targetCRS;
+ this.areaOfInterest = areaOfInterest;
+ }
+
+ /**
+ * Returns the <em>data</em> (not canvas) grid geometry, or {@code null}
if none.
+ */
+ private static GridGeometry dataGeometry(final MapCanvas canvas) {
+ if (canvas instanceof CoverageCanvas) {
+ final GridCoverage coverage = ((CoverageCanvas)
canvas).getCoverage();
+ if (coverage != null) {
+ return coverage.getGridGeometry();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Computes the transform from the source CRS to the target CRS specified
at construction time.
+ * This method is invoked in a background thread and does not need to be
invoked explicitly.
+ */
+ @Override
+ protected MathTransform call() throws Exception {
+ DefaultGeographicBoundingBox bbox = null;
+ if (areaOfInterest != null) try {
+ bbox = new DefaultGeographicBoundingBox();
+ bbox.setBounds(areaOfInterest);
+ } catch (TransformException e) {
+ bbox = null;
+
Logging.recoverableException(Logging.getLogger(Modules.APPLICATION),
getCallerClass(), getCallerMethod(), e);
+ }
+ MathTransform before = null;
+ MathTransform after = null;
+ CoordinateReferenceSystem source = sourceCRS;
+ CoordinateReferenceSystem target = targetCRS;
+ if (dataGeometry != null && dataGeometry.isDefined(GridGeometry.CRS |
GridGeometry.GRID_TO_CRS)) {
+ if (sourceIsGrid = isGridCRS(source)) {
+ before = dataGeometry.getGridToCRS(PixelInCell.CELL_CENTER);
+ source = dataGeometry.getCoordinateReferenceSystem();
+ }
+ if (targetIsGrid = isGridCRS(target)) {
+ after =
dataGeometry.getGridToCRS(PixelInCell.CELL_CENTER).inverse();
+ target = dataGeometry.getCoordinateReferenceSystem();
+ }
+ }
+ operation = CRS.findOperation(source, target, bbox);
+ MathTransform transform = operation.getMathTransform();
+ if (before != null) transform = MathTransforms.concatenate(before,
transform);
+ if (after != null) transform = MathTransforms.concatenate(transform,
after);
+ return transform;
+ }
+
+ /**
+ * If the given CRS is a grid CRS, replaces it by a geospatial CRS if
possible.
+ */
+ static CoordinateReferenceSystem toGeospatial(CoordinateReferenceSystem
crs, final ReadOnlyProperty<MapCanvas> canvas) {
+ if (isGridCRS(crs)) {
+ final GridGeometry dataGeometry = dataGeometry(canvas.getValue());
+ if (dataGeometry != null &&
dataGeometry.isDefined(GridGeometry.CRS)) {
+ return dataGeometry.getCoordinateReferenceSystem();
+ }
+ }
+ return crs;
+ }
+
+ /**
+ * Returns {@code true} if the given coordinate reference system is the
CRS of the grid.
+ * We use the {@link
org.apache.sis.referencing.CommonCRS.Engineering#GRID} datum as a signature.
+ */
+ private static boolean isGridCRS(final CoordinateReferenceSystem crs) {
+ return (crs instanceof SingleCRS) &&
CommonCRS.Engineering.GRID.datum().equals(((SingleCRS) crs).getDatum());
+ }
+
+ /**
+ * Returns the coordinate operation computed by the {@link #call()}
method. The associated transform can be
+ * obtained by {@link #getValue()}. The {@link
CoordinateOperation#getMathTransform()} method should not be
+ * used because it may be incomplete if the source or target CRS was a
grid CRS.
+ */
+ public final CoordinateOperation getOperation() {
+ return operation;
+ }
+
+ /**
+ * Returns the target CRS, giving precedence to {@link
CoordinateOperation#getTargetCRS()} is suitable.
+ * That precedence is because the {@link CoordinateOperation} may provide
a more complete CRS from EPSG
+ * database.
+ */
+ public final CoordinateReferenceSystem getTargetCRS() {
+ if (!targetIsGrid) {
+ final CoordinateReferenceSystem crs = operation.getTargetCRS();
+ if (crs != null) return crs;
+ }
+ return targetCRS;
+ }
+
+ /**
+ * Returns a predicate for determining if {@link OperationFinder} task
need to be executed again.
+ * If there is no need to perform such check, returns {@code null}.
+ *
+ * <p><b>Note:</b> actually recomputing everything is a bit overly
aggresive. We could keep the
+ * {@link CoordinateOperation} found by {@link #call()} and just update
the {@link MathTransform}
+ * before or after the operation. But the use of a grid CRS should be rare
enough that it is not
+ * worth to do this optimization.</p>
+ *
+ * @see StatusBar#fullOperationSearchRequired
+ */
+ final Predicate<MapCanvas> fullOperationSearchRequired() {
+ return (sourceIsGrid | targetIsGrid) ? new UpdateCheck(dataGeometry) :
null;
+ }
+
+ /**
+ * The predicate for determining if {@link OperationFinder} task needs to
be executed again.
+ */
+ private static final class UpdateCheck implements Predicate<MapCanvas> {
+ private final GridGeometry dataGeometry;
+
+ UpdateCheck(final GridGeometry dataGeometry) {
+ this.dataGeometry = dataGeometry;
+ }
+
+ @Override public boolean test(final MapCanvas canvas) {
+ return !dataGeometry.equals(dataGeometry(canvas));
+ }
+ }
+
+ /**
+ * Returns the class to report as the caller in case of non-fatal error.
This is used for logging.
+ */
+ protected abstract Class<?> getCallerClass();
+
+ /**
+ * Returns the method to report as the caller in case of non-fatal error.
This is used for logging.
+ */
+ protected abstract String getCallerMethod();
+}
diff --git
a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
index 75e5d65..afc538a 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
@@ -18,6 +18,7 @@ package org.apache.sis.gui.map;
import java.util.Locale;
import java.util.Optional;
+import java.util.function.Predicate;
import javax.measure.Unit;
import javafx.geometry.Pos;
import javafx.geometry.Insets;
@@ -43,11 +44,9 @@ import javafx.beans.property.ReadOnlyObjectPropertyBase;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener;
-import javafx.concurrent.Task;
import javax.measure.quantity.Length;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.util.FactoryException;
import org.opengis.referencing.ReferenceSystem;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.cs.CoordinateSystem;
@@ -58,7 +57,6 @@ import org.opengis.referencing.operation.TransformException;
import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.apache.sis.referencing.operation.transform.MathTransforms;
-import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
import org.apache.sis.geometry.GeneralDirectPosition;
import org.apache.sis.geometry.CoordinateFormat;
import org.apache.sis.coverage.grid.GridGeometry;
@@ -72,7 +70,6 @@ import org.apache.sis.util.Utilities;
import org.apache.sis.util.Exceptions;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.ComparisonMode;
-import org.apache.sis.util.logging.Logging;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.gui.Widget;
import org.apache.sis.gui.referencing.RecentReferenceSystems;
@@ -81,7 +78,6 @@ import org.apache.sis.internal.gui.BackgroundThreads;
import org.apache.sis.internal.gui.ExceptionReporter;
import org.apache.sis.internal.gui.Resources;
import org.apache.sis.internal.gui.Styles;
-import org.apache.sis.internal.system.Modules;
import org.apache.sis.referencing.CRS;
import org.apache.sis.referencing.IdentifiedObjects;
@@ -194,7 +190,7 @@ public class StatusBar extends Widget implements
EventHandler<MouseEvent> {
*
* @see #updateLocalToPositionCRS()
*/
- private CoordinateOperation objectiveToPositionCRS;
+ private MathTransform objectiveToPositionCRS;
/**
* Conversion from local coordinates to geographic or projected
coordinates of rendered data.
@@ -247,10 +243,22 @@ public class StatusBar extends Widget implements
EventHandler<MouseEvent> {
*
* <p>The target CRS can be obtained by {@link
CoordinateOperation#getTargetCRS()} on
* {@link #objectiveToPositionCRS} or by {@link
CoordinateFormat#getDefaultCRS()}.</p>
+ *
+ * @see #updateLocalToPositionCRS()
*/
private MathTransform localToPositionCRS;
/**
+ * If non-null, determines if {@link #apply(GridGeometry)} needs to update
{@link #localToPositionCRS} with a
+ * potentially costly search for coordinate operation even in context
where it would normally not be required.
+ * An explanation of the context when it may happen is given in {@link
OperationFinder#dataGeometry}.
+ * This is rarely needed for most data (i.e. this field is almost always
{@code null}).
+ *
+ * @see OperationFinder#fullOperationSearchRequired()
+ */
+ private Predicate<MapCanvas> fullOperationSearchRequired;
+
+ /**
* The source local indices before conversion to geospatial coordinates.
* The number of dimensions is often {@value #BIDIMENSIONAL}.
* Shall never be {@code null}.
@@ -434,11 +442,18 @@ public class StatusBar extends Widget implements
EventHandler<MouseEvent> {
}
position.setText(null);
registerMouseListeners(value);
- try {
- apply(value != null ? value.getGridGeometry() : null);
+ /*
+ * Configure this status bar for showing coordinates in the CRS and
with the resolution given by
+ * the canvas grid geometry. This is the same operation than the one
executed every time that a
+ * new rendering occurred.
+ */
+ GridGeometry geometry = null;
+ if (value != null) try {
+ geometry = value.getGridGeometry();
} catch (RenderException e) {
- setErrorMessage(null, e);
+ setRenderingError(e);
}
+ applyCanvasGeometry(geometry);
}
/**
@@ -596,10 +611,14 @@ public class StatusBar extends Widget implements
EventHandler<MouseEvent> {
if (sameCRS) {
updateLocalToPositionCRS();
// Keep the format CRS unchanged since we made
`localToPositionCRS` consistent with its value.
+ if (fullOperationSearchRequired != null &&
fullOperationSearchRequired.test(canvas.get())) {
+ setPositionCRS(format.getDefaultCRS());
+ }
} else {
objectiveToPositionCRS = null;
setFormatCRS(crs, null); // Should
be invoked before to set precision.
- crs = setReplaceablePositionCRS(crs); // May
invoke later setFormatCRS(…) again.
+ crs = OperationFinder.toGeospatial(crs, canvas);
+ crs = setReplaceablePositionCRS(crs); // May
invoke setFormatCRS(…) after background work.
}
format.setGroundPrecision(Quantities.create(resolution, unit));
/*
@@ -627,8 +646,7 @@ public class StatusBar extends Widget implements
EventHandler<MouseEvent> {
*/
private void updateLocalToPositionCRS() {
if (objectiveToPositionCRS != null) {
- localToPositionCRS = MathTransforms.concatenate(
- localToObjectiveCRS.get(),
objectiveToPositionCRS.getMathTransform());
+ localToPositionCRS =
MathTransforms.concatenate(localToObjectiveCRS.get(), objectiveToPositionCRS);
}
}
@@ -672,34 +690,43 @@ public class StatusBar extends Widget implements
EventHandler<MouseEvent> {
private void setPositionCRS(final CoordinateReferenceSystem crs) {
if (crs != null && objectiveCRS != null && objectiveCRS != crs) {
position.setTextFill(Styles.OUTDATED_TEXT);
+ /*
+ * Take snapshots of references to all objects that the background
thread will use.
+ * The background thread shall not read StatusBar fields directly
since they may be
+ * in the middle of changes at any time. All objects are assumed
immutable.
+ */
final Envelope aoi = (systemChooser != null) ?
systemChooser.areaOfInterest.get() : null;
- BackgroundThreads.execute(new Task<CoordinateOperation>() {
+ BackgroundThreads.execute(new OperationFinder(canvas.get(), aoi,
objectiveCRS, crs) {
+ /**
+ * The accuracy to show on the status bar, or {@code null} if
none.
+ * This is computed after {@link CoordinateOperation} has been
determined.
+ */
+ private Length accuracy;
+
/**
* Invoked in a background thread for fetching transformation
to target CRS.
- * The potentially costly part is {@code CRS.findOperation(…)}.
+ * The potentially costly part is {@code CRS.findOperation(…)}
in super.call().
*/
- @Override protected CoordinateOperation call() throws
FactoryException {
- DefaultGeographicBoundingBox bbox = null;
- if (aoi != null) try {
- bbox = new DefaultGeographicBoundingBox();
- bbox.setBounds(aoi);
- } catch (TransformException e) {
- bbox = null;
-
Logging.recoverableException(Logging.getLogger(Modules.APPLICATION),
- StatusBar.class, "setPositionCRS", e);
+ @Override protected MathTransform call() throws Exception {
+ final MathTransform value = super.call();
+ double a = CRS.getLinearAccuracy(getOperation());
+ if (a > 0) {
+ final Unit<Length> unit;
+ if (a < 1) unit = Units.CENTIMETRE;
+ else if (a < 1000) unit = Units.METRE;
+ else unit = Units.KILOMETRE;
+ a =
Units.METRE.getConverterTo(unit).convert(Math.max(a,
Formulas.LINEAR_TOLERANCE));
+ accuracy = Quantities.create(a, unit);
}
- return CRS.findOperation(objectiveCRS, crs, bbox);
+ return value;
}
-
/**
* Invoked in JavaFX thread on success. The {@link
StatusBar#localToPositionCRS} transform
* is set to the transform that we computed in background and
the {@link CoordinateFormat}
* is configured with auxiliary information such as positional
accuracy.
*/
@Override protected void succeeded() {
- final CoordinateOperation operation = getValue();
- final CoordinateReferenceSystem targetCRS =
operation.getTargetCRS();
- setPositionCRS(targetCRS != null ? targetCRS : crs,
operation);
+ setPositionCRS(this, accuracy);
}
/**
@@ -713,6 +740,10 @@ public class StatusBar extends Widget implements
EventHandler<MouseEvent> {
selectedSystem.set(format.getDefaultCRS());
resetPositionCRS(Styles.ERROR_TEXT);
}
+
+ /** For logging purpose if a non-fatal error occurs. */
+ @Override protected Class<?> getCallerClass() {return
StatusBar.class;}
+ @Override protected String getCallerMethod() {return
"setPositionCRS";}
});
} else {
/*
@@ -739,27 +770,17 @@ public class StatusBar extends Widget implements
EventHandler<MouseEvent> {
* and {@link #lastY} are still valid. This assumption should be correct
when
* only the format CRS has been updated and not {@link
#localToObjectiveCRS}.
*
- * @param crs the new CRS. Should not be {@code null}.
- * @param operation the new value to assign to {@link
#objectiveToPositionCRS}
+ * @param finder the completed task with the new {@link
#objectiveToPositionCRS}.
+ * @param accuracy the accuracy to show on the status bar, or {@code
null} if none.
*/
- private void setPositionCRS(final CoordinateReferenceSystem crs, final
CoordinateOperation operation) {
+ private void setPositionCRS(final OperationFinder finder, final Length
accuracy) {
setErrorMessage(null, null);
- Length accuracy = null;
- double a = CRS.getLinearAccuracy(operation);
- if (a > 0) {
- final Unit<Length> unit;
- if (a < 1) unit = Units.CENTIMETRE;
- else if (a < 1000) unit = Units.METRE;
- else unit = Units.KILOMETRE;
- a = Units.METRE.getConverterTo(unit).convert(Math.max(a,
Formulas.LINEAR_TOLERANCE));
- accuracy = Quantities.create(a, unit);
- }
- setFormatCRS(crs, accuracy);
- objectiveToPositionCRS = operation;
+ setFormatCRS(finder.getTargetCRS(), accuracy);
+ objectiveToPositionCRS = finder.getValue();
+ fullOperationSearchRequired = finder.fullOperationSearchRequired();
updateLocalToPositionCRS();
position.setTextFill(Styles.NORMAL_TEXT);
position.setMinWidth(0);
- setErrorMessage(null, null);
if (isPositionVisible()) {
final double x = lastX;
final double y = lastY;
diff --git
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtentCRS.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtentCRS.java
index 597e7b8..dbd5b26 100644
---
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtentCRS.java
+++
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtentCRS.java
@@ -28,15 +28,14 @@ import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.crs.CRSFactory;
import org.opengis.referencing.crs.EngineeringCRS;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.datum.EngineeringDatum;
import org.opengis.referencing.operation.Matrix;
import org.apache.sis.referencing.cs.AbstractCS;
+import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.internal.system.DefaultFactories;
import org.apache.sis.internal.referencing.AxisDirections;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.util.iso.Types;
import org.apache.sis.measure.Units;
-import org.apache.sis.referencing.datum.DefaultEngineeringDatum;
import org.apache.sis.util.Characters;
@@ -53,11 +52,6 @@ import org.apache.sis.util.Characters;
*/
final class GridExtentCRS {
/**
- * The datum for grid.
- */
- private static final EngineeringDatum DATUM = new
DefaultEngineeringDatum(properties("Grid"));
-
- /**
* Do not allow instantiation of this class.
*/
private GridExtentCRS() {
@@ -191,6 +185,7 @@ final class GridExtentCRS {
case 3: cs = csFactory.createAffineCS(properties, axes[0],
axes[1], axes[2]); break;
default: cs = new AbstractCS(properties, axes); break;
}
- return
DefaultFactories.forBuildin(CRSFactory.class).createEngineeringCRS(properties(cs.getName()),
DATUM, cs);
+ return
DefaultFactories.forBuildin(CRSFactory.class).createEngineeringCRS(
+ properties(cs.getName()), CommonCRS.Engineering.GRID.datum(),
cs);
}
}
diff --git
a/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/PlanarCanvas.java
b/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/PlanarCanvas.java
index ca558e4..ffac6e0 100644
---
a/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/PlanarCanvas.java
+++
b/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/PlanarCanvas.java
@@ -16,27 +16,19 @@
*/
package org.apache.sis.portrayal;
-import java.util.Map;
import java.util.Locale;
import java.awt.geom.AffineTransform;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.DirectPosition;
-import org.opengis.referencing.cs.AxisDirection;
import org.opengis.metadata.spatial.DimensionNameType;
import org.apache.sis.measure.Units;
import org.apache.sis.geometry.Envelope2D;
import org.apache.sis.geometry.DirectPosition2D;
-import org.apache.sis.referencing.cs.DefaultCartesianCS;
-import org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis;
-import org.apache.sis.referencing.datum.DefaultEngineeringDatum;
-import org.apache.sis.referencing.crs.DefaultEngineeringCRS;
+import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.referencing.operation.matrix.AffineTransforms2D;
import org.apache.sis.referencing.operation.transform.LinearTransform;
import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
-import static java.util.Collections.singletonMap;
-import static org.opengis.referencing.IdentifiedObject.NAME_KEY;
-
/**
* A canvas for two-dimensional display device using a Cartesian coordinate
system.
@@ -60,19 +52,6 @@ public abstract class PlanarCanvas extends Canvas {
protected static final int BIDIMENSIONAL = 2;
/**
- * The display Coordinate Reference System used by all {@code
PlanarCanvas} instances.
- */
- private static final DefaultEngineeringCRS DISPLAY_CRS;
- static {
- Map<String,?> property = singletonMap(NAME_KEY, "Display on
two-dimensional Cartesian coordinate system");
- DefaultCartesianCS cs = new DefaultCartesianCS(property,
- new DefaultCoordinateSystemAxis(singletonMap(NAME_KEY,
"Column"), "x", AxisDirection.DISPLAY_RIGHT, Units.PIXEL),
- new DefaultCoordinateSystemAxis(singletonMap(NAME_KEY, "Row"),
"y", AxisDirection.DISPLAY_DOWN, Units.PIXEL));
- property = singletonMap(NAME_KEY, cs.getName()); // Reuse the
same Identifier instance.
- DISPLAY_CRS = new DefaultEngineeringCRS(property, new
DefaultEngineeringDatum(property), cs);
- }
-
- /**
* The conversion from {@linkplain #getObjectiveCRS() objective CRS} to
the display coordinate system as a
* Java2D affine transform. This transform will be modified in-place when
user applies zoom, translation or
* rotation on the view area. Subclasses should generally not modify this
affine transform directly; invoke
@@ -90,7 +69,7 @@ public abstract class PlanarCanvas extends Canvas {
* @param locale the locale to use for labels and some messages, or
{@code null} for default.
*/
protected PlanarCanvas(final Locale locale) {
- super(DISPLAY_CRS, locale);
+ super(CommonCRS.Engineering.DISPLAY.crs(), locale);
objectiveToDisplay = new AffineTransform();
}
diff --git
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java
index a8df281..f779f13 100644
---
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java
+++
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java
@@ -35,6 +35,7 @@ import org.opengis.referencing.crs.TemporalCRS;
import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.referencing.crs.GeocentricCRS;
import org.opengis.referencing.crs.ProjectedCRS;
+import org.opengis.referencing.crs.EngineeringCRS;
import org.opengis.referencing.crs.SingleCRS;
import org.opengis.referencing.cs.TimeCS;
import org.opengis.referencing.cs.VerticalCS;
@@ -49,16 +50,20 @@ import org.opengis.referencing.datum.PrimeMeridian;
import org.opengis.referencing.datum.VerticalDatum;
import org.opengis.referencing.datum.VerticalDatumType;
import org.opengis.referencing.datum.TemporalDatum;
+import org.opengis.referencing.datum.EngineeringDatum;
import org.apache.sis.referencing.datum.DefaultVerticalDatum;
import org.apache.sis.referencing.datum.DefaultTemporalDatum;
+import org.apache.sis.referencing.datum.DefaultEngineeringDatum;
import org.apache.sis.referencing.cs.AxesConvention;
import org.apache.sis.referencing.cs.DefaultTimeCS;
import org.apache.sis.referencing.cs.DefaultVerticalCS;
+import org.apache.sis.referencing.cs.DefaultCartesianCS;
import org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis;
import org.apache.sis.referencing.crs.DefaultTemporalCRS;
import org.apache.sis.referencing.crs.DefaultVerticalCRS;
import org.apache.sis.referencing.crs.DefaultGeographicCRS;
import org.apache.sis.referencing.crs.DefaultGeocentricCRS;
+import org.apache.sis.referencing.crs.DefaultEngineeringCRS;
import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
import org.apache.sis.referencing.factory.UnavailableFactoryException;
import org.apache.sis.metadata.iso.citation.Citations;
@@ -69,6 +74,7 @@ import org.apache.sis.internal.system.SystemListener;
import org.apache.sis.internal.system.Modules;
import org.apache.sis.internal.system.Loggers;
import org.apache.sis.internal.util.Constants;
+import org.apache.sis.internal.jdk9.JDK9;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.logging.Logging;
@@ -125,7 +131,7 @@ import static
org.apache.sis.internal.util.StandardDateFormat.MILLISECONDS_PER_D
* </table></blockquote>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
*
* @see org.apache.sis.referencing.factory.CommonAuthorityFactory
*
@@ -1773,6 +1779,140 @@ public enum CommonCRS {
}
}
+
+
+
+ /**
+ * Frequently-used engineering CRS and datum that are guaranteed to be
available in SIS.
+ * Below is an alphabetical list of object names available in this
enumeration:
+ *
+ * <blockquote><table class="sis">
+ * <caption>Temporal objects accessible by enumeration
constants</caption>
+ * <tr><th>Name or alias</th> <th>Object type</th> <th>Enumeration
value</th></tr>
+ * <tr><td>Computer display</td> <td>CRS, Datum</td> <td>{@link
#GEODISPLAY}</td></tr>
+ * <tr><td>Computer display</td> <td>CRS, Datum</td> <td>{@link
#DISPLAY}</td></tr>
+ * </table></blockquote>
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+ public enum Engineering {
+ /**
+ * Cartesian coordinate system with (east, south) oriented axes in
pixel units.
+ * Axis directions are {@link AxisDirection#EAST EAST} and {@link
AxisDirection#SOUTH SOUTH},
+ * which implies that the coordinate system can be related to a
geospatial system in some way.
+ * The CRS is defined by the <cite>OGC Web Map Service
Interface</cite> specification.
+ *
+ * <blockquote><table class="compact">
+ * <caption>Computer display properties</caption>
+ * <tr><th>WMS identifier:</th> <td>CRS:1</td></tr>
+ * <tr><th>Primary names:</th> <td>"Computer display"</td></tr>
+ * <tr><th>Direction:</th>
+ * <td>{@link AxisDirection#EAST},
+ * {@link AxisDirection#SOUTH SOUTH}</td></tr>
+ * <tr><th>Unit:</th> <td>{@link Units#PIXEL}</td></tr>
+ * </table></blockquote>
+ */
+ GEODISPLAY(new DefaultEngineeringDatum(JDK9.mapOf(
+ EngineeringDatum.NAME_KEY, "Computer display",
+ EngineeringDatum.ANCHOR_POINT_KEY, "Origin is in upper
left."))),
+
+ /**
+ * Cartesian coordinate system with (right, down) oriented axes in
pixel units.
+ * This definition does not require the data to be geospatial.
+ *
+ * <blockquote><table class="compact">
+ * <caption>Computer display properties</caption>
+ * <tr><th>Primary names:</th> <td>"Computer display"</td></tr>
+ * <tr><th>Direction:</th>
+ * <td>{@link AxisDirection#DISPLAY_RIGHT},
+ * {@link AxisDirection#DISPLAY_DOWN DISPLAY_DOWN}</td></tr>
+ * <tr><th>Unit:</th> <td>{@link Units#PIXEL}</td></tr>
+ * </table></blockquote>
+ */
+ DISPLAY(GEODISPLAY.datum),
+
+ /**
+ * Cartesian coordinate system with (column, row) oriented axes in
unity units.
+ * This definition does not require the data to be geospatial.
+ *
+ * <blockquote><table class="compact">
+ * <caption>Grid properties</caption>
+ * <tr><th>Primary names:</th> <td>"Cell indices"</td></tr>
+ * <tr><th>Direction:</th>
+ * <td>{@link AxisDirection#COLUMN_POSITIVE},
+ * {@link AxisDirection#ROW_POSITIVE ROW_POSITIVE}</td></tr>
+ * <tr><th>Unit:</th> <td>{@link Units#UNITY}</td></tr>
+ * </table></blockquote>
+ */
+ GRID(new
DefaultEngineeringDatum(singletonMap(EngineeringDatum.NAME_KEY, "Cell
indices")));
+
+ /**
+ * The datum.
+ */
+ private final EngineeringDatum datum;
+
+ /**
+ * The CRS, built when first needed.
+ */
+ private EngineeringCRS crs;
+
+ /**
+ * Creates a new enumeration value with the specified datum.
+ */
+ private Engineering(final EngineeringDatum datum) {
+ this.datum = datum;
+ }
+
+ /**
+ * Returns the coordinate reference system associated to this
engineering object.
+ *
+ * @return the CRS associated to this enum.
+ */
+ public synchronized EngineeringCRS crs() {
+ if (crs == null) {
+ final String x, y;
+ final AxisDirection dx, dy;
+ final Map<String,Object> cs =
singletonMap(CartesianCS.NAME_KEY, datum.getName());
+ final Map<String,Object> properties = new HashMap<>(cs);
+ switch (this) {
+ case GEODISPLAY: {
+ x = "i"; dx = AxisDirection.EAST;
+ y = "j"; dy = AxisDirection.SOUTH;
+ properties.put(EngineeringCRS.NAME_KEY, new
NamedIdentifier(Citations.WMS, "1"));
+ break;
+ }
+ case DISPLAY: {
+ x = "x"; dx = AxisDirection.DISPLAY_RIGHT;
+ y = "y"; dy = AxisDirection.DISPLAY_DOWN;
+ break;
+ }
+ case GRID: {
+ x = "i"; dx = AxisDirection.COLUMN_POSITIVE;
+ y = "j"; dy = AxisDirection.ROW_POSITIVE;
+ break;
+ }
+ default: throw new AssertionError(this);
+ }
+ crs = new DefaultEngineeringCRS(properties, datum, new
DefaultCartesianCS(cs,
+ new
DefaultCoordinateSystemAxis(singletonMap(CartesianCS.NAME_KEY, x), x, dx,
Units.PIXEL),
+ new
DefaultCoordinateSystemAxis(singletonMap(CartesianCS.NAME_KEY, y), y, dy,
Units.PIXEL)));
+ }
+ return crs;
+ }
+
+ /**
+ * Returns the datum associated to this engineering object.
+ *
+ * @return the datum associated to this enum.
+ */
+ public EngineeringDatum datum() {
+ return datum;
+ }
+ }
+
/**
* Puts the name for the given key in a map of properties to be given to
object constructors.
*
diff --git
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityFactory.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityFactory.java
index bb9a9c3..1c7cbe7 100644
---
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityFactory.java
+++
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityFactory.java
@@ -18,7 +18,6 @@ package org.apache.sis.referencing.factory;
import java.util.Map;
import java.util.Set;
-import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Collections;
@@ -31,23 +30,17 @@ import org.opengis.metadata.citation.Citation;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.crs.CRSFactory;
import org.opengis.referencing.crs.CRSAuthorityFactory;
import org.opengis.referencing.crs.EngineeringCRS;
import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.referencing.crs.ProjectedCRS;
import org.opengis.referencing.crs.VerticalCRS;
import org.opengis.referencing.crs.SingleCRS;
-import org.opengis.referencing.cs.CSFactory;
import org.opengis.referencing.cs.CartesianCS;
-import org.opengis.referencing.cs.AxisDirection;
-import org.opengis.referencing.datum.DatumFactory;
-import org.opengis.referencing.datum.EngineeringDatum;
import org.apache.sis.internal.referencing.provider.TransverseMercator.Zoner;
import org.apache.sis.internal.referencing.GeodeticObjectBuilder;
import org.apache.sis.internal.referencing.Resources;
import org.apache.sis.metadata.iso.citation.Citations;
-import org.apache.sis.internal.system.DefaultFactories;
import org.apache.sis.internal.system.Loggers;
import org.apache.sis.internal.util.Constants;
import org.apache.sis.measure.Units;
@@ -189,7 +182,7 @@ import org.apache.sis.util.iso.SimpleInternationalString;
* switching to polar stereographic projections for high latitudes.</p>
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 0.8
+ * @version 1.1
*
* @see CommonCRS
*
@@ -248,13 +241,6 @@ public class CommonAuthorityFactory extends
GeodeticAuthorityFactory implements
private final Map<String,Class<?>> codes;
/**
- * The "Computer display" reference system (CRS:1). Created when first
needed.
- *
- * @see #displayCRS()
- */
- private CoordinateReferenceSystem displayCRS;
-
- /**
* The coordinate system for map projection in metres, created when first
needed.
*/
private volatile CartesianCS projectedCS;
@@ -553,7 +539,7 @@ public class CommonAuthorityFactory extends
GeodeticAuthorityFactory implements
}
final CommonCRS crs;
switch (codeValue) {
- case Constants.CRS1: return displayCRS();
+ case Constants.CRS1: return
CommonCRS.Engineering.GEODISPLAY.crs();
case Constants.CRS84: crs = CommonCRS.WGS84; break;
case Constants.CRS83: crs = CommonCRS.NAD83; break;
case Constants.CRS27: crs = CommonCRS.NAD27; break;
@@ -690,26 +676,6 @@ public class CommonAuthorityFactory extends
GeodeticAuthorityFactory implements
}
/**
- * Returns the "Computer display" reference system (CRS:1). This is rarely
used.
- */
- private synchronized CoordinateReferenceSystem displayCRS() throws
FactoryException {
- if (displayCRS == null) {
- final CSFactory csFactory =
DefaultFactories.forBuildin(CSFactory.class);
- final CartesianCS cs = csFactory.createCartesianCS(
- Collections.singletonMap(CartesianCS.NAME_KEY, "Computer
display"),
-
csFactory.createCoordinateSystemAxis(Collections.singletonMap(CartesianCS.NAME_KEY,
"i"), "i", AxisDirection.EAST, Units.PIXEL),
-
csFactory.createCoordinateSystemAxis(Collections.singletonMap(CartesianCS.NAME_KEY,
"j"), "j", AxisDirection.SOUTH, Units.PIXEL));
-
- final Map<String,Object> properties = new HashMap<>(4);
- properties.put(EngineeringDatum.NAME_KEY, cs.getName());
- properties.put(EngineeringDatum.ANCHOR_POINT_KEY, "Origin is in
upper left.");
- displayCRS =
DefaultFactories.forBuildin(CRSFactory.class).createEngineeringCRS(properties,
-
DefaultFactories.forBuildin(DatumFactory.class).createEngineeringDatum(properties),
cs);
- }
- return displayCRS;
- }
-
- /**
* Creates an exception for an unknown authority code.
*
* @param localCode the unknown authority code, without namespace.
diff --git
a/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/JDK9.java
b/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/JDK9.java
index 1e3c7bb..cc2d3ed 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/JDK9.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/JDK9.java
@@ -27,7 +27,9 @@ import java.util.Arrays;
import java.util.Set;
import java.util.List;
import java.util.Collections;
+import java.util.HashMap;
import java.util.LinkedHashSet;
+import java.util.Map;
import org.apache.sis.internal.util.UnmodifiableArrayList;
@@ -80,6 +82,17 @@ public final class JDK9 {
}
/**
+ * Placeholder for {@code Map.of(...)}.
+ */
+ public static <K,V> Map<K,V> mapOf(final Object... entries) {
+ final Map map = new HashMap();
+ for (int i=0; i<entries.length;) {
+ map.put(entries[i++], entries[i++]);
+ }
+ return map;
+ }
+
+ /**
* Place holder for {@code Buffer.slice()}.
*
* @param b the buffer to slice.