82 lines
2.5 KiB
Go
82 lines
2.5 KiB
Go
package rain
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"log"
|
||
"os"
|
||
"path"
|
||
"regexp"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
"weatherstation/internal/database"
|
||
)
|
||
|
||
var (
|
||
// Matches .../<PRODUCT>/<YYYYMMDD>/<HH>/<mm>/<z>/<y>/<x>.bin
|
||
// Example: /tiles/China/CMPA_RT_China_0P01_HOR-PRE_GISJPG_Tiles/20251007/01/00/7/40/102.bin
|
||
tileRE = regexp.MustCompile(`(?i)/tiles/.+?/([^/]+)/([0-9]{8})/([0-9]{2})/([0-9]{2})/([0-9]+)/([0-9]+)/([0-9]+)\.bin$`)
|
||
)
|
||
|
||
// TileRef references a CMPA rain tile.
|
||
type TileRef struct {
|
||
Product string
|
||
DT time.Time // nominal time in Asia/Shanghai (UTC+8)
|
||
Z, Y, X int
|
||
}
|
||
|
||
// ParseCMPATileURL parses a CMPA tile URL/path and extracts product, time (UTC+8), z/y/x.
|
||
// The timestamp in the path is UTC; we convert to Asia/Shanghai by adding 8h.
|
||
func ParseCMPATileURL(u string) (TileRef, error) {
|
||
p := u
|
||
if i := strings.IndexAny(p, "?#"); i >= 0 {
|
||
p = p[:i]
|
||
}
|
||
p = path.Clean(p)
|
||
m := tileRE.FindStringSubmatch(p)
|
||
if len(m) == 0 {
|
||
return TileRef{}, fmt.Errorf("unrecognized CMPA tile path: %s", u)
|
||
}
|
||
product := m[1]
|
||
yyyymmdd := m[2]
|
||
hh := m[3]
|
||
mm := m[4]
|
||
z := mustAtoi(m[5])
|
||
y := mustAtoi(m[6])
|
||
x := mustAtoi(m[7])
|
||
|
||
// Parse as UTC then shift to CST(+8)
|
||
utcT, err := time.ParseInLocation("20060102 15 04", fmt.Sprintf("%s %s %s", yyyymmdd, hh, mm), time.UTC)
|
||
if err != nil {
|
||
return TileRef{}, fmt.Errorf("parse utc time: %w", err)
|
||
}
|
||
loc, _ := time.LoadLocation("Asia/Shanghai")
|
||
if loc == nil {
|
||
loc = time.FixedZone("CST", 8*3600)
|
||
}
|
||
dt := utcT.In(loc)
|
||
return TileRef{Product: product, DT: dt, Z: z, Y: y, X: x}, nil
|
||
}
|
||
|
||
func mustAtoi(s string) int { n, _ := strconv.Atoi(s); return n }
|
||
|
||
// StoreTileBytes parses the URL, computes metadata and upserts into rain_tiles.
|
||
func StoreTileBytes(ctx context.Context, urlOrPath string, data []byte) error {
|
||
ref, err := ParseCMPATileURL(urlOrPath)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
// 可选:强制 +8 小时(若上游传入的时间已为 UTC 且未转换,可用此开关修正)
|
||
if strings.EqualFold(strings.TrimSpace(strings.ToLower(os.Getenv("RAIN_FORCE_SHIFT8"))), "1") {
|
||
ref.DT = ref.DT.Add(8 * time.Hour)
|
||
}
|
||
// 可选调试:打印解析出的时间(CST)与 URL
|
||
if strings.EqualFold(strings.TrimSpace(strings.ToLower(os.Getenv("RAIN_DEBUG"))), "1") {
|
||
log.Printf("[rain] store tile: url=%s -> product=%s dt(local)=%s z=%d y=%d x=%d",
|
||
urlOrPath, ref.Product, ref.DT.Format("2006-01-02 15:04:05 -0700"), ref.Z, ref.Y, ref.X)
|
||
}
|
||
db := database.GetDB()
|
||
return database.UpsertRainTile(ctx, db, ref.Product, ref.DT, ref.Z, ref.Y, ref.X, 256, 256, data)
|
||
}
|