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 e05a30a6daae3e833f9f157dd10568dbef613a35 Author: Martin Desruisseaux <[email protected]> AuthorDate: Wed Feb 3 00:10:48 2021 +0100 Add a `CoverageCanvasApp` for manual & visual testing of `CoverageCanvas`. --- .../org/apache/sis/gui/coverage/RenderingData.java | 3 +- .../java/org/apache/sis/gui/map/MapCanvas.java | 7 +- .../apache/sis/gui/coverage/CoverageCanvasApp.java | 147 +++++++++++++++++++++ .../org/apache/sis/gui/coverage/GridViewApp.java | 17 ++- .../apache/sis/coverage/grid/SliceGeometry.java | 6 +- .../java/org/apache/sis/image/TiledImageMock.java | 30 ++++- 6 files changed, 201 insertions(+), 9 deletions(-) diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java index 6e08f03..c6f768f 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java @@ -530,10 +530,11 @@ final class RenderingData implements Cloneable { * Converts the given bounds from objective coordinates to pixel coordinates in the source coverage. * * @param bounds objective coordinates. - * @return data coverage cell coordinates (in pixels). + * @return data coverage cell coordinates (in pixels), or {@code null} if unknown. * @throws TransformException if the bounds can not be transformed. */ final Rectangle objectiveToData(final Rectangle2D bounds) throws TransformException { + if (objectiveToCenter == null) return null; return (Rectangle) Shapes2D.transform(MathTransforms.bidimensional(objectiveToCenter), bounds, new Rectangle()); } diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java index 8d53ffc..a5f403b 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java @@ -945,7 +945,12 @@ public abstract class MapCanvas extends PlanarCanvas { crsToDisplay = MathTransforms.linear(m); if (objectiveCRS == null) { objectiveCRS = extent.toEnvelope(crsToDisplay.inverse()).getCoordinateReferenceSystem(); - // CRS computed above should not be null. + /* + * Above code tried to provide a non-null CRS on a "best effort" basis. The objective CRS + * may still be null, there is no obvious answer against that. It is not the display CRS + * if the "display to objective" transform is not identity. A grid CRS is not appropriate + * neither, otherwise `extent.toEnvelope(…)` would have found it. + */ } } else { objectiveCRS = getDisplayCRS(); diff --git a/application/sis-javafx/src/test/java/org/apache/sis/gui/coverage/CoverageCanvasApp.java b/application/sis-javafx/src/test/java/org/apache/sis/gui/coverage/CoverageCanvasApp.java new file mode 100644 index 0000000..ab7964a --- /dev/null +++ b/application/sis-javafx/src/test/java/org/apache/sis/gui/coverage/CoverageCanvasApp.java @@ -0,0 +1,147 @@ +/* + * 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.coverage; + +import java.util.Random; +import java.awt.Point; +import java.awt.image.DataBuffer; +import javafx.application.Application; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.scene.layout.BorderPane; +import javafx.scene.Scene; +import javafx.stage.Stage; +import org.opengis.referencing.datum.PixelInCell; +import org.apache.sis.coverage.grid.GridCoverage2D; +import org.apache.sis.coverage.grid.GridGeometry; +import org.apache.sis.gui.map.StatusBar; +import org.apache.sis.image.TiledImageMock; +import org.apache.sis.image.WritablePixelIterator; +import org.apache.sis.internal.gui.BackgroundThreads; +import org.apache.sis.referencing.CommonCRS; +import org.apache.sis.referencing.operation.transform.MathTransforms; + + +/** + * Shows {@link CoverageCanvas} with random data. The image will have small tiles of size + * {@value #TILE_WIDTH}×{@value #TILE_HEIGHT}. The image will artificially fails to provide + * some tiles in order to test error controls. + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.1 + * @since 1.1 + * @module + */ +public class CoverageCanvasApp extends Application implements ChangeListener<Throwable> { + /** + * Size of the artificial tiles. Should be small enough so we can have many of them. + * Width and height should be different in order to increase the chance to see bugs + * if some code confuse them. + */ + private static final int TILE_WIDTH = 60, TILE_HEIGHT = 50; + + + /** + * Starts the test application. + * + * @param args ignored. + */ + public static void main(final String[] args) { + launch(args); + } + + /** + * Creates and starts the test application. + * + * @param window where to show the application. + */ + @Override + public void start(final Stage window) { + final CoverageCanvas canvas = new CoverageCanvas(); + canvas.errorProperty().addListener(this); + final StatusBar statusBar = new StatusBar(null, canvas); + canvas.statusBar = statusBar; + canvas.setCoverage(createImage()); + final BorderPane pane = new BorderPane(canvas.getView()); + pane.setBottom(statusBar.getView()); + window.setTitle("CoverageCanvas Test"); + window.setScene(new Scene(pane)); + window.setWidth (800); + window.setHeight(600); + window.show(); + } + + /** + * Stops background threads for allowing JVM to exit. + * + * @throws Exception if an error occurred while stopping the threads. + */ + @Override + public void stop() throws Exception { + BackgroundThreads.stop(); + super.stop(); + } + + /** + * Creates a dummy image for testing purpose. Some tiles will + * have artificial errors in order to see the error controls. + */ + private static GridCoverage2D createImage() { + final Random random = new Random(); + final int width = TILE_WIDTH * 15; + final int height = TILE_HEIGHT * 12; + final TiledImageMock image = new TiledImageMock( + DataBuffer.TYPE_BYTE, 1, + random.nextInt(50) - 25, // minX + random.nextInt(50) - 25, // minY + width, height, + TILE_WIDTH, TILE_HEIGHT, + random.nextInt(10) - 5, // minTileX + random.nextInt(10) - 5, // minTileY + false); + image.validate(); + final double sc = 500d / Math.max(width, height); + final WritablePixelIterator it = WritablePixelIterator.create(image); + while (it.next()) { + final Point p = it.getPosition(); + final double d = Math.hypot(p.x - width/2, p.y - height/2); + int value = 0; + if ((Math.round(d) & 16) == 0) { + value = Math.max(0, 255 - (int) (d * sc)); + } + it.setSample(0, value); + } +// image.failRandomly(random); + return new GridCoverage2D(new GridGeometry(null, PixelInCell.CELL_CORNER, + MathTransforms.identity(2), CommonCRS.Engineering.DISPLAY.crs()), null, image); + } + + /** + * Invoked when an exception occurred during rendering. + * + * @param property the {@link CoverageCanvas#errorProperty()}. + * @param oldValue the previous error, or {@code null} if none. + * @param newValue the new error, or {@code null} if cleared. + */ + @Override + @SuppressWarnings("CallToPrintStackTrace") + public void changed(ObservableValue<? extends Throwable> property, Throwable oldValue, Throwable newValue) { + if (newValue != null) { + newValue.printStackTrace(); + } + } +} diff --git a/application/sis-javafx/src/test/java/org/apache/sis/gui/coverage/GridViewApp.java b/application/sis-javafx/src/test/java/org/apache/sis/gui/coverage/GridViewApp.java index c416872..bc457ce 100644 --- a/application/sis-javafx/src/test/java/org/apache/sis/gui/coverage/GridViewApp.java +++ b/application/sis-javafx/src/test/java/org/apache/sis/gui/coverage/GridViewApp.java @@ -23,6 +23,7 @@ import javafx.scene.layout.BorderPane; import javafx.scene.Scene; import javafx.stage.Stage; import org.apache.sis.image.TiledImageMock; +import org.apache.sis.internal.gui.BackgroundThreads; /** @@ -59,9 +60,8 @@ public final strictfp class GridViewApp extends Application { */ @Override public void start(final Stage window) { - final GridView view = new GridView(); - final BorderPane pane = new BorderPane(); - pane.setCenter(view); + final GridView view = new GridView(); + final BorderPane pane = new BorderPane(view); window.setTitle("GridView Test"); window.setScene(new Scene(pane)); window.setWidth (400); @@ -71,6 +71,17 @@ public final strictfp class GridViewApp extends Application { } /** + * Stops background threads for allowing JVM to exit. + * + * @throws Exception if an error occurred while stopping the threads. + */ + @Override + public void stop() throws Exception { + BackgroundThreads.stop(); + super.stop(); + } + + /** * Creates a dummy image for testing purpose. Some tiles will * have artificial errors in order to see the error controls. */ diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/SliceGeometry.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/SliceGeometry.java index 4c03da2..f222077 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/SliceGeometry.java +++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/SliceGeometry.java @@ -241,8 +241,10 @@ final class SliceGeometry implements Function<RenderedImage, GridGeometry> { } final LinearTransform translation = MathTransforms.translation(offset); final MathTransformsOrFactory f = MathTransformsOrFactory.wrap(factory); - gridToCRS = f.concatenate(translation, gridToCRS); - cornerToCRS = f.concatenate(translation, cornerToCRS); + if (gridToCRS != null) { + gridToCRS = f.concatenate(translation, gridToCRS); + cornerToCRS = f.concatenate(translation, cornerToCRS); + } } extent = relativeExtent; } diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/TiledImageMock.java b/core/sis-feature/src/test/java/org/apache/sis/image/TiledImageMock.java index 6dbc034..8e93266 100644 --- a/core/sis-feature/src/test/java/org/apache/sis/image/TiledImageMock.java +++ b/core/sis-feature/src/test/java/org/apache/sis/image/TiledImageMock.java @@ -17,8 +17,13 @@ package org.apache.sis.image; import java.awt.Point; +import java.awt.Transparency; +import java.awt.color.ColorSpace; import java.awt.image.ColorModel; +import java.awt.image.DataBuffer; import java.awt.image.BandedSampleModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.ComponentSampleModel; import java.awt.image.ImagingOpException; import java.awt.image.PixelInterleavedSampleModel; import java.awt.image.Raster; @@ -116,6 +121,12 @@ public final strictfp class TiledImageMock extends PlanarImage implements Writab private AtomicInteger errorSequence; /** + * The color model, created only if requested. + * This is needed only for visualizing the image on screen; most tests do not need it. + */ + private ColorModel colorModel; + + /** * Creates a new tiled image. Testers should invoke {@link #validate()} after construction. * * @param dataType sample data type as one of the {@link java.awt.image.DataBuffer} constants. @@ -154,9 +165,24 @@ public final strictfp class TiledImageMock extends PlanarImage implements Writab } /** - * No color model since this test images is not for rendering on screen. + * Returns a gray scale color model if the data type is byte, or {@code null} otherwise. + * More color models may be supported in future versions if there is a need for them. */ - @Override public ColorModel getColorModel() {return null;} + @Override + public ColorModel getColorModel() { + if (colorModel == null && sampleModel instanceof ComponentSampleModel && sampleModel.getNumBands() == 1) { + final int dataType = sampleModel.getDataType(); + if (dataType <= DataBuffer.TYPE_USHORT) { + colorModel = new ComponentColorModel( + ColorSpace.getInstance(ColorSpace.CS_GRAY), + new int[] {DataBuffer.getDataTypeSize(dataType)}, + false, true, Transparency.OPAQUE, dataType); + } + } + return colorModel; + } + + /** Returns a sample model for data type given to the constructor. */ @Override public SampleModel getSampleModel() {return sampleModel;} /*
