blob: e5fa451e0be1ef91f8807ebbfda4d9f632b3d7d1 [file] [log] [blame]
/*
* 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.internal.processing.isoline;
import java.util.Map;
import java.awt.Shape;
import java.awt.geom.PathIterator;
import java.awt.image.DataBuffer;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.internal.coverage.j2d.RasterFactory;
import org.apache.sis.test.TestCase;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Tests {@link Isolines}.
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
* @version 1.1
* @since 1.1
* @module
*/
public final strictfp class IsolinesTest extends TestCase {
/**
* Tolerance threshold for rounding errors. Needs to take in account that
* {@link org.apache.sis.internal.feature.j2d.Polyline} stores coordinate
* values a single-precision {@code float} numbers.
*/
private static final double TOLERANCE = 1E-8;
/**
* The threshold value. This is used by {@code generateFromXXX(…)} methods.
*/
private double threshold;
/**
* The isoline being tested. This is set by {@code generateFromXXX(…)} methods.
*/
private Shape isoline;
/**
* Tests isolines computed in a contouring grid having only one cell.
* The cell may have zero, one or two line segments.
*
* @throws TransformException if a point can not be transformed to its final coordinate space.
*/
@Test
public void testSingleCell() throws TransformException {
threshold = -5;
generateFromCell(0, 0, 0, 0);
assertNull(isoline);
threshold = 2;
generateFromCell(0, 0, 0, 0);
assertNull(isoline);
/*
* ▘5╌╌╌╌1
* ╎ ╎
* 0╌╌╌╌0
*/
generateFromCell(5, 1, 0, 0);
assertSegmentEquals(0, 0.6, 0.75, 0);
/*
* 0╌╌╌╌5▝
* ╎ ╎
* ▖10╌╌20▗
*/
generateFromCell(0, 5, 10, 20);
assertSegmentEquals(0, 0.2, 0.4, 0);
/*
* 1╌╌╌╌5▝
* ╎ ╎
* 0╌╌╌╌0
*/
generateFromCell(1, 5, 0, 0);
assertSegmentEquals(0.25, 0, 1, 0.6);
/*
* ▘5╌╌╌╌0
* ╎ ╎
* ▖20╌╌10▗
*/
generateFromCell(5, 0, 20, 10);
assertSegmentEquals(0.6, 0, 1, 0.2);
/*
* 1╌╌╌╌0
* ╎ ╎
* ▖5╌╌╌10▗
*/
generateFromCell(1, 0, 5, 10);
assertSegmentEquals(0, 0.25, 1, 0.2);
/*
* ▘5╌╌╌10▝
* ╎ ╎
* 1╌╌╌╌0
*/
generateFromCell(5, 10, 1, 0);
assertSegmentEquals(0, 0.75, 1, 0.8);
/*
* 0╌╌╌╌0
* ╎ ╎
* 1╌╌╌╌5▗
*/
generateFromCell(0, 0, 1, 5);
assertSegmentEquals(1, 0.4, 0.25, 1);
/*
* ▘20╌╌10▝
* ╎ ╎
* ▖5╌╌╌╌0
*/
generateFromCell(20, 10, 5, 0);
assertSegmentEquals(1, 0.8, 0.6, 1);
/*
* 1╌╌╌11▝
* ╎ ╎
* ▖5╌╌╌╌1
*/
generateFromCell(1, 11, 5, 1);
assertSegmentsEqual(0, 0.25, 0.1, 0,
1, 0.9, 0.75, 1);
/*
* ▘11╌╌╌1
* ╎ ╎
* 1╌╌╌╌5▗
*/
generateFromCell(11, 1, 1, 5);
assertSegmentsEqual(0.9, 0, 1, 0.25,
0, 0.9, 0.25, 1);
/*
* ▘5╌╌╌╌1
* ╎ ╎
* ▖10╌╌╌0
*/
generateFromCell(5, 1, 10, 0);
assertSegmentEquals(0.75, 0, 0.8, 1);
/*
* 1╌╌╌╌5▝
* ╎ ╎
* 0╌╌╌10▗
*/
generateFromCell(1, 5, 0, 10);
assertSegmentEquals(0.25, 0, 0.2, 1);
/*
* 0╌╌╌╌0
* ╎ ╎
* ▖5╌╌╌╌1
*/
generateFromCell(0, 0, 5, 1);
assertSegmentEquals(0, 0.4, 0.75, 1);
/*
* ▘10╌╌20▝
* ╎ ╎
* 0╌╌╌╌5▗
*/
generateFromCell(10, 20, 0, 5);
assertSegmentEquals(0, 0.8, 0.4, 1);
}
/**
* Tests isolines computed in a contouring grid having 2×2 cells.
*
* @throws TransformException if a point can not be transformed to its final coordinate space.
*/
@Test
public void testMultiCells() throws TransformException {
threshold = 0.5;
generateFromImage(3,
0,0,0,
0,1,0,
0,0,0);
verifyIsolineFromMultiCells(isoline);
}
/**
* Verifies the isoline generated for level 0.5 on an image of 3×3 pixels having value 1 in the center
* and value zero everywhere else. This is the isoline tested by {@link #testMultiCells()}.
*
* @param isoline the isoline to verify.
*/
public static void verifyIsolineFromMultiCells(final Shape isoline) {
/*
* Expected coordinates:
*
* (1): 1.5 1.0 (2)
* (2): 1.0 0.5 (3) (1)
* (3): 0.5 1.0 (4)
* (4): 1.0 1.5
*/
final double[] buffer = new double[2];
final PathIterator it = isoline.getPathIterator(null);
assertSegmentEquals(it, buffer, 1.5, 1, 1, 0.5);
assertFalse(it.isDone());
assertEquals(PathIterator.SEG_LINETO, it.currentSegment(buffer));
assertEquals("x2", 0.5, buffer[0], TOLERANCE);
assertEquals("y2", 1, buffer[1], TOLERANCE);
it.next();
assertFalse(it.isDone());
assertEquals(PathIterator.SEG_LINETO, it.currentSegment(buffer));
assertEquals("x3", 1, buffer[0], TOLERANCE);
assertEquals("y3", 1.5, buffer[1], TOLERANCE);
it.next();
assertFalse(it.isDone());
assertEquals(PathIterator.SEG_CLOSE, it.currentSegment(buffer));
it.next();
assertTrue(it.isDone());
}
/**
* Tests isolines computed in a contouring grid having more than one band.
* The same values than {@link #testSingleCell()} are used.
*
* @throws TransformException if a point can not be transformed to its final coordinate space.
*/
@Test
public void testSingleCellMultiBands() throws TransformException {
final BufferedImage image = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB);
final WritableRaster raster = image.getRaster();
threshold = 2;
/*
* 0╌╌╌╌5▝ 1╌╌╌╌5▝ ▘11╌╌╌1
* ╎ ╎ ╎ ╎ ╎ ╎
* ▖10╌╌20▗ 0╌╌╌╌0 1╌╌╌╌5▗
*/
raster.setPixel(0, 0, new int[] { 0, 1, 11});
raster.setPixel(1, 0, new int[] { 5, 5, 1});
raster.setPixel(0, 1, new int[] {10, 0, 1});
raster.setPixel(1, 1, new int[] {20, 0, 5});
final Isolines[] isolines = Isolines.generate(image, new double[][] {{threshold}}, null);
assertEquals("Number of bands", 3, isolines.length);
for (int b=0; b<3; b++) {
final Map<Double, Shape> polylines = isolines[b].polylines();
assertEquals(1, polylines.size());
isoline = polylines.get(threshold);
switch (b) {
case 0: assertSegmentEquals(0, 0.2, 0.4, 0); break;
case 1: assertSegmentEquals(0.25, 0, 1, 0.6); break;
case 2: assertSegmentsEqual(0.9, 0, 1, 0.25,
0, 0.9, 0.25, 1);
}
}
}
/**
* Tests isolines computed in a contouring grid having more than one band.
* The same values than {@link #testMultiCells()} are used, but it tests a different
* code path because {@link Isolines} contains a special case for one-banded image.
*
* @throws TransformException if a point can not be transformed to its final coordinate space.
*/
@Test
public void testMultiCellsMultiBands() throws TransformException {
final BufferedImage image = new BufferedImage(3, 3, BufferedImage.TYPE_INT_RGB);
final WritableRaster raster = image.getRaster();
raster.setSample(1, 1, 1, 6);
threshold = 3;
final Isolines[] isolines = Isolines.generate(image, new double[][] {{threshold}}, null);
assertEquals("Number of bands", 3, isolines.length);
assertTrue(isolines[0].polylines().isEmpty());
assertTrue(isolines[2].polylines().isEmpty());
isoline = isolines[1].polylines().get(threshold);
verifyIsolineFromMultiCells(isoline);
}
/**
* Tests a cell containing a NaN value.
*
* @throws TransformException if a point can not be transformed to its final coordinate space.
*/
@Test
public void testNaN() throws TransformException {
threshold = 2;
/*
* ▘5╌╌NaN▝
* ╎ ╎
* 0╌╌╌╌0
*/
generateFromCell(5, Float.NaN, 0, 0);
assertNull(isoline);
}
/**
* Generates isolines from a 2×2 image having the given values.
* The result is stored in {@link #isoline}; it may be {@code null}.
*
* @throws TransformException if a point can not be transformed to its final coordinate space.
*/
private void generateFromCell(float v00, float v10, float v01, float v11) throws TransformException {
generateFromImage(2, v00, v10, v01, v11);
}
/**
* Generates isolines from a size×size image having the given values.
* The result is stored in {@link #isoline}; it may be {@code null}.
*
* @throws TransformException if a point can not be transformed to its final coordinate space.
*/
private void generateFromImage(final int size, final float... values) throws TransformException {
final BufferedImage image = RasterFactory.createGrayScaleImage(DataBuffer.TYPE_FLOAT, size, size, 1, 0, 0, 10);
final WritableRaster raster = image.getRaster();
for (int i=0; i<values.length; i++) {
raster.setSample(i % size, i / size, 0, values[i]);
}
final Isolines[] isolines = Isolines.generate(image, new double[][] {{threshold}}, null);
assertEquals("Number of bands", 1, isolines.length);
final Map<Double, Shape> polylines = isolines[0].polylines();
assertTrue(polylines.size() <= 1);
isoline = polylines.get(threshold);
}
/**
* Asserts that {@link #isoline} is a segment having the given coordinates.
*/
private void assertSegmentEquals(final double x0, final double y0, final double x1, final double y1) {
assertNotNull("isoline", isoline);
final double[] buffer = new double[2];
final PathIterator it = isoline.getPathIterator(null);
assertSegmentEquals(it, buffer, x0, y0, x1, y1);
assertTrue(it.isDone());
}
/**
* Asserts that the iterator contains a {@code SEG_MOVETO} followed by a {@code SEG_LINETO} instruction
* with the given coordinates. The segment is positioned on next segment after this method call.
*/
private static void assertSegmentEquals(final PathIterator it, final double[] buffer,
final double x0, final double y0, final double x1, final double y1)
{
assertFalse(it.isDone());
assertEquals(PathIterator.SEG_MOVETO, it.currentSegment(buffer));
assertEquals("x0", x0, buffer[0], TOLERANCE);
assertEquals("y0", y0, buffer[1], TOLERANCE);
it.next();
assertFalse(it.isDone());
assertEquals(PathIterator.SEG_LINETO, it.currentSegment(buffer));
assertEquals("x1", x1, buffer[0], TOLERANCE);
assertEquals("y1", y1, buffer[1], TOLERANCE);
it.next();
}
/**
* Asserts that {@link #isoline} is two segments having the given coordinates.
*/
private void assertSegmentsEqual(final double x0, final double y0, final double x1, final double y1,
final double x2, final double y2, final double x3, final double y3)
{
assertNotNull("isoline", isoline);
final double[] buffer = new double[2];
final PathIterator it = isoline.getPathIterator(null);
assertSegmentEquals(it, buffer, x0, y0, x1, y1);
assertSegmentEquals(it, buffer, x2, y2, x3, y3);
assertTrue(it.isDone());
}
}