diff --git a/src/composables/__tests__/useDownloadGrid.test.ts b/src/composables/__tests__/useDownloadGrid.test.ts index a97667d..5caa32d 100644 --- a/src/composables/__tests__/useDownloadGrid.test.ts +++ b/src/composables/__tests__/useDownloadGrid.test.ts @@ -7,29 +7,29 @@ import { getDownloadParquetUrl, DOWNLOAD_GRID_URL } from '../../layers/Download- import useSettings from '../useSettings' describe('getDownloadParquetUrl', () => { - it('builds a Source Cooperative URL using year and tile id', () => { + it('builds a Source Cooperative URL using year and tile id, with a year-prefixed filename', () => { expect(getDownloadParquetUrl(2025, 'N40W100')).toBe( - 'https://data.source.coop/ftw/global-field-boundaries/download-tiles/geoparquet/2025/N40W100.parquet', + 'https://data.source.coop/ftw/global-field-boundaries/download-tiles/geoparquet/2025/2025_N40W100.parquet', ) }) it('handles southern/western hemisphere tiles', () => { expect(getDownloadParquetUrl(2024, 'S03E036')).toBe( - 'https://data.source.coop/ftw/global-field-boundaries/download-tiles/geoparquet/2024/S03E036.parquet', + 'https://data.source.coop/ftw/global-field-boundaries/download-tiles/geoparquet/2024/2024_S03E036.parquet', ) }) }) describe('DOWNLOAD_GRID_URL', () => { - it('points to the download-tiles manifest on Source Cooperative', () => { + it('points to the v2 download-tiles manifest on Source Cooperative', () => { expect(DOWNLOAD_GRID_URL).toBe( - 'https://data.source.coop/ftw/global-field-boundaries/download-tiles/ftw-download-grid.geojson', + 'https://data.source.coop/ftw/global-field-boundaries/download-tiles/ftw-download-grid-v2.geojson', ) }) }) describe('featureToGridCell', () => { - it('extracts tile metadata from feature properties', () => { + it('extracts tile metadata from feature properties, including per-year stats dicts', () => { const feature = new Feature({ geometry: new Polygon([ [ @@ -44,8 +44,8 @@ describe('featureToGridCell', () => { lat_min: 40, lon_min: 0, years: [2024, 2025], - feature_count: 1234, - size_bytes: 2_500_000, + feature_counts: { '2024': 1234, '2025': 1502 }, + size_bytes: { '2024': 2_500_000, '2025': 2_780_000 }, }) const cell = featureToGridCell(feature) @@ -54,8 +54,8 @@ describe('featureToGridCell', () => { lat_min: 40, lon_min: 0, years: [2024, 2025], - feature_count: 1234, - size_bytes: 2_500_000, + feature_counts: { '2024': 1234, '2025': 1502 }, + size_bytes: { '2024': 2_500_000, '2025': 2_780_000 }, }) }) @@ -84,7 +84,7 @@ describe('featureToGridCell', () => { years: [2025], }) const cell = featureToGridCell(feature) - expect(cell.feature_count).toBeUndefined() + expect(cell.feature_counts).toBeUndefined() expect(cell.size_bytes).toBeUndefined() }) @@ -177,7 +177,7 @@ describe('useDownloadGrid', () => { expect(handleGridClick(fakeMap, [0, 0])).toBe(true) expect(clickSpy).toHaveBeenCalled() expect(anchor.href).toBe( - 'https://data.source.coop/ftw/global-field-boundaries/download-tiles/geoparquet/2025/N40W100.parquet', + 'https://data.source.coop/ftw/global-field-boundaries/download-tiles/geoparquet/2025/2025_N40W100.parquet', ) expect(anchor.download).toBe('ftw-fields-N40W100-2025.parquet') }) diff --git a/src/composables/useDownloadGrid.ts b/src/composables/useDownloadGrid.ts index ad3e35d..59ae475 100644 --- a/src/composables/useDownloadGrid.ts +++ b/src/composables/useDownloadGrid.ts @@ -11,8 +11,10 @@ export interface GridCell { lat_min: number lon_min: number years: number[] - feature_count?: number - size_bytes?: number + // Per-year dicts keyed by year-as-string (e.g. "2024", "2025"), as served by + // the v2 manifest. Absent years are simply missing keys. + feature_counts?: Record + size_bytes?: Record } const { settings } = useSettings() @@ -27,6 +29,16 @@ const DOWNLOAD_GRID_MAX_ZOOM = 8 // Track maps we've already wired up so we don't register duplicate listeners. const initializedMaps = new WeakSet() +/** Narrow an unknown value to a year-keyed dict of numbers. */ +function toNumericYearDict(value: unknown): Record | undefined { + if (!value || typeof value !== 'object') return undefined + const out: Record = {} + for (const [k, v] of Object.entries(value as Record)) { + if (typeof v === 'number') out[k] = v + } + return Object.keys(out).length > 0 ? out : undefined +} + /** Convert an OL feature from the grid layer into our GridCell DTO. */ export function featureToGridCell(feature: FeatureLike): GridCell { const props = feature.getProperties() @@ -35,8 +47,8 @@ export function featureToGridCell(feature: FeatureLike): GridCell { lat_min: typeof props.lat_min === 'number' ? props.lat_min : 0, lon_min: typeof props.lon_min === 'number' ? props.lon_min : 0, years: Array.isArray(props.years) ? props.years : [], - feature_count: typeof props.feature_count === 'number' ? props.feature_count : undefined, - size_bytes: typeof props.size_bytes === 'number' ? props.size_bytes : undefined, + feature_counts: toNumericYearDict(props.feature_counts), + size_bytes: toNumericYearDict(props.size_bytes), } } diff --git a/src/layers/Download-Grid-Layer.ts b/src/layers/Download-Grid-Layer.ts index 1f7c4d6..8b9f7be 100644 --- a/src/layers/Download-Grid-Layer.ts +++ b/src/layers/Download-Grid-Layer.ts @@ -5,10 +5,10 @@ import { Fill, Stroke, Style } from 'ol/style' import type { FeatureLike } from 'ol/Feature' export const DOWNLOAD_GRID_URL = - 'https://data.source.coop/ftw/global-field-boundaries/download-tiles/ftw-download-grid.geojson' + 'https://data.source.coop/ftw/global-field-boundaries/download-tiles/ftw-download-grid-v2.geojson' export const getDownloadParquetUrl = (year: number, tileId: string) => - `https://data.source.coop/ftw/global-field-boundaries/download-tiles/geoparquet/${year}/${tileId}.parquet` + `https://data.source.coop/ftw/global-field-boundaries/download-tiles/geoparquet/${year}/${year}_${tileId}.parquet` const normalStyle = new Style({ fill: new Fill({ color: 'rgba(0, 136, 136, 0.05)' }),