maptile/util/mbtiles/mbtiles.go

265 lines
6.1 KiB
Go

package mbtiles
import (
"database/sql"
"encoding/json"
"fmt"
"mime"
"path/filepath"
"strconv"
"strings"
"git.zhouxhere.com/zhouxhere/maptile/model"
"git.zhouxhere.com/zhouxhere/maptile/protobuf"
_ "github.com/mattn/go-sqlite3"
"github.com/pkg/errors"
"google.golang.org/protobuf/proto"
)
type MBTiles struct {
*sql.DB
}
func ReadMBTiles() (map[string]*MBTiles, error) {
filePath, err := filepath.Abs("mbtiles/")
if err != nil {
return nil, err
}
files, err := filepath.Glob(filePath + "/*.mbtiles")
if err != nil {
return nil, err
}
mbtiles := make(map[string]*MBTiles)
for _, file := range files {
mbtile, err := New(file)
if err != nil {
return nil, err
}
fileName := filepath.Base(file)
fileType := filepath.Ext(file)
mbtiles[strings.TrimSuffix(fileName, fileType)] = mbtile
}
return mbtiles, nil
}
func New(name string) (*MBTiles, error) {
db, err := sql.Open("sqlite3", name)
if err != nil {
return nil, err
}
// defer db.Close()
return &MBTiles{
db,
}, nil
}
func (m *MBTiles) Close() {
m.DB.Close()
}
func (m *MBTiles) GetMetadata() (map[string]string, error) {
rows, err := m.Query("SELECT name, value FROM metadata")
if err != nil {
return nil, err
}
defer rows.Close()
meta := make(map[string]string)
for rows.Next() {
var name, value string
err = rows.Scan(&name, &value)
if err != nil {
return nil, err
}
meta[name] = value
}
return meta, nil
}
func (m *MBTiles) GetTileJSON() (*model.TileJSON, error) {
metaData, err := m.GetMetadata()
if err != nil {
return nil, err
}
tileJSON := &model.TileJSON{
TileJSON: "3.0.0",
// Tiles: []string{fmt.Sprint("http://localhost:8080/api/v1/mbtiles/%d/{z}/{x}/{y}",)},
}
for name, value := range metaData {
switch name {
case "name":
tileJSON.Name = value
case "description":
tileJSON.Description = value
case "version":
tileJSON.Version = parseVersion(value)
case "maxzoom":
zoom, _ := strconv.Atoi(value)
tileJSON.MaxZoom = &zoom
case "minzoom":
zoom, _ := strconv.Atoi(value)
tileJSON.MinZoom = &zoom
case "fillzoom":
zoom, _ := strconv.Atoi(value)
tileJSON.FillZoom = &zoom
case "format":
tileJSON.Format = value
case "bounds":
bounds := strings.Split(value, ",")
minX, _ := strconv.ParseFloat(bounds[0], 64)
minY, _ := strconv.ParseFloat(bounds[1], 64)
maxX, _ := strconv.ParseFloat(bounds[2], 64)
maxY, _ := strconv.ParseFloat(bounds[3], 64)
tileJSON.Bounds = []float64{minX, minY, maxX, maxY}
case "center":
center := strings.Split(value, ",")
x, _ := strconv.ParseFloat(center[0], 64)
y, _ := strconv.ParseFloat(center[1], 64)
zoom, _ := strconv.Atoi(center[2])
tileJSON.Center = []float64{x, y, float64(zoom)}
case "json":
var layerData map[string]interface{}
err := json.Unmarshal([]byte(value), &layerData)
if err != nil {
continue
}
vectorLayersData, ok := layerData["vector_layers"].([]interface{})
if !ok {
// fmt.Println("Error asserting vector_layers to []interface{}")
continue
}
vectorLayers := make([]model.VectorLayer, len(vectorLayersData))
for i, layerData := range vectorLayersData {
layerMap, ok := layerData.(map[string]interface{})
if !ok {
// fmt.Println("Error asserting layer data to map[string]interface{}")
continue
}
layerJSON, err := json.Marshal(layerMap)
if err != nil {
// fmt.Println("Error marshalling layer data to JSON:", err)
continue
}
var vectorLayer model.VectorLayer
err = json.Unmarshal(layerJSON, &vectorLayer)
if err != nil {
// fmt.Println("Error unmarshalling layer data to VectorLayer:", err)
continue
}
vectorLayers[i] = vectorLayer
}
tileJSON.VectorLayers = vectorLayers
}
}
return tileJSON, nil
}
func (m *MBTiles) GetTile(z, x, y int) ([]byte, string, error) {
metaData, err := m.GetMetadata()
if err != nil {
return nil, "", err
}
mineType := mime.TypeByExtension("." + metaData["format"])
realY := (1 << uint(z)) - y - 1
rows, err := m.Query("SELECT tile_data FROM tiles WHERE zoom_level = ? AND tile_column = ? AND tile_row = ?", z, x, realY)
if err != nil {
return nil, mineType, err
}
defer rows.Close()
if rows.Next() {
var tileData []byte
err = rows.Scan(&tileData)
if err != nil {
return nil, mineType, err
}
// return tileData, mineType, nil
// fmt.Println("Raw tileData:", tileData) // 打印原始 tileData
// fmt.Println("Raw tileData (hex):", hex.EncodeToString(tileData)) // 打印原始 tileData 的十六进制表示
// if tileData[0] == 0x1f && tileData[1] == 0x8b { // 检查 gzip 魔数
// reader, err := gzip.NewReader(bytes.NewReader(tileData))
// if err != nil {
// return nil, "", err
// }
// defer reader.Close()
// uncompressedData, err := io.ReadAll(reader)
// if err != nil {
// return nil, "", err
// }
// tileData = uncompressedData
// }
// tiles := &protobuf.Tile{Layers: []*protobuf.Tile_Layer{}}
// err = proto.Unmarshal(tileData, tiles)
// if err != nil {
// // fmt.Println("Error unmarshalling tileData:", err) // 打印解析错误
// return nil, "", err
// }
// fmt.Println("Parsed tileData:", tiles) // 打印解析后的 tileData
// if metaData["format"] == "pbf" {
// return tileData, "application/x-protobuf", nil
// }
return tileData, "application/x-protobuf", nil
}
blankTile := &protobuf.Tile{
Layers: []*protobuf.Tile_Layer{},
}
tileData, err := proto.Marshal(blankTile)
if err != nil {
return nil, "", errors.Wrap(err, "failed to marshal blank tile")
}
return tileData, "application/x-protobuf", nil
}
func parseVersion(version string) string {
// 将版本号字符串按点分割
parts := strings.SplitN(version, ".", 3)
// 初始化默认值
major, minor, patch := 0, 0, 0
// 解析每个部分并转换为整数
if len(parts) > 0 {
major, _ = strconv.Atoi(parts[0])
}
if len(parts) > 1 {
minor, _ = strconv.Atoi(parts[1])
}
if len(parts) > 2 {
patch, _ = strconv.Atoi(parts[2])
}
return fmt.Sprintf("%d.%d.%d", major, minor, patch)
}
func invertYValue(zoom uint8, y uint32) uint32 {
return (1 << zoom) - 1 - y
}