Skip to content

Commit 3e96d3c

Browse files
authored
Merge pull request #189 from mapbox/nickgerancher/cu-estimation-testing
Nickgerancher/cu estimation testing
2 parents ddf8d21 + ac24967 commit 3e96d3c

File tree

7 files changed

+350
-1
lines changed

7 files changed

+350
-1
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
=======
44

5+
# 1.12.0 (2024-10-23)
6+
- Added command `tilesets estimate-cu` that returns an estimated compute unit value for a user's tileset.
7+
58
# 1.11.1 (2024-08-01)
69
- Added command `tilesets upload-raster-source` to upload raster files as sources
710

README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,30 @@ Note, Windows is not officially supported at this time.
4545
Windows users need to install [GDAL](http://www.lfd.uci.edu/~gohlke/pythonlibs/#gdal) and [rasterio](http://www.lfd.uci.edu/~gohlke/pythonlibs/#rasterio).
4646
Then `pip install 'mapbox-tilesets[estimate-area]'`
4747

48+
## Installing optional `estimate-cu` command
49+
50+
If you are using an x86 Mac or Linux machine, run:
51+
`pip install 'mapbox-tilesets[estimate-cu]'`
52+
53+
Otherwise, you will need to install some dependencies.
54+
55+
### arm64 MacOS
56+
57+
If you're on an arm64 Mac (e.g., with an M1 chip), you'll need to install [GDAL](https://gdal.org/) first. On Mac, a simple way is to use [Homebrew](https://brew.sh/):
58+
59+
```sh
60+
$ brew install gdal
61+
...
62+
$ pip install 'mapbox-tilesets[estimate-cu]'
63+
```
64+
65+
### Windows
66+
67+
Note, Windows is not officially supported at this time.
68+
69+
Windows users need to install [GDAL](http://www.lfd.uci.edu/~gohlke/pythonlibs/#gdal) and [rasterio](http://www.lfd.uci.edu/~gohlke/pythonlibs/#rasterio).
70+
Then `pip install 'mapbox-tilesets[estimate-cu]'`
71+
4872
## Mapbox Access Tokens
4973

5074
In order to use the tilesets endpoints, you need a Mapbox Access Token with `tilesets:write`, `tilesets:read`, and `tilesets:list` scopes. This is a secret token, so do not share it publicly!
@@ -71,6 +95,7 @@ export MAPBOX_ACCESS_TOKEN=my.token
7195
- [`list-sources`](#list-sources)
7296
- [`delete-source`](#delete-source)
7397
- [`estimate-area`](#estimate-area)
98+
- [`estimate-cu`](#estimate-cu)
7499
- Recipes
75100
- [`view-recipe`](#view-recipe)
76101
- [`validate-recipe`](#validate-recipe)
@@ -222,6 +247,33 @@ Usage
222247
tilesets delete-source user source_id
223248
```
224249

250+
### estimate-cu
251+
252+
```shell
253+
tilesets estimate-cu <tileset> -s/--sources <sources> -b/--num-bands <number> --raw
254+
```
255+
256+
Estimates the CU value of a tileset before publishing it. This is useful to understand the estimated cost a given tileset before you start processing the data. Note: This is currently only available to tileset recipes with type `raster` or `rasterarray`.
257+
258+
See https://docs.mapbox.com/help/glossary/compute-unit/ for more information.
259+
260+
Flags:
261+
- `-s` or `--sources` [optional]: Local path to the sources that your recipe points at. This is highly recommeneded.
262+
- `-b` or `--num-bands` [optional]: The number of bands you expect your recipe to select across all layers. This is recommended.
263+
- `--minzoom` [optional]: Use this flag if your recipe does not contain a minzoom value.
264+
- `--maxzoom` [optional]: Use this flag if your recipe does not contain a maxzoom value.
265+
- `--raw` [optional]: This will toggle the pretty print output.
266+
267+
Usage
268+
269+
```shell
270+
# Estimate the CUs for 'account.tileset' with sources located in 'path/to/sources/' and a band count of 20.
271+
tilesets estimate-cu account.tileset -s 'path/to/sources/*.grib2' -b 20
272+
273+
# Estimate the CUs for 'account.tileset' for a single source and a band count of 10 (pretty print the results)
274+
tilesets estimate-cu account.tileset -s 'path/to/sources/helloworld.grib2' -b 10 --raw
275+
```
276+
225277
### estimate-area
226278

227279
```shell

mapbox_tilesets/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""mapbox_tilesets package"""
22

3-
__version__ = "1.11.1"
3+
__version__ = "1.12.0"

mapbox_tilesets/scripts/cli.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
"""Tilesets command line interface"""
22

3+
import os
34
import base64
45
import builtins
56
import json
67
import re
78
import tempfile
9+
from glob import glob
810
from urllib.parse import parse_qs, urlencode, urlparse
911

1012
import click
@@ -840,6 +842,112 @@ def validate_stream(features):
840842
yield feature
841843

842844

845+
@cli.command("estimate-cu")
846+
@click.argument("tileset", required=True, type=str)
847+
@click.option(
848+
"--sources",
849+
"-s",
850+
required=False,
851+
type=click.Path(exists=False),
852+
help="Local sources represented in your tileset's recipe",
853+
)
854+
@click.option(
855+
"--num-bands",
856+
"-b",
857+
required=False,
858+
type=int,
859+
default=15,
860+
help="The number of bands your recipe is selecting",
861+
)
862+
@click.option("--minzoom", required=False, type=int, help="The minzoom value override")
863+
@click.option("--maxzoom", required=False, type=int, help="The maxzoom value override")
864+
@click.option(
865+
"--raw", required=False, type=bool, default=False, is_flag=True, help="Raw CU value"
866+
)
867+
@click.option("--token", "-t", required=False, type=str, help="Mapbox access token")
868+
def estimate_cu(
869+
tileset,
870+
num_bands=15,
871+
minzoom=None,
872+
maxzoom=None,
873+
sources=None,
874+
raw=False,
875+
token=None,
876+
):
877+
"""
878+
Estimates the CUs that will be consumed when processing your recipe into a tileset.
879+
Requires extra installation steps: see https://github.com/mapbox/tilesets-cli/blob/master/README.md
880+
"""
881+
882+
rio = utils.load_module("rasterio")
883+
884+
if sources is None:
885+
click.echo(f"[warning] estimating '{tileset}' with a default global bounds")
886+
sources = ""
887+
888+
total_size = 0
889+
overall_bounds = None
890+
src_list = glob(sources)
891+
892+
if len(src_list) > 0:
893+
with rio.open(src_list[0], mode="r") as ds:
894+
overall_bounds = ds.bounds
895+
896+
total_size += os.path.getsize(src_list[0])
897+
898+
if len(src_list) > 1:
899+
for source in src_list:
900+
try:
901+
with rio.open(source, mode="r") as ds:
902+
if ds.bounds.left < overall_bounds.left:
903+
overall_bounds.left = ds.bounds.left
904+
if ds.bounds.right > overall_bounds.right:
905+
overall_bounds.right = ds.bounds.right
906+
if ds.bounds.top > overall_bounds.top:
907+
overall_bounds.top = ds.bounds.top
908+
if ds.bounds.bottom < overall_bounds.bottom:
909+
overall_bounds.bottom = ds.bounds.bottom
910+
911+
total_size += os.path.getsize(source)
912+
except Exception:
913+
click.echo(f"[warning] skipping invalid source '{source}'")
914+
915+
s = utils._get_session()
916+
mapbox_api = utils._get_api()
917+
mapbox_token = utils._get_token(token)
918+
url = "{0}/tilesets/v1/{1}/estimate".format(mapbox_api, tileset)
919+
920+
query_params = {
921+
"filesize": total_size,
922+
"band_count": num_bands,
923+
"access_token": mapbox_token,
924+
}
925+
926+
if overall_bounds is not None:
927+
query_params["bounds"] = json.dumps([*overall_bounds])
928+
929+
if minzoom is not None:
930+
query_params["minzoom"] = minzoom
931+
932+
if maxzoom is not None:
933+
query_params["maxzoom"] = maxzoom
934+
935+
response = s.get(url, params=query_params)
936+
937+
if not response.ok:
938+
raise errors.TilesetsError(response.text)
939+
940+
parsed = json.loads(response.text)
941+
if "cu" not in parsed:
942+
raise errors.TilesetsError(response.text)
943+
944+
click.echo(
945+
response.text
946+
if raw
947+
else f"\nEstimated CUs for '{tileset}': {click.style(parsed['cu'], bold=True, fg=155)}. To publish your tileset, run 'tilesets publish'."
948+
)
949+
950+
843951
@cli.command("estimate-area")
844952
@cligj.features_in_arg
845953
@click.option(

setup.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ def read(fname):
4444
"estimate-area": [
4545
"supermercado~=0.2.0",
4646
],
47+
"estimate-cu": [
48+
"rasterio~=1.4.1",
49+
],
4750
"test": [
4851
"codecov",
4952
"pytest==6.2.5",

tests/conftest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,17 @@ def token_environ(monkeypatch):
1111
monkeypatch.setenv("MapboxAccessToken", "test-token")
1212

1313

14+
@pytest.fixture(scope="function")
15+
def api_environ(monkeypatch):
16+
monkeypatch.setenv("MAPBOX_API", "https://api.mapbox.com")
17+
18+
1419
class _MockResponse:
1520
def __init__(self, mock_json, status_code=200):
1621
self.text = json.dumps(mock_json)
1722
self._json = mock_json
1823
self.status_code = status_code
24+
self.ok = status_code < 400
1925

2026
def MockResponse(self):
2127
return self

0 commit comments

Comments
 (0)