From 7f56679a46453abf0db44d8d307cbdbf1d912003 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Thu, 1 Oct 2026 16:21:20 -0700 Subject: [PATCH] fix: show the whole weather map when a window cannot cover the area The map window was moved inside the product's bounds without checking that it still covered the requested area, so an area across the antimeridian got an image of only one side. A window must now hold the whole area, clipped to the product, or the tool returns the product's full extent. --- src/tools/queries/atmosphere.js | 21 +++++++++++++++------ src/tools/queries/atmosphere.test.mjs | 16 ++++++++++++++++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/tools/queries/atmosphere.js b/src/tools/queries/atmosphere.js index 838e4849f..f8e32012c 100644 --- a/src/tools/queries/atmosphere.js +++ b/src/tools/queries/atmosphere.js @@ -33,10 +33,12 @@ const round = (value, digits) => /** * A 2:1 box snapped to 0.25° around the area, moved inside the product's - * bounds. Returns null when the box would not fit, meaning the whole - * extent should be shown instead. + * bounds. Returns null when no such box covers the whole area (it would not + * fit, or the area crosses the antimeridian), meaning the whole extent + * should be shown instead. */ export function weatherWindow(area, bounds) { + if (area.west > area.east) return null; const center = areaCenter(area); const halfHeight = Math.max( MIN_HALF_HEIGHT_DEGREES, @@ -57,12 +59,19 @@ export function weatherWindow(area, bounds) { snapDown(bounds.north - height), ); const mapBox = { west, south, east: west + width, north: south + height }; - return mapBox.west >= bounds.west && + const insideProduct = + mapBox.west >= bounds.west && mapBox.east <= bounds.east && mapBox.south >= bounds.south && - mapBox.north <= bounds.north - ? mapBox - : null; + mapBox.north <= bounds.north; + // Moving the box inside the product can uncover part of the area; only a + // box that still holds the whole area (clipped to the product) will do. + const coversArea = + mapBox.west <= Math.max(area.west, bounds.west) && + mapBox.east >= Math.min(area.east, bounds.east) && + mapBox.south <= Math.max(area.south, bounds.south) && + mapBox.north >= Math.min(area.north, bounds.north); + return insideProduct && coversArea ? mapBox : null; } export const getWeatherMap = defineTool({ diff --git a/src/tools/queries/atmosphere.test.mjs b/src/tools/queries/atmosphere.test.mjs index 8623904ab..cf19d83f3 100644 --- a/src/tools/queries/atmosphere.test.mjs +++ b/src/tools/queries/atmosphere.test.mjs @@ -2,6 +2,7 @@ import assert from 'node:assert/strict'; import test from 'node:test'; import { composeCatalog, coreTools } from '../index.js'; import { weatherWindow } from './atmosphere.js'; +import { resolveArea } from '../area.js'; const CONUS = { west: -130, south: 20, east: -60, north: 55 }; const PNG = Uint8Array.from([0x89, 0x50, 0x4e, 0x47]); @@ -156,3 +157,18 @@ test('wind is sampled from the model grid at the location', async () => { (error) => error.code === 'unavailable', ); }); + +test('a weather window that cannot cover the area falls back to the whole map', async () => { + const world = { west: -180, south: -90, east: 180, north: 90 }; + // 100 km around 179.8° crosses the antimeridian. + const dateline = await resolveArea({ lat: 0, lon: 179.8, radius_km: 100 }); + assert.equal(weatherWindow(dateline, world), null); + // Near a product edge the window moves inside and still covers the area. + const conus = { west: -130, south: 20, east: -60, north: 55 }; + const edge = await resolveArea({ lat: 40, lon: -129.5, radius_km: 50 }); + const box = weatherWindow(edge, conus); + assert.ok(box); + assert.ok(box.west <= Math.max(edge.west, conus.west)); + assert.ok(box.east >= edge.east); + assert.ok(box.south <= edge.south && box.north >= edge.north); +});