85 lines
2.1 KiB
Go
85 lines
2.1 KiB
Go
package radar
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"weatherstation/internal/database"
|
|
)
|
|
|
|
var (
|
|
// Matches .../<PRODUCT>/<YYYYMMDD>/<HH>/<mm>/<z>/<y>/<x>.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 CMA radar tile.
|
|
type TileRef struct {
|
|
Product string
|
|
DT time.Time // nominal time in Asia/Shanghai
|
|
Z, Y, X int
|
|
}
|
|
|
|
// ParseCMATileURL parses a CMA tile URL or path and extracts product, time, z/y/x.
|
|
// Accepts full URL or path that ends with /tiles/.../x.bin.
|
|
func ParseCMATileURL(u string) (TileRef, error) {
|
|
// Normalize path part
|
|
p := u
|
|
// Optionally strip query/hash
|
|
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 CMA 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])
|
|
|
|
loc, _ := time.LoadLocation("Asia/Shanghai")
|
|
if loc == nil {
|
|
loc = time.FixedZone("CST", 8*3600)
|
|
}
|
|
dt, err := time.ParseInLocation("20060102 15 04", fmt.Sprintf("%s %s %s", yyyymmdd, hh, mm), loc)
|
|
if err != nil {
|
|
return TileRef{}, fmt.Errorf("parse time: %w", err)
|
|
}
|
|
|
|
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 radar_tiles.
|
|
func StoreTileBytes(ctx context.Context, urlOrPath string, data []byte) error {
|
|
ref, err := ParseCMATileURL(urlOrPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
db := database.GetDB()
|
|
return database.UpsertRadarTile(ctx, db, ref.Product, ref.DT, ref.Z, ref.Y, ref.X, 256, 256, data)
|
|
}
|
|
|
|
// ImportTileFile reads the file and stores it into DB using the URL for metadata.
|
|
func ImportTileFile(ctx context.Context, urlOrPath, filePath string) error {
|
|
b, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
return fmt.Errorf("read file: %w", err)
|
|
}
|
|
return StoreTileBytes(ctx, urlOrPath, b)
|
|
}
|