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 2020/06/29 17:31:21 UTC
[sis] 05/06: Provide Azimuthal Equidistant projections centered on
arbitrary location.
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
commit 899f09b9202b575ae3f39462f85e04bc33b4e713
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Mon Jun 29 19:03:45 2020 +0200
Provide Azimuthal Equidistant projections centered on arbitrary location.
---
.../java/org/apache/sis/gui/coverage/Controls.java | 10 +-
.../apache/sis/gui/coverage/CoverageCanvas.java | 114 ++++++++++--
.../apache/sis/gui/coverage/CoverageControls.java | 58 +++----
.../org/apache/sis/gui/referencing/MenuSync.java | 20 ++-
.../gui/referencing/PositionableProjection.java | 191 +++++++++++++++++++++
.../org/apache/sis/internal/gui/Resources.java | 10 ++
.../apache/sis/internal/gui/Resources.properties | 2 +
.../sis/internal/gui/Resources_fr.properties | 2 +
.../java/org/apache/sis/internal/gui/Styles.java | 2 +-
.../referencing/GeodeticObjectBuilder.java | 9 +-
10 files changed, 363 insertions(+), 55 deletions(-)
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/Controls.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/Controls.java
index e8999d6..5975ecd 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/Controls.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/Controls.java
@@ -23,6 +23,7 @@ import javafx.scene.control.Label;
import javafx.scene.layout.Region;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
+import org.apache.sis.internal.gui.Styles;
import org.apache.sis.coverage.grid.GridCoverage;
import org.apache.sis.util.resources.IndexedResourceBundle;
@@ -48,11 +49,18 @@ abstract class Controls {
private static final Insets NEXT_CAPTION_MARGIN = new Insets(30, 0, 6, 0);
/**
- * Margin for adding an indentation to a node.
+ * Margin for adding an indentation to a node when the node is inside a group
+ * created by {@link Styles#createControlGrid(int, Label...)}.
*/
static final Insets INDENT = new Insets(0, 0, 0, 15);
/**
+ * Margin for adding an indentation to a node when the node is outside a group
+ * created by {@link Styles#createControlGrid(int, Label...)}.
+ */
+ static final Insets INDENT_OUTSIDE = new Insets(0, 0, 0, 15 + Styles.FORM_INSETS.getLeft());
+
+ /**
* The toolbar button for selecting this view.
* This is initialized after construction.
*/
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
index 9f21407..50316e3 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
@@ -24,13 +24,17 @@ import java.awt.Rectangle;
import java.awt.image.RenderedImage;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
import javafx.scene.paint.Color;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.control.Menu;
+import javafx.scene.control.RadioMenuItem;
import javafx.scene.control.ContextMenu;
+import javafx.scene.control.ToggleGroup;
import javafx.scene.input.MouseEvent;
import javafx.event.EventHandler;
import javafx.beans.DefaultProperty;
@@ -65,6 +69,7 @@ import org.apache.sis.image.Interpolation;
import org.apache.sis.gui.map.MapCanvas;
import org.apache.sis.gui.map.MapCanvasAWT;
import org.apache.sis.gui.map.StatusBar;
+import org.apache.sis.gui.referencing.PositionableProjection;
import org.apache.sis.gui.referencing.RecentReferenceSystems;
import org.apache.sis.internal.gui.GUIUtilities;
import org.apache.sis.internal.gui.Resources;
@@ -177,34 +182,76 @@ public class CoverageCanvas extends MapCanvasAWT {
/**
* Creates and register a contextual menu.
+ *
+ * @todo Consider moving to {@link org.apache.sis.gui.map.MapCanvas}.
*/
final ObjectProperty<ReferenceSystem> createContextMenu(final RecentReferenceSystems referenceSystems) {
+ final Resources resources = Resources.forLocale(getLocale());
final MenuHandler handler = new MenuHandler();
+ final Menu systemChoices = referenceSystems.createMenuItems(handler);
+ final Menu localSystems = new Menu(resources.getString(Resources.Keys.CenteredProjection));
+ for (final PositionableProjection projection : PositionableProjection.values()) {
+ final RadioMenuItem item = new RadioMenuItem(projection.toString());
+ item.setToggleGroup(handler.positionables);
+ item.setOnAction((e) -> handler.createProjectedCRS(projection));
+ localSystems.getItems().add(item);
+ }
+ handler.menu.getItems().setAll(systemChoices, localSystems);
+ addPropertyChangeListener(OBJECTIVE_CRS_PROPERTY, handler);
fixedPane.setOnMousePressed(handler);
- final Menu systemChoices = referenceSystems.createMenuItems(handler);
- handler.menu.getItems().setAll(systemChoices);
- return RecentReferenceSystems.getSelectedProperty(systemChoices);
+ return handler.selectedProperty = RecentReferenceSystems.getSelectedProperty(systemChoices);
}
/**
- * Shows or hides the contextual menu when right mouse button is clicked, with information about the
- * location where the click occurred.
+ * Shows or hides the contextual menu when the right mouse button is clicked. This handler can determine
+ * the geographic location where the click occurred. This information is used for changing the projection
+ * while preserving approximately the location, scale and rotation of pixels around the mouse cursor.
*/
@SuppressWarnings("serial") // Not intended to be serialized.
private final class MenuHandler extends DirectPosition2D
- implements EventHandler<MouseEvent>, ChangeListener<ReferenceSystem>
+ implements EventHandler<MouseEvent>, ChangeListener<ReferenceSystem>, PropertyChangeListener
{
- /** The contextual menu to show or hide. */
+ /**
+ * The property to update if a change of CRS occurs in the enclosing canvas. This property is provided
+ * by {@link RecentReferenceSystems}, which listen to changes. Setting this property to a new value
+ * causes the "Referencing systems" radio menus to change the item where the check mark appear.
+ *
+ * <p>This field is initialized by {@link #createContextMenu(RecentReferenceSystems)} and should be
+ * considered final after initialization.</p>
+ */
+ ObjectProperty<ReferenceSystem> selectedProperty;
+
+ /**
+ * The group of {@link PositionableProjection} items for projections created on-the-fly at mouse position.
+ * Those items are not managed by {@link RecentReferenceSystems} so they need to be handled there.
+ */
+ final ToggleGroup positionables;
+
+ /**
+ * The contextual menu to show or hide when mouse button is clicked on the canvas.
+ */
final ContextMenu menu;
- /** Creates a new handler. */
+ /**
+ * {@code true} if we are in the process of setting a CRS generated by {@link PositionableProjection}.
+ */
+ private boolean isPositionableProjection;
+
+ /**
+ * Creates a new handler for contextual menu in enclosing canvas.
+ */
MenuHandler() {
super(getDisplayCRS());
menu = new ContextMenu();
+ positionables = new ToggleGroup();
}
- /** Shows the menu on right mouse click, hide otherwise. */
- @Override public void handle(final MouseEvent event) {
+ /**
+ * Invoked when the user click on the canvas.
+ * Shows the menu on right mouse click, hide otherwise.
+ */
+ @Override
+ public void handle(final MouseEvent event) {
if (event.isSecondaryButtonDown()) {
x = event.getX();
y = event.getY();
@@ -215,14 +262,55 @@ public class CoverageCanvas extends MapCanvasAWT {
}
}
- /** Invoked when user selected a new coordinate reference system among menu items. */
- @Override public void changed(final ObservableValue<? extends ReferenceSystem> property,
- final ReferenceSystem oldValue, final ReferenceSystem newValue)
+ /**
+ * Invoked when user selected a new coordinate reference system among the choices of predefined CRS.
+ * Those CRS are the ones managed by {@link RecentReferenceSystems}, not the ones created on-the-fly.
+ */
+ @Override
+ public void changed(final ObservableValue<? extends ReferenceSystem> property,
+ final ReferenceSystem oldValue, final ReferenceSystem newValue)
{
if (newValue instanceof CoordinateReferenceSystem) {
setObjectiveCRS((CoordinateReferenceSystem) newValue, this, property);
}
}
+
+ /**
+ * Invoked when user selected a projection centered on mouse position. Those CRS are generated on-the-fly
+ * and are generally not on the list of CRS managed by {@link RecentReferenceSystems}.
+ */
+ final void createProjectedCRS(final PositionableProjection projection) {
+ try {
+ DirectPosition2D center = new DirectPosition2D();
+ center = (DirectPosition2D) objectiveToDisplay.inverseTransform(this, center);
+ center.setCoordinateReferenceSystem(getObjectiveCRS());
+ CoordinateReferenceSystem crs = projection.createProjectedCRS(center);
+ try {
+ isPositionableProjection = true;
+ setObjectiveCRS(crs, this, null);
+ } finally {
+ isPositionableProjection = false;
+ }
+ } catch (NoninvertibleTransformException | FactoryException | TransformException e) {
+ ExceptionReporter.show(null, null, e);
+ }
+ }
+
+ /**
+ * Invoked when a canvas property changed, typically after a new coverage has been selected.
+ * The property of interest is {@value CoverageCanvas#OBJECTIVE_CRS_PROPERTY}.
+ * This method updates the CRS selected in the contextual menu.
+ */
+ @Override
+ public void propertyChange(final PropertyChangeEvent event) {
+ final Object value = event.getNewValue();
+ if (value instanceof CoordinateReferenceSystem) {
+ selectedProperty.set((CoordinateReferenceSystem) value);
+ }
+ if (!isPositionableProjection) {
+ positionables.selectToggle(null);
+ }
+ }
}
/**
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java
index 6d707e3..eb8eadf 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java
@@ -18,8 +18,6 @@ package org.apache.sis.gui.coverage;
import java.util.Locale;
import java.util.Objects;
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
import javafx.scene.control.Accordion;
import javafx.scene.control.ColorPicker;
import javafx.scene.control.Control;
@@ -27,6 +25,7 @@ import javafx.scene.control.TitledPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
@@ -38,8 +37,8 @@ import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.util.StringConverter;
import org.opengis.referencing.ReferenceSystem;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.gui.referencing.RecentReferenceSystems;
import org.apache.sis.gui.map.StatusBar;
import org.apache.sis.image.Interpolation;
@@ -55,7 +54,7 @@ import org.apache.sis.util.resources.Vocabulary;
* @since 1.1
* @module
*/
-final class CoverageControls extends Controls implements PropertyChangeListener {
+final class CoverageControls extends Controls {
/**
* The component for showing sample values.
*/
@@ -72,12 +71,6 @@ final class CoverageControls extends Controls implements PropertyChangeListener
private final BorderPane imageAndStatus;
/**
- * The selected reference system in the context menu, or {@code null} if there is no such property.
- * This property is provided by {@link RecentReferenceSystems}.
- */
- private final ObjectProperty<ReferenceSystem> referenceSystem;
-
- /**
* Creates a new set of coverage controls.
*
* @param vocabulary localized set of words, provided in argument because often known by the caller.
@@ -94,26 +87,43 @@ final class CoverageControls extends Controls implements PropertyChangeListener
view.statusBar = statusBar;
imageAndStatus = new BorderPane(view.getView());
imageAndStatus.setBottom(statusBar.getView());
- referenceSystem = view.createContextMenu(referenceSystems);
+ final ObjectProperty<ReferenceSystem> referenceSystem = view.createContextMenu(referenceSystems);
+ final Locale locale = vocabulary.getLocale();
/*
* "Display" section with the following controls:
+ * - Current CRS
* - Interpolation
* - Color stretching
* - Background color
*/
- final GridPane displayPane;
+ final VBox displayPane;
{ // Block for making variables locale to this scope.
+ final Font font = fontOfGroup();
+ final Label crsLabel = new Label(vocabulary.getString(Vocabulary.Keys.ReferenceSystem));
+ final Label crsShown = new Label();
+ crsLabel.setLabelFor(crsShown);
+ crsLabel.setFont(font);
+ crsLabel.setPadding(Styles.FORM_INSETS);
+ crsShown.setPadding(INDENT_OUTSIDE);
+ referenceSystem.addListener((p,o,n) -> {
+ crsShown.setText(IdentifiedObjects.getDisplayName(n, locale));
+ });
+ /*
+ * The pane containing controls will be divided in sections separated by labels:
+ * ones for values and one for colors.
+ */
final int valuesHeader = 0;
final int colorsHeader = 2;
- displayPane = Styles.createControlGrid(valuesHeader + 1,
- label(vocabulary, Vocabulary.Keys.Interpolation, createInterpolationButton(vocabulary.getLocale())),
+ final GridPane gp;
+ gp = Styles.createControlGrid(valuesHeader + 1,
+ label(vocabulary, Vocabulary.Keys.Interpolation, createInterpolationButton(locale)),
label(vocabulary, Vocabulary.Keys.Stretching, Stretching.createButton((p,o,n) -> view.setStyling(n))),
label(vocabulary, Vocabulary.Keys.Background, createBackgroundButton(background)));
/*
- * Insert space (one row) betweeb "interpolation" and "stretching"
+ * Insert space (one row) between "interpolation" and "stretching"
* so we can insert the "colors" section header.
*/
- final ObservableList<Node> items = displayPane.getChildren();
+ final ObservableList<Node> items = gp.getChildren();
for (final Node item : items) {
if (GridPane.getColumnIndex(item) == 0) {
((Label) item).setPadding(INDENT);
@@ -123,7 +133,6 @@ final class CoverageControls extends Controls implements PropertyChangeListener
GridPane.setRowIndex(item, row + 1);
}
}
- final Font font = fontOfGroup();
final Label values = new Label(vocabulary.getString(Vocabulary.Keys.Values));
final Label colors = new Label(vocabulary.getString(Vocabulary.Keys.Colors));
values.setFont(font);
@@ -131,6 +140,7 @@ final class CoverageControls extends Controls implements PropertyChangeListener
GridPane.setConstraints(values, 0, valuesHeader, 2, 1); // Span 2 columns.
GridPane.setConstraints(colors, 0, colorsHeader, 2, 1);
items.addAll(values, colors);
+ displayPane = new VBox(crsLabel, crsShown, gp);
}
/*
* Put all sections together and have the first one expanded by default.
@@ -141,7 +151,6 @@ final class CoverageControls extends Controls implements PropertyChangeListener
controls = new Accordion(p1, p2);
controls.setExpandedPane(p1);
view.coverageProperty.bind(coverage);
- view.addPropertyChangeListener(CoverageCanvas.OBJECTIVE_CRS_PROPERTY, this);
p2.expandedProperty().addListener(new PropertyPaneCreator(view, p2));
}
@@ -262,19 +271,6 @@ final class CoverageControls extends Controls implements PropertyChangeListener
}
/**
- * Invoked when a canvas property changed, typically after a new coverage has been selected.
- * The property of interest is {@value CoverageCanvas#OBJECTIVE_CRS_PROPERTY}.
- * This method updates the CRS selected in the {@link ChoiceBox}.
- */
- @Override
- public void propertyChange(final PropertyChangeEvent event) {
- final Object value = event.getNewValue();
- if (value instanceof CoordinateReferenceSystem) {
- referenceSystem.set((CoordinateReferenceSystem) value);
- }
- }
-
- /**
* Returns the main component, which is showing coverage tabular data.
*/
@Override
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java
index 297cb8d..3f44241 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java
@@ -33,6 +33,8 @@ import org.opengis.referencing.ReferenceSystem;
import org.apache.sis.internal.gui.GUIUtilities;
import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.Utilities;
/**
@@ -230,17 +232,23 @@ final class MenuSync extends SimpleObjectProperty<ReferenceSystem> implements Ev
* to invoke the potentially costly {@link #action}.
*/
@Override
- public void set(final ReferenceSystem system) {
+ public void set(ReferenceSystem system) {
final ReferenceSystem old = get();
if (old != system) {
- super.set(system);
+ final ComparisonMode mode = owner.duplicationCriterion.get();
for (final MenuItem item : menus) {
- if (item instanceof RadioMenuItem && item.getProperties().get(REFERENCE_SYSTEM_KEY) == system) {
- ((RadioMenuItem) item).setSelected(true);
- action.changed(this, old, system);
- return;
+ if (item instanceof RadioMenuItem) {
+ final Object current = item.getProperties().get(REFERENCE_SYSTEM_KEY);
+ if (Utilities.deepEquals(current, system, mode)) {
+ system = (ReferenceSystem) current;
+ super.set(system);
+ ((RadioMenuItem) item).setSelected(true);
+ action.changed(this, old, system);
+ return;
+ }
}
}
+ super.set(system);
group.selectToggle(null);
/*
* Do not invoke action.changed(…) since we have no non-null value to provide.
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/PositionableProjection.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/PositionableProjection.java
new file mode 100644
index 0000000..2f8e6a7
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/PositionableProjection.java
@@ -0,0 +1,191 @@
+/*
+ * 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.referencing;
+
+import java.util.List;
+import java.util.ArrayList;
+import org.opengis.util.CodeList;
+import org.opengis.util.FactoryException;
+import org.opengis.geometry.DirectPosition;
+import org.opengis.referencing.crs.GeographicCRS;
+import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.crs.ProjectedCRS;
+import org.apache.sis.internal.gui.Resources;
+import org.apache.sis.internal.referencing.GeodeticObjectBuilder;
+import org.apache.sis.internal.referencing.ReferencingUtilities;
+import org.apache.sis.measure.AngleFormat;
+import org.apache.sis.measure.Latitude;
+import org.apache.sis.measure.Longitude;
+import org.apache.sis.measure.Units;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.Utilities;
+
+
+/**
+ * Provider of map projections centered on a point of interest.
+ * The point of interest is typically determined by mouse location.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+@SuppressWarnings("serial") // We do not guarantee serialization compatibility.
+public abstract class PositionableProjection extends CodeList<PositionableProjection> {
+ /**
+ * List of all enumerations of this type.
+ * Must be declared before any enum declaration.
+ */
+ private static final List<PositionableProjection> VALUES = new ArrayList<>(1);
+
+ /**
+ * Provides <cite>Azimuthal Equidistant</cite> projection centered on a point of interest.
+ * For projection on the ellipsoid, this is valid only under 800 km of the point of interest.
+ *
+ * @see org.apache.sis.referencing.operation.projection.AzimuthalEquidistant
+ */
+ public static final PositionableProjection AZIMUTHAL_EQUIDISTANT =
+ new PositionableProjection("AZIMUTHAL_EQUIDISTANT", Resources.Keys.AzimuthalEquidistant)
+ {
+ @Override protected ProjectedCRS createProjectedCRS(final GeographicCRS baseCRS,
+ final double latitude, final double longitude) throws FactoryException
+ {
+ return newBuilder(latitude, longitude)
+ .setConversionMethod("Azimuthal Equidistant (Spherical)")
+ .setParameter("Latitude of natural origin", latitude, Units.DEGREE)
+ .setParameter("Longitude of natural origin", longitude, Units.DEGREE)
+ .createProjectedCRS(baseCRS, null);
+ }
+ };
+
+ /**
+ * The projection name as a {@link Resources} keys.
+ */
+ private final short nameKey;
+
+ /**
+ * Constructs an element of the given name. The new element is automatically added to the list
+ * returned by {@link #values()}. Subclasses shall ensure that only one instance is created for
+ * each value because there is no mechanism for removing previously created values.
+ *
+ * @param name the name of the new element. This name shall not be in use by another element of this type.
+ */
+ protected PositionableProjection(final String name) {
+ super(name, VALUES);
+ nameKey = 0;
+ }
+
+ /**
+ * Creates a new enumeration.
+ */
+ private PositionableProjection(final String name, final short nameKey) {
+ super(name, VALUES);
+ this.nameKey = nameKey;
+ }
+
+ /**
+ * Returns the list of {@code PositionableProjection}s.
+ *
+ * @return the list of codes declared in the current JVM.
+ */
+ public static PositionableProjection[] values() {
+ synchronized (VALUES) {
+ return VALUES.toArray(new PositionableProjection[VALUES.size()]);
+ }
+ }
+
+ /**
+ * Returns the list of codes of the same kind than this code list element.
+ * Invoking this method is equivalent to invoking {@link #values()}, except that
+ * this method can be invoked on an instance of the parent {@code CodeList} class.
+ *
+ * @return all code {@linkplain #values() values} for this code list.
+ */
+ @Override
+ public PositionableProjection[] family() {
+ return values();
+ }
+
+ /**
+ * Returns a name for this enumeration which can be used in a user interface.
+ *
+ * @return a human-readable name for the projection created by this enumeration.
+ */
+ @Override
+ public String toString() {
+ return (nameKey != 0) ? Resources.format(nameKey) : name();
+ }
+
+ /**
+ * Creates a map projection centered on the given position. The position must have a coordinate reference system,
+ * but that CRS does not need to be geographic. The projection created by this method will use the same reference
+ * frame (datum) than the given position.
+ *
+ * <p>The default implementation converts the position to latitude and longitude values and delegates to
+ * {@link #createProjectedCRS(org.opengis.referencing.crs.GeographicCRS, double, double)}.</p>
+ *
+ * @param center the position at the center of the projection to create.
+ * @return projection centered on the given position.
+ * @throws FactoryException if an error occurred while creating the projection.
+ * @throws TransformException if an error occurred while converting the given position.
+ */
+ public ProjectedCRS createProjectedCRS(DirectPosition center) throws FactoryException, TransformException {
+ ArgumentChecks.ensureNonNull("center", center);
+ final CoordinateReferenceSystem inherit = center.getCoordinateReferenceSystem();
+ if (inherit == null) {
+ throw new IllegalArgumentException(Errors.format(Errors.Keys.UnspecifiedCRS));
+ }
+ GeographicCRS normalizedCRS = ReferencingUtilities.toNormalizedGeographicCRS(inherit, true, false);
+ if (normalizedCRS == null) {
+ normalizedCRS = CommonCRS.WGS84.geographic();
+ }
+ if (!Utilities.equalsIgnoreMetadata(normalizedCRS, inherit)) {
+ center = CRS.findOperation(inherit, normalizedCRS, null).getMathTransform().transform(center, null);
+ }
+ return createProjectedCRS(normalizedCRS, center.getOrdinate(0), center.getOrdinate(1));
+ }
+
+ /**
+ * Creates a map projection centered on the given latitude and longitude.
+ *
+ * @param baseCRS the base CRS of the projection to create.
+ * @param latitude latitude of projection center.
+ * @param longitude longitude of projection center.
+ * @return projection centered on the given position.
+ * @throws FactoryException if an error occurred while creating the projection.
+ */
+ protected abstract ProjectedCRS createProjectedCRS(final GeographicCRS baseCRS,
+ double latitude, double longitude) throws FactoryException;
+
+ /**
+ * Creates a new builder initialized to the projection name for the given coordinates.
+ */
+ final GeodeticObjectBuilder newBuilder(final double latitude, final double longitude) {
+ final AngleFormat f = new AngleFormat("DD°MM′SS″");
+ final StringBuffer b = new StringBuffer();
+ synchronized (b) {
+ b.append(this).append(" @ ");
+ f.format(new Latitude (latitude), b, null).append(' ');
+ f.format(new Longitude(longitude), b, null);
+ return new GeodeticObjectBuilder().addName(b.toString());
+ }
+ }
+}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
index c76c6e4..eb04449 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
@@ -66,6 +66,11 @@ public final class Resources extends IndexedResourceBundle {
public static final short Along_1 = 35;
/**
+ * Azimuthal equidistant
+ */
+ public static final short AzimuthalEquidistant = 42;
+
+ /**
* Can not close “{0}”. Data may be lost.
*/
public static final short CanNotClose_1 = 2;
@@ -106,6 +111,11 @@ public final class Resources extends IndexedResourceBundle {
public static final short CanNotUseRefSys_1 = 9;
/**
+ * Centered projection
+ */
+ public static final short CenteredProjection = 43;
+
+ /**
* Close
*/
public static final short Close = 10;
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
index e1d9cae..12662a9 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
@@ -22,6 +22,7 @@
AllFiles = All files
Along_1 = Along {0}
+AzimuthalEquidistant = Azimuthal equidistant
CanNotFetchTile_2 = Can not fetch tile ({0}, {1}).
CanNotReadFile_1 = Can not open \u201c{0}\u201d.
CanNotClose_1 = Can not close \u201c{0}\u201d. Data may be lost.
@@ -30,6 +31,7 @@ CanNotCreateXML = Can not create XML document.
CanNotReadResource = A resource contained in the file can not be read. The cause is given below.
CanNotRender = An error occurred while rendering the data.
CanNotUseRefSys_1 = Can not use the \u201c{0}\u201d reference system.
+CenteredProjection = Centered projection
Close = Close
Copy = Copy
CopyAs = Copy as
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
index 9d1faf5..4125597 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
@@ -27,6 +27,7 @@
AllFiles = Tous les fichiers
Along_1 = Selon {0}
+AzimuthalEquidistant = Azimutal \u00e9quidistant
CanNotFetchTile_2 = Ne peut pas obtenir la tuile ({0}, {1}).
CanNotReadFile_1 = Ne peut pas ouvrir \u00ab\u202f{0}\u202f\u00bb.
CanNotClose_1 = Ne peut pas fermer \u00ab\u202f{0}\u202f\u00bb. Il pourrait y avoir une perte de donn\u00e9es.
@@ -35,6 +36,7 @@ CanNotCreateXML = Ne peut pas cr\u00e9er le document XML.
CanNotReadResource = Une ressource contenue dans le fichier ne peut pas \u00eatre lue. La cause est donn\u00e9e ci-dessous.
CanNotRender = Une erreur est survenue lors de l\u2019affichage des donn\u00e9es.
CanNotUseRefSys_1 = Ne peut pas utiliser le syst\u00e8me de r\u00e9f\u00e9rence \u00ab\u202f{0}\u202f\u00bb.
+CenteredProjection = Projection centr\u00e9e
Close = Fermer
Copy = Copier
CopyAs = Copier comme
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
index 5625631..60e4ee0 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
@@ -155,7 +155,7 @@ public final class Styles extends Static {
/**
* Space between a group of controls and the border encompassing the group.
*/
- private static final Insets FORM_INSETS = new Insets(12);
+ public static final Insets FORM_INSETS = new Insets(12);
/**
* Creates a grid pane of two columns and an arbitrary number of rows.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/GeodeticObjectBuilder.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/GeodeticObjectBuilder.java
index 22ad543..8dd8931 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/GeodeticObjectBuilder.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/GeodeticObjectBuilder.java
@@ -63,7 +63,7 @@ import org.apache.sis.referencing.IdentifiedObjects;
* However this class may move in a public package later if we feel confident that its API is mature enough.</p>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 0.6
* @module
*/
@@ -376,11 +376,11 @@ public final class GeodeticObjectBuilder extends Builder<GeodeticObjectBuilder>
* </div>
*
* @param baseCRS coordinate reference system to base the derived CRS on.
- * @param derivedCS the coordinate system for the derived CRS.
+ * @param derivedCS the coordinate system for the derived CRS, or {@code null} for the default.
* @return the projected CRS.
* @throws FactoryException if an error occurred while building the projected CRS.
*/
- public ProjectedCRS createProjectedCRS(final GeographicCRS baseCRS, final CartesianCS derivedCS) throws FactoryException {
+ public ProjectedCRS createProjectedCRS(final GeographicCRS baseCRS, CartesianCS derivedCS) throws FactoryException {
ensureConversionMethodSet();
onCreate(false);
try {
@@ -401,6 +401,9 @@ public final class GeodeticObjectBuilder extends Builder<GeodeticObjectBuilder>
if (name != null) {
properties.put(Conversion.NAME_KEY, name);
}
+ if (derivedCS == null) {
+ derivedCS = factories.getStandardProjectedCS();
+ }
return factories.getCRSFactory().createProjectedCRS(properties, baseCRS, conversion, derivedCS);
} finally {
onCreate(true);