-
Notifications
You must be signed in to change notification settings - Fork 194
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
See #11487: Have josm render data to tiles
Start adding basic rendering tests for tiled rendering. Right now, the test only looks at a point in the center of the tile; there seems to be some positioning/stretching issues at the edges that I need to debug and fix. git-svn-id: https://josm.openstreetmap.de/svn/trunk@19220 0c6e7542-c601-0410-84e7-c038aed88b3b
- Loading branch information
taylor.smock
committed
Sep 11, 2024
1 parent
7e5b840
commit 3763d44
Showing
5 changed files
with
253 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
148 changes: 148 additions & 0 deletions
148
test/unit/org/openstreetmap/josm/data/osm/visitor/paint/StyledTiledMapRendererTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
// License: GPL. For details, see LICENSE file. | ||
package org.openstreetmap.josm.data.osm.visitor.paint; | ||
|
||
import static org.openstreetmap.josm.gui.mappaint.MapCSSRendererTest.assertImageEquals; | ||
|
||
import java.awt.Graphics2D; | ||
import java.awt.image.BufferedImage; | ||
import java.io.File; | ||
import java.io.IOException; | ||
import java.io.UncheckedIOException; | ||
import java.nio.file.Files; | ||
import java.nio.file.Paths; | ||
import java.util.Arrays; | ||
import java.util.Map; | ||
import java.util.concurrent.ExecutionException; | ||
import java.util.concurrent.ExecutorService; | ||
import java.util.function.Function; | ||
import java.util.function.Supplier; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.IntStream; | ||
import java.util.stream.Stream; | ||
|
||
import javax.imageio.ImageIO; | ||
|
||
import org.apache.commons.jcs3.access.CacheAccess; | ||
import org.awaitility.Awaitility; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.Arguments; | ||
import org.junit.jupiter.params.provider.MethodSource; | ||
import org.openstreetmap.josm.TestUtils; | ||
import org.openstreetmap.josm.data.Bounds; | ||
import org.openstreetmap.josm.data.cache.JCSCacheManager; | ||
import org.openstreetmap.josm.data.coor.LatLon; | ||
import org.openstreetmap.josm.data.osm.DataSet; | ||
import org.openstreetmap.josm.data.osm.Node; | ||
import org.openstreetmap.josm.data.osm.OsmPrimitive; | ||
import org.openstreetmap.josm.gui.MainApplication; | ||
import org.openstreetmap.josm.gui.NavigatableComponent; | ||
import org.openstreetmap.josm.testutils.annotations.Main; | ||
import org.openstreetmap.josm.testutils.annotations.Projection; | ||
|
||
/** | ||
* Test class for {@link StyledTiledMapRenderer} | ||
*/ | ||
@Main | ||
@Projection | ||
class StyledTiledMapRendererTest { | ||
static Stream<Arguments> testRender() { | ||
final Function<TileZXY, OsmPrimitive[]> generateNodes = tile -> { | ||
final Bounds bounds = TileZXY.tileToBounds(tile); | ||
return new OsmPrimitive[] { | ||
new Node(bounds.getCenter()), | ||
new Node(bounds.getMin()), | ||
new Node(bounds.getMax()), | ||
new Node(new LatLon(bounds.getMinLat(), bounds.getMaxLon())), | ||
new Node(new LatLon(bounds.getMaxLat(), bounds.getMinLon())), | ||
}; | ||
}; | ||
// Everything but the 5 point nodes is just to make it easier to figure out why it is failing | ||
final Function<TileZXY, Stream<Arguments>> generateTests = tile -> Stream.of( | ||
Arguments.of("Center node", (Supplier<DataSet>) () -> new DataSet(generateNodes.apply(tile)[0]), tile) | ||
); | ||
return Stream.concat( | ||
// Tiles around 0, 0 | ||
IntStream.rangeClosed(2097151, 2097152).mapToObj(x -> IntStream.rangeClosed(2097151, 2097152) | ||
.mapToObj(y -> new TileZXY(22, x, y))).flatMap(Function.identity()), | ||
// Tiles in the four quadrants far away from 0, 0 | ||
Stream.of(new TileZXY(16, 13005, 25030), | ||
new TileZXY(14, 5559, 10949), | ||
new TileZXY(15, 31687, 21229), | ||
new TileZXY(13, 8135, 2145))) | ||
.flatMap(generateTests); | ||
} | ||
|
||
@ParameterizedTest(name = "{0} - {2}") | ||
@MethodSource | ||
void testRender(String testIdentifier, final Supplier<DataSet> dataSetSupplier, final TileZXY tile) | ||
throws InterruptedException, ExecutionException { | ||
final int zoom = tile.zoom(); | ||
final Bounds viewArea = TileZXY.tileToBounds(tile); | ||
final CacheAccess<TileZXY, ImageCache> cache = JCSCacheManager.getCache("StyledTiledMapRendererTest:testRender"); | ||
cache.clear(); | ||
final DataSet ds = dataSetSupplier.get(); | ||
final NavigatableComponent nc = new NavigatableComponent() { | ||
@Override | ||
public int getWidth() { | ||
return 800; | ||
} | ||
|
||
@Override | ||
public int getHeight() { | ||
return 600; | ||
} | ||
}; | ||
nc.zoomTo(viewArea); | ||
final ExecutorService worker = MainApplication.worker; | ||
final BufferedImage oldRenderStyle = render((g2d) -> new StyledMapRenderer(g2d, nc, false), ds, nc); | ||
final Function<Graphics2D, StyledTiledMapRenderer> newRenderer = (g2d) -> { | ||
StyledTiledMapRenderer stmr = new StyledTiledMapRenderer(g2d, nc, false); | ||
stmr.setCache(viewArea, cache, zoom, ignored -> { /* ignored */ }); | ||
return stmr; | ||
}; | ||
// First "renders" schedules off-thread rendering. We need to loop here since a render call may only schedule a small amount of tiles. | ||
int size = -1; | ||
while (size != cache.getMatching(".*").size()) { | ||
size = cache.getMatching(".*").size(); | ||
render(newRenderer, ds, nc); | ||
Awaitility.await().until(() -> cache.getMatching(".*").values().stream().allMatch(i -> i.imageFuture() == null)); | ||
} | ||
worker.submit(() -> { /* Sync */ }).get(); | ||
// Second render actually does the painting | ||
final BufferedImage newRenderStyle = render(newRenderer, ds, nc); | ||
worker.submit(() -> { /* Sync */ }).get(); | ||
var entries = cache.getMatching(".*").entrySet().stream().filter(e -> { | ||
BufferedImage image = (BufferedImage) e.getValue().image(); | ||
return Arrays.stream(image.getRGB(0, 0, image.getWidth(), image.getHeight(), | ||
null, 0, image.getWidth())).anyMatch(i -> i != 0); | ||
}).collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().image())); | ||
try { | ||
assertImageEquals(testIdentifier, oldRenderStyle, newRenderStyle, 0, 0, diff -> { | ||
try { | ||
if (!Files.isDirectory(Paths.get(TestUtils.getTestDataRoot(), "output"))) { | ||
Files.createDirectories(Paths.get(TestUtils.getTestDataRoot(), "output")); | ||
} | ||
final String basename = TestUtils.getTestDataRoot() + "output/" + | ||
testIdentifier + ' ' + tile.zoom() + '-' + tile.x() + '-' + tile.y(); | ||
ImageIO.write(diff, "png", new File(basename + "-diff.png")); | ||
ImageIO.write(newRenderStyle, "png", new File(basename + "-new.png")); | ||
ImageIO.write(oldRenderStyle, "png", new File(basename + "-old.png")); | ||
} catch (IOException e) { | ||
throw new UncheckedIOException(e); | ||
} | ||
}); | ||
} finally { | ||
cache.clear(); | ||
} | ||
} | ||
|
||
private static BufferedImage render(Function<Graphics2D, ? extends StyledMapRenderer> renderer, | ||
final DataSet ds, final NavigatableComponent nc) { | ||
final BufferedImage bufferedImage = new BufferedImage(nc.getWidth(), nc.getHeight(), BufferedImage.TYPE_INT_ARGB); | ||
final Graphics2D g2d = bufferedImage.createGraphics(); | ||
final StyledMapRenderer styledMapRenderer = renderer.apply(g2d); | ||
styledMapRenderer.render(ds, true, nc.getRealBounds()); | ||
g2d.dispose(); | ||
return bufferedImage; | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
test/unit/org/openstreetmap/josm/data/osm/visitor/paint/TileZXYTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// License: GPL. For details, see LICENSE file. | ||
package org.openstreetmap.josm.data.osm.visitor.paint; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
|
||
import java.util.stream.Stream; | ||
|
||
import nl.jqno.equalsverifier.EqualsVerifier; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.Arguments; | ||
import org.junit.jupiter.params.provider.MethodSource; | ||
import org.openstreetmap.josm.data.Bounds; | ||
|
||
class TileZXYTest { | ||
static Stream<Arguments> testBBoxCalculation() { | ||
return Stream.of( | ||
Arguments.of(new Bounds(-85.0511288, -180, 85.0511288, 180), new TileZXY(0, 0, 0)), | ||
Arguments.of(new Bounds(0, -180, 85.0511288, 0), new TileZXY(1, 0, 0)), | ||
Arguments.of(new Bounds(0, 0, 85.0511288, 180), new TileZXY(1, 1, 0)), | ||
Arguments.of(new Bounds(-85.0511288, -180, 0, 0), new TileZXY(1, 0, 1)), | ||
Arguments.of(new Bounds(-85.0511288, 0, 0, 180), new TileZXY(1, 1, 1)), | ||
Arguments.of(new Bounds(0, 0, 0.0006866, 0.0006866), new TileZXY(19, 262144, 262143)), | ||
Arguments.of(new Bounds(0, -0.0006866, 0.0006866, 0), new TileZXY(19, 262143, 262143)), | ||
Arguments.of(new Bounds(-0.0006866, -0.0006866, 0, 0), new TileZXY(19, 262143, 262144)), | ||
Arguments.of(new Bounds(-0.0006866, 0, 0, 0.0006866), new TileZXY(19, 262144, 262144)) | ||
); | ||
} | ||
|
||
@ParameterizedTest | ||
@MethodSource | ||
void testBBoxCalculation(Bounds expected, TileZXY tile) { | ||
assertEquals(expected, TileZXY.tileToBounds(tile)); | ||
} | ||
|
||
static Stream<Arguments> testTileFromLatLon() { | ||
final double delta = 0.00001; // Purely to get off of tile boundaries | ||
return testBBoxCalculation().flatMap(arg -> { | ||
final Bounds expected = (Bounds) arg.get()[0]; | ||
final TileZXY tile = (TileZXY) arg.get()[1]; | ||
return Stream.of(Arguments.of("UL", expected.getMaxLat() - delta, expected.getMinLon() + delta, tile), | ||
Arguments.of("LL", expected.getMinLat() + delta, expected.getMinLon() + delta, tile), | ||
Arguments.of("UR", expected.getMaxLat() - delta, expected.getMaxLon() - delta, tile), | ||
Arguments.of("LR", expected.getMinLat() + delta, expected.getMaxLon() - delta, tile), | ||
Arguments.of("Center", expected.getCenter().lat(), expected.getCenter().lon(), tile)); | ||
}); | ||
} | ||
|
||
@ParameterizedTest(name = "{3} - {0}") | ||
@MethodSource | ||
void testTileFromLatLon(String description, double lat, double lon, TileZXY tile) { | ||
assertEquals(tile, TileZXY.latLonToTile(lat, lon, tile.zoom())); | ||
} | ||
|
||
@Test | ||
void testEqualsContract() { | ||
EqualsVerifier.forClass(TileZXY.class).verify(); | ||
} | ||
} |