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/at", rainTileAtHandler)
|
||||||
api.GET("/rain/nearest", nearestRainTileHandler)
|
api.GET("/rain/nearest", nearestRainTileHandler)
|
||||||
api.GET("/rain/times", rainTileTimesHandler)
|
api.GET("/rain/times", rainTileTimesHandler)
|
||||||
|
api.GET("/rain/tiles_at", rainTilesAtHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取配置的Web端口
|
// 获取配置的Web端口
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package server
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
"weatherstation/internal/database"
|
"weatherstation/internal/database"
|
||||||
@ -286,6 +287,75 @@ func nearestRainTileHandler(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, resp)
|
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 {
|
func decodeRain(buf []byte, w, h int) [][]*float64 {
|
||||||
vals := make([][]*float64, h)
|
vals := make([][]*float64, h)
|
||||||
off := 0
|
off := 0
|
||||||
|
|||||||
@ -269,11 +269,22 @@ const WeatherMap = {
|
|||||||
try{
|
try{
|
||||||
const z=this.tileZ,y=this.tileY,x=this.tileX;
|
const z=this.tileZ,y=this.tileY,x=this.tileX;
|
||||||
if (this.tileProduct === 'rain') {
|
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);
|
const r = await fetch(url);
|
||||||
if(!r.ok){ console.warn('雨量瓦片未找到', dtStr); return; }
|
if(!r.ok){
|
||||||
const t = await r.json();
|
// 回退单块接口
|
||||||
await this.renderTileOnMap('rain', t);
|
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 {
|
} else {
|
||||||
// radar: 取同一时次该 z 下的所有 y/x 瓦片
|
// radar: 取同一时次该 z 下的所有 y/x 瓦片
|
||||||
const url = `/api/radar/tiles_at?z=${z}&dt=${encodeURIComponent(dtStr)}`;
|
const url = `/api/radar/tiles_at?z=${z}&dt=${encodeURIComponent(dtStr)}`;
|
||||||
@ -326,7 +337,8 @@ const WeatherMap = {
|
|||||||
if (product==='rain'){
|
if (product==='rain'){
|
||||||
const edges = [0,5,7.5,10,12.5,15,17.5,20,25,30,40,50,75,100, Infinity];
|
const edges = [0,5,7.5,10,12.5,15,17.5,20,25,30,40,50,75,100, Infinity];
|
||||||
const colors = [
|
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]
|
[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)=>{
|
colorFunc = (mm)=>{
|
||||||
@ -382,13 +394,28 @@ const WeatherMap = {
|
|||||||
canvas.width = w; canvas.height = h;
|
canvas.width = w; canvas.height = h;
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
const imgData = ctx.createImageData(w, h);
|
const imgData = ctx.createImageData(w, h);
|
||||||
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]];
|
let colorFunc;
|
||||||
const colorFunc = (dbz)=>{
|
if (product==='rain'){
|
||||||
let v = Math.max(0, Math.min(75, dbz));
|
const edges = [0,5,7.5,10,12.5,15,17.5,20,25,30,40,50,75,100, Infinity];
|
||||||
if (v===0) return [0,0,0,0];
|
const colors = [
|
||||||
let bin = Math.floor(v/5); if (bin>=colors.length) bin=colors.length-1;
|
// 0值透明,(0,5) 用绿色,不再用白色
|
||||||
const c = colors[bin]; return [c[0],c[1],c[2],220];
|
[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]];
|
||||||
|
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++){
|
for(let row=0; row<h; row++){
|
||||||
const srcRow = t.values[row] || [];
|
const srcRow = t.values[row] || [];
|
||||||
const dstRow = (h-1-row);
|
const dstRow = (h-1-row);
|
||||||
|
|||||||
@ -526,7 +526,7 @@
|
|||||||
<label class="text-sm text-gray-600">叠加显示:</label>
|
<label class="text-sm text-gray-600">叠加显示:</label>
|
||||||
<select id="tileProduct" class="px-2 py-1 border border-gray-300 rounded text-sm">
|
<select id="tileProduct" class="px-2 py-1 border border-gray-300 rounded text-sm">
|
||||||
<option value="none" selected>不显示</option>
|
<option value="none" selected>不显示</option>
|
||||||
<option value="rain">上小时降雨</option>
|
<option value="rain">1h 实际降雨</option>
|
||||||
<option value="radar">水汽含量</option>
|
<option value="radar">水汽含量</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user