Added frost risk
This commit is contained in:
@@ -13,6 +13,7 @@ import XYZ from 'ol/source/XYZ'
|
|||||||
import Feature from 'ol/Feature'
|
import Feature from 'ol/Feature'
|
||||||
import Point from 'ol/geom/Point'
|
import Point from 'ol/geom/Point'
|
||||||
import CircleGeom from 'ol/geom/Circle'
|
import CircleGeom from 'ol/geom/Circle'
|
||||||
|
import { defaults as defaultInteractions } from 'ol/interaction'
|
||||||
import { fromLonLat, toLonLat } from 'ol/proj'
|
import { fromLonLat, toLonLat } from 'ol/proj'
|
||||||
import { Style, Fill, Stroke, Circle as CircleStyle } from 'ol/style'
|
import { Style, Fill, Stroke, Circle as CircleStyle } from 'ol/style'
|
||||||
import 'ol/ol.css'
|
import 'ol/ol.css'
|
||||||
@@ -187,6 +188,7 @@ onMounted(async () => {
|
|||||||
map = new Map({
|
map = new Map({
|
||||||
target: mapEl.value!,
|
target: mapEl.value!,
|
||||||
layers: [buildBaseLayer(props.dark), stationLayer],
|
layers: [buildBaseLayer(props.dark), stationLayer],
|
||||||
|
interactions: defaultInteractions({ altShiftDragRotate: false, pinchRotate: false }),
|
||||||
view: new View({ center: fromLonLat([lon, lat]), zoom: 8, maxZoom: 13 }),
|
view: new View({ center: fromLonLat([lon, lat]), zoom: 8, maxZoom: 13 }),
|
||||||
controls: [],
|
controls: [],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {queryHourly, getCoords, queryCurrent} from './sensors.mjs';
|
import {queryHourly, getCoords, queryCurrent} from './sensors.mjs';
|
||||||
import {getCelestialCurrent} from './celestial.mjs';
|
import {getCelestialCurrent} from './celestial.mjs';
|
||||||
import {localDateStr} from './config.mjs';
|
import {localDateStr} from './config.mjs';
|
||||||
|
import {frostRisk} from './openweather.mjs';
|
||||||
|
|
||||||
export let lastForecast = { ts: 0, slots: [], summary: null }
|
export let lastForecast = { ts: 0, slots: [], summary: null }
|
||||||
export const forecastTTL = 5 * 60 * 60_000
|
export const forecastTTL = 5 * 60 * 60_000
|
||||||
@@ -188,6 +189,7 @@ export async function get24HourForecast(currentSensors) {
|
|||||||
slot.heat_index = Math.round(heatIndex(slot.temperature, slot.humidity) * 100) / 100;
|
slot.heat_index = Math.round(heatIndex(slot.temperature, slot.humidity) * 100) / 100;
|
||||||
slot.vapor_pressure_deficit = vaporPressureDeficit(slot.temperature, slot.humidity);
|
slot.vapor_pressure_deficit = vaporPressureDeficit(slot.temperature, slot.humidity);
|
||||||
slot.humidity_abs = absoluteHumidity(slot.temperature, slot.humidity);
|
slot.humidity_abs = absoluteHumidity(slot.temperature, slot.humidity);
|
||||||
|
slot.frost_risk = frostRisk(slot.temperature, slot.dew_point, slot.humidity)
|
||||||
|
|
||||||
slot.solar_wm2 = slot.sun_elevation > 0
|
slot.solar_wm2 = slot.sun_elevation > 0
|
||||||
? Math.round(Math.sin((slot.sun_elevation * Math.PI) / 180) * 1000 * (1 - slot.clouds * 0.75) * 100) / 100
|
? Math.round(Math.sin((slot.sun_elevation * Math.PI) / 180) * 1000 * (1 - slot.clouds * 0.75) * 100) / 100
|
||||||
@@ -252,6 +254,7 @@ function summarise(slots) {
|
|||||||
precipitation: avg(fin('precipitation')),
|
precipitation: avg(fin('precipitation')),
|
||||||
precipitation_chance: Math.round(peak(fin('precipitation_chance'))),
|
precipitation_chance: Math.round(peak(fin('precipitation_chance'))),
|
||||||
accumulation: finAll('precipitation').reduce((a, b) => a + b, 0),
|
accumulation: finAll('precipitation').reduce((a, b) => a + b, 0),
|
||||||
|
frost_risk: slots.find(s => s.frost_risk !== 'None')?.frost_risk ?? slots.at(-1)?.frost_risk ?? 'None',
|
||||||
uv_index: peak(fin('uv_index')),
|
uv_index: peak(fin('uv_index')),
|
||||||
uv_dose: finAll('uv_dose').reduce((a, b) => a + b, 0),
|
uv_dose: finAll('uv_dose').reduce((a, b) => a + b, 0),
|
||||||
solar_wm2: avg(fin('solar_wm2')),
|
solar_wm2: avg(fin('solar_wm2')),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'url'
|
|||||||
import { tzOffsetMinutes } from './config.mjs'
|
import { tzOffsetMinutes } from './config.mjs'
|
||||||
import { getWeatherCondition } from './forecast.mjs'
|
import { getWeatherCondition } from './forecast.mjs'
|
||||||
import { getCelestialForecast } from './celestial.mjs'
|
import { getCelestialForecast } from './celestial.mjs'
|
||||||
|
import {frostRisk} from './openweather.mjs';
|
||||||
|
|
||||||
const DIR = dirname(fileURLToPath(import.meta.url))
|
const DIR = dirname(fileURLToPath(import.meta.url))
|
||||||
const ICON_DIR = resolve(DIR, 'public', 'icons')
|
const ICON_DIR = resolve(DIR, 'public', 'icons')
|
||||||
@@ -138,6 +139,7 @@ export async function dailyWeather(lat, lon, start, end) {
|
|||||||
precipitation: precip,
|
precipitation: precip,
|
||||||
precipitation_chance: precipChance,
|
precipitation_chance: precipChance,
|
||||||
accumulation: precip,
|
accumulation: precip,
|
||||||
|
frost_risk: frostRisk(temp, dewPoint(temp, humidity), humidity),
|
||||||
uv_index: data.daily.uv_index_max[i] ?? 0,
|
uv_index: data.daily.uv_index_max[i] ?? 0,
|
||||||
uv_index_max: data.daily.uv_index_max[i] ?? 0,
|
uv_index_max: data.daily.uv_index_max[i] ?? 0,
|
||||||
uv_dose: 0,
|
uv_dose: 0,
|
||||||
@@ -241,6 +243,7 @@ export async function hourlyWeather(lat, lon, start, end) {
|
|||||||
precipitation: precip,
|
precipitation: precip,
|
||||||
precipitation_chance: precipChance,
|
precipitation_chance: precipChance,
|
||||||
accumulation: precip,
|
accumulation: precip,
|
||||||
|
frost_risk: frostRisk(temp, dewPoint(temp, humidity), humidity),
|
||||||
uv_index: uv,
|
uv_index: uv,
|
||||||
uv_index_max: uv,
|
uv_index_max: uv,
|
||||||
uv_dose: 0,
|
uv_dose: 0,
|
||||||
|
|||||||
@@ -18,3 +18,11 @@ export async function fetchIcon(icon) {
|
|||||||
await downloadIcon(icon, iconPath);
|
await downloadIcon(icon, iconPath);
|
||||||
return iconPath;
|
return iconPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function frostRisk(tempC, dewPointC, humidity) {
|
||||||
|
if (tempC > 4) return 'None'
|
||||||
|
if (tempC <= 0 && dewPointC <= 0) return 'High'
|
||||||
|
if (tempC <= 2 && humidity > 85) return 'Moderate'
|
||||||
|
if (tempC <= 4) return 'Low'
|
||||||
|
return 'None'
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {cfg} from './config.mjs';
|
import {cfg} from './config.mjs';
|
||||||
|
import { frostRisk } from './forecast.mjs';
|
||||||
|
|
||||||
async function query(c, q, start, end) {
|
async function query(c, q, start, end) {
|
||||||
const url = new URL('/api/v1/query_range', c.DB_HOST);
|
const url = new URL('/api/v1/query_range', c.DB_HOST);
|
||||||
@@ -30,9 +31,15 @@ function metricToFields(results) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function queryCurrent() {
|
export async function queryCurrent() {
|
||||||
const c = cfg();
|
const c = cfg()
|
||||||
const results = await queryInstant(c, `{__name__!=""}`);
|
const results = await queryInstant(c, `{__name__!=""}`)
|
||||||
return {time: new Date(), ...metricToFields(results)};
|
const fields = metricToFields(results)
|
||||||
|
const dp = fields.dew_point ?? null
|
||||||
|
return {
|
||||||
|
time: new Date(),
|
||||||
|
...fields,
|
||||||
|
frost_risk: frostRisk(fields.temperature ?? 99, dp ?? 99, fields.humidity ?? 0),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function queryHourly(start, end) {
|
export async function queryHourly(start, end) {
|
||||||
|
|||||||
@@ -10,17 +10,16 @@ export const spec = {
|
|||||||
'/api/position': {
|
'/api/position': {
|
||||||
get: {
|
get: {
|
||||||
summary: 'Station position',
|
summary: 'Station position',
|
||||||
description: 'Latest GPS position and info',
|
description: 'Latest GPS position and altitude',
|
||||||
parameters: [ ],
|
|
||||||
responses: {
|
responses: {
|
||||||
200: {
|
200: {
|
||||||
description: 'Last known position',
|
description: 'Last known position',
|
||||||
content: { 'application/json': { schema: { $ref: '#/components/schemas/DataRow' } } }
|
content: { 'application/json': { schema: { $ref: '#/components/schemas/Position' } } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'/api/data': {
|
'/api/current': {
|
||||||
get: {
|
get: {
|
||||||
summary: 'Current conditions',
|
summary: 'Current conditions',
|
||||||
description: 'Latest reading of all metrics — sensor, forecast, celestial & space weather merged into a single flat object',
|
description: 'Latest reading of all metrics — sensor, forecast, celestial & space weather merged into a single flat object',
|
||||||
@@ -82,153 +81,79 @@ export const spec = {
|
|||||||
Position: {
|
Position: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
lat: { type: 'number', description: 'Latitude' },
|
latitude: { type: 'number', description: 'Latitude (°)' },
|
||||||
lon: { type: 'number', description: 'Longitude' },
|
longitude: { type: 'number', description: 'Longitude (°)' },
|
||||||
alt: { type: 'number', description: 'Altitude' },
|
altitude: { type: 'number', description: 'Altitude (m)' },
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DataRow: {
|
DataRow: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
time: { type: 'string', description: 'ISO8601 timestamp or date' },
|
time: { type: 'string', format: 'date-time', description: 'ISO8601 timestamp' },
|
||||||
// Environment
|
// Environment
|
||||||
temperature: { type: 'number', description: 'Temperature (°C)' },
|
temperature: { type: 'number', description: 'Temperature (°C)' },
|
||||||
env_temp_f: { type: 'number', description: 'Temperature (°F)' },
|
env_temp_min_c: { type: 'number', description: 'Daily min temperature (°C)' },
|
||||||
env_temp_min_c: { type: 'number', description: 'Daily min temperature (°C)' },
|
env_temp_max_c: { type: 'number', description: 'Daily max temperature (°C)' },
|
||||||
env_temp_max_c: { type: 'number', description: 'Daily max temperature (°C)' },
|
humidity: { type: 'number', description: 'Relative humidity (%)' },
|
||||||
humidity: { type: 'number', description: 'Relative humidity (%)' },
|
humidity_abs: { type: 'number', description: 'Absolute humidity (g/m³)' },
|
||||||
dew_point: { type: 'number', description: 'Dew point (°C)' },
|
dew_point: { type: 'number', description: 'Dew point (°C)' },
|
||||||
heat_index: { type: 'number', description: 'Feels like / heat index (°C)' },
|
heat_index: { type: 'number', description: 'Feels like / heat index (°C)' },
|
||||||
pressure_hpa: { type: 'number', description: 'Station pressure (hPa)' },
|
vapor_pressure_deficit: { type: 'number', description: 'Vapour pressure deficit (kPa)' },
|
||||||
pressure_slp: { type: 'number', description: 'Sea level pressure (hPa)' },
|
pressure_hpa: { type: 'number', description: 'Station pressure (hPa)' },
|
||||||
pressure_rate: { type: 'number', description: 'Pressure change rate (hPa/hr)' },
|
pressure_slp: { type: 'number', description: 'Sea level pressure (hPa)' },
|
||||||
pressure_trend: { type: 'string', enum: ['Rising','Falling','Stable'], description: 'Pressure trend label' },
|
pressure_rate: { type: 'number', description: 'Pressure change rate (hPa/hr)' },
|
||||||
humidity_abs: { type: 'number', description: 'Absolute humidity (g/m³)' },
|
pressure_trend: { type: 'string', enum: ['Rising','Falling','Stable'], description: 'Pressure trend label' },
|
||||||
vapor_pressure_deficit: { type: 'number', description: 'Vapour pressure deficit (kPa)' },
|
frost_risk: { type: 'string', enum: ['None','Low','Moderate','High'], description: 'Frost risk level' },
|
||||||
air_quality_ohms: { type: 'number', description: 'BME680 gas resistance (Ω)' },
|
air_quality: { type: 'number', description: 'Air quality index score (0–100)' },
|
||||||
air_quality: { type: 'number', description: 'Air quality index score (0–100)' },
|
air_quality_label: { type: 'string', enum: ['Excellent','Good','Fair','Poor','Very Poor'] },
|
||||||
air_quality_label: { type: 'string', enum: ['Excellent','Good','Fair','Poor','Very Poor'] },
|
// Light & Solar
|
||||||
frost_risk: { type: 'string', enum: ['None','Low','Moderate','High'] },
|
lux: { type: 'number', description: 'Illuminance (lux)' },
|
||||||
// Light
|
uv_index: { type: 'number', description: 'UV index' },
|
||||||
lux: { type: 'number', description: 'Illuminance (lux)' },
|
uv_index_max: { type: 'number', description: 'Daily max UV index' },
|
||||||
uv_index: { type: 'number', description: 'UV index' },
|
uv_dose: { type: 'number', description: 'Accumulated UV dose today (mJ/cm²)' },
|
||||||
solar_wm2: { type: 'number', description: 'Solar irradiance (W/m²)' },
|
solar_wm2: { type: 'number', description: 'Solar irradiance (W/m²)' },
|
||||||
uv_dose: { type: 'number', description: 'Accumulated UV dose today (mJ/cm²)' },
|
daily_light_integral: { type: 'number', description: 'Daily light integral (mol/m²/day)' },
|
||||||
light_burn_time_min: { type: 'number', description: 'Time to sunburn skin type 2 (min)' },
|
clouds: { type: 'number', description: 'Cloud cover fraction (0–1)' },
|
||||||
clouds: { type: 'number', description: 'Estimated cloud cover (%)' },
|
visibility: { type: 'number', description: 'Estimated visibility (km)' },
|
||||||
light_cloud_label: { type: 'string', enum: ['Clear','Partly Cloudy','Mostly Cloudy','Overcast'] },
|
daylight: { type: 'number', description: 'Hours of daylight' },
|
||||||
light_dli: { type: 'number', description: 'Daily light integral (mol/m²/day)' },
|
// Weather
|
||||||
daylight: { type: 'number', description: 'Hours above daylight threshold today' },
|
label: { type: 'string', description: 'Human readable weather label' },
|
||||||
visibility: { type: 'number', description: 'Estimated visibility (km)' },
|
icon: { type: 'string', description: 'OWM icon code e.g. 01d' },
|
||||||
uv_index_max: { type: 'number', description: 'Daily max UV index' },
|
raining: { type: 'string', enum: ['True','False'] },
|
||||||
solar_wm2_sum: { type: 'number', description: 'Daily solar radiation sum (MJ/m²)' },
|
precipitation: { type: 'number', description: 'Precipitation (mm)' },
|
||||||
// Wind (Open-Meteo)
|
precipitation_chance: { type: 'number', description: 'Precipitation probability (%)' },
|
||||||
wind_speed_kmh: { type: 'number', description: 'Wind speed (km/h)' },
|
accumulation: { type: 'number', description: 'Accumulated precipitation (mm)' },
|
||||||
wind_direction_deg: { type: 'number', description: 'Wind direction (°)' },
|
storm_trend: { type: 'number', description: '1=worsening, 0=stable, -1=improving' },
|
||||||
wind_gusts_kmh: { type: 'number', description: 'Wind gusts (km/h)' },
|
// Wind
|
||||||
wind_speed_max_kmh: { type: 'number', description: 'Daily max wind speed (km/h)' },
|
wind_speed: { type: 'number', description: 'Wind speed (km/h)' },
|
||||||
wind_gusts_max_kmh: { type: 'number', description: 'Daily max wind gusts (km/h)' },
|
wind_gusts: { type: 'number', description: 'Wind gusts (km/h)' },
|
||||||
// Forecast
|
wind_direction: { type: 'number', description: 'Wind direction (°)' },
|
||||||
forecast_weathercode: { type: 'number', description: 'WMO weather code' },
|
|
||||||
forecast_weather_label: { type: 'string', description: 'Human readable weather label' },
|
|
||||||
forecast_weather_icon: { type: 'string', description: 'Local icon path e.g. 01d' },
|
|
||||||
forecast_precipitation_mm: { type: 'number', description: 'Precipitation (mm)' },
|
|
||||||
forecast_precipitation_probability: { type: 'number', description: 'Precipitation probability (%)' },
|
|
||||||
forecast_snowfall_mm: { type: 'number', description: 'Snowfall (mm)' },
|
|
||||||
forecast_snow_depth_m: { type: 'number', description: 'Snow depth (m)' },
|
|
||||||
forecast_cape: { type: 'number', description: 'Convective available potential energy (J/kg)' },
|
|
||||||
forecast_freezing_level_m: { type: 'number', description: 'Freezing level altitude (m)' },
|
|
||||||
forecast_evapotranspiration_mm:{ type: 'number', description: 'Evapotranspiration (mm)' },
|
|
||||||
// Seismic
|
|
||||||
seismic_ax: { type: 'number', description: 'Peak acceleration X axis (g)' },
|
|
||||||
seismic_ay: { type: 'number', description: 'Peak acceleration Y axis (g)' },
|
|
||||||
seismic_az: { type: 'number', description: 'Peak acceleration Z axis (g, includes gravity)' },
|
|
||||||
seismic_magnitude: { type: 'number', description: 'Peak seismic magnitude (g, gravity removed)' },
|
|
||||||
// Compass
|
|
||||||
compass_heading: { type: 'number', description: 'Magnetic heading (°)' },
|
|
||||||
compass_x: { type: 'number' },
|
|
||||||
compass_y: { type: 'number' },
|
|
||||||
compass_z: { type: 'number' },
|
|
||||||
// Ground / Accumulation
|
|
||||||
ground_distance: { type: 'number', description: 'LIDAR ground distance (cm)' },
|
|
||||||
lidar_strength: { type: 'number', description: 'LIDAR signal strength' },
|
|
||||||
ground_calibration: { type: 'number', description: 'Calibrated baseline distance (cm)' },
|
|
||||||
accumulation: { type: 'number', description: 'Snow/flood depth (cm)' },
|
|
||||||
ground_accumulation_type: { type: 'string', enum: ['snow','slush','ice','flood'], description: 'Accumulation type' },
|
|
||||||
// Lightning
|
// Lightning
|
||||||
lightning_distance: { type: 'number', description: 'Lightning strike distance (km)' },
|
lightning_rate: { type: 'number', description: 'Strike rate (strikes/hr)' },
|
||||||
lightning_energy: { type: 'number', description: 'Lightning energy' },
|
lightning_distance: { type: 'number', description: 'Lightning strike distance (km)' },
|
||||||
lightning_strikes_per_hour: { type: 'number', description: 'Strike rate (strikes/hr)' },
|
|
||||||
storm_direction: { type: 'string', enum: ['Approaching','Retreating','Stationary'] },
|
|
||||||
lightning_false_positive: { type: 'number', description: '1 if disturber/false positive' },
|
|
||||||
lightning_detector_sensitivity:{ type: 'number', description: 'AS3935 noise floor setting (0–7)' },
|
|
||||||
// GPS
|
|
||||||
latitude: { type: 'number', description: 'Latitude (°)' },
|
|
||||||
longitude: { type: 'number', description: 'Longitude (°)' },
|
|
||||||
altitude: { type: 'number', description: 'Altitude (m)' },
|
|
||||||
gps_satellites: { type: 'number', description: 'Satellites in view' },
|
|
||||||
gps_speed_kmh: { type: 'number', description: 'Ground speed (km/h)' },
|
|
||||||
gps_heading: { type: 'number', description: 'GPS heading (°)' },
|
|
||||||
// Sun
|
// Sun
|
||||||
sun_elevation: { type: 'number', description: 'Solar elevation angle (°)' },
|
sun_elevation: { type: 'number', description: 'Solar elevation angle (°)' },
|
||||||
sun_azimuth: { type: 'number', description: 'Solar azimuth (°)' },
|
sun_azimuth: { type: 'number', description: 'Solar azimuth (°)' },
|
||||||
sun_sunrise: { type: 'string', description: 'Sunrise time (ISO8601)' },
|
sunrise: { type: 'string', format: 'date-time', description: 'Sunrise time' },
|
||||||
sun_sunset: { type: 'string', description: 'Sunset time (ISO8601)' },
|
sunset: { type: 'string', format: 'date-time', description: 'Sunset time' },
|
||||||
sun_solar_noon: { type: 'string', description: 'Solar noon (ISO8601)' },
|
daytime: { type: 'boolean', description: 'True if between sunrise and sunset' },
|
||||||
sun_day_length_hours: { type: 'number', description: 'Day length (hours)' },
|
|
||||||
sun_golden_hour_morning_start: { type: 'string' },
|
|
||||||
sun_golden_hour_morning_end: { type: 'string' },
|
|
||||||
sun_golden_hour_evening_start: { type: 'string' },
|
|
||||||
sun_golden_hour_evening_end: { type: 'string' },
|
|
||||||
sun_is_day: { type: 'number', description: '1 if daytime' },
|
|
||||||
sun_ssn: { type: 'number', description: 'Estimated sunspot number' },
|
|
||||||
sun_activity_label: { type: 'string', enum: ['Very Low','Low','Moderate','High','Very High'] },
|
|
||||||
// Moon
|
// Moon
|
||||||
moon_phase: { type: 'string', description: 'Moon phase name' },
|
moon_phase: { type: 'string', description: 'Moon phase name' },
|
||||||
moon_illumination_pct: { type: 'number', description: 'Moon illumination (%)' },
|
moon_illumination: { type: 'number', description: 'Moon illumination (%)' },
|
||||||
moon_moonrise: { type: 'string', description: 'Moonrise time (ISO8601)' },
|
moon_elevation: { type: 'number', description: 'Moon elevation (°)' },
|
||||||
moon_moonset: { type: 'string', description: 'Moonset time (ISO8601)' },
|
moon_azimuth: { type: 'number', description: 'Moon azimuth (°)' },
|
||||||
moon_next_full: { type: 'string', description: 'Next full moon (ISO8601)' },
|
moonrise: { type: 'string', format: 'date-time', description: 'Moonrise time' },
|
||||||
moon_next_new: { type: 'string', description: 'Next new moon (ISO8601)' },
|
moonset: { type: 'string', format: 'date-time', description: 'Moonset time' },
|
||||||
// Season
|
moon_new: { type: 'string', format: 'date-time', description: 'Next new moon' },
|
||||||
season_next_event: { type: 'string', description: 'Next solstice/equinox name' },
|
moon_full: { type: 'string', format: 'date-time', description: 'Next full moon' },
|
||||||
season_next_event_date: { type: 'string' },
|
// Seismic
|
||||||
season_last_event: { type: 'string' },
|
seismic_magnitude: { type: 'number', description: 'Peak seismic magnitude (g)' },
|
||||||
season_last_event_date: { type: 'string' },
|
// Ground
|
||||||
// Space weather
|
ground_distance: { type: 'number', description: 'LIDAR ground distance (cm)' },
|
||||||
space_kp_index: { type: 'number', description: 'Planetary K index (0–9)' },
|
// GPS
|
||||||
space_ap_index: { type: 'number', description: 'Ap geomagnetic index' },
|
latitude: { type: 'number', description: 'Latitude (°)' },
|
||||||
space_geomagnetic_storm: { type: 'string', enum: ['None','Minor','Moderate','Strong','Severe'] },
|
longitude: { type: 'number', description: 'Longitude (°)' },
|
||||||
space_solar_wind_speed_kms: { type: 'number', description: 'Solar wind speed (km/s)' },
|
altitude: { type: 'number', description: 'Altitude (m)' },
|
||||||
space_solar_wind_density: { type: 'number', description: 'Solar wind proton density (p/cm³)' },
|
|
||||||
space_solar_wind_temp: { type: 'number', description: 'Solar wind temperature (K)' },
|
|
||||||
space_imf_bz: { type: 'number', description: 'IMF Bz component (nT)' },
|
|
||||||
space_imf_bt: { type: 'number', description: 'IMF total field Bt (nT)' },
|
|
||||||
space_solar_flux_f107: { type: 'number', description: 'Solar flux F10.7 index' },
|
|
||||||
// Marine
|
|
||||||
wave_height: { type: 'number', description: 'Significant wave height (m)' },
|
|
||||||
wave_direction: { type: 'number', description: 'Wave direction (°)' },
|
|
||||||
wave_period: { type: 'number', description: 'Wave period (s)' },
|
|
||||||
swell_wave_height: { type: 'number', description: 'Swell wave height (m)' },
|
|
||||||
swell_wave_direction: { type: 'number', description: 'Swell direction (°)' },
|
|
||||||
swell_wave_period: { type: 'number', description: 'Swell period (s)' },
|
|
||||||
wind_wave_height: { type: 'number', description: 'Wind wave height (m)' },
|
|
||||||
wave_height_max: { type: 'number', description: 'Daily max wave height (m)' },
|
|
||||||
// AQ
|
|
||||||
pm10: { type: 'number', description: 'PM10 (μg/m³)' },
|
|
||||||
pm2_5: { type: 'number', description: 'PM2.5 (μg/m³)' },
|
|
||||||
carbon_monoxide: { type: 'number', description: 'CO (μg/m³)' },
|
|
||||||
nitrogen_dioxide: { type: 'number', description: 'NO₂ (μg/m³)' },
|
|
||||||
sulphur_dioxide: { type: 'number', description: 'SO₂ (μg/m³)' },
|
|
||||||
ozone: { type: 'number', description: 'O₃ (μg/m³)' },
|
|
||||||
aerosol_optical_depth: { type: 'number' },
|
|
||||||
dust: { type: 'number', description: 'Dust (μg/m³)' },
|
|
||||||
alder_pollen: { type: 'number', description: 'Alder pollen (grains/m³)' },
|
|
||||||
birch_pollen: { type: 'number', description: 'Birch pollen (grains/m³)' },
|
|
||||||
grass_pollen: { type: 'number', description: 'Grass pollen (grains/m³)' },
|
|
||||||
mugwort_pollen: { type: 'number', description: 'Mugwort pollen (grains/m³)' },
|
|
||||||
olive_pollen: { type: 'number', description: 'Olive pollen (grains/m³)' },
|
|
||||||
ragweed_pollen: { type: 'number', description: 'Ragweed pollen (grains/m³)' },
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user