From 7a1e0f83e383a4a979fe2f2fe4f7689a651f15aa Mon Sep 17 00:00:00 2001 From: Emux Date: Wed, 26 Feb 2025 15:54:54 +0200 Subject: [PATCH] Multi-map MapFileTileSource.setPriority (#1176) --- docs/Changelog.md | 1 + .../src/org/oscim/test/MapsforgeTest.java | 2 + vtm/src/org/oscim/core/BoundingBox.java | 20 ++++ .../tiling/source/mapfile/MapDatabase.java | 77 ++++++++++++- .../source/mapfile/MapFileTileSource.java | 33 ++++++ .../source/mapfile/MultiMapDatabase.java | 101 +++++++++++++++++- .../mapfile/MultiMapFileTileSource.java | 1 + 7 files changed, 229 insertions(+), 6 deletions(-) diff --git a/docs/Changelog.md b/docs/Changelog.md index a5619e1b7..233bab59f 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -2,6 +2,7 @@ ## Next version +- Multi-map `MapFileTileSource.setPriority` [#1176](https://github.com/mapsforge/vtm/pull/1176) - Color filter theme resources [#1175](https://github.com/mapsforge/vtm/pull/1175) - `ThemeCallback.getBitmap` - Update JTS, OkHttp dependencies [#1172](https://github.com/mapsforge/vtm/pull/1172) [#1173](https://github.com/mapsforge/vtm/pull/1173) diff --git a/vtm-playground/src/org/oscim/test/MapsforgeTest.java b/vtm-playground/src/org/oscim/test/MapsforgeTest.java index a8e2eb840..77fbf30f2 100644 --- a/vtm-playground/src/org/oscim/test/MapsforgeTest.java +++ b/vtm-playground/src/org/oscim/test/MapsforgeTest.java @@ -65,6 +65,8 @@ public void createLayers() { for (File mapFile : mapFiles) { MapFileTileSource mapFileTileSource = new MapFileTileSource(); mapFileTileSource.setMapFile(mapFile.getAbsolutePath()); + if ("world.map".equalsIgnoreCase(mapFile.getName())) + mapFileTileSource.setPriority(-1); tileSource.add(mapFileTileSource); } //tileSource.setDeduplicate(true); diff --git a/vtm/src/org/oscim/core/BoundingBox.java b/vtm/src/org/oscim/core/BoundingBox.java index 811191977..b27cb41fb 100644 --- a/vtm/src/org/oscim/core/BoundingBox.java +++ b/vtm/src/org/oscim/core/BoundingBox.java @@ -102,6 +102,26 @@ public BoundingBox(List geoPoints) { this.maxLongitudeE6 = maxLon; } + /** + * @param latitude the latitude coordinate in degrees. + * @param longitude the longitude coordinate in degrees. + * @return true if this BoundingBox contains the given coordinates, false otherwise. + */ + public boolean contains(double latitude, double longitude) { + return this.getMinLatitude() <= latitude && this.getMaxLatitude() >= latitude + && this.getMinLongitude() <= longitude && this.getMaxLongitude() >= longitude; + } + + /** + * @param latitudeE6 the latitude coordinate in microdegrees (degrees * 10^6). + * @param longitudeE6 the longitude coordinate in microdegrees (degrees * 10^6). + * @return true if this BoundingBox contains the given coordinates, false otherwise. + */ + public boolean contains(int latitudeE6, int longitudeE6) { + return this.minLatitudeE6 <= latitudeE6 && this.maxLatitudeE6 >= latitudeE6 + && this.minLongitudeE6 <= longitudeE6 && this.maxLongitudeE6 >= longitudeE6; + } + /** * @param geoPoint the point whose coordinates should be checked. * @return true if this BoundingBox contains the given GeoPoint, false diff --git a/vtm/src/org/oscim/tiling/source/mapfile/MapDatabase.java b/vtm/src/org/oscim/tiling/source/mapfile/MapDatabase.java index 25750151d..e0e75c934 100644 --- a/vtm/src/org/oscim/tiling/source/mapfile/MapDatabase.java +++ b/vtm/src/org/oscim/tiling/source/mapfile/MapDatabase.java @@ -222,6 +222,14 @@ public class MapDatabase implements ITileDataSource { private boolean deduplicate; + /** + * Priority of this MapDatabase. A higher number means a higher priority. Negative numbers have a special + * meaning, they should only be used for so-called background maps. Data from background maps is only read + * if no other map has provided a (complete) map tile. The most famous example of a background map is a + * low-resolution world map. The default priority is 0. + */ + private int priority = 0; + public MapDatabase(MapFileTileSource tileSource) throws IOException { mTileSource = tileSource; try { @@ -426,6 +434,31 @@ void setDeduplicate(boolean deduplicate) { this.deduplicate = deduplicate; } + /** + * Returns the priority of this MapDatabase. A higher number means a higher priority. Negative numbers + * have a special meaning, they should only be used for so-called background maps. Data from background + * maps is only read if no other map has provided a (complete) map tile. The most famous example of a + * background map is a low-resolution world map. + * + * @return The priority of this MapDatabase. Default is 0. + */ + public int getPriority() { + return priority; + } + + /** + * Sets the priority of this MapDatabase. A higher number means a higher priority. Negative numbers have + * a special meaning, they should only be used for so-called background maps. Data from background maps is + * only read if no other map has provided a (complete) map tile. The most famous example of a background + * map is a low-resolution world map. The default priority is 0. + * + * @param priority Priority of this MapDatabase. Negative number means background map priority (see above + * for the description). + */ + public void setPriority(int priority) { + this.priority = priority; + } + private void setTileClipping(QueryParameters queryParameters, SubFileParameter subFileParameter, long currentRow, long currentCol) { long numRows = queryParameters.toBlockY - queryParameters.fromBlockY; @@ -1239,8 +1272,48 @@ public void restrictToZoomRange(int minZoom, int maxZoom) { * @return true if tile is part of database. */ public boolean supportsTile(Tile tile) { - return tile.getBoundingBox().intersects(mTileSource.getMapInfo().boundingBox) - && (tile.zoomLevel >= this.zoomLevelMin && tile.zoomLevel <= this.zoomLevelMax); + return supportsArea(tile.getBoundingBox(), tile.zoomLevel); + } + + /** + * Returns true if MapDatabase contains a complete tile. + * + * @param tile tile to be rendered. + * @return true if complete tile is part of database. + */ + public boolean supportsFullTile(Tile tile) { + return supportsFullArea(tile.getBoundingBox(), tile.zoomLevel); + } + + /** + * Returns true if MapDatabase covers (even partially) certain area in required zoom level. + * + * @param boundingBox area we test + * @param zoomLevel zoom level we test + * @return true if area is part of the database. + */ + public boolean supportsArea(BoundingBox boundingBox, int zoomLevel) { + return boundingBox.intersects(mTileSource.getMapInfo().boundingBox) + && (zoomLevel >= this.zoomLevelMin && zoomLevel <= this.zoomLevelMax); + } + + /** + * Returns true if MapDatabase covers certain area completely in required zoom level. + * + * @param boundingBox area we test + * @param zoomLevel zoom level we test + * @return true if complete area is part of the database. + */ + public boolean supportsFullArea(BoundingBox boundingBox, int zoomLevel) { + final BoundingBox bbox1 = mTileSource.getMapInfo().boundingBox; + final BoundingBox bbox2 = boundingBox; + return bbox1.intersects(bbox2) + && zoomLevel >= this.zoomLevelMin + && zoomLevel <= this.zoomLevelMax + && bbox1.contains(bbox2.maxLatitudeE6, bbox2.maxLongitudeE6) + && bbox1.contains(bbox2.minLatitudeE6, bbox2.minLongitudeE6) + && bbox1.contains(bbox2.maxLatitudeE6, bbox2.minLongitudeE6) + && bbox1.contains(bbox2.minLatitudeE6, bbox2.maxLongitudeE6); } /** diff --git a/vtm/src/org/oscim/tiling/source/mapfile/MapFileTileSource.java b/vtm/src/org/oscim/tiling/source/mapfile/MapFileTileSource.java index b2befeecc..544d716ef 100644 --- a/vtm/src/org/oscim/tiling/source/mapfile/MapFileTileSource.java +++ b/vtm/src/org/oscim/tiling/source/mapfile/MapFileTileSource.java @@ -54,6 +54,14 @@ public class MapFileTileSource extends TileSource implements IMapFileTileSource private String preferredLanguage; private Callback callback; + /** + * Priority of this MapFileTileSource. A higher number means a higher priority. Negative numbers have a special + * meaning, they should only be used for so-called background maps. Data from background maps is only read + * if no other map has provided a (complete) map tile. The most famous example of a background map is a + * low-resolution world map. The default priority is 0. + */ + private int priority = 0; + public MapFileTileSource() { this(Viewport.MIN_ZOOM_LEVEL, Viewport.MAX_ZOOM_LEVEL); } @@ -106,6 +114,31 @@ public void setPreferredLanguage(String preferredLanguage) { this.preferredLanguage = preferredLanguage; } + /** + * Returns the priority of this MapFileTileSource. A higher number means a higher priority. Negative numbers + * have a special meaning, they should only be used for so-called background maps. Data from background + * maps is only read if no other map has provided a (complete) map tile. The most famous example of a + * background map is a low-resolution world map. + * + * @return The priority of this MapFileTileSource. Default is 0. + */ + public int getPriority() { + return priority; + } + + /** + * Sets the priority of this MapFileTileSource. A higher number means a higher priority. Negative numbers have + * a special meaning, they should only be used for so-called background maps. Data from background maps is + * only read if no other map has provided a (complete) map tile. The most famous example of a background + * map is a low-resolution world map. The default priority is 0. + * + * @param priority Priority of this MapFileTileSource. Negative number means background map priority (see above + * for the description). + */ + public void setPriority(int priority) { + this.priority = priority; + } + @Override public OpenResult open() { if (mapFileInputStream == null && !options.containsKey("file")) diff --git a/vtm/src/org/oscim/tiling/source/mapfile/MultiMapDatabase.java b/vtm/src/org/oscim/tiling/source/mapfile/MultiMapDatabase.java index d6c967aaa..377e55688 100644 --- a/vtm/src/org/oscim/tiling/source/mapfile/MultiMapDatabase.java +++ b/vtm/src/org/oscim/tiling/source/mapfile/MultiMapDatabase.java @@ -14,6 +14,7 @@ */ package org.oscim.tiling.source.mapfile; +import org.oscim.core.BoundingBox; import org.oscim.core.Tile; import org.oscim.layers.tile.MapTile; import org.oscim.tiling.ITileDataSink; @@ -22,6 +23,8 @@ import org.oscim.tiling.TileDataSink; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.logging.Logger; @@ -44,7 +47,15 @@ public boolean add(MapDatabase mapDatabase) { if (mapDatabases.contains(mapDatabase)) { throw new IllegalArgumentException("Duplicate map database"); } - return mapDatabases.add(mapDatabase); + mapDatabases.add(mapDatabase); + Collections.sort(mapDatabases, new Comparator() { + @Override + public int compare(MapDatabase md1, MapDatabase md2) { + // Reverse order + return -Integer.compare(md1.getPriority(), md2.getPriority()); + } + }); + return true; } @Override @@ -64,14 +75,21 @@ public void query(MapTile tile, ITileDataSink sink) { } TileDataSink dataSink = new TileDataSink(sink); + boolean isTileFilled = false; for (int i = 0, n = mapDatabases.size(); i < n; i++) { MapDatabase mapDatabase = mapDatabases.get(i); + if (isTileFilled && mapDatabase.getPriority() < 0) { + break; + } if (mapDatabase.supportsTile(tile)) { mapDatabase.setDeduplicate(deduplicate); dataSink.level = i + 1; dataSink.levels = n; mapDatabase.query(tile, dataSink); } + if (mapDatabase.supportsFullTile(tile)) { + isTileFilled = true; + } } sink.completed(QueryResult.SUCCESS); } catch (Throwable t) { @@ -96,7 +114,11 @@ public void cancel() { public MapReadResult readLabels(Tile tile, boolean deduplicate) { MapReadResult mapReadResult = new MapReadResult(); + boolean isTileFilled = false; for (MapDatabase mdb : mapDatabases) { + if (isTileFilled && mdb.getPriority() < 0) { + break; + } if (mdb.supportsTile(tile)) { MapReadResult result = mdb.readLabels(tile); if (result == null) { @@ -106,14 +128,22 @@ public MapReadResult readLabels(Tile tile, boolean deduplicate) { mapReadResult.isWater = isWater; mapReadResult.add(result, deduplicate); } + if (mdb.supportsFullTile(tile)) { + isTileFilled = true; + } } return mapReadResult; } public MapReadResult readLabels(Tile upperLeft, Tile lowerRight, boolean deduplicate) { MapReadResult mapReadResult = new MapReadResult(); + boolean isTileFilled = false; for (MapDatabase mdb : mapDatabases) { - if (mdb.supportsTile(upperLeft)) { + if (isTileFilled && mdb.getPriority() < 0) { + break; + } + if (mdb.supportsArea(upperLeft.getBoundingBox().extendBoundingBox(lowerRight.getBoundingBox()), + upperLeft.zoomLevel)) { MapReadResult result = mdb.readLabels(upperLeft, lowerRight); if (result == null) { continue; @@ -122,13 +152,21 @@ public MapReadResult readLabels(Tile upperLeft, Tile lowerRight, boolean dedupli mapReadResult.isWater = isWater; mapReadResult.add(result, deduplicate); } + if (mdb.supportsFullArea(upperLeft.getBoundingBox().extendBoundingBox(lowerRight.getBoundingBox()), + upperLeft.zoomLevel)) { + isTileFilled = true; + } } return mapReadResult; } public MapReadResult readMapData(Tile tile, boolean deduplicate) { MapReadResult mapReadResult = new MapReadResult(); + boolean isTileFilled = false; for (MapDatabase mdb : mapDatabases) { + if (isTileFilled && mdb.getPriority() < 0) { + break; + } if (mdb.supportsTile(tile)) { MapReadResult result = mdb.readMapData(tile); if (result == null) { @@ -138,14 +176,22 @@ public MapReadResult readMapData(Tile tile, boolean deduplicate) { mapReadResult.isWater = isWater; mapReadResult.add(result, deduplicate); } + if (mdb.supportsFullTile(tile)) { + isTileFilled = true; + } } return mapReadResult; } public MapReadResult readMapData(Tile upperLeft, Tile lowerRight, boolean deduplicate) { MapReadResult mapReadResult = new MapReadResult(); + boolean isTileFilled = false; for (MapDatabase mdb : mapDatabases) { - if (mdb.supportsTile(upperLeft)) { + if (isTileFilled && mdb.getPriority() < 0) { + break; + } + if (mdb.supportsArea(upperLeft.getBoundingBox().extendBoundingBox(lowerRight.getBoundingBox()), + upperLeft.zoomLevel)) { MapReadResult result = mdb.readMapData(upperLeft, lowerRight); if (result == null) { continue; @@ -154,13 +200,21 @@ public MapReadResult readMapData(Tile upperLeft, Tile lowerRight, boolean dedupl mapReadResult.isWater = isWater; mapReadResult.add(result, deduplicate); } + if (mdb.supportsFullArea(upperLeft.getBoundingBox().extendBoundingBox(lowerRight.getBoundingBox()), + upperLeft.zoomLevel)) { + isTileFilled = true; + } } return mapReadResult; } public MapReadResult readPoiData(Tile tile, boolean deduplicate) { MapReadResult mapReadResult = new MapReadResult(); + boolean isTileFilled = false; for (MapDatabase mdb : mapDatabases) { + if (isTileFilled && mdb.getPriority() < 0) { + break; + } if (mdb.supportsTile(tile)) { MapReadResult result = mdb.readPoiData(tile); if (result == null) { @@ -170,14 +224,22 @@ public MapReadResult readPoiData(Tile tile, boolean deduplicate) { mapReadResult.isWater = isWater; mapReadResult.add(result, deduplicate); } + if (mdb.supportsFullTile(tile)) { + isTileFilled = true; + } } return mapReadResult; } public MapReadResult readPoiData(Tile upperLeft, Tile lowerRight, boolean deduplicate) { MapReadResult mapReadResult = new MapReadResult(); + boolean isTileFilled = false; for (MapDatabase mdb : mapDatabases) { - if (mdb.supportsTile(upperLeft)) { + if (isTileFilled && mdb.getPriority() < 0) { + break; + } + if (mdb.supportsArea(upperLeft.getBoundingBox().extendBoundingBox(lowerRight.getBoundingBox()), + upperLeft.zoomLevel)) { MapReadResult result = mdb.readPoiData(upperLeft, lowerRight); if (result == null) { continue; @@ -186,6 +248,10 @@ public MapReadResult readPoiData(Tile upperLeft, Tile lowerRight, boolean dedupl mapReadResult.isWater = isWater; mapReadResult.add(result, deduplicate); } + if (mdb.supportsFullArea(upperLeft.getBoundingBox().extendBoundingBox(lowerRight.getBoundingBox()), + upperLeft.zoomLevel)) { + isTileFilled = true; + } } return mapReadResult; } @@ -198,4 +264,31 @@ public boolean supportsTile(Tile tile) { } return false; } + + public boolean supportsFullTile(Tile tile) { + for (MapDatabase mdb : mapDatabases) { + if (mdb.supportsFullTile(tile)) { + return true; + } + } + return false; + } + + public boolean supportsArea(BoundingBox boundingBox, int zoomLevel) { + for (MapDatabase mdb : mapDatabases) { + if (mdb.supportsArea(boundingBox, zoomLevel)) { + return true; + } + } + return false; + } + + public boolean supportsFullArea(BoundingBox boundingBox, int zoomLevel) { + for (MapDatabase mdb : mapDatabases) { + if (mdb.supportsFullArea(boundingBox, zoomLevel)) { + return true; + } + } + return false; + } } diff --git a/vtm/src/org/oscim/tiling/source/mapfile/MultiMapFileTileSource.java b/vtm/src/org/oscim/tiling/source/mapfile/MultiMapFileTileSource.java index 84a802606..c549f3429 100644 --- a/vtm/src/org/oscim/tiling/source/mapfile/MultiMapFileTileSource.java +++ b/vtm/src/org/oscim/tiling/source/mapfile/MultiMapFileTileSource.java @@ -79,6 +79,7 @@ public ITileDataSource getDataSource() { int[] zoomLevels = zoomsByTileSource.get(mapFileTileSource); if (zoomLevels != null) mapDatabase.restrictToZoomRange(zoomLevels[0], zoomLevels[1]); + mapDatabase.setPriority(mapFileTileSource.getPriority()); multiMapDatabase.add(mapDatabase); } catch (IOException e) { log.fine(e.toString());