feat: 新增彩云气象数据下载的时间控制
This commit is contained in:
parent
aa53a21685
commit
0b0512f5b2
@ -50,6 +50,7 @@ func StartGinServer() error {
|
|||||||
api.GET("/radar/weather_at", radarWeatherAtHandler)
|
api.GET("/radar/weather_at", radarWeatherAtHandler)
|
||||||
api.GET("/radar/weather_aliases", radarWeatherAliasesHandler)
|
api.GET("/radar/weather_aliases", radarWeatherAliasesHandler)
|
||||||
api.GET("/radar/aliases", radarConfigAliasesHandler)
|
api.GET("/radar/aliases", radarConfigAliasesHandler)
|
||||||
|
api.GET("/radar/weather_nearest", radarWeatherNearestHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取配置的Web端口
|
// 获取配置的Web端口
|
||||||
|
|||||||
@ -520,6 +520,86 @@ func radarWeatherAtHandler(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, resp)
|
c.JSON(http.StatusOK, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// radarWeatherNearestHandler returns the nearest radar_weather record to the given dt.
|
||||||
|
// prefer=lte will pick the latest record not later than dt; else chooses absolute nearest.
|
||||||
|
func radarWeatherNearestHandler(c *gin.Context) {
|
||||||
|
alias := c.Query("alias")
|
||||||
|
if alias == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "缺少 alias 参数"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dtStr := c.Query("dt")
|
||||||
|
if dtStr == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "缺少 dt 参数(YYYY-MM-DD HH:MM:SS)"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
prefer := c.DefaultQuery("prefer", "lte") // lte|nearest
|
||||||
|
loc, _ := time.LoadLocation("Asia/Shanghai")
|
||||||
|
if loc == nil {
|
||||||
|
loc = time.FixedZone("CST", 8*3600)
|
||||||
|
}
|
||||||
|
target, err := time.ParseInLocation("2006-01-02 15:04:05", dtStr, loc)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 dt 格式"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var row *sql.Row
|
||||||
|
db := database.GetDB()
|
||||||
|
if prefer == "nearest" {
|
||||||
|
const q = `
|
||||||
|
SELECT alias, lat, lon, dt,
|
||||||
|
temperature, humidity, cloudrate, visibility, dswrf,
|
||||||
|
wind_speed, wind_direction, pressure
|
||||||
|
FROM radar_weather
|
||||||
|
WHERE alias = $1
|
||||||
|
ORDER BY ABS(EXTRACT(EPOCH FROM (dt - $2))) ASC
|
||||||
|
LIMIT 1`
|
||||||
|
row = db.QueryRow(q, alias, target)
|
||||||
|
} else { // lte default
|
||||||
|
const q = `
|
||||||
|
SELECT alias, lat, lon, dt,
|
||||||
|
temperature, humidity, cloudrate, visibility, dswrf,
|
||||||
|
wind_speed, wind_direction, pressure
|
||||||
|
FROM radar_weather
|
||||||
|
WHERE alias = $1 AND dt <= $2
|
||||||
|
ORDER BY dt DESC
|
||||||
|
LIMIT 1`
|
||||||
|
row = db.QueryRow(q, alias, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r radarWeatherRecord
|
||||||
|
if err := row.Scan(
|
||||||
|
&r.Alias, &r.Lat, &r.Lon, &r.DT,
|
||||||
|
&r.Temperature, &r.Humidity, &r.Cloudrate, &r.Visibility, &r.Dswrf,
|
||||||
|
&r.WindSpeed, &r.WindDirection, &r.Pressure,
|
||||||
|
); err != nil {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": "未找到就近的实时气象数据"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ageMin := int(target.Sub(r.DT).Minutes())
|
||||||
|
if ageMin < 0 { // for nearest mode, could be future relative to target
|
||||||
|
ageMin = -ageMin
|
||||||
|
}
|
||||||
|
resp := gin.H{
|
||||||
|
"alias": r.Alias,
|
||||||
|
"lat": r.Lat,
|
||||||
|
"lon": r.Lon,
|
||||||
|
"dt": r.DT.Format("2006-01-02 15:04:05"),
|
||||||
|
"temperature": r.Temperature,
|
||||||
|
"humidity": r.Humidity,
|
||||||
|
"cloudrate": r.Cloudrate,
|
||||||
|
"visibility": r.Visibility,
|
||||||
|
"dswrf": r.Dswrf,
|
||||||
|
"wind_speed": r.WindSpeed,
|
||||||
|
"wind_direction": r.WindDirection,
|
||||||
|
"pressure": r.Pressure,
|
||||||
|
"age_minutes": ageMin,
|
||||||
|
"stale": ageMin > 120,
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
|
||||||
// radarWeatherAliasesHandler 返回 radar_weather 中存在的站点别名及经纬度(按最近记录去重)
|
// radarWeatherAliasesHandler 返回 radar_weather 中存在的站点别名及经纬度(按最近记录去重)
|
||||||
func radarWeatherAliasesHandler(c *gin.Context) {
|
func radarWeatherAliasesHandler(c *gin.Context) {
|
||||||
const q = `
|
const q = `
|
||||||
|
|||||||
@ -113,11 +113,16 @@
|
|||||||
setRealtimeBox(j);
|
setRealtimeBox(j);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadRealtimeAt(alias, dtStr) {
|
async function loadRealtimeNearest(alias, dtStr) {
|
||||||
const res = await fetch(`/api/radar/weather_at?alias=${encodeURIComponent(alias)}&dt=${encodeURIComponent(dtStr)}`);
|
const res = await fetch(`/api/radar/weather_nearest?prefer=lte&alias=${encodeURIComponent(alias)}&dt=${encodeURIComponent(dtStr)}`);
|
||||||
if (!res.ok) { return false; }
|
if (!res.ok) return false;
|
||||||
const j = await res.json();
|
const j = await res.json();
|
||||||
setRealtimeBox(j);
|
setRealtimeBox(j);
|
||||||
|
const bar = document.getElementById('rt_stale');
|
||||||
|
if (bar) {
|
||||||
|
if (j.stale) { bar.classList.remove('hidden'); }
|
||||||
|
else { bar.classList.add('hidden'); }
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,13 +157,8 @@
|
|||||||
const sel = document.getElementById('timeSelect');
|
const sel = document.getElementById('timeSelect');
|
||||||
sel.value = t.dt;
|
sel.value = t.dt;
|
||||||
}
|
}
|
||||||
// 同步气象:按瓦片时间向下取整到10分钟,查询该桶的实况
|
// 同步气象:就近(优先<=)匹配该瓦片时间
|
||||||
if (gAlias) {
|
if (gAlias) { try { await loadRealtimeNearest(gAlias, t.dt); } catch(e){} }
|
||||||
const bucket = dtToBucket10(t.dt);
|
|
||||||
if (bucket) {
|
|
||||||
await loadRealtimeAt(gAlias, bucket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
maybeCalcSector();
|
maybeCalcSector();
|
||||||
maybePlotSquare();
|
maybePlotSquare();
|
||||||
maybeCalcNearbyRain();
|
maybeCalcNearbyRain();
|
||||||
@ -188,10 +188,7 @@
|
|||||||
document.getElementById('tile_res').textContent = fmt(t.res_deg, 6);
|
document.getElementById('tile_res').textContent = fmt(t.res_deg, 6);
|
||||||
status.textContent = '';
|
status.textContent = '';
|
||||||
renderTilePlot(t);
|
renderTilePlot(t);
|
||||||
if (gAlias) {
|
if (gAlias) { try { await loadRealtimeNearest(gAlias, t.dt); } catch(e){} }
|
||||||
const bucket = dtToBucket10(t.dt);
|
|
||||||
if (bucket) { await loadRealtimeAt(gAlias, bucket); }
|
|
||||||
}
|
|
||||||
maybeCalcSector();
|
maybeCalcSector();
|
||||||
maybePlotSquare();
|
maybePlotSquare();
|
||||||
maybeCalcNearbyRain();
|
maybeCalcNearbyRain();
|
||||||
@ -557,16 +554,17 @@
|
|||||||
document.getElementById('btnLoad').addEventListener('click', async ()=>{
|
document.getElementById('btnLoad').addEventListener('click', async ()=>{
|
||||||
const sel = document.getElementById('stationSelect');
|
const sel = document.getElementById('stationSelect');
|
||||||
if (!sel || !sel.value) return;
|
if (!sel || !sel.value) return;
|
||||||
const alias = sel.options[sel.selectedIndex].textContent || sel.value;
|
const aliasText = sel.options[sel.selectedIndex].textContent || sel.value;
|
||||||
|
const aliasParam = sel.value;
|
||||||
gZ = Number(sel.options[sel.selectedIndex].dataset.z || 0);
|
gZ = Number(sel.options[sel.selectedIndex].dataset.z || 0);
|
||||||
gY = Number(sel.options[sel.selectedIndex].dataset.y || 0);
|
gY = Number(sel.options[sel.selectedIndex].dataset.y || 0);
|
||||||
gX = Number(sel.options[sel.selectedIndex].dataset.x || 0);
|
gX = Number(sel.options[sel.selectedIndex].dataset.x || 0);
|
||||||
const lat = Number(sel.options[sel.selectedIndex].dataset.lat);
|
const lat = Number(sel.options[sel.selectedIndex].dataset.lat);
|
||||||
const lon = Number(sel.options[sel.selectedIndex].dataset.lon);
|
const lon = Number(sel.options[sel.selectedIndex].dataset.lon);
|
||||||
gStLat = isNaN(lat)? null : lat; gStLon = isNaN(lon)? null : lon;
|
gStLat = isNaN(lat)? null : lat; gStLon = isNaN(lon)? null : lon;
|
||||||
gAlias = alias;
|
gAlias = aliasParam;
|
||||||
document.getElementById('rt_zyx').textContent = `z=${gZ}, y=${gY}, x=${gX}`;
|
document.getElementById('rt_zyx').textContent = `z=${gZ}, y=${gY}, x=${gX}`;
|
||||||
try { await loadRealtime(alias); } catch (e) { console.warn(e); }
|
try { await loadRealtime(aliasParam); } catch (e) { console.warn(e); }
|
||||||
if (gZ && gY && gX) {
|
if (gZ && gY && gX) {
|
||||||
const from = fromDTLocalInput(document.getElementById('tsStart').value);
|
const from = fromDTLocalInput(document.getElementById('tsStart').value);
|
||||||
const to = fromDTLocalInput(document.getElementById('tsEnd').value);
|
const to = fromDTLocalInput(document.getElementById('tsEnd').value);
|
||||||
@ -678,7 +676,7 @@
|
|||||||
<div>下行短波:<span id="rt_dswrf"></span> W/m²</div>
|
<div>下行短波:<span id="rt_dswrf"></span> W/m²</div>
|
||||||
<div>气压:<span id="rt_p"></span> Pa</div>
|
<div>气压:<span id="rt_p"></span> Pa</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="rt_stale" class="mt-2 hidden p-2 text-sm text-yellow-800 bg-yellow-50 border border-yellow-200 rounded">提示:该瓦片时次的就近实况相差超过 2 小时</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card my-4">
|
<div class="card my-4">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user