feat: 优化地图瓦片显示
This commit is contained in:
parent
7b3ce46f04
commit
93a94c3149
@ -138,6 +138,7 @@ func StartGinServer() error {
|
||||
api.GET("/rain/at", rainTileAtHandler)
|
||||
api.GET("/rain/nearest", nearestRainTileHandler)
|
||||
api.GET("/rain/times", rainTileTimesHandler)
|
||||
api.GET("/rain/tiles_at", rainTilesAtHandler)
|
||||
}
|
||||
|
||||
// 获取配置的Web端口
|
||||
|
||||
@ -3,6 +3,7 @@ package server
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
"weatherstation/internal/database"
|
||||
@ -286,6 +287,75 @@ func nearestRainTileHandler(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// rainTilesAtHandler 返回指定 z 在 dt 时次的全部雨量瓦片(不同 y/x)集合
|
||||
// GET /api/rain/tiles_at?z=7&dt=YYYY-MM-DD HH:MM:SS
|
||||
func rainTilesAtHandler(c *gin.Context) {
|
||||
z := parseIntDefault(c.Query("z"), 7)
|
||||
dtStr := c.Query("dt")
|
||||
if dtStr == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "缺少 dt 参数(YYYY-MM-DD HH:MM:SS)"})
|
||||
return
|
||||
}
|
||||
loc, _ := time.LoadLocation("Asia/Shanghai")
|
||||
if loc == nil {
|
||||
loc = time.FixedZone("CST", 8*3600)
|
||||
}
|
||||
dt, err := time.ParseInLocation("2006-01-02 15:04:05", dtStr, loc)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 dt 格式"})
|
||||
return
|
||||
}
|
||||
|
||||
const q = `
|
||||
SELECT dt, z, y, x, width, height, west, south, east, north, res_deg, data
|
||||
FROM rain_tiles
|
||||
WHERE z=$1 AND dt=$2
|
||||
ORDER BY y, x`
|
||||
rows, qerr := database.GetDB().Query(q, z, dt)
|
||||
if qerr != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("数据库查询失败: %v", qerr)})
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var tiles []rainTileResponse
|
||||
for rows.Next() {
|
||||
var r rainTileRecord
|
||||
if err := rows.Scan(&r.DT, &r.Z, &r.Y, &r.X, &r.Width, &r.Height, &r.West, &r.South, &r.East, &r.North, &r.ResDeg, &r.Data); err != nil {
|
||||
continue
|
||||
}
|
||||
w, h := r.Width, r.Height
|
||||
if w <= 0 || h <= 0 || len(r.Data) < w*h*2 {
|
||||
continue
|
||||
}
|
||||
vals := decodeRain(r.Data, w, h)
|
||||
tiles = append(tiles, rainTileResponse{
|
||||
DT: r.DT.In(loc).Format("2006-01-02 15:04:05"),
|
||||
Z: r.Z,
|
||||
Y: r.Y,
|
||||
X: r.X,
|
||||
Width: r.Width,
|
||||
Height: r.Height,
|
||||
West: r.West,
|
||||
South: r.South,
|
||||
East: r.East,
|
||||
North: r.North,
|
||||
ResDeg: r.ResDeg,
|
||||
Values: vals,
|
||||
})
|
||||
}
|
||||
|
||||
if len(tiles) == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "未找到指定时间的雨量瓦片集合"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"dt": dt.In(loc).Format("2006-01-02 15:04:05"),
|
||||
"tiles": tiles,
|
||||
})
|
||||
}
|
||||
|
||||
func decodeRain(buf []byte, w, h int) [][]*float64 {
|
||||
vals := make([][]*float64, h)
|
||||
off := 0
|
||||
|
||||
@ -269,11 +269,22 @@ const WeatherMap = {
|
||||
try{
|
||||
const z=this.tileZ,y=this.tileY,x=this.tileX;
|
||||
if (this.tileProduct === 'rain') {
|
||||
const url = `/api/rain/at?z=${z}&y=${y}&x=${x}&dt=${encodeURIComponent(dtStr)}`;
|
||||
// 雨量:同一时次下叠加所有 y/x 瓦片
|
||||
const url = `/api/rain/tiles_at?z=${z}&dt=${encodeURIComponent(dtStr)}`;
|
||||
const r = await fetch(url);
|
||||
if(!r.ok){ console.warn('雨量瓦片未找到', dtStr); return; }
|
||||
const t = await r.json();
|
||||
await this.renderTileOnMap('rain', t);
|
||||
if(!r.ok){
|
||||
// 回退单块接口
|
||||
const url1 = `/api/rain/at?z=${z}&y=${y}&x=${x}&dt=${encodeURIComponent(dtStr)}`;
|
||||
const r1 = await fetch(url1);
|
||||
if(!r1.ok){ console.warn('雨量瓦片未找到', dtStr); return; }
|
||||
const t1 = await r1.json();
|
||||
await this.renderTileOnMap('rain', t1);
|
||||
return;
|
||||
}
|
||||
const j = await r.json();
|
||||
const tiles = Array.isArray(j.tiles) ? j.tiles : [];
|
||||
if (tiles.length === 0) { console.warn('该时次无雨量瓦片集合'); this.clearTileOverlays(); return; }
|
||||
await this.renderTilesOnMap('rain', tiles);
|
||||
} else {
|
||||
// radar: 取同一时次该 z 下的所有 y/x 瓦片
|
||||
const url = `/api/radar/tiles_at?z=${z}&dt=${encodeURIComponent(dtStr)}`;
|
||||
@ -326,7 +337,8 @@ const WeatherMap = {
|
||||
if (product==='rain'){
|
||||
const edges = [0,5,7.5,10,12.5,15,17.5,20,25,30,40,50,75,100, Infinity];
|
||||
const colors = [
|
||||
[255,255,255], [126,212,121], [110,200,109], [97,169,97], [81,148,76], [90,158,112],
|
||||
// 0值透明,(0,5) 用绿色,不再用白色
|
||||
[126,212,121], [126,212,121], [110,200,109], [97,169,97], [81,148,76], [90,158,112],
|
||||
[143,194,254], [92,134,245], [66,87,240], [45,48,214], [26,15,166], [63,22,145], [191,70,148], [213,1,146], [213,1,146]
|
||||
];
|
||||
colorFunc = (mm)=>{
|
||||
@ -382,13 +394,28 @@ const WeatherMap = {
|
||||
canvas.width = w; canvas.height = h;
|
||||
const ctx = canvas.getContext('2d');
|
||||
const imgData = ctx.createImageData(w, h);
|
||||
let colorFunc;
|
||||
if (product==='rain'){
|
||||
const edges = [0,5,7.5,10,12.5,15,17.5,20,25,30,40,50,75,100, Infinity];
|
||||
const colors = [
|
||||
// 0值透明,(0,5) 用绿色,不再用白色
|
||||
[126,212,121], [126,212,121], [110,200,109], [97,169,97], [81,148,76], [90,158,112],
|
||||
[143,194,254], [92,134,245], [66,87,240], [45,48,214], [26,15,166], [63,22,145], [191,70,148], [213,1,146], [213,1,146]
|
||||
];
|
||||
colorFunc = (mm)=>{
|
||||
if (mm===0) return [0,0,0,0];
|
||||
let idx=0; while(idx<edges.length-1 && !(mm>=edges[idx] && mm<edges[idx+1])) idx++;
|
||||
const c = colors[Math.min(idx, colors.length-1)]; return [c[0],c[1],c[2],220];
|
||||
};
|
||||
} else {
|
||||
const colors = [[0,0,255],[0,191,255],[0,255,255],[127,255,212],[124,252,0],[173,255,47],[255,255,0],[255,215,0],[255,165,0],[255,140,0],[255,69,0],[255,0,0],[220,20,60],[199,21,133],[139,0,139]];
|
||||
const colorFunc = (dbz)=>{
|
||||
colorFunc = (dbz)=>{
|
||||
let v = Math.max(0, Math.min(75, dbz));
|
||||
if (v===0) return [0,0,0,0];
|
||||
let bin = Math.floor(v/5); if (bin>=colors.length) bin=colors.length-1;
|
||||
const c = colors[bin]; return [c[0],c[1],c[2],220];
|
||||
};
|
||||
}
|
||||
for(let row=0; row<h; row++){
|
||||
const srcRow = t.values[row] || [];
|
||||
const dstRow = (h-1-row);
|
||||
|
||||
@ -526,7 +526,7 @@
|
||||
<label class="text-sm text-gray-600">叠加显示:</label>
|
||||
<select id="tileProduct" class="px-2 py-1 border border-gray-300 rounded text-sm">
|
||||
<option value="none" selected>不显示</option>
|
||||
<option value="rain">上小时降雨</option>
|
||||
<option value="rain">1h 实际降雨</option>
|
||||
<option value="radar">水汽含量</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user