Files
geointel/frontend/src/components/map/mapWorkspaceUtils.test.ts
T
Codex 20829f1a24
GeoIntel release gates / Compile, test, contracts and builds (push) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (push) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (push) Canceled after 0s
fix(map): make area analysis scale-aware
2026-07-21 21:59:56 +02:00

142 lines
5.2 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
bboxesEqual,
isMunicipalityAreaName,
isSelectionBoundedDataset,
normalizeBboxFromCorners,
parseBboxInput,
productCoversZones,
resultMetricLabel,
selectedAreaCoverageZones,
selectionAnalysisScale,
selectionAreaSquareMetres,
selectionDimensions,
splitSelectionBbox,
} from './mapWorkspaceUtils'
import type { VectorSelectionBBox, VectorSelectionResponse } from '../../types'
describe('map workspace selection guards', () => {
it('normalizes drag corners into an EPSG:4326 bbox', () => {
expect(normalizeBboxFromCorners([5.2, 51.3], [4.8, 50.9])).toEqual({
min_x: 4.8,
min_y: 50.9,
max_x: 5.2,
max_y: 51.3,
crs: 'EPSG:4326',
})
})
it('rejects empty, inverted and degenerate manual selections', () => {
expect(parseBboxInput({ min_x: '', min_y: '', max_x: '', max_y: '' })).toBeNull()
expect(parseBboxInput({ min_x: '5', min_y: '51', max_x: '4', max_y: '52' })).toBeNull()
expect(parseBboxInput({ min_x: '4', min_y: '51', max_x: '4', max_y: '52' })).toBeNull()
})
it('treats sub-nanodegree bbox drift as the same persisted selection', () => {
const bbox = normalizeBboxFromCorners([4.98, 51.15], [5.02, 51.19])
expect(bboxesEqual(bbox, { ...bbox, max_x: bbox.max_x + 1e-10 })).toBe(true)
expect(bboxesEqual(bbox, { ...bbox, max_x: bbox.max_x + 1e-5 })).toBe(false)
})
it('computes a positive bounded selection area and renders governed metrics', () => {
const bbox = normalizeBboxFromCorners([5.0, 51.0], [5.01, 51.01])
expect(selectionAreaSquareMetres(bbox)).toBeGreaterThan(700_000)
expect(selectionAreaSquareMetres(bbox)).toBeLessThan(900_000)
const result = {
feature_count: 12,
truncated: false,
summary: {
metric_key: 'forest_area',
metric_value: 14.236,
metric_unit: 'ha',
},
} as unknown as VectorSelectionResponse
expect(resultMetricLabel(result)).toBe('14,24 ha')
})
it('maps national work areas to regional provider zones without merging sources', () => {
expect(selectedAreaCoverageZones('Belgium land')).toEqual([
'belgium',
'flanders',
'wallonia',
'brussels',
])
expect(selectedAreaCoverageZones('RC Ardennes inland')).toEqual(['wallonia'])
expect(selectedAreaCoverageZones('Brussels-Capital Region')).toEqual(['brussels'])
expect(selectedAreaCoverageZones('Language boundary')).toEqual(['flanders', 'wallonia'])
expect(productCoversZones(['wallonia'], ['flanders', 'wallonia'])).toBe(true)
expect(productCoversZones(['brussels'], ['wallonia'])).toBe(false)
})
it('recognizes Dutch and release-golden municipality areas as local scope', () => {
expect(isMunicipalityAreaName('Gemeente Mol')).toBe(true)
expect(isMunicipalityAreaName('RC Golden - Mol municipality')).toBe(true)
expect(isMunicipalityAreaName('Flanders')).toBe(false)
expect(isMunicipalityAreaName('RC Golden - Kempen region')).toBe(false)
})
it('distinguishes selection-bounded acquisitions from reusable regional datasets', () => {
expect(isSelectionBoundedDataset({
geometry_clipped_to_selection: true,
bbox_epsg4326: [4.55, 50.58, 4.56, 50.59],
})).toBe(true)
expect(isSelectionBoundedDataset({
coverage_scope: 'bounded_selection',
bbox_epsg4326: [5.1, 51.17, 5.11, 51.18],
})).toBe(true)
expect(isSelectionBoundedDataset({
geometry_clipped_to_selection: false,
bbox_epsg4326: [2.5, 49.5, 6.4, 51.6],
})).toBe(false)
expect(isSelectionBoundedDataset({
geometry_clipped_to_selection: true,
})).toBe(false)
})
it('classifies local, regional and overview selections before provider calls', () => {
const bbox = (widthDegrees: number, heightDegrees: number): VectorSelectionBBox => ({
min_x: 5,
min_y: 51,
max_x: 5 + widthDegrees,
max_y: 51 + heightDegrees,
crs: 'EPSG:4326',
})
expect(selectionAnalysisScale(bbox(0.1, 0.1))).toBe('detail')
expect(selectionAnalysisScale(bbox(0.35, 0.25))).toBe('regional')
expect(selectionAnalysisScale(bbox(1, 1))).toBe('overview')
})
it('splits regional selections into provider-safe tiles without changing the outer bounds', () => {
const selection: VectorSelectionBBox = {
min_x: 5,
min_y: 51,
max_x: 5.5,
max_y: 51.35,
crs: 'EPSG:4326',
}
const tiles = splitSelectionBbox(selection)
expect(tiles.length).toBeGreaterThan(1)
expect(Math.min(...tiles.map((tile) => tile.min_x))).toBe(selection.min_x)
expect(Math.min(...tiles.map((tile) => tile.min_y))).toBe(selection.min_y)
expect(Math.max(...tiles.map((tile) => tile.max_x))).toBe(selection.max_x)
expect(Math.max(...tiles.map((tile) => tile.max_y))).toBe(selection.max_y)
for (const tile of tiles) {
const dimensions = selectionDimensions(tile)
expect(dimensions.widthMetres).toBeLessThanOrEqual(18_100)
expect(dimensions.heightMetres).toBeLessThanOrEqual(18_100)
}
})
it('refuses an unbounded detail fan-out', () => {
const selection: VectorSelectionBBox = {
min_x: 4,
min_y: 50,
max_x: 6,
max_y: 52,
crs: 'EPSG:4326',
}
expect(() => splitSelectionBbox(selection)).toThrow('detailpartities')
})
})