Skip to content
Merged
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
12 changes: 11 additions & 1 deletion src/abovepy/_mosaic.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,17 @@ def _build_vrt(
Path
Path to the created VRT file.
"""
from osgeo import gdal
try:
from osgeo import gdal
except ImportError as exc:
from abovepy._exceptions import MosaicError

raise MosaicError(
"VRT construction requires the GDAL Python bindings, which are not "
"installed by default. Install with `conda install -c conda-forge gdal` "
"(recommended) or `pip install gdal` (requires matching system GDAL headers). "
"Alternatively, pass an output path ending in `.tif` to merge via rasterio."
) from exc

gdal.UseExceptions()

Expand Down
28 changes: 17 additions & 11 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ def test_search_dem_phase3_frankfort(self, frankfort_bbox):
"""Search returns DEM tiles for the Frankfort area."""
tiles = abovepy.search(bbox=frankfort_bbox, product="dem_phase3", max_items=10)
assert len(tiles) > 0
assert "tile_id" in tiles.columns
assert "asset_url" in tiles.columns
assert "tile_id" in tiles.tiles.columns
assert "asset_url" in tiles.tiles.columns

def test_search_by_county(self):
"""County-based search returns results."""
Expand All @@ -59,7 +59,7 @@ def test_asset_urls_are_accessible(self, frankfort_bbox):
import httpx

tiles = abovepy.search(bbox=frankfort_bbox, product="dem_phase3", max_items=1)
url = tiles.iloc[0]["asset_url"]
url = tiles.tiles.iloc[0]["asset_url"]
resp = httpx.head(url, follow_redirects=True, timeout=30)
assert resp.status_code == 200

Expand Down Expand Up @@ -90,7 +90,7 @@ def test_dem_products(self, frankfort_bbox, product):
"""DEM products return tiles for Frankfort area."""
tiles = abovepy.search(bbox=frankfort_bbox, product=product, max_items=3)
assert len(tiles) > 0
assert tiles.iloc[0]["product"] == product
assert tiles.tiles.iloc[0]["product"] == product

@pytest.mark.parametrize(
"product",
Expand All @@ -113,9 +113,11 @@ def test_ortho_products(self, frankfort_bbox, product):
"laz_phase3",
],
)
def test_laz_products(self, frankfort_bbox, product):
"""LiDAR products return tiles for Frankfort area."""
tiles = abovepy.search(bbox=frankfort_bbox, product=product, max_items=3)
def test_laz_products(self, pike_county_bbox, product):
"""LiDAR products return tiles. Uses Pike County because Phase 1 LAZ
does not cover Franklin County (Frankfort); Phase 1 was flown
county-by-county 2010-2017 and Franklin was not in that batch."""
tiles = abovepy.search(bbox=pike_county_bbox, product=product, max_items=3)
assert len(tiles) > 0


Expand Down Expand Up @@ -198,7 +200,7 @@ class TestLiveRead:
def test_read_cog_windowed(self, frankfort_bbox):
"""Read a real tile with a windowed bbox."""
tiles = abovepy.search(bbox=frankfort_bbox, product="dem_phase3", max_items=1)
url = tiles.iloc[0]["asset_url"]
url = tiles.tiles.iloc[0]["asset_url"]
data, profile = abovepy.read(url, bbox=frankfort_bbox)
assert data.shape[0] >= 1
assert profile["crs"] is not None
Expand All @@ -207,7 +209,7 @@ def test_read_cog_windowed(self, frankfort_bbox):
def test_read_full_tile(self, frankfort_bbox):
"""Read a full tile without bbox clipping."""
tiles = abovepy.search(bbox=frankfort_bbox, product="dem_phase3", max_items=1)
url = tiles.iloc[0]["asset_url"]
url = tiles.tiles.iloc[0]["asset_url"]
data, profile = abovepy.read(url)
assert data.shape[1] > 0
assert data.shape[2] > 0
Expand All @@ -216,7 +218,7 @@ def test_read_full_tile(self, frankfort_bbox):
def test_read_returns_epsg3089(self, frankfort_bbox):
"""Read tile CRS should be EPSG:3089."""
tiles = abovepy.search(bbox=frankfort_bbox, product="dem_phase3", max_items=1)
url = tiles.iloc[0]["asset_url"]
url = tiles.tiles.iloc[0]["asset_url"]
_, profile = abovepy.read(url)
crs_str = str(profile["crs"])
assert "3089" in crs_str
Expand Down Expand Up @@ -253,6 +255,10 @@ def test_download_skip_existing(self, frankfort_bbox):
@pytest.mark.slow
def test_mosaic_vrt(self, frankfort_bbox):
"""Download 2 tiles, mosaic to VRT, verify it's readable."""
pytest.importorskip(
"osgeo",
reason="VRT construction requires GDAL Python bindings (not a default runtime dep).",
)
tiles = abovepy.search(bbox=frankfort_bbox, product="dem_phase3", max_items=2)
if len(tiles) < 2:
pytest.skip("Need at least 2 tiles for mosaic test")
Expand Down Expand Up @@ -308,6 +314,6 @@ def test_laz_tile_url_accessible(self, frankfort_bbox):
tiles = abovepy.search(bbox=frankfort_bbox, product="laz_phase2", max_items=1)
if tiles.empty:
pytest.skip("No COPC tiles found in Frankfort area")
url = tiles.iloc[0]["asset_url"]
url = tiles.tiles.iloc[0]["asset_url"]
resp = httpx.head(url, follow_redirects=True, timeout=30)
assert resp.status_code == 200
Loading