Skip to content

Commit

Permalink
Add tms parameter to cli for MosaicJSON (#233)
Browse files Browse the repository at this point in the history
* start of better support for tms in cli

* updated some tests

* rename of tms parameter and adding to function def

* Update test_cli.py

* fix

* optional tms

* added stac asset for test in progress

* should be working but seeing very odd behavior locally, may need to restart computer...

* skip mars mosaic create if pyproj is too old

* reformat

---------

Co-authored-by: vincentsarago <[email protected]>
  • Loading branch information
AndrewAnnex and vincentsarago authored Oct 4, 2024
1 parent 45fd81e commit b862d48
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 22 deletions.
4 changes: 2 additions & 2 deletions cogeo_mosaic/backends/sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def write(self, overwrite: bool = False):
:layers
);
""",
self.mosaic_def.model_dump(exclude={"tiles"}),
self.mosaic_def.model_dump(exclude={"tiles"}, mode="json"),
)

self.db.executemany(
Expand Down Expand Up @@ -271,7 +271,7 @@ def update(
layers = :layers
WHERE name=:name
""",
self.mosaic_def.model_dump(exclude={"tiles"}),
self.mosaic_def.model_dump(exclude={"tiles"}, mode="json"),
)

if add_first:
Expand Down
13 changes: 11 additions & 2 deletions cogeo_mosaic/mosaic.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,13 +247,15 @@ def from_urls(
minzoom: Optional[int] = None,
maxzoom: Optional[int] = None,
max_threads: int = 20,
tilematrixset: Optional[morecantile.TileMatrixSet] = None,
quiet: bool = True,
**kwargs,
):
"""Create mosaicjson from COG urls.
Attributes:
urls (list): List of COGs.
tilematrixset: (morecantile.TileMatrixSet), optional (default: "WebMercatorQuad")
minzoom (int): Force mosaic min-zoom.
maxzoom (int): Force mosaic max-zoom.
max_threads (int): Max threads to use (default: 20).
Expand All @@ -271,7 +273,9 @@ def from_urls(
>>> MosaicJSON.from_urls(["1.tif", "2.tif"])
"""
features = get_footprints(urls, max_threads=max_threads, quiet=quiet)
features = get_footprints(
urls, max_threads=max_threads, tms=tilematrixset, quiet=quiet
)

if minzoom is None:
data_minzoom = {feat["properties"]["minzoom"] for feat in features}
Expand All @@ -297,7 +301,12 @@ def from_urls(
raise MultipleDataTypeError("Dataset should have the same data type")

return cls._create_mosaic(
features, minzoom=minzoom, maxzoom=maxzoom, quiet=quiet, **kwargs
features,
minzoom=minzoom,
maxzoom=maxzoom,
tilematrixset=tilematrixset,
quiet=quiet,
**kwargs,
)

@classmethod
Expand Down
135 changes: 119 additions & 16 deletions cogeo_mosaic/scripts/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import multiprocessing
import os
import sys
from typing import Optional

import click
import cligj
Expand All @@ -20,7 +21,7 @@
else:
from importlib.metadata import entry_points

tms = morecantile.tms.get("WebMercatorQuad")
default_tms = morecantile.tms.get("WebMercatorQuad")


@with_plugins(entry_points(group="cogeo_mosaic.plugins"))
Expand Down Expand Up @@ -59,6 +60,11 @@ def cogeo_cli():
default=lambda: os.environ.get("MAX_THREADS", multiprocessing.cpu_count() * 5),
help="threads",
)
@click.option(
"--tms",
help="Path to TileMatrixSet JSON file or valid TMS id.",
type=str,
)
@click.option("--name", type=str, help="Mosaic name")
@click.option("--description", type=str, help="Mosaic description")
@click.option("--attribution", type=str, help="Image attibution")
Expand All @@ -78,12 +84,22 @@ def create(
min_tile_cover,
tile_cover_sort,
threads,
tms,
name,
description,
attribution,
quiet,
):
"""Create mosaic definition file."""
tilematrixset: Optional[morecantile.TileMatrixSet] = None
if tms:
if tms.endswith(".json"):
with open(tms, "r") as f:
tilematrixset = morecantile.TileMatrixSet(**json.load(f))

else:
tilematrixset = morecantile.tms.get(tms)

input_files = [file.strip() for file in input_files if file.strip()]
mosaicjson = MosaicJSON.from_urls(
input_files,
Expand All @@ -92,6 +108,7 @@ def create(
quadkey_zoom=quadkey_zoom,
minimum_tile_cover=min_tile_cover,
tile_cover_sort=tile_cover_sort,
tilematrixset=tilematrixset,
max_threads=threads,
quiet=quiet,
)
Expand All @@ -104,7 +121,11 @@ def create(
mosaicjson.attribution = attribution

if output:
with MosaicBackend(output, mosaic_def=mosaicjson) as mosaic:
with MosaicBackend(
output,
mosaic_def=mosaicjson,
tms=tilematrixset or default_tms,
) as mosaic:
mosaic.write(overwrite=True)
else:
click.echo(mosaicjson.model_dump_json(exclude_none=True))
Expand All @@ -115,11 +136,26 @@ def create(
@click.option(
"--url", type=str, required=True, help="URL to which the mosaic should be uploaded."
)
def upload(file, url):
@click.option(
"--tms",
help="Path to TileMatrixSet JSON file or valid TMS id.",
type=str,
)
def upload(file, url, tms):
"""Upload mosaic definition file."""
mosaicjson = json.load(file)
tilematrixset: Optional[morecantile.TileMatrixSet] = None
if tms:
if tms.endswith(".json"):
with open(tms, "r") as f:
tilematrixset = morecantile.TileMatrixSet(**json.load(f))

with MosaicBackend(url, mosaic_def=mosaicjson) as mosaic:
else:
tilematrixset = morecantile.tms.get(tms)

mosaicjson = MosaicJSON.model_validate(json.load(file))

tilematrixset = tilematrixset or mosaicjson.tilematrixset or default_tms
with MosaicBackend(url, mosaic_def=mosaicjson, tms=tilematrixset) as mosaic:
mosaic.write(overwrite=True)


Expand All @@ -140,6 +176,11 @@ def upload(file, url):
@click.option(
"--tile-cover-sort", help="Sort files by covering %", is_flag=True, default=False
)
@click.option(
"--tms",
help="Path to TileMatrixSet JSON file or valid TMS id.",
type=str,
)
@click.option("--name", type=str, help="Mosaic name")
@click.option("--description", type=str, help="Mosaic description")
@click.option("--attribution", type=str, help="Image attibution")
Expand All @@ -159,12 +200,22 @@ def create_from_features(
quadkey_zoom,
min_tile_cover,
tile_cover_sort,
tms,
name,
description,
attribution,
quiet,
):
"""Create mosaic definition file."""
tilematrixset: Optional[morecantile.TileMatrixSet] = None
if tms:
if tms.endswith(".json"):
with open(tms, "r") as f:
tilematrixset = morecantile.TileMatrixSet(**json.load(f))

else:
tilematrixset = morecantile.tms.get(tms)

mosaicjson = MosaicJSON.from_features(
list(features),
minzoom,
Expand All @@ -173,6 +224,7 @@ def create_from_features(
accessor=lambda feature: feature["properties"][property],
minimum_tile_cover=min_tile_cover,
tile_cover_sort=tile_cover_sort,
tilematrixset=tilematrixset,
quiet=quiet,
)

Expand All @@ -184,7 +236,11 @@ def create_from_features(
mosaicjson.attribution = attribution

if output:
with MosaicBackend(output, mosaic_def=mosaicjson) as mosaic:
with MosaicBackend(
output,
mosaic_def=mosaicjson,
tms=tilematrixset or default_tms,
) as mosaic:
mosaic.write(overwrite=True)
else:
click.echo(mosaicjson.model_dump_json(exclude_none=True))
Expand All @@ -200,6 +256,11 @@ def create_from_features(
is_flag=True,
default=True,
)
@click.option(
"--tms",
help="Path to TileMatrixSet JSON file or valid TMS id.",
type=str,
)
@click.option(
"--threads",
type=int,
Expand All @@ -213,11 +274,20 @@ def create_from_features(
is_flag=True,
default=False,
)
def update(input_files, input_mosaic, min_tile_cover, add_first, threads, quiet):
def update(input_files, input_mosaic, min_tile_cover, tms, add_first, threads, quiet):
"""Update mosaic definition file."""
tilematrixset: Optional[morecantile.TileMatrixSet] = None
if tms:
if tms.endswith(".json"):
with open(tms, "r") as f:
tilematrixset = morecantile.TileMatrixSet(**json.load(f))

else:
tilematrixset = morecantile.tms.get(tms)

input_files = input_files.read().splitlines()
features = get_footprints(input_files, max_threads=threads)
with MosaicBackend(input_mosaic) as mosaic:
features = get_footprints(input_files, tms=tilematrixset, max_threads=threads)
with MosaicBackend(input_mosaic, tms=tilematrixset or default_tms) as mosaic:
mosaic.update(
features,
add_first=add_first,
Expand All @@ -235,18 +305,34 @@ def update(input_files, input_mosaic, min_tile_cover, add_first, threads, quiet)
default=lambda: os.environ.get("MAX_THREADS", multiprocessing.cpu_count() * 5),
help="threads",
)
@click.option(
"--tms",
help="Path to TileMatrixSet JSON file or valid TMS id.",
type=str,
)
@click.option(
"--quiet",
"-q",
help="Remove progressbar and other non-error output.",
is_flag=True,
default=False,
)
def footprint(input_files, output, threads, quiet):
def footprint(input_files, output, threads, tms, quiet):
"""Create mosaic definition file."""
tilematrixset: Optional[morecantile.TileMatrixSet] = None
if tms:
if tms.endswith(".json"):
with open(tms, "r") as f:
tilematrixset = morecantile.TileMatrixSet(**json.load(f))

else:
tilematrixset = morecantile.tms.get(tms)

input_files = input_files.read().splitlines()
foot = {
"features": get_footprints(input_files, max_threads=threads, quiet=quiet),
"features": get_footprints(
input_files, max_threads=threads, tms=tilematrixset, quiet=quiet
),
"type": "FeatureCollection",
}

Expand All @@ -266,9 +352,26 @@ def footprint(input_files, output, threads, quiet):
is_flag=True,
help="Print as JSON.",
)
def info(input, to_json):
@click.option(
"--tms",
help="Path to TileMatrixSet JSON file or valid TMS id.",
type=str,
)
def info(input, to_json, tms):
"""Return info about the mosaic."""
with MosaicBackend(input) as mosaic:
tilematrixset: Optional[morecantile.TileMatrixSet] = None
if tms:
if tms.endswith(".json"):
with open(tms, "r") as f:
tilematrixset = morecantile.TileMatrixSet(**json.load(f))

else:
tilematrixset = morecantile.tms.get(tms)

with MosaicBackend(
input,
tms=tilematrixset or default_tms,
) as mosaic:
_info = {
"Path": input,
"Backend": mosaic._backend_name,
Expand All @@ -285,7 +388,7 @@ def info(input, to_json):
}

geo = {
"TileMatrixSet": "WebMercatorQuad",
"TileMatrixSet": mosaic.tms.id,
"BoundingBox": mosaic.mosaic_def.bounds,
"Center": mosaic.mosaic_def.center,
"Min Zoom": mosaic.mosaic_def.minzoom,
Expand Down Expand Up @@ -352,8 +455,8 @@ def to_geojson(input, collect):
features = []
with MosaicBackend(input) as mosaic:
for qk, assets in mosaic.mosaic_def.tiles.items():
tile = tms.quadkey_to_tile(qk)
west, south, east, north = tms.bounds(tile)
tile = mosaic.tms.quadkey_to_tile(qk)
west, south, east, north = mosaic.tms.bounds(tile)

geom = {
"type": "Polygon",
Expand Down
6 changes: 4 additions & 2 deletions cogeo_mosaic/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import sys
from concurrent import futures
from contextlib import ExitStack
from typing import Dict, List, Sequence, Tuple
from typing import Dict, List, Optional, Sequence, Tuple

import click
import morecantile
Expand Down Expand Up @@ -78,7 +78,7 @@ def get_dataset_info(

def get_footprints(
dataset_list: Sequence[str],
tms: morecantile.TileMatrixSet = WEB_MERCATOR_TMS,
tms: Optional[morecantile.TileMatrixSet] = None,
max_threads: int = 20,
quiet: bool = True,
) -> List:
Expand All @@ -100,6 +100,8 @@ def get_footprints(
tuple of footprint feature.
"""
tms = tms or WEB_MERCATOR_TMS

with ExitStack() as ctx:
fout = ctx.enter_context(open(os.devnull, "w")) if quiet else sys.stderr
with futures.ThreadPoolExecutor(max_workers=max_threads) as executor:
Expand Down
Loading

0 comments on commit b862d48

Please sign in to comment.