|
| 1 | +import asyncio |
| 2 | +from contextlib import asynccontextmanager |
| 3 | + |
| 4 | +import click |
| 5 | + |
| 6 | +from planet.cli.cmds import command |
| 7 | +from planet.cli.io import echo_json |
| 8 | +from planet.cli.session import CliSession |
| 9 | +from planet.cli.types import BoundingBox, DateTime, Geometry |
| 10 | +from planet.cli.validators import check_geom |
| 11 | +from planet.clients.mosaics import MosaicsClient |
| 12 | + |
| 13 | + |
| 14 | +@asynccontextmanager |
| 15 | +async def client(ctx): |
| 16 | + async with CliSession() as sess: |
| 17 | + cl = MosaicsClient(sess, base_url=ctx.obj['BASE_URL']) |
| 18 | + yield cl |
| 19 | + |
| 20 | + |
| 21 | +include_links = click.option("--links", |
| 22 | + is_flag=True, |
| 23 | + help=("If enabled, include API links")) |
| 24 | + |
| 25 | +name_contains = click.option( |
| 26 | + "--name-contains", |
| 27 | + type=str, |
| 28 | + help=("Match if the name contains text, case-insensitive")) |
| 29 | + |
| 30 | +bbox = click.option('--bbox', |
| 31 | + type=BoundingBox(), |
| 32 | + help=("Region to download as comma-delimited strings: " |
| 33 | + " lon_min,lat_min,lon_max,lat_max")) |
| 34 | + |
| 35 | +interval = click.option("--interval", |
| 36 | + type=str, |
| 37 | + help=("Match this interval, e.g. 1 mon")) |
| 38 | + |
| 39 | +acquired_gt = click.option("--acquired_gt", |
| 40 | + type=DateTime(), |
| 41 | + help=("Imagery acquisition after than this date")) |
| 42 | + |
| 43 | +acquired_lt = click.option("--acquired_lt", |
| 44 | + type=DateTime(), |
| 45 | + help=("Imagery acquisition before than this date")) |
| 46 | + |
| 47 | +geometry = click.option('--geometry', |
| 48 | + type=Geometry(), |
| 49 | + callback=check_geom, |
| 50 | + help=("A geojson geometry to search with. " |
| 51 | + "Can be a string, filename, or - for stdin.")) |
| 52 | + |
| 53 | + |
| 54 | +def _strip_links(resource): |
| 55 | + if isinstance(resource, dict): |
| 56 | + resource.pop("_links", None) |
| 57 | + return resource |
| 58 | + |
| 59 | + |
| 60 | +async def _output(result, pretty, include_links=False): |
| 61 | + if asyncio.iscoroutine(result): |
| 62 | + result = await result |
| 63 | + if result is None: |
| 64 | + raise click.ClickException("not found") |
| 65 | + if not include_links: |
| 66 | + _strip_links(result) |
| 67 | + echo_json(result, pretty) |
| 68 | + else: |
| 69 | + results = [_strip_links(r) async for r in result] |
| 70 | + echo_json(results, pretty) |
| 71 | + |
| 72 | + |
| 73 | +@click.group() # type: ignore |
| 74 | +@click.pass_context |
| 75 | +@click.option('-u', |
| 76 | + '--base-url', |
| 77 | + default=None, |
| 78 | + help='Assign custom base Mosaics API URL.') |
| 79 | +def mosaics(ctx, base_url): |
| 80 | + """Commands for interacting with the Mosaics API""" |
| 81 | + ctx.obj['BASE_URL'] = base_url |
| 82 | + |
| 83 | + |
| 84 | +@mosaics.group() # type: ignore |
| 85 | +def series(): |
| 86 | + """Commands for interacting with Mosaic Series through the Mosaics API""" |
| 87 | + |
| 88 | + |
| 89 | +@command(mosaics, name="contributions") |
| 90 | +@click.argument("name") |
| 91 | +@click.argument("quad") |
| 92 | +async def quad_contributions(ctx, name, quad, pretty): |
| 93 | + '''Get contributing scenes for a mosaic quad |
| 94 | +
|
| 95 | + Example: |
| 96 | +
|
| 97 | + planet mosaics contribution global_monthly_2025_04_mosaic 575-1300 |
| 98 | + ''' |
| 99 | + async with client(ctx) as cl: |
| 100 | + item = await cl.get_quad(name, quad) |
| 101 | + await _output(cl.get_quad_contributions(item), pretty) |
| 102 | + |
| 103 | + |
| 104 | +@command(mosaics, name="info") |
| 105 | +@click.argument("name", required=True) |
| 106 | +@include_links |
| 107 | +async def mosaic_info(ctx, name, pretty, links): |
| 108 | + """Get information for a specific mosaic |
| 109 | +
|
| 110 | + Example: |
| 111 | +
|
| 112 | + planet mosaics info global_monthly_2025_04_mosaic |
| 113 | + """ |
| 114 | + async with client(ctx) as cl: |
| 115 | + await _output(cl.get_mosaic(name), pretty, links) |
| 116 | + |
| 117 | + |
| 118 | +@command(mosaics, name="list") |
| 119 | +@name_contains |
| 120 | +@interval |
| 121 | +@acquired_gt |
| 122 | +@acquired_lt |
| 123 | +@include_links |
| 124 | +async def mosaics_list(ctx, |
| 125 | + name_contains, |
| 126 | + interval, |
| 127 | + acquired_gt, |
| 128 | + acquired_lt, |
| 129 | + pretty, |
| 130 | + links): |
| 131 | + """List all mosaics |
| 132 | +
|
| 133 | + Example: |
| 134 | +
|
| 135 | + planet mosaics info global_monthly_2025_04_mosaic |
| 136 | + """ |
| 137 | + async with client(ctx) as cl: |
| 138 | + await _output( |
| 139 | + cl.list_mosaics(name_contains=name_contains, |
| 140 | + interval=interval, |
| 141 | + acquired_gt=acquired_gt, |
| 142 | + acquired_lt=acquired_lt), |
| 143 | + pretty, |
| 144 | + links) |
| 145 | + |
| 146 | + |
| 147 | +@command(series, name="info") |
| 148 | +@click.argument("name", required=True) |
| 149 | +@include_links |
| 150 | +async def series_info(ctx, name, pretty, links): |
| 151 | + """Get information for a specific series |
| 152 | +
|
| 153 | + Example: |
| 154 | +
|
| 155 | + planet series info "Global Quarterly" |
| 156 | + """ |
| 157 | + async with client(ctx) as cl: |
| 158 | + await _output(cl.get_series(name), pretty, links) |
| 159 | + |
| 160 | + |
| 161 | +@command(series, name="list") |
| 162 | +@name_contains |
| 163 | +@interval |
| 164 | +@acquired_gt |
| 165 | +@acquired_lt |
| 166 | +@include_links |
| 167 | +async def series_list(ctx, |
| 168 | + name_contains, |
| 169 | + interval, |
| 170 | + acquired_gt, |
| 171 | + acquired_lt, |
| 172 | + pretty, |
| 173 | + links): |
| 174 | + """List series |
| 175 | +
|
| 176 | + Example: |
| 177 | +
|
| 178 | + planet mosaics series list --name-contains=Global |
| 179 | + """ |
| 180 | + async with client(ctx) as cl: |
| 181 | + await _output( |
| 182 | + cl.list_series( |
| 183 | + name_contains, |
| 184 | + interval, |
| 185 | + acquired_gt, |
| 186 | + acquired_lt, |
| 187 | + ), |
| 188 | + pretty, |
| 189 | + links) |
| 190 | + |
| 191 | + |
| 192 | +@command(series, name="list-mosaics") |
| 193 | +@click.argument("name", required=True) |
| 194 | +@click.option("--latest", |
| 195 | + is_flag=True, |
| 196 | + help=("Get the latest mosaic in the series")) |
| 197 | +@acquired_gt |
| 198 | +@acquired_lt |
| 199 | +@include_links |
| 200 | +async def list_series_mosaics(ctx, |
| 201 | + name, |
| 202 | + acquired_gt, |
| 203 | + acquired_lt, |
| 204 | + latest, |
| 205 | + links, |
| 206 | + pretty): |
| 207 | + """List mosaics in a series |
| 208 | +
|
| 209 | + Example: |
| 210 | +
|
| 211 | + planet mosaics series list-mosaics global_monthly_2025_04_mosaic |
| 212 | + """ |
| 213 | + async with client(ctx) as cl: |
| 214 | + await _output( |
| 215 | + cl.list_series_mosaics(name, |
| 216 | + acquired_gt=acquired_gt, |
| 217 | + acquired_lt=acquired_lt, |
| 218 | + latest=latest), |
| 219 | + pretty, |
| 220 | + links) |
| 221 | + |
| 222 | + |
| 223 | +@command(mosaics, name="search") |
| 224 | +@click.argument("name", required=True) |
| 225 | +@bbox |
| 226 | +@geometry |
| 227 | +@click.option("--summary", |
| 228 | + is_flag=True, |
| 229 | + help=("Get a count of how many quads would be returned")) |
| 230 | +@include_links |
| 231 | +async def list_quads(ctx, name, bbox, geometry, summary, links, pretty): |
| 232 | + """Search quads |
| 233 | +
|
| 234 | + Example: |
| 235 | +
|
| 236 | + planet mosaics search global_monthly_2025_04_mosaic --bbox -100,40,-100,41 |
| 237 | + """ |
| 238 | + async with client(ctx) as cl: |
| 239 | + mosaic = await cl.get_mosaic(name) |
| 240 | + if mosaic is None: |
| 241 | + raise click.ClickException("No mosaic named " + name) |
| 242 | + await _output( |
| 243 | + cl.list_quads(mosaic, |
| 244 | + minimal=False, |
| 245 | + bbox=bbox, |
| 246 | + geometry=geometry, |
| 247 | + summary=summary), |
| 248 | + pretty, |
| 249 | + links) |
| 250 | + |
| 251 | + |
| 252 | +@command(mosaics, name="download") |
| 253 | +@click.argument("name", required=True) |
| 254 | +@click.option('--output-dir', |
| 255 | + default='.', |
| 256 | + help=('Directory for file download.'), |
| 257 | + type=click.Path(exists=True, |
| 258 | + resolve_path=True, |
| 259 | + writable=True, |
| 260 | + file_okay=False)) |
| 261 | +@bbox |
| 262 | +@geometry |
| 263 | +async def download(ctx, name, output_dir, bbox, geometry, **kwargs): |
| 264 | + """Download quads from a mosaic |
| 265 | +
|
| 266 | + Example: |
| 267 | +
|
| 268 | + planet mosaics search global_monthly_2025_04_mosaic --bbox -100,40,-100,41 |
| 269 | + """ |
| 270 | + quiet = ctx.obj['QUIET'] |
| 271 | + async with client(ctx) as cl: |
| 272 | + mosaic = await cl.get_mosaic(name) |
| 273 | + if mosaic is None: |
| 274 | + raise click.ClickException("No mosaic named " + name) |
| 275 | + await cl.download_quads(mosaic, |
| 276 | + bbox=bbox, |
| 277 | + geometry=geometry, |
| 278 | + directory=output_dir, |
| 279 | + progress_bar=not quiet) |
0 commit comments