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 f7fc74b400 Reuse the code of `TileMatrixSet.toString()` in the JavaFX
widget. It produces a table easier to read.
f7fc74b400 is described below
commit f7fc74b4004d327a9016d021ce0125b292351101
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Feb 25 19:07:41 2026 +0100
Reuse the code of `TileMatrixSet.toString()` in the JavaFX widget.
It produces a table easier to read.
---
.../main/org/apache/sis/feature/FeatureFormat.java | 8 +-
.../apache/sis/storage/tiling/ImagePyramid.java | 7 +-
.../apache/sis/storage/tiling/ImageTileMatrix.java | 9 +-
.../sis/storage/tiling/TileMatrixFormatter.java | 310 -------------
.../sis/storage/tiling/TileMatrixSetFormat.java | 504 +++++++++++++++++++++
.../main/org/apache/sis/io/TabularFormat.java | 4 +-
.../apache/sis/gui/coverage/CoverageStyling.java | 16 +-
.../apache/sis/gui/coverage/TileMatrixSetPane.java | 304 +++++++------
.../apache/sis/gui/internal/AlignedTableCell.java | 87 ++++
9 files changed, 775 insertions(+), 474 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureFormat.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureFormat.java
index 5414533597..4d0fcc8aa5 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureFormat.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureFormat.java
@@ -64,7 +64,8 @@ import org.opengis.feature.Operation;
/**
* Formats {@linkplain AbstractFeature features} or {@linkplain
DefaultFeatureType feature types} in a tabular format.
- * This format assumes a monospaced font and an encoding supporting drawing
box characters (e.g. UTF-8).
+ * This format assumes a monospaced font and a character encoding which
supports the drawing of box characters,
+ * such as <abbr>UTF</abbr>-8.
*
* <h2>Example</h2>
* A feature named “City” and containing 3 properties (“name”, “population”
and “twin town”)
@@ -83,7 +84,7 @@ import org.opengis.feature.Operation;
*
* <h2>Limitations</h2>
* <ul>
- * <li>The current implementation can only format features — parsing is not
yet implemented.</li>
+ * <li>The current implementation can only format features — parsing is not
supported.</li>
* <li>{@code FeatureFormat}, like most {@code java.text.Format} subclasses,
is not thread-safe.</li>
* </ul>
*
@@ -759,10 +760,11 @@ format: for (final AttributeType<?>
ct : ((AttributeType<?>)
}
/**
- * Not yet supported.
+ * Not supported.
*
* @return currently never return.
* @throws ParseException currently always thrown.
+ * @hidden Not implemented.
*/
@Override
public Object parse(final CharSequence text, final ParsePosition pos)
throws ParseException {
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImagePyramid.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImagePyramid.java
index 5f5b8f3b5c..da376fb1f8 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImagePyramid.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImagePyramid.java
@@ -34,7 +34,6 @@ import
org.apache.sis.coverage.grid.IncompleteGridGeometryException;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.geometry.ImmutableEnvelope;
import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.base.StoreUtilities;
import org.apache.sis.util.collection.BackingStoreException;
import org.apache.sis.util.iso.Names;
import org.apache.sis.util.logging.Logging;
@@ -194,7 +193,7 @@ final class ImagePyramid extends AbstractMap<GenericName,
ImageTileMatrix>
level = provider.levelOfIdentifier(tip.toString());
} catch (IllegalArgumentException e) {
if (required) throw e;
- Logging.ignorableException(StoreUtilities.LOGGER,
ImagePyramid.class, "indexOf", e);
+ Logging.ignorableException(ImageTileMatrix.LOGGER,
ImagePyramid.class, "indexOf", e);
return -1;
}
if (level >= lowerMatrixIndex && level < upperMatrixIndex) {
@@ -446,9 +445,9 @@ final class ImagePyramid extends AbstractMap<GenericName,
ImageTileMatrix>
*/
@Override
public String toString() {
- final var f = new TileMatrixFormatter(locale);
+ final var f = new TileMatrixSetFormat(locale, null);
synchronized (matrices) {
- return f.format(this);
+ return f.format(this, true);
}
}
}
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImageTileMatrix.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImageTileMatrix.java
index 82e4bec8da..fdda986dba 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImageTileMatrix.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImageTileMatrix.java
@@ -23,6 +23,7 @@ import java.util.stream.StreamSupport;
import java.awt.Rectangle;
import java.awt.image.RenderedImage;
import java.nio.file.Path;
+import java.util.logging.Logger;
import org.opengis.util.GenericName;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.referencing.operation.matrix.Matrices;
@@ -34,7 +35,6 @@ import org.apache.sis.storage.NoSuchDataException;
import org.apache.sis.storage.UnsupportedQueryException;
import org.apache.sis.storage.InternalDataStoreException;
import org.apache.sis.storage.Resource;
-import org.apache.sis.storage.base.StoreUtilities;
import org.apache.sis.coverage.grid.GridExtent;
import org.apache.sis.coverage.grid.GridGeometry;
import org.apache.sis.coverage.grid.GridCoverage2D;
@@ -63,6 +63,11 @@ import org.apache.sis.storage.internal.Resources;
* @author Martin Desruisseaux (Geomatys)
*/
final class ImageTileMatrix implements TileMatrix {
+ /**
+ * Logger for the tiling package.
+ */
+ static final Logger LOGGER =
Logger.getLogger("org.apache.sis.storage.tiling");
+
/**
* An alphanumeric identifier which is unique in the {@code TileMatrixSet}
that contains this {@code TileMatrix}.
* The identifier contains the zoom level as a number encoded in
<abbr>ASCII</abbr>.
@@ -267,7 +272,7 @@ final class ImageTileMatrix implements TileMatrix {
}
return TileStatus.UNKNOWN;
} catch (ArithmeticException e) {
- Logging.ignorableException(StoreUtilities.LOGGER,
ImageTileMatrix.class, "getTileStatus", e);
+ Logging.ignorableException(LOGGER, ImageTileMatrix.class,
"getTileStatus", e);
}
return TileStatus.OUTSIDE_EXTENT;
}
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileMatrixFormatter.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileMatrixFormatter.java
deleted file mode 100644
index 21b7123042..0000000000
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileMatrixFormatter.java
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.storage.tiling;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.util.Locale;
-import java.util.ArrayList;
-import java.text.NumberFormat;
-import org.opengis.util.GenericName;
-import org.opengis.referencing.operation.TransformException;
-import org.apache.sis.util.CharSequences;
-import org.apache.sis.util.resources.Vocabulary;
-import org.apache.sis.util.internal.shared.Numerics;
-import org.apache.sis.util.collection.TableColumn;
-import org.apache.sis.util.collection.TreeTableFormat;
-import org.apache.sis.util.collection.BackingStoreException;
-import org.apache.sis.referencing.IdentifiedObjects;
-import org.apache.sis.coverage.grid.GridExtent;
-import org.apache.sis.coverage.grid.IncompleteGridGeometryException;
-import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
-import org.apache.sis.io.TableAppender;
-
-
-/**
- * Formatter of tile matrix sets.
- *
- * @author Martin Desruisseaux (Geomatys)
- */
-final class TileMatrixFormatter {
- /**
- * Helper class for string representation of an image pyramids as a table.
- * Each instance describes one table row for one {@link ImageTileMatrix}.
- */
- private static final class Row {
- /** The tile matrix identifier. */
- private final String identifier;
-
- /** The tile matrix resolution in each <abbr>CRS</abbr> dimensions. */
- private final double[] resolution;
-
- /** The string representations of the tile matrix resolution. */
- private final String[] formattedResolution;
-
- /** The number of tiles in each grid dimension. */
- private final String[] tileCount;
-
- /** The tile sizes in pixels. */
- private final String[] tileSize;
-
- /**
- * Creates one row in the table for the given matrix.
- *
- * @param matrix the matrix for which to store information.
- * @param integerFormat the format to use for integer values.
- * @throws BackingStoreException if an error occurred while extracting
information.
- * @throws IncompleteGridGeometryException if the tiling scheme has
not extent or resolution.
- * Tile matrices with such tiling scheme should not have been
constructed in first place.
- */
- private Row(final TileMatrix matrix, final NumberFormat integerFormat)
{
- final GenericName id = matrix.getIdentifier();
- identifier = (id != null) ? id.toString() : "";
- resolution = matrix.getResolution();
- formattedResolution = new String[resolution.length];
- final GridExtent ge = matrix.getTilingScheme().getExtent();
- tileCount = new String[ge.getDimension()];
- for (int i=0; i<tileCount.length; i++) {
- tileCount[i] = integerFormat.format(ge.getSize(i));
- }
- final int[] ts = ImageTileMatrix.getTileSize(matrix);
- tileSize = new String[ts != null ? ts.length : 0];
- for (int i=0; i<tileSize.length; i++) {
- tileSize[i] = integerFormat.format(ts[i]);
- }
- }
-
- /**
- * Creates the string representation of resolutions.
- */
- final void formatResolutions(final NumberFormat[] formats) {
- for (int i=0; i<resolution.length; i++) {
- formattedResolution[i] = formats[i].format(resolution[i]);
- }
- }
- }
-
- /**
- * The locale specified at construction time. May be {@code null}.
- */
- private final Locale locale;
-
- /**
- * Resources for table header.
- */
- private final Vocabulary vocabulary;
-
- /**
- * The object to use for formatting integer values.
- */
- private final NumberFormat integerFormat;
-
- /**
- * The error that occurred while formatting a value.
- */
- private Throwable error;
-
- /**
- * Creates a new formatter using the given locale.
- */
- TileMatrixFormatter(final Locale locale) {
- this.locale = locale;
- vocabulary = Vocabulary.forLocale(locale);
- integerFormat = (locale != null)
- ? NumberFormat.getIntegerInstance(locale)
- : NumberFormat.getIntegerInstance();
- }
-
- /**
- * Returns a string representation of the given tile matrices.
- * Each tile matrix is formatted as a row in a table.
- *
- * @param matrices the tile matrices to format.
- * @return the string representation of the table of tile matrices.
- */
- final String format(final TileMatrixSet matrices) {
- final var buffer = new StringBuilder(1000);
- try {
- formatHeader(matrices, buffer);
- formatTable(matrices, buffer);
- if (error == null) {
- return buffer.toString();
- }
- final var writer = new
StringWriter(buffer.length()).append(buffer);
- vocabulary.appendLabel(Vocabulary.Keys.Warnings, writer);
- error.printStackTrace(new PrintWriter(writer.append(' ')));
- return writer.append(System.lineSeparator()).toString();
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
-
- /**
- * Formats the header of the Tile Matrix Set (<abbr>TMS</abbr>)
representation.
- * The header contains the <abbr>TMS</abbr> identifier, the Coordinate
Reference System
- * and the geographic bounding box.
- *
- * @param matrices the Tile Matrix Set (<abbr>TMS</abbr>) to format.
- * @param buffer where to format the header.
- * @throws IOException should never happen but handled by the caller for
convenience.
- */
- private void formatHeader(final TileMatrixSet matrices, final
StringBuilder buffer) throws IOException {
-
buffer.append(vocabulary.getString(Vocabulary.Keys.TileMatrixSets)).append(' ')
- .append(vocabulary.getString(Vocabulary.Keys.Quoted_1,
matrices.getIdentifier()))
- .append(System.lineSeparator());
- try {
- final String crs =
IdentifiedObjects.getDisplayName(matrices.getCoordinateReferenceSystem(),
locale);
- if (crs != null) {
- vocabulary.appendLabel(Vocabulary.Keys.ReferenceSystem,
buffer);
- buffer.append(' ').append(crs).append(System.lineSeparator());
- }
- matrices.getEnvelope().ifPresent((envelope) -> {
- try {
- final var bbox = new DefaultGeographicBoundingBox();
- bbox.setBounds(envelope);
- bbox.setInclusion(null);
- final var mf = new TreeTableFormat(locale, null);
- mf.setColumns(TableColumn.NAME, TableColumn.VALUE);
- buffer.append(mf.format(bbox.asTreeTable()));
- } catch (TransformException e) {
- addError(e);
- }
- });
- } catch (BackingStoreException e) {
- addError(e.getCause());
- }
- }
-
- /**
- * Updates an array of maximal length of string representations in the
given columns.
- * The {@code lenghts} array is updated in-place.
- */
- private static void updateMaximalLengths(final int[] lengths, final
String[] columns) {
- for (int i = Math.min(lengths.length, columns.length); --i >= 0;) {
- final int length = columns[i].length();
- if (length > lengths[i]) {
- lengths[i] = length;
- }
- }
- }
-
- /**
- * Appends spaces in front of the given columns in order to have the
specified lengths.
- */
- private static void rightAlign(final int[] lengths, final String[]
columns) {
- for (int i = Math.min(lengths.length, columns.length); --i >= 0;) {
- final String column = columns[i];
- final int more = lengths[i] - column.length();
- if (more > 0) {
- columns[i] = CharSequences.spaces(more) + columns[i];
- }
- }
- }
-
- /**
- * Formats the main body of the Tile Matrix Set (<abbr>TMS</abbr>)
representation.
- * This is formatted as a table.
- *
- * @param matrices the Tile Matrix Set (<abbr>TMS</abbr>) to format.
- * @param buffer where to format the main body.
- * @throws IOException should never happen but handled by the caller for
convenience.
- */
- private void formatTable(final TileMatrixSet matrices, final StringBuilder
buffer) throws IOException {
- final var rows = new ArrayList<Row>();
- int crsDimension = 0, gridDimension = 0, sizeDimension = 0;
- for (final TileMatrix matrix : matrices.getTileMatrices().values())
try {
- final var row = new Row(matrix, integerFormat);
- crsDimension = Math.max(crsDimension,
row.formattedResolution.length);
- gridDimension = Math.max(gridDimension, row.tileCount.length);
- sizeDimension = Math.max(sizeDimension, row.tileSize.length);
- rows.add(row); // Add only on success.
- } catch (RuntimeException e) {
- addError(e);
- }
- /*
- * Find the number of fraction digits to use for showing the
resolution.
- */
- final var values = new double[rows.size()];
- final var formats = new NumberFormat[crsDimension];
- for (int i=0; i<crsDimension; i++) {
- for (int j=0; j<values.length; j++) {
- values[j] = rows.get(j).resolution[i];
- }
- final NumberFormat format = (locale != null)
- ? NumberFormat.getNumberInstance(locale)
- : NumberFormat.getNumberInstance();
- final int n = Numerics.suggestFractionDigits(values);
- format.setMinimumFractionDigits(n);
- format.setMaximumFractionDigits(n);
- formats[i] = format;
- }
- /*
- * At this point, all values have been formatted as character strings.
- * Compute the maximum lengths in each column in order to align the
values.
- */
- final int[] resolutionLengths = new int[crsDimension];
- final int[] tileCountLengths = new int[gridDimension];
- final int[] tileSizeLengths = new int[sizeDimension];
- for (final Row row : rows) {
- row.formatResolutions(formats);
- updateMaximalLengths(resolutionLengths, row.formattedResolution);
- updateMaximalLengths(tileCountLengths, row.tileCount);
- updateMaximalLengths(tileSizeLengths, row.tileSize);
- }
- for (final Row row : rows) {
- rightAlign(resolutionLengths, row.formattedResolution);
- rightAlign(tileCountLengths, row.tileCount);
- rightAlign(tileSizeLengths, row.tileSize);
- }
- /*
- * All data are prepared. Write the table.
- */
- final var table = new TableAppender(buffer);
- table.appendHorizontalSeparator();
-
table.append(vocabulary.getString(Vocabulary.Keys.Identifier)).nextColumn();
- if (crsDimension != 0)
table.append(vocabulary.getString(Vocabulary.Keys.Resolution)).nextColumn();
- if (gridDimension != 0)
table.append(vocabulary.getString(Vocabulary.Keys.TileCount)) .nextColumn();
- if (sizeDimension != 0)
table.append(vocabulary.getString(Vocabulary.Keys.TileSize)) .nextColumn();
- table.appendHorizontalSeparator();
- for (final Row row : rows) {
- table.setCellAlignment(TableAppender.ALIGN_LEFT);
- table.append(row.identifier).nextColumn();
- table.setCellAlignment(TableAppender.ALIGN_RIGHT);
- if (crsDimension != 0) table.append(String.join(" × ",
row.formattedResolution)).nextColumn();
- if (gridDimension != 0) table.append(String.join(" × ",
row.tileCount)) .nextColumn();
- if (sizeDimension != 0) table.append(String.join(" × ",
row.tileSize)) .nextColumn();
- table.nextLine();
- }
- table.appendHorizontalSeparator();
- table.flush();
- }
-
- /**
- * Records that an error occurred.
- *
- * @param e the error that occurred.
- */
- private void addError(final Throwable e) {
- if (error == null) {
- error = e;
- } else {
- error.addSuppressed(e);
- }
- }
-}
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileMatrixSetFormat.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileMatrixSetFormat.java
new file mode 100644
index 0000000000..38049ca48a
--- /dev/null
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileMatrixSetFormat.java
@@ -0,0 +1,504 @@
+/*
+ * 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.storage.tiling;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.text.Format;
+import java.util.Locale;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.TimeZone;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Arrays;
+import java.util.StringJoiner;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import org.opengis.util.GenericName;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.TransformException;
+import org.opengis.metadata.extent.GeographicBoundingBox;
+import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.logging.Logging;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.util.internal.shared.Numerics;
+import org.apache.sis.util.collection.TableColumn;
+import org.apache.sis.util.collection.TreeTableFormat;
+import org.apache.sis.util.collection.BackingStoreException;
+import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.coverage.grid.IncompleteGridGeometryException;
+import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
+import org.apache.sis.io.CompoundFormat;
+import org.apache.sis.io.TableAppender;
+
+
+/**
+ * Formats Tile Matrix Sets (<abbr>TMS</abbr>) in a tabular format.
+ * This format assumes a monospaced font and a character encoding which
supports the drawing of box characters,
+ * such as <abbr>UTF</abbr>-8.
+ *
+ * <h2>Limitations</h2>
+ * <ul>
+ * <li>The current implementation can only format tile matrices — parsing is
supported.</li>
+ * <li>{@code TileMatrixSetFormat}, like most {@code java.text.Format}
subclasses, is not thread-safe.</li>
+ * </ul>
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.7
+ * @since 1.7
+ */
+public class TileMatrixSetFormat extends CompoundFormat<TileMatrixSet> {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = 2612127094991996016L;
+
+ /**
+ * Helper class for string representation of an image pyramid as a table.
+ * Each instance describes one table row for one {@link ImageTileMatrix}.
+ */
+ private static final class Row {
+ /** The tile matrix identifier. */
+ final String identifier;
+
+ /** The tile matrix resolution in each <abbr>CRS</abbr> dimensions. */
+ final double[] resolution;
+
+ /** The string representations of the tile matrix resolution. */
+ final String[] formattedResolution;
+
+ /** The number of tiles in each grid dimension. */
+ final String[] tileCount;
+
+ /** The tile sizes in pixels. */
+ final String[] tileSize;
+
+ /** The extent of the tiling scheme. */
+ private final GridExtent tilingScheme;
+
+ /**
+ * Creates one row in the table for the given matrix.
+ *
+ * @param matrix the matrix for which to store information.
+ * @param integerFormat a number format configured for integer
values.
+ * @throws BackingStoreException if an error occurred while extracting
information.
+ * @throws IncompleteGridGeometryException if the tiling scheme has
not extent or resolution.
+ * Tile matrices with such tiling scheme should not have been
constructed in first place.
+ */
+ Row(final TileMatrix matrix, final NumberFormat integerFormat) {
+ final GenericName id = matrix.getIdentifier();
+ identifier = (id != null) ? id.toString() : "";
+ resolution = matrix.getResolution();
+ formattedResolution = new String[resolution.length];
+ tilingScheme = matrix.getTilingScheme().getExtent();
+ tileCount = new String[tilingScheme.getDimension()];
+ for (int i=0; i<tileCount.length; i++) {
+ tileCount[i] = integerFormat.format(tilingScheme.getSize(i));
+ }
+ final int[] ts = ImageTileMatrix.getTileSize(matrix);
+ tileSize = new String[ts != null ? ts.length : 0];
+ for (int i=0; i<tileSize.length; i++) {
+ tileSize[i] = integerFormat.format(ts[i]);
+ }
+ }
+
+ /**
+ * Updates the axis name in the dimension <var>i</var> of the grid
extent.
+ * This method verifies that all grid dimensions have the same name.
+ */
+ private final void searchCommonAxisName(final String[] gridAxes, final
int i) {
+ tilingScheme.getAxisType(i).ifPresent((axis) -> {
+ final String name = axis.identifier().orElseGet(() ->
axis.name().toLowerCase(Locale.US));
+ final String current = gridAxes[i];
+ if (current == null) {
+ gridAxes[i] = name;
+ } else if (!current.equals(name)) {
+ gridAxes[i] = "";
+ }
+ });
+ }
+
+ /**
+ * Creates the string representation of the resolutions in all
resolution columns of all rows.
+ * This method computes the number of fraction digits based on the
resolution values of all rows.
+ *
+ * @param rows the rows in which to format the resolutions.
+ * @param format the number formats to use. Its number of fraction
digits will be updated.
+ * @return the maximal number of resolution values found in all rows.
+ * This value should be equal to the number of dimensions in
the <abbr>CRS</abbr>.
+ */
+ static int formatResolutions(final List<Row> rows, final NumberFormat
format) {
+ final var values = new double[rows.size()];
+ for (int i=0; ; i++) {
+ int count = 0;
+ for (final Row row : rows) {
+ if (i < row.resolution.length) {
+ values[count++] = row.resolution[i];
+ }
+ }
+ if (count == 0) return i;
+ final int n =
Numerics.suggestFractionDigits(ArraysExt.resize(values, count));
+ format.setMinimumFractionDigits(n);
+ format.setMaximumFractionDigits(n);
+ for (final Row row : rows) {
+ if (i < row.resolution.length) {
+ row.formattedResolution[i] =
format.format(row.resolution[i]);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * The error that occurred while formatting a Tile Matrix Set.
+ */
+ private transient Throwable error;
+
+ /**
+ * Creates a new formatter using the given locale and timezone.
+ *
+ * @param locale the locale for the new {@code Format}, or {@code
null} for {@code Locale.ROOT}.
+ * @param timezone the timezone, or {@code null} for UTC.
+ */
+ public TileMatrixSetFormat(final Locale locale, final TimeZone timezone) {
+ super(locale, timezone);
+ }
+
+ /**
+ * Returns the type of values formatted by this {@code Format} instance.
+ *
+ * @return the type of values formatted by this {@code Format} instance.
+ */
+ @Override
+ public final Class<TileMatrixSet> getValueType() {
+ return TileMatrixSet.class;
+ }
+
+ /**
+ * Formats the properties of the given Tile Matrix Set for presentation
before the main table.
+ * The properties are formatted as {@link String}s using the locale given
at construction time.
+ * The returned map contains the following entries if the corresponding
properties were found:
+ *
+ * <table class="sis">
+ * <caption>Tile Matrix Set (<abbr>TMS</abbr>) formatted
properties</caption>
+ * <tr><th>Key</th> <th>Value type</th>
<th>Description</th></tr>
+ * <tr><td>{@code "identifier"}</td> <td>{@link String}</td>
<td>Identifier of the <abbr>TMS</abbr>.</td></tr>
+ * <tr><td>{@code "crsName"}</td> <td>{@link String}</td>
<td>Name of the <abbr>CRS</abbr>.</td></tr>
+ * <tr><td>{@code "crs"}</td> <td>{@link
CoordinateReferenceSystem}</td> <td>The <abbr>CRS</abbr>.</td></tr>
+ * <tr><td>{@code "bbox"}</td> <td>{@link
DefaultGeographicBoundingBox}</td> <td>Bounding box of the
<abbr>TMS</abbr>.</td></tr>
+ * </table>
+ *
+ * The returned properties can be completed by a call to {@link
#formatTable(Iterable, Map)}.
+ *
+ * @param matrices the tile matrices to format.
+ * @return properties of the header of the given tile matrix set.
+ */
+ public Map<String, Object> formatHeader(final TileMatrixSet matrices) {
+ final var addTo = new HashMap<String, Object>();
+ addTo.put("identifier", matrices.getIdentifier());
+ try {
+ final CoordinateReferenceSystem crs =
matrices.getCoordinateReferenceSystem();
+ addTo.put("crsName", IdentifiedObjects.getDisplayName(crs,
getLocale()));
+ addTo.put("crs", crs);
+ matrices.getEnvelope().ifPresent((envelope) -> {
+ try {
+ final var bbox = new DefaultGeographicBoundingBox();
+ bbox.setBounds(envelope);
+ bbox.setInclusion(null);
+ addTo.put("bbox", bbox);
+ } catch (TransformException e) {
+ // Ignore because this exception may be normal if the
envelope has no spatial component.
+ Logging.ignorableException(ImageTileMatrix.LOGGER,
TileMatrixSetFormat.class, "format", e);
+ }
+ });
+ } catch (BackingStoreException e) {
+ // The CRS or envelope may be missing in the header, but we may
still be able to format the table.
+ addError(e.getCause());
+ }
+ addTo.values().removeIf(Objects::isNull);
+ return addTo;
+ }
+
+ /**
+ * Formats the properties of the given Tile Matrices in a way suitable to
a tabular format.
+ * The properties are formatted as {@link String}s using the locale given
at construction time.
+ * The returned map contains the following entries if the corresponding
properties were found:
+ *
+ * <table class="sis">
+ * <caption>Tile Matrices formatted properties</caption>
+ * <tr><th>Key</th> <th>Value type</th>
<th>Description</th></tr>
+ * <tr><td>{@code "identifiers"}</td> <td>{@code String[]}</td>
<td>Column of the identifier of each Tile Matrix.</td></tr>
+ * <tr><td>{@code "resolutions"}</td> <td>{@code String[][]}</td>
<td>Columns of the resolution of each Tile Matrix.</td></tr>
+ * <tr><td>{@code "tileCounts"}</td> <td>{@code String[][]}</td>
<td>Columns of the number of tiles of each Tile Matrix.</td></tr>
+ * <tr><td>{@code "tileSizes"}</td> <td>{@code String[][]}</td>
<td>Columns of the tile size of each Tile Matrix.</td></tr>
+ * <tr><td>{@code "gridAxes"}</td> <td>{@code String[]}</td>
<td>Name of grid axes.</td></tr>
+ * </table>
+ *
+ * The {@code "tileSizes"} property may be absent if not applicable.
+ * Implementations other than the default implementation may also choose
to add or remove more properties.
+ *
+ * <p>This method returns the number of rows in the table to format.
+ * The length of the {@code "identifiers"} array is that number of rows.
+ * All other arrays have a length equal to the number of columns in a
group of columns,
+ * which is 2 in the usual case of two-dimensional Tile Matrix Sets.
+ * The {@code "resolutions[i]"}, {@code "tileCounts[i]"} and {@code
"tileSizes[i]"} arrays,
+ * where <var>i</var> is a column index, all have a length equal to the
number of rows.
+ * Some element may be {@code null} if the corresponding data could not be
extracted.</p>
+ *
+ * @param matrices the tile matrices to format.
+ * @param addTo where to put the properties of the given tile matrix
set.
+ * @return number of rows in the table to format.
+ */
+ public int formatTable(final Iterable<? extends TileMatrix> matrices,
final Map<String, Object> addTo) {
+ final var rows = new ArrayList<Row>();
+ int gridDimension = 0, sizeDimension = 0;
+ final var integerFormat = (NumberFormat) getFormat(Long.class);
+ for (final TileMatrix matrix : matrices) try {
+ final var row = new Row(matrix, integerFormat);
+ gridDimension = Math.max(gridDimension, row.tileCount.length);
+ sizeDimension = Math.max(sizeDimension, row.tileSize.length);
+ rows.add(row); // Add only on success.
+ } catch (RuntimeException e) {
+ addError(e); // Skip the row. Next row will be tried.
+ }
+ final int crsDimension = Row.formatResolutions(rows, (NumberFormat)
getFormat(Double.class));
+ final int numRows = rows.size();
+ final var identifiers = new String[numRows];
+ final var resolutions = new String[ crsDimension][numRows];
+ final var tileCounts = new String[gridDimension][numRows];
+ final var tileSizes = new String[sizeDimension][numRows];
+ final var gridAxes = new String[gridDimension];
+ for (int j=0; j<numRows; j++) {
+ final Row row = rows.get(j);
+ identifiers[j] = row.identifier;
+ for (int i = Math.min(crsDimension, row.resolution.length); --i >=
0;) {
+ resolutions[i][j] = row.formattedResolution[i];
+ }
+ for (int i = Math.min(gridDimension, row.tileCount.length); --i >=
0;) {
+ tileCounts[i][j] = row.tileCount[i];
+ row.searchCommonAxisName(gridAxes, i);
+ }
+ for (int i = Math.min(sizeDimension, row.tileSize.length); --i >=
0;) {
+ tileSizes[i][j] = row.tileSize[i];
+ }
+ }
+ if (numRows != 0) addTo.put("identifiers", identifiers);
+ if (crsDimension != 0) addTo.put("resolutions", resolutions);
+ if (gridDimension != 0) addTo.put("tileCounts", tileCounts);
+ if (sizeDimension != 0) addTo.put("tileSizes", tileSizes);
+ for (int i=0; i<gridAxes.length; i++) {
+ final String name = gridAxes[i];
+ if (name != null && name.isBlank()) {
+ gridAxes[i] = null;
+ }
+ }
+ if (!ArraysExt.allEquals(gridAxes, null)) {
+ addTo.put("gridAxes", gridAxes);
+ }
+ return numRows;
+ }
+
+ /**
+ * Returns localized resources for the table header.
+ */
+ private Vocabulary vocabulary() {
+ return Vocabulary.forLocale(getLocale());
+ }
+
+ /**
+ * Formats a textual representation of the given tile matrices.
+ * This method delegates to {@link #format(TileMatrixSet, Appendable)}
+ * with a temporary buffer.
+ *
+ * @param matrices the tile matrices to format.
+ * @return a string representation of the given Tile Matrix Set.
+ */
+ public String format(TileMatrixSet matrices) {
+ return format(matrices, false);
+ }
+
+ /**
+ * Formats the given tile matrices, optionally with a report of exceptions.
+ *
+ * @param matrices the tile matrices to format.
+ * @param reportError whether to report exceptions.
+ * @return a string representation of the given Tile Matrix Set.
+ */
+ final String format(final TileMatrixSet matrices, final boolean
reportError) {
+ final var buffer = new StringBuilder(1000);
+ try {
+ format(matrices, buffer);
+ if (!reportError || error == null) {
+ return buffer.toString();
+ }
+ final var writer = new
StringWriter(buffer.length()).append(buffer);
+ vocabulary().appendLabel(Vocabulary.Keys.Warnings, writer);
+ error.printStackTrace(new PrintWriter(writer.append(' ')));
+ return writer.append(System.lineSeparator()).toString();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ /**
+ * Formats a textual representation of the given tile matrices.
+ * First, a header is formatted with the <abbr>TMS</abbr> identifier,
+ * the Coordinate Reference System and the geographic bounding box.
+ * Then, each tile matrix is formatted as a row in a table.
+ *
+ * @param matrices the tile matrices to format.
+ * @param toAppendTo where to format the object.
+ * @throws IOException if an error occurred while writing to the given
appendable.
+ */
+ @Override
+ public void format(final TileMatrixSet matrices, final Appendable
toAppendTo) throws IOException {
+ final Vocabulary vocabulary = vocabulary();
+ final Map<String, Object> properties = formatHeader(matrices);
+ final int numRows = formatTable(matrices.getTileMatrices().values(),
properties);
+ Object value = properties.get("identifier");
+ if (value != null) {
+
toAppendTo.append(vocabulary.getString(Vocabulary.Keys.TileMatrixSets)).append('
')
+ .append(vocabulary.getString(Vocabulary.Keys.Quoted_1,
value))
+ .append(System.lineSeparator());
+ }
+ value = properties.get("crsName");
+ if (value != null) {
+ vocabulary.appendLabel(Vocabulary.Keys.ReferenceSystem,
toAppendTo);
+ toAppendTo.append('
').append(value.toString()).append(System.lineSeparator());
+ }
+ value = properties.get("bbox");
+ if (value != null) {
+ final var bbox = (DefaultGeographicBoundingBox) value;
+ final var mf = (TreeTableFormat)
getFormat(GeographicBoundingBox.class);
+ mf.setColumns(TableColumn.NAME, TableColumn.VALUE);
+ toAppendTo.append(mf.format(bbox.asTreeTable()));
+ }
+ /*
+ * The header was formatted by above code. The following code formats
the main table.
+ * First, we get the values to format in all columns and prepend the
number of spaces
+ * needed for right alignment.
+ */
+ final var identifiers = (String[]) properties.get("identifiers");
+ final var resolutions = (String[][]) properties.get("resolutions");
+ final var tileCounts = (String[][]) properties.get("tileCounts");
+ final var tileSizes = (String[][]) properties.get("tileSizes");
+ String[][][] columnGroups = {resolutions, tileCounts, tileSizes};
+ columnGroups = ArraysExt.resize(columnGroups,
ArraysExt.removeNulls(columnGroups));
+ for (final String[][] columns : columnGroups) {
+ for (final String[] column : columns) {
+
Arrays.stream(column).mapToInt(String::length).max().ifPresent((length) -> {
+ for (int j=0; j<column.length; j++) {
+ String e = column[j];
+ column[j] = CharSequences.spaces(length - e.length())
+ e;
+ }
+ });
+ }
+ }
+ final var table = new TableAppender(toAppendTo);
+ table.appendHorizontalSeparator();
+ if (identifiers != null)
table.append(vocabulary.getString(Vocabulary.Keys.Identifier)).nextColumn();
+ if (resolutions != null)
table.append(vocabulary.getString(Vocabulary.Keys.Resolution)).nextColumn();
+ if (tileCounts != null)
table.append(vocabulary.getString(Vocabulary.Keys.TileCount)) .nextColumn();
+ if (tileSizes != null)
table.append(vocabulary.getString(Vocabulary.Keys.TileSize)) .nextColumn();
+ table.appendHorizontalSeparator();
+ for (int j=0; j<numRows; j++) {
+ if (identifiers != null) {
+ table.setCellAlignment(TableAppender.ALIGN_LEFT);
+ table.append(identifiers[j]).nextColumn();
+ table.setCellAlignment(TableAppender.ALIGN_RIGHT);
+ }
+ for (final String[][] columns : columnGroups) {
+ final var joiner = new StringJoiner(" × ");
+ for (String[] column : columns) {
+ joiner.add(column[j]);
+ }
+ table.append(joiner.toString()).nextColumn();
+ }
+ table.nextLine();
+ }
+ table.appendHorizontalSeparator();
+ table.flush();
+ }
+
+ /**
+ * Not supported.
+ *
+ * @return currently never return.
+ * @throws ParseException currently always thrown.
+ * @hidden Not implemented.
+ */
+ @Override
+ public TileMatrixSet parse(CharSequence text, ParsePosition pos) throws
ParseException {
+ throw new ParseException(Errors.forLocale(getLocale())
+ .getString(Errors.Keys.UnsupportedOperation_1, "parse"),
pos.getIndex());
+ }
+
+ /**
+ * Creates a new format to use for formatting values of the given type.
+ * This method adds the creation of {@link TreeTableFormat}.
+ *
+ * @param valueType the base type of values to parse or format.
+ * @return the format to use for parsing of formatting values of the given
type, or {@code null} if none.
+ * @hidden the addition compared to parent class are implementation
details.
+ */
+ @Override
+ protected Format createFormat(final Class<?> valueType) {
+ if (valueType == GeographicBoundingBox.class) {
+ return new TreeTableFormat(getLocale(), getTimeZone());
+ }
+ return super.createFormat(valueType);
+ }
+
+ /**
+ * Records that an error occurred.
+ *
+ * @param e the error that occurred.
+ */
+ private void addError(final Throwable e) {
+ if (error == null) {
+ error = e;
+ } else {
+ error.addSuppressed(e);
+ }
+ }
+
+ /**
+ * If an error occurred while formatting the Tile Matrix Set, returns that
error.
+ *
+ * @return the error that occurred during formatting.
+ */
+ public Optional<Throwable> getError() {
+ return Optional.ofNullable(error);
+ }
+
+ /**
+ * Clears the error status. This method should be invoked if the same
{@code TileMatrixSetFormat}
+ * instance is used for formatting many {@code TileMatrixSet} instances.
+ */
+ public void clear() {
+ error = null;
+ }
+}
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/TabularFormat.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/TabularFormat.java
index fa2cdc28e3..d77210b5c1 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/TabularFormat.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/TabularFormat.java
@@ -37,7 +37,7 @@ import org.apache.sis.util.resources.Errors;
* <li>{@link #setColumnSeparatorPattern(String)}</li>
* </ul>
*
- * <h2>Note for subclass implementions</h2>
+ * <h2>Note for subclass implementations</h2>
* This base class takes care of splitting a column separator pattern into its
components
* ({@link #beforeFill}, {@link #fillCharacter} and {@link #columnSeparator})
* for easier usage in {@code format(…)} method implementations.
@@ -133,7 +133,7 @@ public abstract class TabularFormat<T> extends
CompoundFormat<T> {
* or {@code null} for the {@linkplain Locale#ROOT root
locale}.
* @param timezone the timezone, or {@code null} for UTC.
*/
- public TabularFormat(final Locale locale, final TimeZone timezone) {
+ protected TabularFormat(final Locale locale, final TimeZone timezone) {
super(locale, timezone);
beforeFill = "";
fillCharacter = ' ';
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageStyling.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageStyling.java
index 0b52887686..8a862b330f 100644
---
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageStyling.java
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageStyling.java
@@ -22,8 +22,6 @@ import java.util.HashMap;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Function;
-import javafx.geometry.Pos;
-import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.MenuItem;
@@ -36,6 +34,7 @@ import org.opengis.util.InternationalString;
import org.apache.sis.coverage.Category;
import org.apache.sis.image.Colorizer;
import org.apache.sis.gui.internal.Resources;
+import org.apache.sis.gui.internal.AlignedTableCell;
import org.apache.sis.gui.internal.ImmutableObjectProperty;
import org.apache.sis.gui.controls.ColorRamp;
import org.apache.sis.gui.controls.ColorColumnHandler;
@@ -201,7 +200,7 @@ final class CoverageStyling extends
ColorColumnHandler<Category>
final TableView<Category> createCategoryTable(final Resources resources,
final Vocabulary vocabulary) {
final var name = new
TableColumn<Category,String>(vocabulary.getString(Vocabulary.Keys.Name));
name.setCellValueFactory(CoverageStyling::getCategoryName);
- name.setCellFactory(CoverageStyling::createNameCell);
+ name.setCellFactory(AlignedTableCell.centerLeft());
name.setEditable(false);
name.setId("name");
/*
@@ -222,17 +221,6 @@ final class CoverageStyling extends
ColorColumnHandler<Category>
return table;
}
- /**
- * Invoked for creating a cell for the "name" column.
- * Returns the JavaFX default cell except for vertical alignment, which is
centered.
- */
- private static TableCell<Category,String> createNameCell(final
TableColumn<Category,String> column) {
- @SuppressWarnings("unchecked")
- final var cell = (TableCell<Category,String>)
TableColumn.DEFAULT_CELL_FACTORY.call(column);
- cell.setAlignment(Pos.CENTER_LEFT);
- return cell;
- }
-
/**
* Invoked when the table needs to render a text in the "Name" column of
the category table.
*/
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/TileMatrixSetPane.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/TileMatrixSetPane.java
index 27df9994e0..df58248410 100644
---
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/TileMatrixSetPane.java
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/TileMatrixSetPane.java
@@ -16,9 +16,11 @@
*/
package org.apache.sis.gui.coverage;
-import java.util.Collection;
+import java.util.Arrays;
import java.util.Locale;
import java.util.List;
+import java.util.Map;
+import java.util.function.IntFunction;
import javafx.collections.ObservableList;
import javafx.beans.value.ObservableValue;
import javafx.beans.property.ObjectProperty;
@@ -38,29 +40,30 @@ import javafx.util.StringConverter;
import org.opengis.util.GenericName;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.metadata.spatial.DimensionNameType;
-import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.tiling.TileMatrix;
import org.apache.sis.storage.tiling.TileMatrixSet;
import org.apache.sis.storage.tiling.TiledResource;
-import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.storage.tiling.TileMatrixSetFormat;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.gui.Widget;
+import org.apache.sis.gui.internal.AlignedTableCell;
import org.apache.sis.gui.internal.BackgroundThreads;
import org.apache.sis.gui.internal.ExceptionReporter;
-import org.apache.sis.util.collection.Containers;
/**
* A visual representation of the internal tile matrices defined in a {@link
TiledResource}.
+ * Each {@link TileMatrix} instance is presented as a row in a table. The
columns are the tile
+ * matrix identifier, the resolution, the number of tiles and (if applicable)
the tile size.
*
- * @todo change the text area to a split pane with a tree view on the left and
a description pane on the right
- * @todo if the resource is writable, add tiling modification controls
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.7
*
- * @author Johann Sorel (Geomatys)
+ * @see TileMatrixSetFormat
*
- * @sinec 1.7
+ * @since 1.7
*/
public class TileMatrixSetPane extends Widget {
/**
@@ -86,12 +89,12 @@ public class TileMatrixSetPane extends Widget {
};
/**
- * The locale for texts in controls, or {@code null} for the default
locale.
+ * The object to use for formatting Tile Matrix Set properties.
*/
- private final Locale locale;
+ private final TileMatrixSetFormat formatter;
/**
- * The data shown in this widget.
+ * The resource for which this widget is showing the Tile Matrix Sets
(<abbr>TMS</abbr>).
*
* @see #getContent()
* @see #setContent(TiledResource)
@@ -104,7 +107,7 @@ public class TileMatrixSetPane extends Widget {
private final ComboBox<TileMatrixSet> tileMatriceSets;
/**
- * The label where to write the name of the Coordinate Reference System.
+ * The label where to write the name of the Coordinate Reference System of
the selected <abbr>TMS</abbr>.
*/
private final Label crsName;
@@ -116,91 +119,106 @@ public class TileMatrixSetPane extends Widget {
/**
* The columns for the resolution along each <abbr>CRS</abbr> axis.
* The number of columns is the number of <abbr>CRS</abbr> dimensions.
+ * Values are {@link Double} numbers, for formatted as {@link String}s
+ * by {@link TileMatrixSetFormat}.
*/
- private final TableColumn<Row, Double> tileResolutionColumns;
+ private final TableColumn<Row, String> tileResolutionColumns;
/**
* The columns for the number of tiles in each dimension.
* The number of columns is the number of grid dimensions.
+ * Values are {@link Long}s, for formatted as {@link String}s by {@link
TileMatrixSetFormat}.
*/
- private final TableColumn<Row, Long> tileCountColumns;
+ private final TableColumn<Row, String> tileCountColumns;
+
+ /**
+ * The columns for the tile size in each dimension.
+ * The number of columns is the number of grid dimensions.
+ * Values are {@link Integer}s, for formatted as {@link String}s by {@link
TileMatrixSetFormat}.
+ */
+ private final TableColumn<Row, String> tileSizeColumns;
/**
* A row in the table of tile matrices shown by {@link #tileMatrices}.
- * The resolution and tile count are group of columns.
+ * The resolution, tile count and tile size are groups of columns.
*/
private static final class Row {
/**
* Property for the Tile Matrix identifier.
*/
- private final SimpleStringProperty identifier;
+ final SimpleStringProperty identifier;
/**
* Resolution along each <abbr>CRS</abbr> dimension.
* They are the values to show in the group of {@link
#tileResolutionColumns}.
*/
- private final SimpleObjectProperty<Double>[] resolution;
+ final SimpleStringProperty[] resolution;
/**
* Number of tiles along each grid dimension.
* They are the values to show in the group {@link #tileCountColumns}.
*/
- private final SimpleObjectProperty<Long>[] tileCount;
+ final SimpleStringProperty[] tileCount;
/**
- * Creates a new row for the given tile matrix.
- *
- * @param matrix the matrix for which to create a row in the table.
+ * Tile size along each grid dimension.
+ * They are the values to show in the group {@link #tileSizeColumns}.
*/
- @SuppressWarnings({"this-escape", "rawtypes", "unchecked"})
- Row(final TileMatrix matrix) {
- identifier = new SimpleStringProperty(this, "identifier");
- final GenericName id = matrix.getIdentifier();
- if (id != null) {
- identifier.setValue(id.toString());
- }
- final double[] r = matrix.getResolution();
- resolution = new SimpleObjectProperty[r.length];
- for (int i=0; i<resolution.length; i++) {
- (resolution[i] = new SimpleObjectProperty<>(this,
"resolution")).set(r[i]);
- }
- final GridExtent extent = matrix.getTilingScheme().getExtent();
- tileCount = new SimpleObjectProperty[extent.getDimension()];
- for (int i=0; i<tileCount.length; i++) {
- (tileCount[i] = new SimpleObjectProperty<>(this,
"tileCount")).set(extent.getSize(i));
- }
- }
+ final SimpleStringProperty[] tileSize;
/**
- * The callback for getting the identifier value of a row.
+ * Creates a new row for the given properties at the specified row
index.
*/
- static final Callback<TableColumn.CellDataFeatures<Row, String>,
ObservableValue<String>>
- IDENTIFIER = (cell) -> cell.getValue().identifier;
+ @SuppressWarnings("this-escape")
+ Row(final int row,
+ final String[] identifiers,
+ final String[][] resolutions,
+ final String[][] tileCounts,
+ final String[][] tileSizes)
+ {
+ identifier = new SimpleStringProperty(this, "identifier",
identifiers == null ? null : identifiers[row]);
+ resolution = new SimpleStringProperty[resolutions != null ?
resolutions.length : 0];
+ tileCount = new SimpleStringProperty[tileCounts != null ?
tileCounts.length : 0];
+ tileSize = new SimpleStringProperty[tileSizes != null ?
tileSizes.length : 0];
+ Arrays.setAll(resolution, (i) -> new SimpleStringProperty(this,
"resolution", resolutions[i][row]));
+ Arrays.setAll(tileCount, (i) -> new SimpleStringProperty(this,
"tileCount", tileCounts [i][row]));
+ Arrays.setAll(tileSize, (i) -> new SimpleStringProperty(this,
"tileSize", tileSizes [i][row]));
+ }
/**
- * The callback for getting resolution values in the specified
<abbr>CRS</abbr> dimension.
+ * Returns the group of columns identified by the given index.
*
- * @param i a <abbr>CRS</abbr> dimension.
- * @return getter of resolution values in the specified
<abbr>CRS</abbr> dimension.
+ * @param groupIndex 1 for resolution, 2 for tile count or 3 for
tile size. 0 is reserved for identifier.
+ * @return group of columns at the given index.
*/
- static Callback<TableColumn.CellDataFeatures<Row, Double>,
ObservableValue<Double>> resolution(final int i) {
- return (cell) -> {
- final SimpleObjectProperty<Double>[] resolution =
cell.getValue().resolution;
- return (i >= 0 && i < resolution.length) ? resolution[i] :
null;
- };
+ @SuppressWarnings("ReturnOfCollectionOrArrayField")
+ final SimpleStringProperty[] group(final int groupIndex) {
+ switch (groupIndex) {
+ case 1: return resolution;
+ case 2: return tileCount;
+ case 3: return tileSize;
+ default: throw new AssertionError(groupIndex);
+ }
}
/**
- * The callback for getting tile count values of a row in the
specified dimension.
+ * The callback for getting the value of the "identifier" column of a
row.
+ */
+ static final Callback<TableColumn.CellDataFeatures<Row, String>,
ObservableValue<String>>
+ IDENTIFIER_GETTER = (cell) -> cell.getValue().identifier;
+
+ /**
+ * The callback for getting the value of a column other than
"identifier".
*
- * @param i a grid dimension.
- * @return getter of tile count values in the specified grid dimension.
+ * @param groupIndex 1 for resolution, 2 for tile count or 3 for
tile size. 0 is reserved for identifier.
+ * @param columnIndex the column index in the specified group. This
is a <abbr>CRS</abbr> or grid dimension.
+ * @return getter of values in the specified column of the specified
group of columns.
*/
- static Callback<TableColumn.CellDataFeatures<Row, Long>,
ObservableValue<Long>> tileCount(final int i) {
- return (cell) -> {
- final SimpleObjectProperty<Long>[] tileCount =
cell.getValue().tileCount;
- return (i >= 0 && i < tileCount.length) ? tileCount[i] : null;
- };
+ static Callback<TableColumn.CellDataFeatures<Row, String>,
ObservableValue<String>>
+ getter(final int groupIndex, final int columnIndex)
+ {
+ // Index should never be out of bounds because of the way that we
built the arrays.
+ return (cell) -> cell.getValue().group(groupIndex)[columnIndex];
}
}
@@ -226,11 +244,11 @@ public class TileMatrixSetPane extends Widget {
*/
@SuppressWarnings("this-escape")
public TileMatrixSetPane(final Locale locale) {
+ formatter = new TileMatrixSetFormat(locale, null);
view = new GridPane();
view.setPadding(SPACE_ON_TOP);
view.setVgap(9);
view.setHgap(12);
- this.locale = locale;
final Vocabulary vocabulary = Vocabulary.forLocale(locale);
/*
* Combox box for choosing the Tile Matrix Set.
@@ -270,12 +288,13 @@ public class TileMatrixSetPane extends Widget {
identifier = new
TableColumn<>(vocabulary.getString(Vocabulary.Keys.Identifier));
tileResolutionColumns = new
TableColumn<>(vocabulary.getString(Vocabulary.Keys.Resolution));
tileCountColumns = new
TableColumn<>(vocabulary.getString(Vocabulary.Keys.TileCount));
- identifier.setCellValueFactory(Row.IDENTIFIER);
+ tileSizeColumns = new
TableColumn<>(vocabulary.getString(Vocabulary.Keys.TileSize));
+ identifier.setCellValueFactory(Row.IDENTIFIER_GETTER);
tileMatrices = new TableView<>();
tileMatrices.setEditable(false);
tileMatrices.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_SUBSEQUENT_COLUMNS);
- tileMatrices.getColumns().setAll(List.of(identifier,
tileResolutionColumns, tileCountColumns));
+ tileMatrices.getColumns().setAll(List.of(identifier,
tileResolutionColumns, tileCountColumns, tileSizeColumns));
GridPane.setHgrow(tileMatrices, Priority.ALWAYS);
GridPane.setVgrow(tileMatrices, Priority.ALWAYS);
GridPane.setColumnSpan(tileMatrices, 2);
@@ -286,6 +305,45 @@ public class TileMatrixSetPane extends Widget {
tileMatriceSets.getSelectionModel().selectedItemProperty().addListener((p,o,n)
-> tileMatrixSetChanged(n));
}
+ /**
+ * Adds or removes columns in the "resolution", "tile count" or "tile
size" group of columns.
+ * After this method calls, the number of columns of the group will be
equal to {@code numCols}.
+ * If that number is zero, the element is removed from the table.
+ *
+ * @param row any row that can be used for determining {@code
numCols}, or {@code null} if none.
+ * @param groupIndex 1 for resolution, 2 for tile count or 3 for tile
size. 0 is reserved for identifier.
+ * @param group group of columns identified by {@code groupIndex}.
+ * @param headerText provider of labels for the column header. The
function can return {@code null}.
+ */
+ private void addOrRemoveColumns(final Row row, final int groupIndex,
+ final TableColumn<Row, String> group,
+ final IntFunction<String> headerText)
+ {
+ final int numCols = (row != null) ? row.group(groupIndex).length : 0;
+ final ObservableList<TableColumn<Row, ?>> columns = group.getColumns();
+ for (int columnIndex = 0; columnIndex < numCols; columnIndex++) {
+ String header = headerText.apply(columnIndex);
+ if (header == null) {
+ header = String.valueOf(columnIndex);
+ }
+ if (columnIndex < columns.size()) {
+ columns.get(columnIndex).setText(header);
+ } else {
+ final var column = new TableColumn<Row, String>(header);
+ column.setCellFactory(AlignedTableCell.baselineRight());
+ column.setCellValueFactory(Row.getter(groupIndex,
columnIndex));
+ columns.add(column);
+ }
+ }
+ columns.remove(numCols, columns.size());
+ final ObservableList<TableColumn<Row, ?>> table =
tileMatrices.getColumns();
+ if (columns.isEmpty()) {
+ table.remove(group);
+ } else if (!table.contains(group)) {
+ table.add(group);
+ }
+ }
+
/**
* Sets the data for which to show the tiling.
* This is a convenience method for setting {@link #contentProperty} value.
@@ -365,91 +423,57 @@ public class TileMatrixSetPane extends Widget {
tileMatrices.getItems().clear();
crsName.setText(null);
if (newValue != null) {
- BackgroundThreads.execute(new Task<TileMatrix[]>() {
- /** The coordinate reference system. */
- private CoordinateReferenceSystem crs;
-
- /** Name of the Coordinate Reference System. */
- private String crsDisplayName;
-
- /** The grid extent of the tiling scheme, using the first row
as a representative value. */
- private GridExtent tilingScheme;
-
- /** Fetches the Tile Matrices in a background thread. */
- @Override protected TileMatrix[] call() {
- crs = newValue.getCoordinateReferenceSystem();
- if (crs != null) {
- crsDisplayName = IdentifiedObjects.getDisplayName(crs,
locale);
- }
- final Collection<? extends TileMatrix> matrices =
newValue.getTileMatrices().values();
- final TileMatrix first = Containers.peekFirst(matrices);
- if (first != null) {
- tilingScheme = first.getTilingScheme().getExtent();
+ BackgroundThreads.execute(new Task<Map<?,?>>() {
+ /** The number of rows. */
+ private int numRows;
+
+ /** The warning, or {@code null} if none. */
+ private Throwable warning;
+
+ /** Fetches the Tile Matrix properties in a background thread.
*/
+ @Override protected Map<?,?> call() {
+ final Map<String, Object> properties;
+ synchronized (formatter) {
+ try {
+ properties = formatter.formatHeader(newValue);
+ numRows =
formatter.formatTable(newValue.getTileMatrices().values(), properties);
+ warning = formatter.getError().orElse(null);
+ } finally {
+ formatter.clear();
+ }
}
- return matrices.toArray(TileMatrix[]::new);
+ return properties;
}
/** Invoked in JavaFX thread on success. */
@Override protected void succeeded() {
- crsName.setText(crsDisplayName);
- final TileMatrix[] matrices = getValue();
- final Row[] rows = new Row[matrices.length];
- int crsDimension = 0, gridDimension = 0;
+ final Map<?,?> properties = getValue();
+ crsName.setText((String) properties.get("crsName"));
+ final var identifiers = (String[])
properties.get("identifiers");
+ final var resolutions = (String[][])
properties.get("resolutions");
+ final var tileCounts = (String[][])
properties.get("tileCounts");
+ final var tileSizes = (String[][])
properties.get("tileSizes");
+ final var rows = new Row[numRows];
for (int i=0; i<rows.length; i++) {
- final var row = new Row(matrices[i]);
- crsDimension = Math.max(crsDimension,
row.resolution.length);
- gridDimension = Math.max(gridDimension,
row.tileCount.length);
- rows[i] = row;
+ rows[i] = new Row(i, identifiers, resolutions,
tileCounts, tileSizes);
}
tileMatrices.getItems().setAll(rows);
- /*
- * Add or remove columns for the CRS dimensions.
- */
- {
- final CoordinateSystem cs = (crs != null) ?
crs.getCoordinateSystem() : null;
- final ObservableList<TableColumn<Row, ?>> columns =
tileResolutionColumns.getColumns();
- for (int i=0; i<crsDimension; i++) {
- final String header = (i < cs.getDimension())
- ? cs.getAxis(i).getAbbreviation() :
String.valueOf(i);
- if (i < columns.size()) {
- columns.get(i).setText(header);
- } else {
- final var column = new TableColumn<Row,
Double>(header);
- column.setCellValueFactory(Row.resolution(i));
- columns.add(column);
- }
- }
- final int size = columns.size();
- if (crsDimension < size) {
- columns.remove(crsDimension, size);
- }
- }
- /*
- * Add or remove columns for the grid dimensions.
- * We use the first row for getting the grid axis
identifiers.
- */
- final ObservableList<TableColumn<Row, ?>> columns =
tileCountColumns.getColumns();
- if (rows.length != 0) {
- for (int i=0; i<gridDimension; i++) {
- String header = null;
- if (i < tilingScheme.getDimension()) {
- header =
tilingScheme.getAxisType(i).flatMap(DimensionNameType::identifier).orElse(null);
- }
- if (header == null) {
- header = String.valueOf(i);
- }
- if (i < columns.size()) {
- columns.get(i).setText(header);
- } else {
- final var column = new TableColumn<Row,
Long>(header);
- column.setCellValueFactory(Row.tileCount(i));
- columns.add(column);
- }
- }
- }
- final int size = columns.size();
- if (crsDimension < size) {
- columns.remove(crsDimension, size);
+ final Row first = (rows.length != 0) ? rows[0] : null;
+ final var crs = (CoordinateReferenceSystem)
properties.get("crs");
+ final CoordinateSystem cs = (crs != null) ?
crs.getCoordinateSystem() : null;
+ addOrRemoveColumns(first, 1, tileResolutionColumns,
(columnIndex) -> {
+ if (cs == null || columnIndex >= cs.getDimension())
return null;
+ return cs.getAxis(columnIndex).getAbbreviation();
+ });
+ final var gridAxes = (String[]) properties.get("gridAxes");
+ final IntFunction<String> headerText = (columnIndex) -> {
+ if (gridAxes == null || columnIndex >=
gridAxes.length) return null;
+ return gridAxes[columnIndex];
+ };
+ addOrRemoveColumns(first, 2, tileCountColumns, headerText);
+ addOrRemoveColumns(first, 3, tileSizeColumns, headerText);
+ if (warning != null) {
+ reportError(warning);
}
}
@@ -463,6 +487,8 @@ public class TileMatrixSetPane extends Widget {
/**
* Invoked when a background task failed.
+ *
+ * @todo Should write the message somewhere instead of a window popup.
*/
private void reportError(final Throwable exception) {
ExceptionReporter.canNotUseResource(view, exception);
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/AlignedTableCell.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/AlignedTableCell.java
new file mode 100644
index 0000000000..a21516d1dd
--- /dev/null
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/AlignedTableCell.java
@@ -0,0 +1,87 @@
+/*
+ * 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.internal;
+
+import javafx.geometry.Pos;
+import javafx.scene.control.TableCell;
+import javafx.scene.control.TableColumn;
+import javafx.util.Callback;
+
+
+/**
+ * A factory of table cell with some alignment.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ *
+ * @param <S> type of rows in the table.
+ * @param <V> type of values in the table.
+ */
+public final class AlignedTableCell<S,T> implements Callback<TableColumn<S,T>,
TableCell<S,T>> {
+ /**
+ * A factory of cells that are vertically centered.
+ */
+ private static final AlignedTableCell<?,?> CENTER_LEFT = new
AlignedTableCell<>(Pos.CENTER_LEFT);
+
+ /**
+ * A factory of cells that are right-aligned.
+ */
+ private static final AlignedTableCell<?,?> BASELINE_RIGHT = new
AlignedTableCell<>(Pos.BASELINE_RIGHT);
+
+ /**
+ * The desired alignment of text in the field.
+ */
+ private final Pos alignment;
+
+ /**
+ * Creates a new factory.
+ *
+ * @param alignment the desired alignment of text in the field
+ */
+ private AlignedTableCell(final Pos alignment) {
+ this.alignment = alignment;
+ }
+
+ /**
+ * Creates a new cell.
+ *
+ * @param column the column for which to create a cell.
+ * @return cell factory for the given column.
+ */
+ @Override
+ public TableCell<S,T> call(final TableColumn<S,T> column) {
+ @SuppressWarnings("unchecked")
+ final var cell = (TableCell<S,T>)
TableColumn.DEFAULT_CELL_FACTORY.call(column);
+ cell.setAlignment(alignment);
+ return cell;
+ }
+
+ /**
+ * Returns a factory of cells that are vertically centered.
+ */
+ @SuppressWarnings("unchecked")
+ public static <S,T> AlignedTableCell<S,T> centerLeft() {
+ return (AlignedTableCell<S,T>) CENTER_LEFT;
+ }
+
+ /**
+ * Returns a factory of cells that are right-aligned.
+ */
+ @SuppressWarnings("unchecked")
+ public static <S,T> AlignedTableCell<S,T> baselineRight() {
+ return (AlignedTableCell<S,T>) BASELINE_RIGHT;
+ }
+}