Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .codacy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
exclude_paths:
- 'tests/**'
- 'docs/**'
- 'examples/**'
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- run: pip install -e ".[dev]"
- run: ruff check src/ tests/
- run: ruff format --check src/ tests/
- run: mypy --strict src/abovepy/
- run: mypy src/abovepy/

test:
needs: lint
Expand Down
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ include = ["src/abovepy"]
[tool.hatch.build.targets.wheel]
packages = ["src/abovepy"]

[tool.hatch.build.targets.wheel.force-include]
"src/abovepy/templates" = "abovepy/templates"

[tool.ruff]
target-version = "py311"
line-length = 100
Expand Down Expand Up @@ -109,7 +112,7 @@ strict = true
warn_unused_ignores = false

[[tool.mypy.overrides]]
module = ["abovepy.terrain", "abovepy.titiler._pgstac", "abovepy.viz._urls"]
module = ["abovepy.terrain", "abovepy.titiler._pgstac", "abovepy.viz._urls", "abovepy.package", "abovepy.qgis"]
disable_error_code = ["no-any-return", "arg-type", "operator", "attr-defined", "var-annotated"]

[[tool.mypy.overrides]]
Expand Down
4 changes: 4 additions & 0 deletions src/abovepy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
CountyError,
DownloadError,
MosaicError,
PackageError,
ProductError,
ReadError,
SearchError,
Expand All @@ -40,6 +41,7 @@
from abovepy.client import KyFromAboveClient
from abovepy.mosaics import county_mosaic_url
from abovepy.obliques import list_oblique_seasons, search_obliques
from abovepy.package import Package
from abovepy.products import Product, ProductType, list_products
from abovepy.result import SearchResult
from abovepy.stac import clear_cache
Expand Down Expand Up @@ -255,6 +257,8 @@ def info(source: str | None = None) -> pd.DataFrame | dict[str, Any]:
"DownloadError",
"KyFromAboveClient",
"MosaicError",
"Package",
"PackageError",
"Product",
"ProductError",
"ProductType",
Expand Down
4 changes: 4 additions & 0 deletions src/abovepy/_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,7 @@ class BboxError(AbovepyError, ValueError):

class AnalysisError(AbovepyError):
"""Raised when a terrain/analysis computation fails."""


class PackageError(AbovepyError):
"""Raised when deliverable packaging fails."""
67 changes: 66 additions & 1 deletion src/abovepy/cli.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""CLI for abovepy — ``abovepy <subcommand>`` or ``python -m abovepy <subcommand>``.

Subcommands: search, download, mosaic, info, products, tile-url, preview, estimate.
Subcommands: search, download, mosaic, info, products, tile-url, preview, estimate, package.
"""

from __future__ import annotations
Expand Down Expand Up @@ -126,6 +126,25 @@ def _build_parser() -> argparse.ArgumentParser:
_add_format_arg(p_estimate, choices=["table", "json"])
p_estimate.set_defaults(func=_cmd_estimate)

# --- package ---
p_package = subparsers.add_parser("package", help="Package tiles into a delivery folder")
_add_product_arg(p_package)
_add_area_args(p_package)
p_package.add_argument("--max-items", type=int, default=500, help="Max tiles (default: 500)")
p_package.add_argument("--output-dir", "-o", required=True, help="Output directory")
p_package.add_argument(
"--no-preview", action="store_true", default=False, help="Skip preview image"
)
p_package.add_argument(
"--no-qgis", action="store_true", default=False, help="Skip QGIS project"
)
p_package.add_argument(
"--no-checksums", action="store_true", default=False, help="Skip checksums"
)
p_package.add_argument("--overwrite", action="store_true", help="Overwrite existing output")
p_package.add_argument("--workers", type=int, default=4, help="Concurrent workers (default: 4)")
p_package.set_defaults(func=_cmd_package)

return parser


Expand Down Expand Up @@ -354,6 +373,52 @@ def _cmd_estimate(args: argparse.Namespace) -> None:
print(f"Total est: {est['total_mb']} MB")


def _cmd_package(args: argparse.Namespace) -> None:
"""Execute the 'package' subcommand."""
import abovepy

bbox = _parse_bbox(args.bbox) if args.bbox else None
point = _parse_point(args.point) if args.point else None
buffer_feet = getattr(args, "buffer_feet", None)

print("Searching for tiles...", file=sys.stderr)
result = abovepy.search(
product=args.product,
bbox=bbox,
county=args.county,
point=point,
buffer_miles=args.buffer,
buffer_feet=buffer_feet,
max_items=args.max_items,
)

if result.empty:
print("No tiles found.", file=sys.stderr)
sys.exit(1)

est = result.estimate_size()
print(
f"Found {est['tile_count']} tile(s), ~{est['total_mb']} MB. Packaging...",
file=sys.stderr,
)

pkg = result.package(
output_dir=args.output_dir,
include_preview=not args.no_preview,
qgis_project=not args.no_qgis,
checksums=not args.no_checksums,
overwrite=args.overwrite,
max_workers=args.workers,
)

print(f"Package complete: {pkg.output_dir}")
print(f" Tiles: {pkg.tile_count}")
print(f" Size: {pkg.total_size_mb} MB")
print(f" Files: {len(pkg.files)}")
if pkg.has_qgis_project:
print(f" QGIS: {pkg.output_dir.name}.qgs")


# ---------------------------------------------------------------------------
# Argument helpers
# ---------------------------------------------------------------------------
Expand Down
Loading
Loading