From ce9f545749e55346fb56b0d3796474298977dc39 Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Wed, 24 Jan 2024 10:37:47 +0100 Subject: [PATCH] add tms options --- CHANGES.md | 4 ++ README.md | 13 +++--- tests/fixtures/WGS1984Quad.json | 1 + tests/test_cli.py | 31 +++++++++++++ tilebench/scripts/cli.py | 79 ++++++++++++++++++++++++--------- 5 files changed, 102 insertions(+), 26 deletions(-) create mode 100644 tests/fixtures/WGS1984Quad.json diff --git a/CHANGES.md b/CHANGES.md index 21828c0..c9b29a3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,8 @@ +## 0.12.0 (2024-01-24) + +* allow `tms` options in CLI (`profile`, `random` and `get-zooms`) to select TileMatrixSet + ## 0.11.0 (2023-10-18) * update requirements diff --git a/README.md b/README.md index a3c884c..5b2b72c 100644 --- a/README.md +++ b/README.md @@ -40,17 +40,18 @@ Note: In GDAL 3.2, logging capabilities for /vsicurl, /vsis3 and the like was ad You can install `tilebench` using pip ```bash -$ pip install -U pip -$ pip install -U tilebench +$ python -m pip install -U pip +$ python -m pip install -U tilebench ``` or install from source: ```bash -$ git clone https://github.com/developmentseed/tilebench.git -$ cd tilebench -$ pip install -U pip -$ pip install -e . +git clone https://github.com/developmentseed/tilebench.git +cd tilebench + +python -m pip install -U pip +python -m pip install -e . ``` ## API diff --git a/tests/fixtures/WGS1984Quad.json b/tests/fixtures/WGS1984Quad.json new file mode 100644 index 0000000..8a2b901 --- /dev/null +++ b/tests/fixtures/WGS1984Quad.json @@ -0,0 +1 @@ +{"title":"EPSG:4326 for the World","id":"WorldCRS84Quad","uri":"http://www.opengis.net/def/tilematrixset/OGC/1.0/WorldCRS84Quad","orderedAxes":["Lat","Lon"],"crs":"http://www.opengis.net/def/crs/EPSG/0/4326","wellKnownScaleSet":"http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad","tileMatrices":[{"id":"0","scaleDenominator":279541132.014358,"cellSize":0.703125,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":2,"matrixHeight":1},{"id":"1","scaleDenominator":139770566.007179,"cellSize":0.3515625,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":4,"matrixHeight":2},{"id":"2","scaleDenominator":69885283.0035897,"cellSize":0.17578125,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":8,"matrixHeight":4},{"id":"3","scaleDenominator":34942641.5017948,"cellSize":0.087890625,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":16,"matrixHeight":8},{"id":"4","scaleDenominator":17471320.7508974,"cellSize":0.0439453125,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":32,"matrixHeight":16},{"id":"5","scaleDenominator":8735660.37544871,"cellSize":0.02197265625,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":64,"matrixHeight":32},{"id":"6","scaleDenominator":4367830.18772435,"cellSize":0.010986328125,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":128,"matrixHeight":64},{"id":"7","scaleDenominator":2183915.09386217,"cellSize":0.0054931640625,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":256,"matrixHeight":128},{"id":"8","scaleDenominator":1091957.54693108,"cellSize":0.00274658203125,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":512,"matrixHeight":256},{"id":"9","scaleDenominator":545978.773465544,"cellSize":0.001373291015625,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":1024,"matrixHeight":512},{"id":"10","scaleDenominator":272989.386732772,"cellSize":0.0006866455078125,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":2048,"matrixHeight":1024},{"id":"11","scaleDenominator":136494.693366386,"cellSize":0.00034332275390625,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":4096,"matrixHeight":2048},{"id":"12","scaleDenominator":68247.346683193,"cellSize":0.000171661376953125,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":8192,"matrixHeight":4096},{"id":"13","scaleDenominator":34123.6733415964,"cellSize":0.0000858306884765625,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":16384,"matrixHeight":8192},{"id":"14","scaleDenominator":17061.8366707982,"cellSize":0.0000429153442382812,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":32768,"matrixHeight":16384},{"id":"15","scaleDenominator":8530.91833539913,"cellSize":0.0000214576721191406,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":65536,"matrixHeight":32768},{"id":"16","scaleDenominator":4265.45916769956,"cellSize":0.0000107288360595703,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":131072,"matrixHeight":65536},{"id":"17","scaleDenominator":2132.72958384978,"cellSize":5.36441802978515e-6,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":262144,"matrixHeight":131072},{"id":"18","scaleDenominator":1066.36479192489,"cellSize":2.68220901489258e-6,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":524288,"matrixHeight":262144},{"id":"19","scaleDenominator":533.182395962445,"cellSize":1.34110450744629e-6,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":1048576,"matrixHeight":524288},{"id":"20","scaleDenominator":266.591197981222,"cellSize":6.7055225372314e-7,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":2097152,"matrixHeight":1048576},{"id":"21","scaleDenominator":133.295598990611,"cellSize":3.3527612686157e-7,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":4194304,"matrixHeight":2097152},{"id":"22","scaleDenominator":66.6477994953056,"cellSize":1.6763806343079e-7,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":8388608,"matrixHeight":4194304},{"id":"23","scaleDenominator":33.3238997476528,"cellSize":8.381903171539e-8,"cornerOfOrigin":"topLeft","pointOfOrigin":[90.0,-180.0],"tileWidth":256,"tileHeight":256,"matrixWidth":16777216,"matrixHeight":8388608}]} diff --git a/tests/test_cli.py b/tests/test_cli.py index a65b4ec..3ed3821 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,6 +1,7 @@ """Test CLI.""" import json +import os from unittest.mock import patch from click.testing import CliRunner @@ -9,6 +10,8 @@ COG_PATH = "https://noaa-eri-pds.s3.amazonaws.com/2022_Hurricane_Ian/20221002a_RGB/20221002aC0795145w325100n.tif" +TMS = os.path.join(os.path.dirname(__file__), "fixtures", "WGS1984Quad.json") + def test_profile(): """Should work as expected.""" @@ -73,6 +76,8 @@ def test_get_zoom(): assert result.exit_code == 0 log = json.loads(result.output) assert ["minzoom", "maxzoom"] == list(log) + assert log["minzoom"] == 14 + assert log["maxzoom"] == 19 result = runner.invoke( cli, ["get-zooms", COG_PATH, "--reader", "rio_tiler.io.Reader"] @@ -118,3 +123,29 @@ def test_viz(launch): assert not result.exception assert result.exit_code == 0 assert "14-" in result.output + + +def test_tms(): + """Should work as expected.""" + runner = CliRunner() + + result = runner.invoke(cli, ["profile", COG_PATH, "--tms", TMS]) + assert not result.exception + assert result.exit_code == 0 + log = json.loads(result.output) + assert ["HEAD", "GET", "Timing"] == list(log) + # Make sure we didn't cache any request when `--tile` is not provided + assert "0-" in log["GET"]["ranges"][0] + + result = runner.invoke(cli, ["get-zooms", COG_PATH, "--tms", TMS]) + assert not result.exception + assert result.exit_code == 0 + log = json.loads(result.output) + assert ["minzoom", "maxzoom"] == list(log) + assert log["minzoom"] == 13 + assert log["maxzoom"] == 18 + + result = runner.invoke(cli, ["random", COG_PATH, "--tms", TMS]) + assert not result.exception + assert result.exit_code == 0 + assert "-" in result.output diff --git a/tilebench/scripts/cli.py b/tilebench/scripts/cli.py index 6e91d42..341f09e 100644 --- a/tilebench/scripts/cli.py +++ b/tilebench/scripts/cli.py @@ -16,7 +16,7 @@ from tilebench import profile as profiler from tilebench.viz import TileDebug -tms = morecantile.tms.get("WebMercatorQuad") +default_tms = morecantile.tms.get("WebMercatorQuad") # The CLI command group. @@ -53,6 +53,11 @@ def cli(): type=str, help="rio-tiler Reader (BaseReader). Default is `rio_tiler.io.COGReader`", ) +@click.option( + "--tms", + help="Path to TileMatrixSet JSON file.", + type=click.Path(), +) @click.option( "--config", "config", @@ -62,9 +67,23 @@ def cli(): help="GDAL configuration options.", ) def profile( - input, tile, tilesize, zoom, add_kernels, add_stdout, add_cprofile, reader, config + input, + tile, + tilesize, + zoom, + add_kernels, + add_stdout, + add_cprofile, + reader, + tms, + config, ): """Profile COGReader Mercator Tile read.""" + tilematrixset = default_tms + if tms: + with open(tms, "r") as f: + tilematrixset = morecantile.TileMatrixSet(**json.load(f)) + if reader: module, classname = reader.rsplit(".", 1) reader = getattr(importlib.import_module(module), classname) # noqa @@ -75,19 +94,19 @@ def profile( if not tile: with rasterio.Env(CPL_VSIL_CURL_NON_CACHED=parse_path(input).as_vsi()): - with Reader(input, tms=tms) as cog: + with Reader(input, tms=tilematrixset) as cog: if zoom is None: zoom = randint(cog.minzoom, cog.maxzoom) w, s, e, n = cog.geographic_bounds # Truncate BBox to the TMS bounds - w = max(tms.bbox.left, w) - s = max(tms.bbox.bottom, s) - e = min(tms.bbox.right, e) - n = min(tms.bbox.top, n) + w = max(tilematrixset.bbox.left, w) + s = max(tilematrixset.bbox.bottom, s) + e = min(tilematrixset.bbox.right, e) + n = min(tilematrixset.bbox.top, n) - ul_tile = tms.tile(w, n, zoom) - lr_tile = tms.tile(e, s, zoom) + ul_tile = tilematrixset.tile(w, n, zoom) + lr_tile = tilematrixset.tile(e, s, zoom) extrema = { "x": {"min": ul_tile.x, "max": lr_tile.x + 1}, "y": {"min": ul_tile.y, "max": lr_tile.y + 1}, @@ -109,7 +128,7 @@ def profile( config=config, ) def _read_tile(src_path: str, x: int, y: int, z: int, tilesize: int = 256): - with Reader(src_path) as cog: + with Reader(src_path, tms=tilematrixset) as cog: return cog.tile(x, y, z, tilesize=tilesize) (_, _), stats = _read_tile(input, tile_x, tile_y, tile_z, tilesize) @@ -124,8 +143,18 @@ def _read_tile(src_path: str, x: int, y: int, z: int, tilesize: int = 256): type=str, help="rio-tiler Reader (BaseReader). Default is `rio_tiler.io.COGReader`", ) -def get_zooms(input, reader): +@click.option( + "--tms", + help="Path to TileMatrixSet JSON file.", + type=click.Path(), +) +def get_zooms(input, reader, tms): """Get Mercator Zoom levels.""" + tilematrixset = default_tms + if tms: + with open(tms, "r") as f: + tilematrixset = morecantile.TileMatrixSet(**json.load(f)) + if reader: module, classname = reader.rsplit(".", 1) reader = getattr(importlib.import_module(module), classname) # noqa @@ -134,7 +163,7 @@ def get_zooms(input, reader): Reader = reader or COGReader - with Reader(input, tms=tms) as cog: + with Reader(input, tms=tilematrixset) as cog: click.echo(json.dumps({"minzoom": cog.minzoom, "maxzoom": cog.maxzoom})) @@ -146,8 +175,18 @@ def get_zooms(input, reader): type=str, help="rio-tiler Reader (BaseReader). Default is `rio_tiler.io.COGReader`", ) -def random(input, zoom, reader): +@click.option( + "--tms", + help="Path to TileMatrixSet JSON file.", + type=click.Path(), +) +def random(input, zoom, reader, tms): """Get random tile.""" + tilematrixset = default_tms + if tms: + with open(tms, "r") as f: + tilematrixset = morecantile.TileMatrixSet(**json.load(f)) + if reader: module, classname = reader.rsplit(".", 1) reader = getattr(importlib.import_module(module), classname) # noqa @@ -156,19 +195,19 @@ def random(input, zoom, reader): Reader = reader or COGReader - with Reader(input, tms=tms) as cog: + with Reader(input, tms=tilematrixset) as cog: if zoom is None: zoom = randint(cog.minzoom, cog.maxzoom) w, s, e, n = cog.geographic_bounds # Truncate BBox to the TMS bounds - w = max(tms.bbox.left, w) - s = max(tms.bbox.bottom, s) - e = min(tms.bbox.right, e) - n = min(tms.bbox.top, n) + w = max(tilematrixset.bbox.left, w) + s = max(tilematrixset.bbox.bottom, s) + e = min(tilematrixset.bbox.right, e) + n = min(tilematrixset.bbox.top, n) - ul_tile = tms.tile(w, n, zoom) - lr_tile = tms.tile(e, s, zoom) + ul_tile = tilematrixset.tile(w, n, zoom) + lr_tile = tilematrixset.tile(e, s, zoom) extrema = { "x": {"min": ul_tile.x, "max": lr_tile.x + 1}, "y": {"min": ul_tile.y, "max": lr_tile.y + 1},