|
23 | 23 | logger = logging.getLogger(__name__) |
24 | 24 |
|
25 | 25 |
|
26 | | -def _bbox_around(lat: float, lng: float, half_side_m: float) -> tuple[float, float, float, float]: |
27 | | - """Return a lon/lat bbox ``(minx, miny, maxx, maxy)`` centred on ``(lat, lng)``.""" |
28 | | - dlat = (half_side_m / 111_320.0) |
29 | | - dlng = (half_side_m / (111_320.0 * max(math.cos(math.radians(lat)), 1e-6))) |
| 26 | +def _bbox_around( |
| 27 | + lat: float, lng: float, half_x_m: float, half_y_m: float | None = None, |
| 28 | +) -> tuple[float, float, float, float]: |
| 29 | + """Return a lon/lat bbox ``(minx, miny, maxx, maxy)`` centred on ``(lat, lng)``. |
| 30 | +
|
| 31 | + ``half_x_m`` controls the east-west extent; ``half_y_m`` controls |
| 32 | + north-south (defaults to ``half_x_m`` for a square footprint). |
| 33 | + """ |
| 34 | + if half_y_m is None: |
| 35 | + half_y_m = half_x_m |
| 36 | + dlat = half_y_m / 111_320.0 |
| 37 | + dlng = half_x_m / (111_320.0 * max(math.cos(math.radians(lat)), 1e-6)) |
30 | 38 | return (lng - dlng, lat - dlat, lng + dlng, lat + dlat) |
31 | 39 |
|
32 | 40 |
|
@@ -63,7 +71,11 @@ async def render_preview( |
63 | 71 | logger.warning("URL signing failed for %s, using unsigned", loc.slug, exc_info=exc) |
64 | 72 | signed_url = latest.cog_url |
65 | 73 |
|
66 | | - minx, miny, maxx, maxy = _bbox_around(parcel.latitude, parcel.longitude, half_side_m) |
| 74 | + # Scale bbox to match image aspect ratio so Titiler doesn't stretch |
| 75 | + aspect = width / height |
| 76 | + half_y = half_side_m |
| 77 | + half_x = half_side_m * aspect |
| 78 | + minx, miny, maxx, maxy = _bbox_around(parcel.latitude, parcel.longitude, half_x, half_y) |
67 | 79 | titiler_url = ( |
68 | 80 | f"{settings.titiler_url}/cog/bbox/" |
69 | 81 | f"{minx},{miny},{maxx},{maxy}/{width}x{height}.jpg" |
|
0 commit comments