Skip to content

Commit 6d7780d

Browse files
haithcoatjclaude
andcommitted
Support year-prefixed download tile filenames and v2 manifest
Source Cooperative renamed the per-tile GeoParquet files to include a year prefix and published a new manifest with per-year stats dicts. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 01e18df commit 6d7780d

3 files changed

Lines changed: 30 additions & 18 deletions

File tree

src/composables/__tests__/useDownloadGrid.test.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,29 @@ import { getDownloadParquetUrl, DOWNLOAD_GRID_URL } from '../../layers/Download-
77
import useSettings from '../useSettings'
88

99
describe('getDownloadParquetUrl', () => {
10-
it('builds a Source Cooperative URL using year and tile id', () => {
10+
it('builds a Source Cooperative URL using year and tile id, with a year-prefixed filename', () => {
1111
expect(getDownloadParquetUrl(2025, 'N40W100')).toBe(
12-
'https://data.source.coop/ftw/global-field-boundaries/download-tiles/geoparquet/2025/N40W100.parquet',
12+
'https://data.source.coop/ftw/global-field-boundaries/download-tiles/geoparquet/2025/2025_N40W100.parquet',
1313
)
1414
})
1515

1616
it('handles southern/western hemisphere tiles', () => {
1717
expect(getDownloadParquetUrl(2024, 'S03E036')).toBe(
18-
'https://data.source.coop/ftw/global-field-boundaries/download-tiles/geoparquet/2024/S03E036.parquet',
18+
'https://data.source.coop/ftw/global-field-boundaries/download-tiles/geoparquet/2024/2024_S03E036.parquet',
1919
)
2020
})
2121
})
2222

2323
describe('DOWNLOAD_GRID_URL', () => {
24-
it('points to the download-tiles manifest on Source Cooperative', () => {
24+
it('points to the v2 download-tiles manifest on Source Cooperative', () => {
2525
expect(DOWNLOAD_GRID_URL).toBe(
26-
'https://data.source.coop/ftw/global-field-boundaries/download-tiles/ftw-download-grid.geojson',
26+
'https://data.source.coop/ftw/global-field-boundaries/download-tiles/ftw-download-grid-v2.geojson',
2727
)
2828
})
2929
})
3030

3131
describe('featureToGridCell', () => {
32-
it('extracts tile metadata from feature properties', () => {
32+
it('extracts tile metadata from feature properties, including per-year stats dicts', () => {
3333
const feature = new Feature({
3434
geometry: new Polygon([
3535
[
@@ -44,8 +44,8 @@ describe('featureToGridCell', () => {
4444
lat_min: 40,
4545
lon_min: 0,
4646
years: [2024, 2025],
47-
feature_count: 1234,
48-
size_bytes: 2_500_000,
47+
feature_counts: { '2024': 1234, '2025': 1502 },
48+
size_bytes: { '2024': 2_500_000, '2025': 2_780_000 },
4949
})
5050

5151
const cell = featureToGridCell(feature)
@@ -54,8 +54,8 @@ describe('featureToGridCell', () => {
5454
lat_min: 40,
5555
lon_min: 0,
5656
years: [2024, 2025],
57-
feature_count: 1234,
58-
size_bytes: 2_500_000,
57+
feature_counts: { '2024': 1234, '2025': 1502 },
58+
size_bytes: { '2024': 2_500_000, '2025': 2_780_000 },
5959
})
6060
})
6161

@@ -84,7 +84,7 @@ describe('featureToGridCell', () => {
8484
years: [2025],
8585
})
8686
const cell = featureToGridCell(feature)
87-
expect(cell.feature_count).toBeUndefined()
87+
expect(cell.feature_counts).toBeUndefined()
8888
expect(cell.size_bytes).toBeUndefined()
8989
})
9090

@@ -177,7 +177,7 @@ describe('useDownloadGrid', () => {
177177
expect(handleGridClick(fakeMap, [0, 0])).toBe(true)
178178
expect(clickSpy).toHaveBeenCalled()
179179
expect(anchor.href).toBe(
180-
'https://data.source.coop/ftw/global-field-boundaries/download-tiles/geoparquet/2025/N40W100.parquet',
180+
'https://data.source.coop/ftw/global-field-boundaries/download-tiles/geoparquet/2025/2025_N40W100.parquet',
181181
)
182182
expect(anchor.download).toBe('ftw-fields-N40W100-2025.parquet')
183183
})

src/composables/useDownloadGrid.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ export interface GridCell {
1111
lat_min: number
1212
lon_min: number
1313
years: number[]
14-
feature_count?: number
15-
size_bytes?: number
14+
// Per-year dicts keyed by year-as-string (e.g. "2024", "2025"), as served by
15+
// the v2 manifest. Absent years are simply missing keys.
16+
feature_counts?: Record<string, number>
17+
size_bytes?: Record<string, number>
1618
}
1719

1820
const { settings } = useSettings()
@@ -27,6 +29,16 @@ const DOWNLOAD_GRID_MAX_ZOOM = 8
2729
// Track maps we've already wired up so we don't register duplicate listeners.
2830
const initializedMaps = new WeakSet<Map>()
2931

32+
/** Narrow an unknown value to a year-keyed dict of numbers. */
33+
function toNumericYearDict(value: unknown): Record<string, number> | undefined {
34+
if (!value || typeof value !== 'object') return undefined
35+
const out: Record<string, number> = {}
36+
for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
37+
if (typeof v === 'number') out[k] = v
38+
}
39+
return Object.keys(out).length > 0 ? out : undefined
40+
}
41+
3042
/** Convert an OL feature from the grid layer into our GridCell DTO. */
3143
export function featureToGridCell(feature: FeatureLike): GridCell {
3244
const props = feature.getProperties()
@@ -35,8 +47,8 @@ export function featureToGridCell(feature: FeatureLike): GridCell {
3547
lat_min: typeof props.lat_min === 'number' ? props.lat_min : 0,
3648
lon_min: typeof props.lon_min === 'number' ? props.lon_min : 0,
3749
years: Array.isArray(props.years) ? props.years : [],
38-
feature_count: typeof props.feature_count === 'number' ? props.feature_count : undefined,
39-
size_bytes: typeof props.size_bytes === 'number' ? props.size_bytes : undefined,
50+
feature_counts: toNumericYearDict(props.feature_counts),
51+
size_bytes: toNumericYearDict(props.size_bytes),
4052
}
4153
}
4254

src/layers/Download-Grid-Layer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import { Fill, Stroke, Style } from 'ol/style'
55
import type { FeatureLike } from 'ol/Feature'
66

77
export const DOWNLOAD_GRID_URL =
8-
'https://data.source.coop/ftw/global-field-boundaries/download-tiles/ftw-download-grid.geojson'
8+
'https://data.source.coop/ftw/global-field-boundaries/download-tiles/ftw-download-grid-v2.geojson'
99

1010
export const getDownloadParquetUrl = (year: number, tileId: string) =>
11-
`https://data.source.coop/ftw/global-field-boundaries/download-tiles/geoparquet/${year}/${tileId}.parquet`
11+
`https://data.source.coop/ftw/global-field-boundaries/download-tiles/geoparquet/${year}/${year}_${tileId}.parquet`
1212

1313
const normalStyle = new Style({
1414
fill: new Fill({ color: 'rgba(0, 136, 136, 0.05)' }),

0 commit comments

Comments
 (0)