@sailboat-computer/data-storage
Version:
Shared data storage library for sailboat computer v3
176 lines (145 loc) • 7.36 kB
text/typescript
/**
* Unit tests for geotemporal grid downsampling strategy
*/
import { createGeotemporalGridDownsampler } from '../../../../src/downsampling/strategies/geotemporal-grid';
import { GeotemporalGridStrategy } from '../../../../src/types';
import {
createWeatherDataGrid,
createWeatherTimeSeries,
createStormWeatherDataPoint
} from '../../../fixtures/weather-data';
import {
createSeaStateDataGrid,
createSeaStateTimeSeries,
createRoughSeaStateDataPoint
} from '../../../fixtures/sea-state-data';
describe('GeotemporalGridDownsampler', () => {
const downsampler = createGeotemporalGridDownsampler();
// Test strategy configuration
const weatherStrategy: GeotemporalGridStrategy = {
type: 'geotemporal-grid',
baseGridSize: 1.0,
adaptiveGridLevels: 3,
coastalResolutionBoost: 2.0,
temporalHierarchy: {
recent: { maxAge: 7, resolution: 1 }, // 7 days, 1 hour resolution
mediumTerm: { maxAge: 30, resolution: 6 }, // 30 days, 6 hour resolution
longTerm: { maxAge: 90, resolution: 24 }, // 90 days, 1 day resolution
historical: { maxAge: 365, resolution: 168 }, // 365 days, 1 week resolution
seasonal: { resolution: 720 } // 1 month resolution
},
vesselPosition: { latitude: 37.7749, longitude: -122.4194 },
criticalFeatureThresholds: {
pressureChangeRate: 1.0, // 1 mb/hour
windSpeed: 25, // 25 knots
waveHeight: 2.5, // 2.5 meters
temperatureGradient: 5.0 // 5°C per degree
}
};
const seaStateStrategy: GeotemporalGridStrategy = {
type: 'geotemporal-grid',
baseGridSize: 0.5, // Higher base resolution for sea state
adaptiveGridLevels: 3,
temporalHierarchy: {
recent: { maxAge: 3, resolution: 1 }, // 3 days, 1 hour resolution
mediumTerm: { maxAge: 14, resolution: 3 }, // 14 days, 3 hour resolution
longTerm: { maxAge: 60, resolution: 12 }, // 60 days, 12 hour resolution
historical: { maxAge: 180, resolution: 72 }, // 180 days, 3 day resolution
seasonal: { resolution: 720 } // 1 month resolution
},
criticalFeatureThresholds: {
waveHeight: 2.0, // 2.0 meters
temperatureGradient: 3.0 // 3°C per degree (for currents)
}
};
describe('type property', () => {
it('should return the correct type', () => {
expect(downsampler.type).toBe('geotemporal-grid');
});
});
describe('downsample method', () => {
it('should return original data if less than 3 points are provided', async () => {
const data = createWeatherTimeSeries(37.7749, -122.4194, 2, 3600000, new Date());
const result = await downsampler.downsample(data, weatherStrategy);
expect(result).toEqual(data);
});
it('should downsample weather data', async () => {
const data = createWeatherDataGrid(37.7749, -122.4194, 1.0, 10, new Date());
const result = await downsampler.downsample(data, weatherStrategy);
// Result should be smaller than original data
expect(result.length).toBeLessThan(data.length);
// Result should have downsampling metadata
expect(result[0].metadata.tags.downsampled).toBe('true');
expect(result[0].metadata.tags.downsamplingStrategy).toBe('geotemporal-grid');
expect(result[0].metadata.tags.downsampledFrom).toBe(data.length.toString());
// Result should have grid cell information
expect(result[0].metadata.tags.gridCell).toBeDefined();
expect(result[0].metadata.tags.gridLevel).toBeDefined();
expect(result[0].metadata.tags.bucketResolution).toBeDefined();
expect(result[0].metadata.tags.pointCount).toBeDefined();
});
it('should downsample sea state data', async () => {
const data = createSeaStateDataGrid(37.8, -122.5, 0.5, 10, new Date());
const result = await downsampler.downsample(data, seaStateStrategy);
// Result should be smaller than original data
expect(result.length).toBeLessThan(data.length);
// Result should have downsampling metadata
expect(result[0].metadata.tags.downsampled).toBe('true');
expect(result[0].metadata.tags.downsamplingStrategy).toBe('geotemporal-grid');
expect(result[0].metadata.tags.downsampledFrom).toBe(data.length.toString());
});
it('should preserve critical weather features', async () => {
// Create weather data grid
const baseData = createWeatherDataGrid(37.7749, -122.4194, 1.0, 10, new Date());
// Add a storm point with wind speed above threshold
const stormPoint = createStormWeatherDataPoint(37.7749, -122.4194, new Date().toISOString());
const data = [...baseData, stormPoint];
const result = await downsampler.downsample(data, weatherStrategy);
// Find the storm point in the result
const resultStormPoint = result.find(point =>
point.data.windSpeed >= weatherStrategy.criticalFeatureThresholds!.windSpeed!
);
// Storm point should be preserved
expect(stormPoint).toBeDefined();
expect(stormPoint!.data.windSpeed).toBeGreaterThanOrEqual(weatherStrategy.criticalFeatureThresholds!.windSpeed!);
});
it('should preserve critical sea state features', async () => {
// Create sea state data grid
const baseData = createSeaStateDataGrid(37.8, -122.5, 0.5, 10, new Date());
// Add a rough sea state point with high waves
const roughPoint = createRoughSeaStateDataPoint(37.8, -122.5, new Date().toISOString());
const data = [...baseData, roughPoint];
const result = await downsampler.downsample(data, seaStateStrategy);
// Find the high wave point in the result
const highWavePoint = result.find(point =>
point.data.waveHeight >= seaStateStrategy.criticalFeatureThresholds!.waveHeight!
);
// High wave point should be preserved
expect(highWavePoint).toBeDefined();
expect(highWavePoint!.data.waveHeight).toBeGreaterThanOrEqual(seaStateStrategy.criticalFeatureThresholds!.waveHeight!);
});
it('should create higher resolution grid cells near vessel position', async () => {
const data = createWeatherDataGrid(37.7749, -122.4194, 2.0, 15, new Date());
const result = await downsampler.downsample(data, weatherStrategy);
// Get grid levels
const gridLevels = result.map(point => parseInt(point.metadata.tags.gridLevel));
// Should have multiple grid levels
const uniqueLevels = new Set(gridLevels);
expect(uniqueLevels.size).toBeGreaterThan(1);
// Should have at least one high-resolution cell (level > 0)
expect(Math.max(...gridLevels)).toBeGreaterThan(0);
});
it('should handle errors gracefully', async () => {
// Create weather data
const data = createWeatherTimeSeries(37.7749, -122.4194, 10, 3600000, new Date());
// Remove coordinates to make it invalid
data.forEach(item => {
delete item.data.latitude;
delete item.data.longitude;
});
// Should return original data on error
const result = await downsampler.downsample(data, weatherStrategy);
expect(result).toEqual(data);
});
});
});