diff --git a/.gitignore b/.gitignore index 6facea9..fa50fbb 100755 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,9 @@ logs .env.* !.env.example -__debug_bin*.exe \ No newline at end of file +__debug_bin*.exe + +/fonts +/icons +/mbtiles +/style/** \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index bb42ed0..e98fb88 100755 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,5 +10,6 @@ "username": "maptile", "password": "maptile_passwd" } - ] + ], + "liveServer.settings.port": 5501 } \ No newline at end of file diff --git a/api/api.go b/api/api.go index 91b9875..7f2668f 100755 --- a/api/api.go +++ b/api/api.go @@ -2,6 +2,7 @@ package api import ( "context" + "fmt" "log/slog" "net/http" "reflect" @@ -52,6 +53,12 @@ func NewAPI(e *echo.Echo, s *store.Store) { }) } + api.Use(middleware.CORSWithConfig(middleware.CORSConfig{ + AllowOrigins: []string{"*"}, + AllowMethods: []string{http.MethodGet, http.MethodPut, http.MethodPost, http.MethodDelete}, + // AllowHeaders: []string{"Content-Type", "Authorization"}, + })) + // swagger api.GET("/swagger/*", echoSwagger.WrapHandler) api.GET("/swagger", func(c echo.Context) error { @@ -59,9 +66,9 @@ func NewAPI(e *echo.Echo, s *store.Store) { }) api.Register() - // for _, route := range api.Routes() { - // fmt.Println(route.Method, route.Path) - // } + for _, route := range api.Routes() { + fmt.Println(route.Method, route.Path) + } } func (a *API) Register() { @@ -70,25 +77,36 @@ func (a *API) Register() { group := a.Group("/api/v1") - for i := 0; i < v.NumMethod(); i++ { + for i := range v.NumMethod() { method := v.Method(i) methodType := t.Method(i) methodName := methodType.Name + if method.Type().String() != "func(echo.Context) error" { + continue + } + // 使用正则表达式匹配方法名 - re := regexp.MustCompile(`^(Post|Get|Put|Delete)([A-Za-z]+)(By[A-Za-z]+)?$`) + re := regexp.MustCompile(`^(Post|Get|Put|Delete)(\w+)$`) matches := re.FindStringSubmatch(methodName) if len(matches) == 0 { continue } action := matches[1] - route := strings.ToLower(matches[2]) - if matches[3] != "" { - param := strings.ToLower(matches[3][2:]) - route = "/" + route + "/:" + param - } else { - route = "/" + route + otherParams := strings.Split(matches[2], "By") + routeStr := strings.ToLower(otherParams[0]) + + route := "/" + routeStr + + otherStr := strings.Join(otherParams[1:], "") + otherConditions := strings.Split(otherStr, "And") + if len(otherConditions) > 0 { + for i := range otherConditions { + if otherConditions[i] != "" { + route = route + "/:" + strings.ToLower(otherConditions[i]) + } + } } switch action { diff --git a/api/feature.go b/api/feature.go index 027b9bf..ef3453e 100755 --- a/api/feature.go +++ b/api/feature.go @@ -15,7 +15,7 @@ import ( // @Produce json // @Param page query int false "页码" // @Param size query int false "每页数量" -// @Success 200 {object} Pagination[FeaturePublic] +// @Success 200 {object} Pagination[model.FeaturePublic] // @Router /features [get] // GetFeatures 处理获取特征列表的请求 func (a *API) GetFeatures(c echo.Context) error { @@ -51,8 +51,8 @@ func (a *API) GetFeatures(c echo.Context) error { // @Tags feature // @Accept json // @Produce json -// @Param feature body store.FeatureCreate true "要素" -// @Success 200 {object} Response[FeaturePublic] +// @Param feature body model.FeatureCreate true "要素" +// @Success 200 {object} Response[model.FeaturePublic] // @Router /feature [post] func (a *API) PostFeature(c echo.Context) error { var featureCreate model.FeatureCreate @@ -73,7 +73,7 @@ func (a *API) PostFeature(c echo.Context) error { // @Accept json // @Produce json // @Param id path string true "要素 ID" -// @Success 200 {object} Response[FeaturePublic] +// @Success 200 {object} Response[model.FeaturePublic] // @Router /feature/{id} [get] func (a *API) GetFeatureByID(c echo.Context) error { return nil @@ -85,8 +85,8 @@ func (a *API) GetFeatureByID(c echo.Context) error { // @Tags feature // @Accept json // @Produce json -// @Param feature body store.FeatureUpdate true "要素" -// @Success 200 {object} Response[FeaturePublic] +// @Param feature body model.FeatureUpdate true "要素" +// @Success 200 {object} Response[model.FeaturePublic] // @Router /feature [put] func (a *API) PutFeature(c echo.Context) error { return nil @@ -98,8 +98,8 @@ func (a *API) PutFeature(c echo.Context) error { // @Tags feature // @Accept json // @Produce json -// @Param feature body store.FeatureDeleteOrBanned true "要素" -// @Success 200 {object} Response[FeaturePublic] +// @Param feature body model.FeatureDeleteOrBanned true "要素" +// @Success 200 {object} Response[model.FeaturePublic] // @Router /feature/{id} [delete] func (a *API) DeleteFeature(c echo.Context) error { return nil @@ -111,8 +111,8 @@ func (a *API) DeleteFeature(c echo.Context) error { // @Tags feature // @Accept json // @Produce json -// @Param feature body store.FeatureDeleteOrBanned true "要素" -// @Success 200 {object} Response[FeaturePublic] +// @Param feature body model.FeatureDeleteOrBanned true "要素" +// @Success 200 {object} Response[model.FeaturePublic] // @Router /feature/banned/{id} [post] func (a *API) BannedFeature(c echo.Context) error { return nil diff --git a/api/mbtiles.go b/api/mbtiles.go new file mode 100644 index 0000000..2a91c7c --- /dev/null +++ b/api/mbtiles.go @@ -0,0 +1,86 @@ +package api + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/labstack/echo/v4" +) + +// GetMbtilesByName 获取指定名称的MBTiles瓦片 +// @Summary 获取指定名称的MBTiles瓦片 +// @Description 获取指定名称的MBTiles瓦片 +// @Tags MBTiles +// @Accept json +// @Produce json +// @Param name path string true "MBTiles名称" +// @Success 200 {object} model.TileJSON +// @Router /mbtiles/{name} [get] +func (a *API) GetMbtilesByName(c echo.Context) error { + name := c.Param("name") + + if a.store.MBTiles[name] == nil { + return c.String(http.StatusBadRequest, "MBTiles not found") + } + + data, err := a.store.MBTiles[name].GetTileJSON() + data.Tiles = []string{fmt.Sprintf("http://localhost:8080/api/v1/mbtiles/%s/{z}/{x}/{y}", name)} + if err != nil { + return c.String(http.StatusBadRequest, "Failed to get metadata") + } + + return c.JSON(http.StatusOK, data) +} + +// GetMbtilesByNameAndZAndXAndY 获取指定名称、z、x、y的MBTiles瓦片 +// @Summary 获取指定名称、z、x、y的MBTiles瓦片 +// @Description 获取指定名称、z、x、y的MBTiles瓦片 +// @Tags MBTiles +// @Accept json +// @Produce application/octet-stream +// @Param name path string true "MBTiles名称" +// @Param z path string true "z值" +// @Param x path string true "x值" +// @Param y path string true "y值" +// @Success 200 {string} string "成功" +// @Router /mbtiles/{name}/{z}/{x}/{y} [get] +func (a *API) GetMbtilesByNameAndZAndXAndY(c echo.Context) error { + name := c.Param("name") + + if a.store.MBTiles[name] == nil { + return c.String(http.StatusBadRequest, "MBTiles not found") + } + + z := c.Param("z") + zValue, err := strconv.Atoi(z) + if err != nil { + return c.String(http.StatusBadRequest, "Invalid z value") + } + x := c.Param("x") + xValue, err := strconv.Atoi(x) + if err != nil { + return c.String(http.StatusBadRequest, "Invalid x value") + } + y := c.Param("y") + yValue, err := strconv.Atoi(y) + if err != nil { + return c.String(http.StatusBadRequest, "Invalid y value") + } + + if a.store.MBTiles[name] == nil { + return c.String(http.StatusBadRequest, "MBTiles not found") + } + + data, resType, err := a.store.MBTiles[name].GetTile(zValue, xValue, yValue) + + if err != nil { + return c.String(http.StatusNotFound, "Tile not found") + } + + if len(data) > 0 && data[0] == 0x1f && data[1] == 0x8b { + c.Response().Header().Set("Content-Encoding", "gzip") + } + + return c.Blob(http.StatusOK, resType, data) +} diff --git a/docs/swagger.json b/docs/swagger.json index 9dee23c..b8a0de0 100755 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -24,7 +24,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/store.FeatureUpdate" + "$ref": "#/definitions/model.FeatureUpdate" } } ], @@ -32,7 +32,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api.Response-model_Feature" + "$ref": "#/definitions/api.Response-model_FeaturePublic" } } } @@ -56,7 +56,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/store.FeatureCreate" + "$ref": "#/definitions/model.FeatureCreate" } } ], @@ -64,7 +64,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api.Response-model_Feature" + "$ref": "#/definitions/api.Response-model_FeaturePublic" } } } @@ -90,7 +90,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/store.FeatureDeleteOrBanned" + "$ref": "#/definitions/model.FeatureDeleteOrBanned" } } ], @@ -98,7 +98,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api.Response-model_Feature" + "$ref": "#/definitions/api.Response-model_FeaturePublic" } } } @@ -130,7 +130,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api.Response-model_Feature" + "$ref": "#/definitions/api.Response-model_FeaturePublic" } } } @@ -154,7 +154,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/store.FeatureDeleteOrBanned" + "$ref": "#/definitions/model.FeatureDeleteOrBanned" } } ], @@ -162,7 +162,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api.Response-model_Feature" + "$ref": "#/definitions/api.Response-model_FeaturePublic" } } } @@ -199,7 +199,60 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api.Pagination-model_Feature" + "$ref": "#/definitions/api.Pagination-model_FeaturePublic" + } + } + } + } + }, + "/mbtiles/{name}/{z}/{x}/{y}": { + "get": { + "description": "获取指定名称、z、x、y的MBTiles瓦片", + "consumes": [ + "application/json" + ], + "produces": [ + "application/octet-stream" + ], + "tags": [ + "MBTiles" + ], + "summary": "获取指定名称、z、x、y的MBTiles瓦片", + "parameters": [ + { + "type": "string", + "description": "MBTiles名称", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "z值", + "name": "z", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "x值", + "name": "x", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "y值", + "name": "y", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "成功", + "schema": { + "type": "string" } } } @@ -207,13 +260,13 @@ } }, "definitions": { - "api.Pagination-model_Feature": { + "api.Pagination-model_FeaturePublic": { "type": "object", "properties": { "data": { "type": "array", "items": { - "$ref": "#/definitions/model.Feature" + "$ref": "#/definitions/model.FeaturePublic" } }, "page": { @@ -227,28 +280,56 @@ } } }, - "api.Response-model_Feature": { + "api.Response-model_FeaturePublic": { "type": "object", "properties": { "code": { "type": "integer" }, "data": { - "$ref": "#/definitions/model.Feature" + "$ref": "#/definitions/model.FeaturePublic" }, "message": { "type": "string" } } }, - "model.Feature": { + "model.FeatureCreate": { + "type": "object", + "required": [ + "geometry", + "name" + ], + "properties": { + "geometry": { + "type": "object", + "additionalProperties": true + }, + "name": { + "type": "string" + } + } + }, + "model.FeatureDeleteOrBanned": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string" + } + } + }, + "model.FeaturePublic": { "type": "object", "properties": { "created_at": { - "type": "integer" + "type": "string" }, "geometry": { - "type": "string" + "type": "object", + "additionalProperties": true }, "id": { "type": "string" @@ -260,37 +341,11 @@ "type": "string" }, "updated_at": { - "type": "integer" - } - } - }, - "store.FeatureCreate": { - "type": "object", - "required": [ - "geometry", - "name" - ], - "properties": { - "geometry": { - "type": "string" - }, - "name": { "type": "string" } } }, - "store.FeatureDeleteOrBanned": { - "type": "object", - "required": [ - "id" - ], - "properties": { - "id": { - "type": "string" - } - } - }, - "store.FeatureUpdate": { + "model.FeatureUpdate": { "type": "object", "required": [ "geometry", @@ -299,7 +354,8 @@ ], "properties": { "geometry": { - "type": "string" + "type": "object", + "additionalProperties": true }, "id": { "type": "string" diff --git a/go.mod b/go.mod index ae8932a..f35045d 100755 --- a/go.mod +++ b/go.mod @@ -1,11 +1,15 @@ module git.zhouxhere.com/zhouxhere/maptile -go 1.22.5 +go 1.23.0 + +toolchain go1.23.8 require ( + github.com/mattn/go-sqlite3 v1.14.22 github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 github.com/twpayne/go-geom v1.6.0 - golang.org/x/image v0.0.0-20211028202545-6944b10bf410 + golang.org/x/image v0.25.0 + google.golang.org/protobuf v1.34.2 ) require ( @@ -18,6 +22,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/magiconair/properties v1.8.7 // indirect @@ -66,7 +71,7 @@ require ( golang.org/x/crypto v0.33.0 // indirect golang.org/x/net v0.35.0 // indirect golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/text v0.23.0 // indirect golang.org/x/tools v0.30.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 0eaf550..92d5ab9 100755 --- a/go.sum +++ b/go.sum @@ -80,6 +80,8 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -179,26 +181,26 @@ golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= -golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ= -golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= +golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/icons/airfield_11.svg b/icons/airfield_11.svg deleted file mode 100644 index 0d2b3b6..0000000 --- a/icons/airfield_11.svg +++ /dev/null @@ -1,3 +0,0 @@ -airfield-11.svg \ No newline at end of file diff --git a/icons/airport_11.svg b/icons/airport_11.svg deleted file mode 100644 index 66cd4aa..0000000 --- a/icons/airport_11.svg +++ /dev/null @@ -1,3 +0,0 @@ -airport-11.svg \ No newline at end of file diff --git a/icons/alcohol_shop_11.svg b/icons/alcohol_shop_11.svg deleted file mode 100644 index 301c2e4..0000000 --- a/icons/alcohol_shop_11.svg +++ /dev/null @@ -1,13 +0,0 @@ -alcohol-shop-11.svg \ No newline at end of file diff --git a/icons/amusement_park_11.svg b/icons/amusement_park_11.svg deleted file mode 100644 index 41df22a..0000000 --- a/icons/amusement_park_11.svg +++ /dev/null @@ -1,23 +0,0 @@ -amusement-park-11.svg \ No newline at end of file diff --git a/icons/aquarium_11.svg b/icons/aquarium_11.svg deleted file mode 100644 index 8ece76c..0000000 --- a/icons/aquarium_11.svg +++ /dev/null @@ -1,13 +0,0 @@ -aquarium-11.svg \ No newline at end of file diff --git a/icons/art_gallery_11.svg b/icons/art_gallery_11.svg deleted file mode 100644 index 9eb5887..0000000 --- a/icons/art_gallery_11.svg +++ /dev/null @@ -1,7 +0,0 @@ -art-gallery-11.svg \ No newline at end of file diff --git a/icons/attraction_11.svg b/icons/attraction_11.svg deleted file mode 100644 index a2884c4..0000000 --- a/icons/attraction_11.svg +++ /dev/null @@ -1,5 +0,0 @@ -attraction-11.svg \ No newline at end of file diff --git a/icons/bakery_11.svg b/icons/bakery_11.svg deleted file mode 100644 index 392207e..0000000 --- a/icons/bakery_11.svg +++ /dev/null @@ -1,5 +0,0 @@ -bakery-11.svg \ No newline at end of file diff --git a/icons/bank_11.svg b/icons/bank_11.svg deleted file mode 100644 index 69ac8fc..0000000 --- a/icons/bank_11.svg +++ /dev/null @@ -1,23 +0,0 @@ -bank-11.svg \ No newline at end of file diff --git a/icons/bar_11.svg b/icons/bar_11.svg deleted file mode 100644 index 1d13721..0000000 --- a/icons/bar_11.svg +++ /dev/null @@ -1,3 +0,0 @@ -bar-11.svg \ No newline at end of file diff --git a/icons/beer_11.svg b/icons/beer_11.svg deleted file mode 100644 index 9c6f80c..0000000 --- a/icons/beer_11.svg +++ /dev/null @@ -1,9 +0,0 @@ -beer-11.svg \ No newline at end of file diff --git a/icons/bicycle_11.svg b/icons/bicycle_11.svg deleted file mode 100644 index 3b184c3..0000000 --- a/icons/bicycle_11.svg +++ /dev/null @@ -1,15 +0,0 @@ -bicycle-11.svg \ No newline at end of file diff --git a/icons/bicycle_rental_11.svg b/icons/bicycle_rental_11.svg deleted file mode 100644 index e738bbb..0000000 --- a/icons/bicycle_rental_11.svg +++ /dev/null @@ -1,33 +0,0 @@ -bicycle-share-11.svg \ No newline at end of file diff --git a/icons/bus_11.svg b/icons/bus_11.svg deleted file mode 100644 index a7b50a8..0000000 --- a/icons/bus_11.svg +++ /dev/null @@ -1,11 +0,0 @@ -bus-11.svg \ No newline at end of file diff --git a/icons/cafe_11.svg b/icons/cafe_11.svg deleted file mode 100644 index 006a507..0000000 --- a/icons/cafe_11.svg +++ /dev/null @@ -1,5 +0,0 @@ -cafe-11.svg \ No newline at end of file diff --git a/icons/campsite_11.svg b/icons/campsite_11.svg deleted file mode 100644 index 83bf08d..0000000 --- a/icons/campsite_11.svg +++ /dev/null @@ -1,7 +0,0 @@ -campsite-11.svg \ No newline at end of file diff --git a/icons/car_11.svg b/icons/car_11.svg deleted file mode 100644 index 3cfb5c2..0000000 --- a/icons/car_11.svg +++ /dev/null @@ -1,7 +0,0 @@ -car-11.svg \ No newline at end of file diff --git a/icons/castle_11.svg b/icons/castle_11.svg deleted file mode 100644 index c9b086e..0000000 --- a/icons/castle_11.svg +++ /dev/null @@ -1,19 +0,0 @@ -castle-11.svg \ No newline at end of file diff --git a/icons/cemetery_11.svg b/icons/cemetery_11.svg deleted file mode 100644 index 71925e0..0000000 --- a/icons/cemetery_11.svg +++ /dev/null @@ -1,7 +0,0 @@ -cemetery-11.svg \ No newline at end of file diff --git a/icons/cinema_11.svg b/icons/cinema_11.svg deleted file mode 100644 index 3e21879..0000000 --- a/icons/cinema_11.svg +++ /dev/null @@ -1,11 +0,0 @@ -cinema-11.svg \ No newline at end of file diff --git a/icons/circle_11.svg b/icons/circle_11.svg deleted file mode 100644 index 389f8bb..0000000 --- a/icons/circle_11.svg +++ /dev/null @@ -1 +0,0 @@ -circle-11.svg \ No newline at end of file diff --git a/icons/circle_stroked_11.svg b/icons/circle_stroked_11.svg deleted file mode 100644 index 654766c..0000000 --- a/icons/circle_stroked_11.svg +++ /dev/null @@ -1,5 +0,0 @@ -circle-stroked-11.svg \ No newline at end of file diff --git a/icons/clothing_store_11.svg b/icons/clothing_store_11.svg deleted file mode 100644 index 5406cb9..0000000 --- a/icons/clothing_store_11.svg +++ /dev/null @@ -1,3 +0,0 @@ -clothing-store-11.svg \ No newline at end of file diff --git a/icons/college_11.svg b/icons/college_11.svg deleted file mode 100644 index 1eec8c6..0000000 --- a/icons/college_11.svg +++ /dev/null @@ -1,5 +0,0 @@ -college-11.svg \ No newline at end of file diff --git a/icons/dentist_11.svg b/icons/dentist_11.svg deleted file mode 100644 index b292a32..0000000 --- a/icons/dentist_11.svg +++ /dev/null @@ -1,5 +0,0 @@ -dentist-11.svg \ No newline at end of file diff --git a/icons/doctor_11.svg b/icons/doctor_11.svg deleted file mode 100644 index 2537ca2..0000000 --- a/icons/doctor_11.svg +++ /dev/null @@ -1,17 +0,0 @@ -doctor-11.svg \ No newline at end of file diff --git a/icons/dog_park_11.svg b/icons/dog_park_11.svg deleted file mode 100644 index 08af4e3..0000000 --- a/icons/dog_park_11.svg +++ /dev/null @@ -1,11 +0,0 @@ -dog-park-11.svg \ No newline at end of file diff --git a/icons/drinking_water_11.svg b/icons/drinking_water_11.svg deleted file mode 100644 index 6ca28b1..0000000 --- a/icons/drinking_water_11.svg +++ /dev/null @@ -1,5 +0,0 @@ -drinking-water-11.svg \ No newline at end of file diff --git a/icons/embassy_11.svg b/icons/embassy_11.svg deleted file mode 100644 index 1e2e51b..0000000 --- a/icons/embassy_11.svg +++ /dev/null @@ -1,11 +0,0 @@ -embassy-11.svg \ No newline at end of file diff --git a/icons/entrance_11.svg b/icons/entrance_11.svg deleted file mode 100644 index 71ff699..0000000 --- a/icons/entrance_11.svg +++ /dev/null @@ -1,9 +0,0 @@ -entrance-11.svg \ No newline at end of file diff --git a/icons/fast_food_11.svg b/icons/fast_food_11.svg deleted file mode 100644 index 281ed32..0000000 --- a/icons/fast_food_11.svg +++ /dev/null @@ -1,11 +0,0 @@ -fast-food-11.svg \ No newline at end of file diff --git a/icons/ferry_terminal_11.svg b/icons/ferry_terminal_11.svg deleted file mode 100644 index 69e1ae6..0000000 --- a/icons/ferry_terminal_11.svg +++ /dev/null @@ -1,21 +0,0 @@ -ferry-11.svg \ No newline at end of file diff --git a/icons/fire_station_11.svg b/icons/fire_station_11.svg deleted file mode 100644 index 559f76a..0000000 --- a/icons/fire_station_11.svg +++ /dev/null @@ -1,5 +0,0 @@ -fire-station-11.svg \ No newline at end of file diff --git a/icons/fuel_11.svg b/icons/fuel_11.svg deleted file mode 100644 index 40eb1c7..0000000 --- a/icons/fuel_11.svg +++ /dev/null @@ -1,9 +0,0 @@ -fuel-11.svg \ No newline at end of file diff --git a/icons/garden_11.svg b/icons/garden_11.svg deleted file mode 100644 index 9e480b4..0000000 --- a/icons/garden_11.svg +++ /dev/null @@ -1,13 +0,0 @@ -garden-11.svg \ No newline at end of file diff --git a/icons/golf_11.svg b/icons/golf_11.svg deleted file mode 100644 index 93acf2d..0000000 --- a/icons/golf_11.svg +++ /dev/null @@ -1,13 +0,0 @@ -golf-11.svg \ No newline at end of file diff --git a/icons/grocery_11.svg b/icons/grocery_11.svg deleted file mode 100644 index b04e3c0..0000000 --- a/icons/grocery_11.svg +++ /dev/null @@ -1,11 +0,0 @@ -grocery-11.svg \ No newline at end of file diff --git a/icons/harbor_11.svg b/icons/harbor_11.svg deleted file mode 100644 index cb91835..0000000 --- a/icons/harbor_11.svg +++ /dev/null @@ -1,11 +0,0 @@ -harbor-11.svg \ No newline at end of file diff --git a/icons/heliport_11.svg b/icons/heliport_11.svg deleted file mode 100644 index 5673328..0000000 --- a/icons/heliport_11.svg +++ /dev/null @@ -1,9 +0,0 @@ -heliport-11.svg \ No newline at end of file diff --git a/icons/hospital_11.svg b/icons/hospital_11.svg deleted file mode 100644 index 204ba28..0000000 --- a/icons/hospital_11.svg +++ /dev/null @@ -1,5 +0,0 @@ -hospital-11.svg \ No newline at end of file diff --git a/icons/ice_cream_11.svg b/icons/ice_cream_11.svg deleted file mode 100644 index d96d7e2..0000000 --- a/icons/ice_cream_11.svg +++ /dev/null @@ -1,7 +0,0 @@ -ice-cream-11.svg \ No newline at end of file diff --git a/icons/information_11.svg b/icons/information_11.svg deleted file mode 100644 index f796dcf..0000000 --- a/icons/information_11.svg +++ /dev/null @@ -1,7 +0,0 @@ -information-11.svg \ No newline at end of file diff --git a/icons/laundry_11.svg b/icons/laundry_11.svg deleted file mode 100644 index b4788fd..0000000 --- a/icons/laundry_11.svg +++ /dev/null @@ -1,3 +0,0 @@ -laundry-11.svg \ No newline at end of file diff --git a/icons/library_11.svg b/icons/library_11.svg deleted file mode 100644 index c136c08..0000000 --- a/icons/library_11.svg +++ /dev/null @@ -1,19 +0,0 @@ -library-11.svg \ No newline at end of file diff --git a/icons/lodging_11.svg b/icons/lodging_11.svg deleted file mode 100644 index 9dcd222..0000000 --- a/icons/lodging_11.svg +++ /dev/null @@ -1,7 +0,0 @@ -lodging-11.svg \ No newline at end of file diff --git a/icons/marker_11.svg b/icons/marker_11.svg deleted file mode 100644 index d639a15..0000000 --- a/icons/marker_11.svg +++ /dev/null @@ -1,3 +0,0 @@ -marker-11.svg \ No newline at end of file diff --git a/icons/monument_11.svg b/icons/monument_11.svg deleted file mode 100644 index 4e415a1..0000000 --- a/icons/monument_11.svg +++ /dev/null @@ -1,3 +0,0 @@ -monument-11.svg \ No newline at end of file diff --git a/icons/mountain_11.svg b/icons/mountain_11.svg deleted file mode 100644 index 9030f98..0000000 --- a/icons/mountain_11.svg +++ /dev/null @@ -1,7 +0,0 @@ -mountain-11.svg \ No newline at end of file diff --git a/icons/museum_11.svg b/icons/museum_11.svg deleted file mode 100644 index f20a451..0000000 --- a/icons/museum_11.svg +++ /dev/null @@ -1,7 +0,0 @@ -museum-11.svg \ No newline at end of file diff --git a/icons/music_11.svg b/icons/music_11.svg deleted file mode 100644 index 84b3595..0000000 --- a/icons/music_11.svg +++ /dev/null @@ -1,7 +0,0 @@ -music-11.svg \ No newline at end of file diff --git a/icons/oneway.svg b/icons/oneway.svg deleted file mode 100644 index 2be2c22..0000000 --- a/icons/oneway.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/icons/park_11.svg b/icons/park_11.svg deleted file mode 100644 index 310bb16..0000000 --- a/icons/park_11.svg +++ /dev/null @@ -1,17 +0,0 @@ -park-11.svg \ No newline at end of file diff --git a/icons/pharmacy_11.svg b/icons/pharmacy_11.svg deleted file mode 100644 index 50cdc03..0000000 --- a/icons/pharmacy_11.svg +++ /dev/null @@ -1,3 +0,0 @@ -pharmacy-11.svg \ No newline at end of file diff --git a/icons/picnic_site_11.svg b/icons/picnic_site_11.svg deleted file mode 100644 index f405c97..0000000 --- a/icons/picnic_site_11.svg +++ /dev/null @@ -1,5 +0,0 @@ -picnic-site-11.svg \ No newline at end of file diff --git a/icons/pitch_11.svg b/icons/pitch_11.svg deleted file mode 100644 index 385537f..0000000 --- a/icons/pitch_11.svg +++ /dev/null @@ -1,11 +0,0 @@ -stadium-11.svg \ No newline at end of file diff --git a/icons/place_of_worship_11.svg b/icons/place_of_worship_11.svg deleted file mode 100644 index 4f2059d..0000000 --- a/icons/place_of_worship_11.svg +++ /dev/null @@ -1,3 +0,0 @@ -place-of-worship-11.svg \ No newline at end of file diff --git a/icons/playground_11.svg b/icons/playground_11.svg deleted file mode 100644 index 306a698..0000000 --- a/icons/playground_11.svg +++ /dev/null @@ -1,17 +0,0 @@ -playground-11.svg \ No newline at end of file diff --git a/icons/police_11.svg b/icons/police_11.svg deleted file mode 100644 index 8e21550..0000000 --- a/icons/police_11.svg +++ /dev/null @@ -1,7 +0,0 @@ -police-11.svg \ No newline at end of file diff --git a/icons/post_11.svg b/icons/post_11.svg deleted file mode 100644 index db6b1d1..0000000 --- a/icons/post_11.svg +++ /dev/null @@ -1,7 +0,0 @@ -post-11.svg \ No newline at end of file diff --git a/icons/prison_11.svg b/icons/prison_11.svg deleted file mode 100644 index 08cdb56..0000000 --- a/icons/prison_11.svg +++ /dev/null @@ -1,3 +0,0 @@ -prison-11.svg \ No newline at end of file diff --git a/icons/rail_light_11.svg b/icons/rail_light_11.svg deleted file mode 100644 index 837640d..0000000 --- a/icons/rail_light_11.svg +++ /dev/null @@ -1,7 +0,0 @@ -rail-light-11.svg \ No newline at end of file diff --git a/icons/rail_metro_11.svg b/icons/rail_metro_11.svg deleted file mode 100644 index ffe9013..0000000 --- a/icons/rail_metro_11.svg +++ /dev/null @@ -1,7 +0,0 @@ -rail-metro-11.svg \ No newline at end of file diff --git a/icons/railway_11.svg b/icons/railway_11.svg deleted file mode 100644 index c120f10..0000000 --- a/icons/railway_11.svg +++ /dev/null @@ -1,15 +0,0 @@ -rail-11.svg \ No newline at end of file diff --git a/icons/religious_christian_11.svg b/icons/religious_christian_11.svg deleted file mode 100644 index 52cd8ad..0000000 --- a/icons/religious_christian_11.svg +++ /dev/null @@ -1 +0,0 @@ -religious-christian-11.svg \ No newline at end of file diff --git a/icons/religious_jewish_11.svg b/icons/religious_jewish_11.svg deleted file mode 100644 index 643ccb2..0000000 --- a/icons/religious_jewish_11.svg +++ /dev/null @@ -1 +0,0 @@ -religious-jewish-11.svg \ No newline at end of file diff --git a/icons/religious_muslim_11.svg b/icons/religious_muslim_11.svg deleted file mode 100644 index 39bcfcd..0000000 --- a/icons/religious_muslim_11.svg +++ /dev/null @@ -1,5 +0,0 @@ -religious-muslim-11.svg \ No newline at end of file diff --git a/icons/restaurant_11.svg b/icons/restaurant_11.svg deleted file mode 100644 index f2ce880..0000000 --- a/icons/restaurant_11.svg +++ /dev/null @@ -1,3 +0,0 @@ -restaurant-11.svg \ No newline at end of file diff --git a/icons/road_1.svg b/icons/road_1.svg deleted file mode 100644 index 8eeff24..0000000 --- a/icons/road_1.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - \ No newline at end of file diff --git a/icons/road_2.svg b/icons/road_2.svg deleted file mode 100644 index 7a1db4c..0000000 --- a/icons/road_2.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - \ No newline at end of file diff --git a/icons/road_3.svg b/icons/road_3.svg deleted file mode 100644 index 4d10e7c..0000000 --- a/icons/road_3.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/icons/road_4.svg b/icons/road_4.svg deleted file mode 100644 index 733e526..0000000 --- a/icons/road_4.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - \ No newline at end of file diff --git a/icons/road_5.svg b/icons/road_5.svg deleted file mode 100644 index a89e944..0000000 --- a/icons/road_5.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/icons/road_6.svg b/icons/road_6.svg deleted file mode 100644 index 740fe4a..0000000 --- a/icons/road_6.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - \ No newline at end of file diff --git a/icons/rocket_11.svg b/icons/rocket_11.svg deleted file mode 100644 index c2fd2ba..0000000 --- a/icons/rocket_11.svg +++ /dev/null @@ -1,5 +0,0 @@ -rocket-11.svg \ No newline at end of file diff --git a/icons/school_11.svg b/icons/school_11.svg deleted file mode 100644 index 25a04a1..0000000 --- a/icons/school_11.svg +++ /dev/null @@ -1 +0,0 @@ -school-11.svg \ No newline at end of file diff --git a/icons/shop_11.svg b/icons/shop_11.svg deleted file mode 100644 index 49f7514..0000000 --- a/icons/shop_11.svg +++ /dev/null @@ -1,11 +0,0 @@ -shop-11.svg \ No newline at end of file diff --git a/icons/stadium_11.svg b/icons/stadium_11.svg deleted file mode 100644 index 385537f..0000000 --- a/icons/stadium_11.svg +++ /dev/null @@ -1,11 +0,0 @@ -stadium-11.svg \ No newline at end of file diff --git a/icons/star_11.svg b/icons/star_11.svg deleted file mode 100644 index 2fc55eb..0000000 --- a/icons/star_11.svg +++ /dev/null @@ -1,3 +0,0 @@ -star-11.svg \ No newline at end of file diff --git a/icons/suitcase_11.svg b/icons/suitcase_11.svg deleted file mode 100644 index 8f3156b..0000000 --- a/icons/suitcase_11.svg +++ /dev/null @@ -1,3 +0,0 @@ -suitcase-11.svg \ No newline at end of file diff --git a/icons/swimming_11.svg b/icons/swimming_11.svg deleted file mode 100644 index ba40068..0000000 --- a/icons/swimming_11.svg +++ /dev/null @@ -1,9 +0,0 @@ -swimming-11.svg \ No newline at end of file diff --git a/icons/theatre_11.svg b/icons/theatre_11.svg deleted file mode 100644 index bcbade9..0000000 --- a/icons/theatre_11.svg +++ /dev/null @@ -1,17 +0,0 @@ -theatre-11.svg \ No newline at end of file diff --git a/icons/toilet_11.svg b/icons/toilet_11.svg deleted file mode 100644 index 3e11dbb..0000000 --- a/icons/toilet_11.svg +++ /dev/null @@ -1,19 +0,0 @@ -toilet-11.svg \ No newline at end of file diff --git a/icons/town_hall_11.svg b/icons/town_hall_11.svg deleted file mode 100644 index e81c324..0000000 --- a/icons/town_hall_11.svg +++ /dev/null @@ -1 +0,0 @@ -town-hall-11.svg \ No newline at end of file diff --git a/icons/triangle_11.svg b/icons/triangle_11.svg deleted file mode 100644 index 2609394..0000000 --- a/icons/triangle_11.svg +++ /dev/null @@ -1,5 +0,0 @@ -triangle-11.svg \ No newline at end of file diff --git a/icons/triangle_stroked_11.svg b/icons/triangle_stroked_11.svg deleted file mode 100644 index e53e1b5..0000000 --- a/icons/triangle_stroked_11.svg +++ /dev/null @@ -1,5 +0,0 @@ -triangle-stroked-11.svg \ No newline at end of file diff --git a/icons/us-highway_1.svg b/icons/us-highway_1.svg deleted file mode 100644 index ddafb0a..0000000 --- a/icons/us-highway_1.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - \ No newline at end of file diff --git a/icons/us-highway_2.svg b/icons/us-highway_2.svg deleted file mode 100644 index ddafb0a..0000000 --- a/icons/us-highway_2.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - \ No newline at end of file diff --git a/icons/us-highway_3.svg b/icons/us-highway_3.svg deleted file mode 100644 index 94bb67e..0000000 --- a/icons/us-highway_3.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - \ No newline at end of file diff --git a/icons/us-interstate_1.svg b/icons/us-interstate_1.svg deleted file mode 100644 index 6551cf9..0000000 --- a/icons/us-interstate_1.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - \ No newline at end of file diff --git a/icons/us-interstate_2.svg b/icons/us-interstate_2.svg deleted file mode 100644 index 2672ae2..0000000 --- a/icons/us-interstate_2.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - \ No newline at end of file diff --git a/icons/us-interstate_3.svg b/icons/us-interstate_3.svg deleted file mode 100644 index 06cdc4c..0000000 --- a/icons/us-interstate_3.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - \ No newline at end of file diff --git a/icons/us-state_1.svg b/icons/us-state_1.svg deleted file mode 100644 index 94bde51..0000000 --- a/icons/us-state_1.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/icons/us-state_2.svg b/icons/us-state_2.svg deleted file mode 100644 index 73503d0..0000000 --- a/icons/us-state_2.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/icons/us-state_3.svg b/icons/us-state_3.svg deleted file mode 100644 index 670ea92..0000000 --- a/icons/us-state_3.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/icons/us-state_4.svg b/icons/us-state_4.svg deleted file mode 100644 index a8e9fc9..0000000 --- a/icons/us-state_4.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/icons/us-state_5.svg b/icons/us-state_5.svg deleted file mode 100644 index 1e8f393..0000000 --- a/icons/us-state_5.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/icons/us-state_6.svg b/icons/us-state_6.svg deleted file mode 100644 index 62496b5..0000000 --- a/icons/us-state_6.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/icons/veterinary_11.svg b/icons/veterinary_11.svg deleted file mode 100644 index 0920a03..0000000 --- a/icons/veterinary_11.svg +++ /dev/null @@ -1,23 +0,0 @@ -veterinary-11.svg \ No newline at end of file diff --git a/icons/volcano_11.svg b/icons/volcano_11.svg deleted file mode 100644 index 073b844..0000000 --- a/icons/volcano_11.svg +++ /dev/null @@ -1,5 +0,0 @@ -volcano-11.svg \ No newline at end of file diff --git a/icons/wave.svg b/icons/wave.svg deleted file mode 100644 index 3f9bdda..0000000 --- a/icons/wave.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - diff --git a/icons/zoo_11.svg b/icons/zoo_11.svg deleted file mode 100644 index 0ff94d7..0000000 --- a/icons/zoo_11.svg +++ /dev/null @@ -1,7 +0,0 @@ -zoo-11.svg \ No newline at end of file diff --git a/main.go b/main.go index fa64951..63feb54 100755 --- a/main.go +++ b/main.go @@ -1,63 +1,62 @@ package main import ( - "fmt" - "image/png" - "os" "path/filepath" - // "git.zhouxhere.com/zhouxhere/maptile/bin" + "git.zhouxhere.com/zhouxhere/maptile/bin" + "git.zhouxhere.com/zhouxhere/maptile/util/fontnik" + "git.zhouxhere.com/zhouxhere/maptile/util/mbtiles" "git.zhouxhere.com/zhouxhere/maptile/util/sprite" ) +// Protobuf 定义需提前通过 protoc 生成 Go 代码 + +//go:generate protoc --go_out=. --go_opt=paths=source_relative ./protobuf/*.proto func main() { - // bin.RunCMD() + bin.RunCMD() // testSprite() - testFont() + // testFont() + // testMBtiles() } func testSprite() { - sprites, err := sprite.NewSprite(1, sprite.Vertical, 4, 30) + filePath, err := filepath.Abs("icons/") if err != nil { panic(err) } - files, err := os.ReadDir("icons") + spriteBuilder, err := sprite.NewSpriteBuilder(filePath, "style/sprite") if err != nil { panic(err) } - images := []sprite.ImageInfo{} - - for _, file := range files { - if file.IsDir() { - continue - } - filePath, err := filepath.Abs("icons/" + file.Name()) - if err != nil { - continue - } - images = append(images, sprite.ImageInfo{ - Name: file.Name(), - Path: filePath, - }) - } - - fmt.Println((images)) - spritesheet, err := sprites.MergeImages(images) - if err != nil { - panic(err) - } - - file, err := os.Create("ouput.png") - if err != nil { - panic(err) - } - defer file.Close() - png.Encode(file, spritesheet) - + spriteBuilder.Build() } func testFont() { + filePath, err := filepath.Abs("fonts/") + if err != nil { + panic(err) + } + fonts, err := fontnik.NewFontnik(filePath, "style/font") + if err != nil { + panic(err) + } + + fonts.ToPBF() +} + +func testMBtiles() { + filePath, err := filepath.Abs("mbtiles/") + if err != nil { + panic(err) + } + + mbtiles, err := mbtiles.New(filepath.Join(filePath, "world_cities.mbtiles")) + if err != nil { + panic(err) + } + + mbtiles.GetTile(6, 38, 33) } diff --git a/model/tilejson.go b/model/tilejson.go new file mode 100644 index 0000000..b6cc493 --- /dev/null +++ b/model/tilejson.go @@ -0,0 +1,160 @@ +package model + +type VectorLayer struct { + ID string `json:"id"` + Fields map[string]string `json:"fields"` + + Description string `json:"description,omitempty"` + Minzoom *int `json:"minzoom,omitempty"` + Maxzoom *int `json:"maxzoom,omitempty"` +} + +type TileJSON struct { + TileJSON string `json:"tilejson"` + Tiles []string `json:"tiles"` + VectorLayers []VectorLayer `json:"vector_layers"` + + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Version string `json:"version,omitempty"` + Attribution string `json:"attribution,omitempty"` + Scheme string `json:"scheme,omitempty"` + MinZoom *int `json:"minzoom,omitempty"` + MaxZoom *int `json:"maxzoom,omitempty"` + Bounds []float64 `json:"bounds,omitempty"` + Center []float64 `json:"center,omitempty"` + FillZoom *int `json:"fillZoom,omitempty"` + Grids []string `json:"grids,omitempty"` + Data []string `json:"data,omitempty"` + Legend string `json:"legend,omitempty"` + Format string `json:"format,omitempty"` +} + +func NewVectorLayer(id string, fields map[string]string, opts ...func(*VectorLayer)) VectorLayer { + layer := VectorLayer{ + ID: id, + Fields: fields, + } + + for _, opt := range opts { + opt(&layer) + } + + return layer +} + +func WithVectorLayerDescription(description string) func(*VectorLayer) { + return func(v *VectorLayer) { + v.Description = description + } +} + +func WithVectorLayerMinzoom(minzoom int) func(*VectorLayer) { + return func(v *VectorLayer) { + v.Minzoom = &minzoom + } +} + +func WithVectorLayerMaxzoom(maxzoom int) func(*VectorLayer) { + return func(v *VectorLayer) { + v.Maxzoom = &maxzoom + } +} + +func NewTileJSON(tiles []string, VectorLayers []VectorLayer, opts ...func(*TileJSON)) TileJSON { + tileJSON := TileJSON{ + TileJSON: "3.0.0", + Tiles: tiles, + VectorLayers: VectorLayers, + } + + for _, opt := range opts { + opt(&tileJSON) + } + + return tileJSON +} + +func WithTileJSONName(name string) func(*TileJSON) { + return func(t *TileJSON) { + t.Name = name + } +} + +func WithTileJSONDescription(description string) func(*TileJSON) { + return func(t *TileJSON) { + t.Description = description + } +} + +func WithTileJSONVersion(version string) func(*TileJSON) { + return func(t *TileJSON) { + t.Version = version + } +} + +func WithTileJSONAttribution(attribution string) func(*TileJSON) { + return func(t *TileJSON) { + t.Attribution = attribution + } +} + +func WithTileJSONScheme(scheme string) func(*TileJSON) { + return func(t *TileJSON) { + t.Scheme = scheme + } +} + +func WithTileJSONFormat(format string) func(*TileJSON) { + return func(t *TileJSON) { + t.Format = format + } +} + +func WithTileJSONBounds(bounds []float64) func(*TileJSON) { + return func(t *TileJSON) { + t.Bounds = bounds + } +} + +func WithTileJSONCenter(center []float64) func(*TileJSON) { + return func(t *TileJSON) { + t.Center = center + } +} + +func WithTileJSONFillZoom(fillZoom int) func(*TileJSON) { + return func(t *TileJSON) { + t.FillZoom = &fillZoom + } +} + +func WithTileJSONGrids(grids []string) func(*TileJSON) { + return func(t *TileJSON) { + t.Grids = grids + } +} + +func WithTileJSONData(data []string) func(*TileJSON) { + return func(t *TileJSON) { + t.Data = data + } +} + +func WithTileJSONLegend(legend string) func(*TileJSON) { + return func(t *TileJSON) { + t.Legend = legend + } +} + +func WithTileJSONMinZoom(minZoom int) func(*TileJSON) { + return func(t *TileJSON) { + t.MinZoom = &minZoom + } +} + +func WithTileJSONMaxZoom(maxZoom int) func(*TileJSON) { + return func(t *TileJSON) { + t.MaxZoom = &maxZoom + } +} diff --git a/protobuf/glyphs.pb.go b/protobuf/glyphs.pb.go new file mode 100644 index 0000000..7d487e5 --- /dev/null +++ b/protobuf/glyphs.pb.go @@ -0,0 +1,295 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v5.29.3 +// source: protobuf/glyphs.proto + +package protobuf + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Stores a glyph with metrics and optional SDF bitmap information. +type Glyph struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id *uint32 `protobuf:"varint,1,req,name=id" json:"id,omitempty"` + // A signed distance field of the glyph with a border of 3 pixels. + Bitmap []byte `protobuf:"bytes,2,opt,name=bitmap" json:"bitmap,omitempty"` + // Glyph metrics. + Width *uint32 `protobuf:"varint,3,req,name=width" json:"width,omitempty"` + Height *uint32 `protobuf:"varint,4,req,name=height" json:"height,omitempty"` + Left *int32 `protobuf:"zigzag32,5,req,name=left" json:"left,omitempty"` + Top *int32 `protobuf:"zigzag32,6,req,name=top" json:"top,omitempty"` + Advance *uint32 `protobuf:"varint,7,req,name=advance" json:"advance,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Glyph) Reset() { + *x = Glyph{} + mi := &file_protobuf_glyphs_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Glyph) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Glyph) ProtoMessage() {} + +func (x *Glyph) ProtoReflect() protoreflect.Message { + mi := &file_protobuf_glyphs_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Glyph.ProtoReflect.Descriptor instead. +func (*Glyph) Descriptor() ([]byte, []int) { + return file_protobuf_glyphs_proto_rawDescGZIP(), []int{0} +} + +func (x *Glyph) GetId() uint32 { + if x != nil && x.Id != nil { + return *x.Id + } + return 0 +} + +func (x *Glyph) GetBitmap() []byte { + if x != nil { + return x.Bitmap + } + return nil +} + +func (x *Glyph) GetWidth() uint32 { + if x != nil && x.Width != nil { + return *x.Width + } + return 0 +} + +func (x *Glyph) GetHeight() uint32 { + if x != nil && x.Height != nil { + return *x.Height + } + return 0 +} + +func (x *Glyph) GetLeft() int32 { + if x != nil && x.Left != nil { + return *x.Left + } + return 0 +} + +func (x *Glyph) GetTop() int32 { + if x != nil && x.Top != nil { + return *x.Top + } + return 0 +} + +func (x *Glyph) GetAdvance() uint32 { + if x != nil && x.Advance != nil { + return *x.Advance + } + return 0 +} + +// Stores fontstack information and a list of faces. +type Fontstack struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"` + Range *string `protobuf:"bytes,2,req,name=range" json:"range,omitempty"` + Glyphs []*Glyph `protobuf:"bytes,3,rep,name=glyphs" json:"glyphs,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Fontstack) Reset() { + *x = Fontstack{} + mi := &file_protobuf_glyphs_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Fontstack) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Fontstack) ProtoMessage() {} + +func (x *Fontstack) ProtoReflect() protoreflect.Message { + mi := &file_protobuf_glyphs_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Fontstack.ProtoReflect.Descriptor instead. +func (*Fontstack) Descriptor() ([]byte, []int) { + return file_protobuf_glyphs_proto_rawDescGZIP(), []int{1} +} + +func (x *Fontstack) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +func (x *Fontstack) GetRange() string { + if x != nil && x.Range != nil { + return *x.Range + } + return "" +} + +func (x *Fontstack) GetGlyphs() []*Glyph { + if x != nil { + return x.Glyphs + } + return nil +} + +type Glyphs struct { + state protoimpl.MessageState `protogen:"open.v1"` + Stacks []*Fontstack `protobuf:"bytes,1,rep,name=stacks" json:"stacks,omitempty"` + extensionFields protoimpl.ExtensionFields + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Glyphs) Reset() { + *x = Glyphs{} + mi := &file_protobuf_glyphs_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Glyphs) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Glyphs) ProtoMessage() {} + +func (x *Glyphs) ProtoReflect() protoreflect.Message { + mi := &file_protobuf_glyphs_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Glyphs.ProtoReflect.Descriptor instead. +func (*Glyphs) Descriptor() ([]byte, []int) { + return file_protobuf_glyphs_proto_rawDescGZIP(), []int{2} +} + +func (x *Glyphs) GetStacks() []*Fontstack { + if x != nil { + return x.Stacks + } + return nil +} + +var File_protobuf_glyphs_proto protoreflect.FileDescriptor + +const file_protobuf_glyphs_proto_rawDesc = "" + + "\n" + + "\x15protobuf/glyphs.proto\x12\bprotobuf\"\x9d\x01\n" + + "\x05glyph\x12\x0e\n" + + "\x02id\x18\x01 \x02(\rR\x02id\x12\x16\n" + + "\x06bitmap\x18\x02 \x01(\fR\x06bitmap\x12\x14\n" + + "\x05width\x18\x03 \x02(\rR\x05width\x12\x16\n" + + "\x06height\x18\x04 \x02(\rR\x06height\x12\x12\n" + + "\x04left\x18\x05 \x02(\x11R\x04left\x12\x10\n" + + "\x03top\x18\x06 \x02(\x11R\x03top\x12\x18\n" + + "\aadvance\x18\a \x02(\rR\aadvance\"^\n" + + "\tfontstack\x12\x12\n" + + "\x04name\x18\x01 \x02(\tR\x04name\x12\x14\n" + + "\x05range\x18\x02 \x02(\tR\x05range\x12'\n" + + "\x06glyphs\x18\x03 \x03(\v2\x0f.protobuf.glyphR\x06glyphs\"<\n" + + "\x06glyphs\x12+\n" + + "\x06stacks\x18\x01 \x03(\v2\x13.protobuf.fontstackR\x06stacks*\x05\b\x10\x10\x80@B0H\x03Z,git.zhouxhere.com/zhouxhere/maptile/protobuf" + +var ( + file_protobuf_glyphs_proto_rawDescOnce sync.Once + file_protobuf_glyphs_proto_rawDescData []byte +) + +func file_protobuf_glyphs_proto_rawDescGZIP() []byte { + file_protobuf_glyphs_proto_rawDescOnce.Do(func() { + file_protobuf_glyphs_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_protobuf_glyphs_proto_rawDesc), len(file_protobuf_glyphs_proto_rawDesc))) + }) + return file_protobuf_glyphs_proto_rawDescData +} + +var file_protobuf_glyphs_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_protobuf_glyphs_proto_goTypes = []any{ + (*Glyph)(nil), // 0: protobuf.glyph + (*Fontstack)(nil), // 1: protobuf.fontstack + (*Glyphs)(nil), // 2: protobuf.glyphs +} +var file_protobuf_glyphs_proto_depIdxs = []int32{ + 0, // 0: protobuf.fontstack.glyphs:type_name -> protobuf.glyph + 1, // 1: protobuf.glyphs.stacks:type_name -> protobuf.fontstack + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_protobuf_glyphs_proto_init() } +func file_protobuf_glyphs_proto_init() { + if File_protobuf_glyphs_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_protobuf_glyphs_proto_rawDesc), len(file_protobuf_glyphs_proto_rawDesc)), + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_protobuf_glyphs_proto_goTypes, + DependencyIndexes: file_protobuf_glyphs_proto_depIdxs, + MessageInfos: file_protobuf_glyphs_proto_msgTypes, + }.Build() + File_protobuf_glyphs_proto = out.File + file_protobuf_glyphs_proto_goTypes = nil + file_protobuf_glyphs_proto_depIdxs = nil +} diff --git a/protobuf/glyphs.proto b/protobuf/glyphs.proto new file mode 100644 index 0000000..8192068 --- /dev/null +++ b/protobuf/glyphs.proto @@ -0,0 +1,35 @@ +syntax = "proto2"; + +package protobuf; + +option optimize_for = LITE_RUNTIME; + +option go_package = "git.zhouxhere.com/zhouxhere/maptile/protobuf"; + +// Stores a glyph with metrics and optional SDF bitmap information. +message glyph { + required uint32 id = 1; + + // A signed distance field of the glyph with a border of 3 pixels. + optional bytes bitmap = 2; + + // Glyph metrics. + required uint32 width = 3; + required uint32 height = 4; + required sint32 left = 5; + required sint32 top = 6; + required uint32 advance = 7; +} + +// Stores fontstack information and a list of faces. +message fontstack { + required string name = 1; + required string range = 2; + repeated glyph glyphs = 3; +} + +message glyphs { + repeated fontstack stacks = 1; + + extensions 16 to 8191; +} \ No newline at end of file diff --git a/protobuf/vector_tile.pb.go b/protobuf/vector_tile.pb.go new file mode 100644 index 0000000..390fc5a --- /dev/null +++ b/protobuf/vector_tile.pb.go @@ -0,0 +1,506 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v5.29.3 +// source: protobuf/vector_tile.proto + +package protobuf + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// GeomType is described in section 4.3.4 of the specification +type Tile_GeomType int32 + +const ( + Tile_UNKNOWN Tile_GeomType = 0 + Tile_POINT Tile_GeomType = 1 + Tile_LINESTRING Tile_GeomType = 2 + Tile_POLYGON Tile_GeomType = 3 +) + +// Enum value maps for Tile_GeomType. +var ( + Tile_GeomType_name = map[int32]string{ + 0: "UNKNOWN", + 1: "POINT", + 2: "LINESTRING", + 3: "POLYGON", + } + Tile_GeomType_value = map[string]int32{ + "UNKNOWN": 0, + "POINT": 1, + "LINESTRING": 2, + "POLYGON": 3, + } +) + +func (x Tile_GeomType) Enum() *Tile_GeomType { + p := new(Tile_GeomType) + *p = x + return p +} + +func (x Tile_GeomType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Tile_GeomType) Descriptor() protoreflect.EnumDescriptor { + return file_protobuf_vector_tile_proto_enumTypes[0].Descriptor() +} + +func (Tile_GeomType) Type() protoreflect.EnumType { + return &file_protobuf_vector_tile_proto_enumTypes[0] +} + +func (x Tile_GeomType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *Tile_GeomType) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = Tile_GeomType(num) + return nil +} + +// Deprecated: Use Tile_GeomType.Descriptor instead. +func (Tile_GeomType) EnumDescriptor() ([]byte, []int) { + return file_protobuf_vector_tile_proto_rawDescGZIP(), []int{0, 0} +} + +type Tile struct { + state protoimpl.MessageState `protogen:"open.v1"` + Layers []*Tile_Layer `protobuf:"bytes,3,rep,name=layers" json:"layers,omitempty"` + extensionFields protoimpl.ExtensionFields + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Tile) Reset() { + *x = Tile{} + mi := &file_protobuf_vector_tile_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Tile) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Tile) ProtoMessage() {} + +func (x *Tile) ProtoReflect() protoreflect.Message { + mi := &file_protobuf_vector_tile_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Tile.ProtoReflect.Descriptor instead. +func (*Tile) Descriptor() ([]byte, []int) { + return file_protobuf_vector_tile_proto_rawDescGZIP(), []int{0} +} + +func (x *Tile) GetLayers() []*Tile_Layer { + if x != nil { + return x.Layers + } + return nil +} + +// Variant type encoding +// The use of values is described in section 4.1 of the specification +type Tile_Value struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Exactly one of these values must be present in a valid message + StringValue *string `protobuf:"bytes,1,opt,name=string_value,json=stringValue" json:"string_value,omitempty"` + FloatValue *float32 `protobuf:"fixed32,2,opt,name=float_value,json=floatValue" json:"float_value,omitempty"` + DoubleValue *float64 `protobuf:"fixed64,3,opt,name=double_value,json=doubleValue" json:"double_value,omitempty"` + IntValue *int64 `protobuf:"varint,4,opt,name=int_value,json=intValue" json:"int_value,omitempty"` + UintValue *uint64 `protobuf:"varint,5,opt,name=uint_value,json=uintValue" json:"uint_value,omitempty"` + SintValue *int64 `protobuf:"zigzag64,6,opt,name=sint_value,json=sintValue" json:"sint_value,omitempty"` + BoolValue *bool `protobuf:"varint,7,opt,name=bool_value,json=boolValue" json:"bool_value,omitempty"` + extensionFields protoimpl.ExtensionFields + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Tile_Value) Reset() { + *x = Tile_Value{} + mi := &file_protobuf_vector_tile_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Tile_Value) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Tile_Value) ProtoMessage() {} + +func (x *Tile_Value) ProtoReflect() protoreflect.Message { + mi := &file_protobuf_vector_tile_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Tile_Value.ProtoReflect.Descriptor instead. +func (*Tile_Value) Descriptor() ([]byte, []int) { + return file_protobuf_vector_tile_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *Tile_Value) GetStringValue() string { + if x != nil && x.StringValue != nil { + return *x.StringValue + } + return "" +} + +func (x *Tile_Value) GetFloatValue() float32 { + if x != nil && x.FloatValue != nil { + return *x.FloatValue + } + return 0 +} + +func (x *Tile_Value) GetDoubleValue() float64 { + if x != nil && x.DoubleValue != nil { + return *x.DoubleValue + } + return 0 +} + +func (x *Tile_Value) GetIntValue() int64 { + if x != nil && x.IntValue != nil { + return *x.IntValue + } + return 0 +} + +func (x *Tile_Value) GetUintValue() uint64 { + if x != nil && x.UintValue != nil { + return *x.UintValue + } + return 0 +} + +func (x *Tile_Value) GetSintValue() int64 { + if x != nil && x.SintValue != nil { + return *x.SintValue + } + return 0 +} + +func (x *Tile_Value) GetBoolValue() bool { + if x != nil && x.BoolValue != nil { + return *x.BoolValue + } + return false +} + +// Features are described in section 4.2 of the specification +type Tile_Feature struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id *uint64 `protobuf:"varint,1,opt,name=id,def=0" json:"id,omitempty"` + // Tags of this feature are encoded as repeated pairs of + // integers. + // A detailed description of tags is located in sections + // 4.2 and 4.4 of the specification + Tags []uint32 `protobuf:"varint,2,rep,packed,name=tags" json:"tags,omitempty"` + // The type of geometry stored in this feature. + Type *Tile_GeomType `protobuf:"varint,3,opt,name=type,enum=protobuf.Tile_GeomType,def=0" json:"type,omitempty"` + // Contains a stream of commands and parameters (vertices). + // A detailed description on geometry encoding is located in + // section 4.3 of the specification. + Geometry []uint32 `protobuf:"varint,4,rep,packed,name=geometry" json:"geometry,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +// Default values for Tile_Feature fields. +const ( + Default_Tile_Feature_Id = uint64(0) + Default_Tile_Feature_Type = Tile_UNKNOWN +) + +func (x *Tile_Feature) Reset() { + *x = Tile_Feature{} + mi := &file_protobuf_vector_tile_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Tile_Feature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Tile_Feature) ProtoMessage() {} + +func (x *Tile_Feature) ProtoReflect() protoreflect.Message { + mi := &file_protobuf_vector_tile_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Tile_Feature.ProtoReflect.Descriptor instead. +func (*Tile_Feature) Descriptor() ([]byte, []int) { + return file_protobuf_vector_tile_proto_rawDescGZIP(), []int{0, 1} +} + +func (x *Tile_Feature) GetId() uint64 { + if x != nil && x.Id != nil { + return *x.Id + } + return Default_Tile_Feature_Id +} + +func (x *Tile_Feature) GetTags() []uint32 { + if x != nil { + return x.Tags + } + return nil +} + +func (x *Tile_Feature) GetType() Tile_GeomType { + if x != nil && x.Type != nil { + return *x.Type + } + return Default_Tile_Feature_Type +} + +func (x *Tile_Feature) GetGeometry() []uint32 { + if x != nil { + return x.Geometry + } + return nil +} + +// Layers are described in section 4.1 of the specification +type Tile_Layer struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Any compliant implementation must first read the version + // number encoded in this message and choose the correct + // implementation for this version number before proceeding to + // decode other parts of this message. + Version *uint32 `protobuf:"varint,15,req,name=version,def=1" json:"version,omitempty"` + Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"` + // The actual features in this tile. + Features []*Tile_Feature `protobuf:"bytes,2,rep,name=features" json:"features,omitempty"` + // Dictionary encoding for keys + Keys []string `protobuf:"bytes,3,rep,name=keys" json:"keys,omitempty"` + // Dictionary encoding for values + Values []*Tile_Value `protobuf:"bytes,4,rep,name=values" json:"values,omitempty"` + // Although this is an "optional" field it is required by the specification. + // See https://github.com/mapbox/vector-tile-spec/issues/47 + Extent *uint32 `protobuf:"varint,5,opt,name=extent,def=4096" json:"extent,omitempty"` + extensionFields protoimpl.ExtensionFields + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +// Default values for Tile_Layer fields. +const ( + Default_Tile_Layer_Version = uint32(1) + Default_Tile_Layer_Extent = uint32(4096) +) + +func (x *Tile_Layer) Reset() { + *x = Tile_Layer{} + mi := &file_protobuf_vector_tile_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Tile_Layer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Tile_Layer) ProtoMessage() {} + +func (x *Tile_Layer) ProtoReflect() protoreflect.Message { + mi := &file_protobuf_vector_tile_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Tile_Layer.ProtoReflect.Descriptor instead. +func (*Tile_Layer) Descriptor() ([]byte, []int) { + return file_protobuf_vector_tile_proto_rawDescGZIP(), []int{0, 2} +} + +func (x *Tile_Layer) GetVersion() uint32 { + if x != nil && x.Version != nil { + return *x.Version + } + return Default_Tile_Layer_Version +} + +func (x *Tile_Layer) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +func (x *Tile_Layer) GetFeatures() []*Tile_Feature { + if x != nil { + return x.Features + } + return nil +} + +func (x *Tile_Layer) GetKeys() []string { + if x != nil { + return x.Keys + } + return nil +} + +func (x *Tile_Layer) GetValues() []*Tile_Value { + if x != nil { + return x.Values + } + return nil +} + +func (x *Tile_Layer) GetExtent() uint32 { + if x != nil && x.Extent != nil { + return *x.Extent + } + return Default_Tile_Layer_Extent +} + +var File_protobuf_vector_tile_proto protoreflect.FileDescriptor + +const file_protobuf_vector_tile_proto_rawDesc = "" + + "\n" + + "\x1aprotobuf/vector_tile.proto\x12\bprotobuf\"\xd7\x05\n" + + "\x04Tile\x12,\n" + + "\x06layers\x18\x03 \x03(\v2\x14.protobuf.Tile.LayerR\x06layers\x1a\xf2\x01\n" + + "\x05Value\x12!\n" + + "\fstring_value\x18\x01 \x01(\tR\vstringValue\x12\x1f\n" + + "\vfloat_value\x18\x02 \x01(\x02R\n" + + "floatValue\x12!\n" + + "\fdouble_value\x18\x03 \x01(\x01R\vdoubleValue\x12\x1b\n" + + "\tint_value\x18\x04 \x01(\x03R\bintValue\x12\x1d\n" + + "\n" + + "uint_value\x18\x05 \x01(\x04R\tuintValue\x12\x1d\n" + + "\n" + + "sint_value\x18\x06 \x01(\x12R\tsintValue\x12\x1d\n" + + "\n" + + "bool_value\x18\a \x01(\bR\tboolValue*\b\b\b\x10\x80\x80\x80\x80\x02\x1a\x8a\x01\n" + + "\aFeature\x12\x11\n" + + "\x02id\x18\x01 \x01(\x04:\x010R\x02id\x12\x16\n" + + "\x04tags\x18\x02 \x03(\rB\x02\x10\x01R\x04tags\x124\n" + + "\x04type\x18\x03 \x01(\x0e2\x17.protobuf.Tile.GeomType:\aUNKNOWNR\x04type\x12\x1e\n" + + "\bgeometry\x18\x04 \x03(\rB\x02\x10\x01R\bgeometry\x1a\xd6\x01\n" + + "\x05Layer\x12\x1b\n" + + "\aversion\x18\x0f \x02(\r:\x011R\aversion\x12\x12\n" + + "\x04name\x18\x01 \x02(\tR\x04name\x122\n" + + "\bfeatures\x18\x02 \x03(\v2\x16.protobuf.Tile.FeatureR\bfeatures\x12\x12\n" + + "\x04keys\x18\x03 \x03(\tR\x04keys\x12,\n" + + "\x06values\x18\x04 \x03(\v2\x14.protobuf.Tile.ValueR\x06values\x12\x1c\n" + + "\x06extent\x18\x05 \x01(\r:\x044096R\x06extent*\b\b\x10\x10\x80\x80\x80\x80\x02\"?\n" + + "\bGeomType\x12\v\n" + + "\aUNKNOWN\x10\x00\x12\t\n" + + "\x05POINT\x10\x01\x12\x0e\n" + + "\n" + + "LINESTRING\x10\x02\x12\v\n" + + "\aPOLYGON\x10\x03*\x05\b\x10\x10\x80@B0H\x03Z,git.zhouxhere.com/zhouxhere/maptile/protobuf" + +var ( + file_protobuf_vector_tile_proto_rawDescOnce sync.Once + file_protobuf_vector_tile_proto_rawDescData []byte +) + +func file_protobuf_vector_tile_proto_rawDescGZIP() []byte { + file_protobuf_vector_tile_proto_rawDescOnce.Do(func() { + file_protobuf_vector_tile_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_protobuf_vector_tile_proto_rawDesc), len(file_protobuf_vector_tile_proto_rawDesc))) + }) + return file_protobuf_vector_tile_proto_rawDescData +} + +var file_protobuf_vector_tile_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_protobuf_vector_tile_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_protobuf_vector_tile_proto_goTypes = []any{ + (Tile_GeomType)(0), // 0: protobuf.Tile.GeomType + (*Tile)(nil), // 1: protobuf.Tile + (*Tile_Value)(nil), // 2: protobuf.Tile.Value + (*Tile_Feature)(nil), // 3: protobuf.Tile.Feature + (*Tile_Layer)(nil), // 4: protobuf.Tile.Layer +} +var file_protobuf_vector_tile_proto_depIdxs = []int32{ + 4, // 0: protobuf.Tile.layers:type_name -> protobuf.Tile.Layer + 0, // 1: protobuf.Tile.Feature.type:type_name -> protobuf.Tile.GeomType + 3, // 2: protobuf.Tile.Layer.features:type_name -> protobuf.Tile.Feature + 2, // 3: protobuf.Tile.Layer.values:type_name -> protobuf.Tile.Value + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_protobuf_vector_tile_proto_init() } +func file_protobuf_vector_tile_proto_init() { + if File_protobuf_vector_tile_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_protobuf_vector_tile_proto_rawDesc), len(file_protobuf_vector_tile_proto_rawDesc)), + NumEnums: 1, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_protobuf_vector_tile_proto_goTypes, + DependencyIndexes: file_protobuf_vector_tile_proto_depIdxs, + EnumInfos: file_protobuf_vector_tile_proto_enumTypes, + MessageInfos: file_protobuf_vector_tile_proto_msgTypes, + }.Build() + File_protobuf_vector_tile_proto = out.File + file_protobuf_vector_tile_proto_goTypes = nil + file_protobuf_vector_tile_proto_depIdxs = nil +} diff --git a/protobuf/vector_tile.proto b/protobuf/vector_tile.proto new file mode 100644 index 0000000..828a0fc --- /dev/null +++ b/protobuf/vector_tile.proto @@ -0,0 +1,80 @@ +package protobuf; + +option optimize_for = LITE_RUNTIME; + +option go_package = "git.zhouxhere.com/zhouxhere/maptile/protobuf"; + +message Tile { + + // GeomType is described in section 4.3.4 of the specification + enum GeomType { + UNKNOWN = 0; + POINT = 1; + LINESTRING = 2; + POLYGON = 3; + } + + // Variant type encoding + // The use of values is described in section 4.1 of the specification + message Value { + // Exactly one of these values must be present in a valid message + optional string string_value = 1; + optional float float_value = 2; + optional double double_value = 3; + optional int64 int_value = 4; + optional uint64 uint_value = 5; + optional sint64 sint_value = 6; + optional bool bool_value = 7; + + extensions 8 to max; + } + + // Features are described in section 4.2 of the specification + message Feature { + optional uint64 id = 1 [ default = 0 ]; + + // Tags of this feature are encoded as repeated pairs of + // integers. + // A detailed description of tags is located in sections + // 4.2 and 4.4 of the specification + repeated uint32 tags = 2 [ packed = true ]; + + // The type of geometry stored in this feature. + optional GeomType type = 3 [ default = UNKNOWN ]; + + // Contains a stream of commands and parameters (vertices). + // A detailed description on geometry encoding is located in + // section 4.3 of the specification. + repeated uint32 geometry = 4 [ packed = true ]; + } + + // Layers are described in section 4.1 of the specification + message Layer { + // Any compliant implementation must first read the version + // number encoded in this message and choose the correct + // implementation for this version number before proceeding to + // decode other parts of this message. + required uint32 version = 15 [ default = 1 ]; + + required string name = 1; + + // The actual features in this tile. + repeated Feature features = 2; + + // Dictionary encoding for keys + repeated string keys = 3; + + // Dictionary encoding for values + repeated Value values = 4; + + // Although this is an "optional" field it is required by the specification. + // See https://github.com/mapbox/vector-tile-spec/issues/47 + optional uint32 extent = 5 [ default = 4096 ]; + + extensions 16 to max; + } + + repeated Layer layers = 3; + + extensions 16 to 8191; +} \ No newline at end of file diff --git a/store/store.go b/store/store.go index 003d053..23cedce 100755 --- a/store/store.go +++ b/store/store.go @@ -4,6 +4,7 @@ import ( "context" "embed" + "git.zhouxhere.com/zhouxhere/maptile/util/mbtiles" "github.com/golang-migrate/migrate/v4" _ "github.com/golang-migrate/migrate/v4/database/postgres" "github.com/golang-migrate/migrate/v4/source/iofs" @@ -15,8 +16,10 @@ import ( var sqlMigrations embed.FS type Store struct { - dsn string - DB *sqlx.DB + dsn string + DB *sqlx.DB + MBTiles map[string]*mbtiles.MBTiles + // pmtiles map[string]*PMTiles } func NewStore(dsn string) (*Store, error) { @@ -26,9 +29,16 @@ func NewStore(dsn string) (*Store, error) { return nil, err } + mbtiles, err := mbtiles.ReadMBTiles() + + if err != nil { + return nil, err + } + return &Store{ - dsn: dsn, - DB: db, + dsn: dsn, + DB: db, + MBTiles: mbtiles, }, nil } diff --git a/util/font/font.go b/util/font/font.go deleted file mode 100644 index 32f9fa1..0000000 --- a/util/font/font.go +++ /dev/null @@ -1 +0,0 @@ -package font diff --git a/util/fontnik/fontnik.go b/util/fontnik/fontnik.go new file mode 100644 index 0000000..0921de7 --- /dev/null +++ b/util/fontnik/fontnik.go @@ -0,0 +1,369 @@ +package fontnik + +import ( + "fmt" + "image" + "image/draw" + "math" + "os" + "path/filepath" + "strings" + + "git.zhouxhere.com/zhouxhere/maptile/protobuf" + "github.com/pkg/errors" + "golang.org/x/image/font" + "golang.org/x/image/font/opentype" + "golang.org/x/image/font/sfnt" + "golang.org/x/image/math/fixed" + "google.golang.org/protobuf/proto" +) + +const ( + DefaultBlockSize = 256 + DefaultFontSize = 24 + DefaultMaxCode = 0x9FFFF +) + +// var ( +// BasicLatinBlock = [2]rune{0x0000, 0x007F} +// CJKBlock = [2]rune{0x4E00, 0x9FFF} +// EmojiBlock = [2]rune{0x1F600, 0x1F64F} +// ) + +type FontFace struct { + Name string + Font font.Face + yStart int + maxCode int +} + +type Fontnik struct { + outPath string + FontFaces []*FontFace +} + +func NewFontnik(fontPath, outPath string) (*Fontnik, error) { + basePath, err := os.Executable() + if err != nil { + return nil, errors.Errorf("could not get execute path: %v", err) + } + defaultOutPath := "style/font" + if outPath == "" { + outPath = defaultOutPath + } + + if err := os.MkdirAll(filepath.Join(filepath.Dir(basePath), outPath), 0644); err != nil { + return nil, err + } + + files, err := os.ReadDir(fontPath) + if err != nil { + panic(err) + } + + fontFaces := []*FontFace{} + + for _, file := range files { + if file.IsDir() { + continue + } + filePath := filepath.Join(fontPath, file.Name()) + + if !strings.HasSuffix(file.Name(), ".ttf") && !strings.HasSuffix(file.Name(), ".otf") { + // return nil, errors.Errorf("font file not support %s", file.Name()) + continue + } + + fontBytes, err := os.ReadFile(filePath) + if err != nil { + // return nil, fmt.Errorf("读取字体文件失败: %v", err) + continue + } + + openFont, err := opentype.Parse(fontBytes) + if err != nil { + // return nil, fmt.Errorf("解析字体失败: %v", err) + continue + } + + fontFamily, err := openFont.Name(&sfnt.Buffer{}, sfnt.NameIDFamily) + if err != nil { + return nil, err + } + + fontSubFamily, err := openFont.Name(&sfnt.Buffer{}, sfnt.NameIDSubfamily) + if err != nil { + return nil, err + } + + fontFamily = fmt.Sprintf("%s %s", fontFamily, fontSubFamily) + + face, err := opentype.NewFace(openFont, &opentype.FaceOptions{ + Size: DefaultFontSize, + DPI: 72, + Hinting: font.HintingFull, + }) + if err != nil { + return nil, err + } + + metrics := face.Metrics() + // fontDesignedHeight := metrics.Ascent.Floor() + metrics.Descent.Floor() + // fixed := int(math.Round(float64(metrics.Height.Floor()-fontDesignedHeight)/2)) + 1 + + fontFaces = append(fontFaces, &FontFace{ + Name: fontFamily, + Font: face, + // yStart: metrics.Height.Floor() + metrics.Descent.Floor() + fixed, + yStart: metrics.Ascent.Floor(), + // maxCode: openFont.NumGlyphs(), + maxCode: DefaultMaxCode, + }) + } + + return &Fontnik{ + outPath: filepath.Join(filepath.Dir(basePath), outPath), + FontFaces: fontFaces, + }, nil +} + +func (f *Fontnik) ToPBF() { + + if _, err := os.Stat(f.outPath); err != nil { + err = os.MkdirAll(f.outPath, 0644) + if err != nil { + panic(err) + } + } + + for _, face := range f.FontFaces { + + if _, err := os.Stat(filepath.Join(f.outPath, face.Name)); err != nil { + err = os.MkdirAll(filepath.Join(f.outPath, face.Name), 0644) + if err != nil { + panic(err) + } + } + + fmt.Println("Font:", face.Name, "MaxCode:", face.maxCode) + + for start := 0; start <= int(face.maxCode); start += DefaultBlockSize { + + endCode := min(start+DefaultBlockSize-1, face.maxCode) + + stack, err := face.ProcessRange(start, endCode) + if err != nil { + return + } + + if err := f.SaveStack(stack); err != nil { + fmt.Println(err) + return + } + } + } +} + +func (f *Fontnik) SaveStack(stack *protobuf.Fontstack) error { + data, err := proto.Marshal(&protobuf.Glyphs{Stacks: []*protobuf.Fontstack{stack}}) + if err != nil { + return err + } + + filename := fmt.Sprintf("%s/%s/%s.pbf", + f.outPath, + *stack.Name, + *stack.Range, + ) + return os.WriteFile(filename, data, 0644) +} + +func (ff *FontFace) ProcessRange(start, end int) (*protobuf.Fontstack, error) { + fontRange := fmt.Sprintf("%d-%d", start, end) + stack := &protobuf.Fontstack{ + Name: &ff.Name, + Range: &fontRange, + Glyphs: []*protobuf.Glyph{}, + } + + for code := start; code <= end; code++ { + + glyph := ff.RenderGlyph(rune(code)) + + if glyph != nil { + stack.Glyphs = append(stack.Glyphs, glyph) + } + } + + return stack, nil +} + +func (ff *FontFace) RenderGlyph(code rune) *protobuf.Glyph { + + bounds, mask, maskp, advance, ok := ff.Font.Glyph(fixed.P(0, ff.yStart), code) + if !ok { + return nil + } + + size := bounds.Size() + + width := uint32(size.X) + height := uint32(size.Y) + + if width == 0 || height == 0 { + return nil + } + + buffer := int(3) + id := uint32(code) + + top := -int32(bounds.Min.Y) + left := int32(bounds.Min.X) + a := uint32(advance.Floor()) + + g := &protobuf.Glyph{ + Id: &id, + Width: &width, + Height: &height, + Left: &left, + Top: &top, + Advance: &a, + } + + w := int(*g.Width) + buffer*2 + h := int(*g.Height) + buffer*2 + + dst := image.NewRGBA(image.Rect(0, 0, w, h)) + draw.DrawMask(dst, dst.Bounds(), &image.Uniform{image.Black}, image.Point{}, mask, maskp.Sub(image.Pt(buffer, buffer)), draw.Over) + + g.Bitmap = CalcSDF(dst, 8, 0.25) + + return g +} + +const INF = 1e20 + +func CalcSDF(img image.Image, radius float64, cutoff float64) []uint8 { + size := img.Bounds().Size() + w, h := size.X, size.Y + + gridOuter := make([]float64, w*h) + gridInner := make([]float64, w*h) + + f := make([]float64, w*h) + d := make([]float64, w*h) + v := make([]float64, w*h) + z := make([]float64, w*h) + + for y := range h { + for x := range w { + i := x + y*w + _, _, _, a := img.At(x, y).RGBA() + + alpha := float64(a) / math.MaxUint16 + + outer := float64(0) + inner := INF + + if alpha != 1 { + if alpha == 0 { + outer = INF + inner = 0 + } else { + outer = math.Pow(math.Max(0, 0.5-alpha), 2) + inner = math.Pow(math.Max(0, alpha-0.5), 2) + } + } + + gridOuter[i] = outer + gridInner[i] = inner + } + } + + edt(gridOuter, w, h, f, d, v, z) + edt(gridInner, w, h, f, d, v, z) + + alphas := make([]uint8, w*h) + + for y := range h { + for x := range w { + i := x + y*w + d := gridOuter[i] - gridInner[i] + + a := math.Max(0, math.Min(255, math.Round(255-255*(d/radius+cutoff)))) + + alphas[i] = uint8(a) + } + } + + return alphas +} + +// 2D Euclidean distance transform by Felzenszwalb & Huttenlocher https://cs.brown.edu/~pff/papers/dt-final.pdf +func edt(data []float64, width int, height int, f []float64, d []float64, v []float64, z []float64) { + for x := range width { + for y := range height { + f[y] = data[y*width+x] + } + + edt1d(f, d, v, z, height) + + for y := range height { + data[y*width+x] = d[y] + } + } + + for y := range height { + for x := range width { + f[x] = data[y*width+x] + } + + edt1d(f, d, v, z, width) + + for x := range width { + data[y*width+x] = math.Sqrt(d[x]) + } + } +} + +// 1D squared distance transform +func edt1d(f []float64, d []float64, v []float64, z []float64, n int) { + v[0] = 0 + z[0] = -INF + z[1] = +INF + + for q, k := 1, 0; q < (n); q++ { + getS := func() float64 { + return ((f[q] + float64(q)*float64(q)) - (f[int(v[k])] + v[k]*v[k])) / (2*float64(q) - 2*v[k]) + } + + s := getS() + + for { + if s <= float64(z[k]) { + k-- + s = getS() + continue + } + break + } + + k++ + + v[k] = float64(q) + z[k] = float64(s) + z[k+1] = +INF + } + + for q, k := 0, 0; q < n; q++ { + for { + if z[k+1] < float64(q) { + k++ + continue + } + break + } + + d[q] = (float64(q)-v[k])*(float64(q)-v[k]) + f[int(v[k])] + } +} diff --git a/util/mbtiles/mbtiles.go b/util/mbtiles/mbtiles.go new file mode 100644 index 0000000..c58b8b0 --- /dev/null +++ b/util/mbtiles/mbtiles.go @@ -0,0 +1,264 @@ +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 +} diff --git a/util/sprite/sprite.go b/util/sprite/sprite.go index b8d607c..7d1ae78 100644 --- a/util/sprite/sprite.go +++ b/util/sprite/sprite.go @@ -1,11 +1,14 @@ package sprite import ( + "bytes" + "encoding/json" "fmt" "image" - "image/color" "image/png" + "math" "os" + "path/filepath" "strings" "github.com/pkg/errors" @@ -14,191 +17,434 @@ import ( "golang.org/x/image/draw" ) -type Direction int - -const ( - Horizontal = iota - Vertical -) - -func (d Direction) String() string { - switch d { - case Horizontal: - return "Horizontal" - case Vertical: - return "Vertical" - default: - return "Unknown" - } -} - -type Image struct { - Name string - Width, Height int - X, Y int -} - type Sprite struct { - x, y int - padding int - direction Direction - standard int - count int - Images []Image -} - -func NewSprite(padding int, direct int, count int, standard int) (*Sprite, error) { - - if Direction(direct).String() == "Unknown" { - return nil, errors.Errorf("direction %d is unknown", direct) - } - - if count <= 0 { - return nil, errors.Errorf("count %d need to max than 0", count) - } - - if standard <= 0 { - return nil, errors.Errorf("standard %d need to max than 0", standard) - } - - return &Sprite{ - x: 0, - y: 0, - padding: padding, - direction: Direction(direct), - count: count, - standard: standard, - Images: []Image{}, - }, nil -} - -func (s *Sprite) MergeImages(infos []ImageInfo) (image.Image, error) { - width, height := 0, 0 - max := 0 - spritesheet := image.NewRGBA(image.Rect(0, 0, 0, 0)) - for i, info := range infos { - img, err := info.ReadInfo() - if err != nil { - return nil, err - } - - x, y := 0, 0 - imgWidth, imgHeight := img.Bounds().Dx(), img.Bounds().Dy() - perCount := (i + 1) / s.count - nowCount := i % s.count - - if s.direction == Horizontal { - scale := float64(s.standard) / float64(imgHeight) - imgWidth = int(float64(imgWidth) * scale) - imgHeight = s.standard - - if perCount == 0 { - height = nowCount * (s.standard + s.padding*2) - } else { - height = s.count * (s.standard + s.padding*2) - } - - y = (s.standard+2*s.padding)*nowCount + s.padding - if nowCount == 0 { - x = width + s.padding - max = imgWidth - width = width + imgWidth + s.padding*2 - } else { - if imgWidth > max { - width = width + (imgWidth - max) - max = imgWidth - } - x = width - max - s.padding - } - } - - if s.direction == Vertical { - scale := float64(s.standard) / float64(imgWidth) - imgHeight = int(float64(imgHeight) * scale) - imgWidth = s.standard - - if perCount == 0 { - width = (nowCount + 1) * (s.standard + s.padding*2) - } else { - width = s.count * (s.standard + s.padding*2) - } - - x = (s.standard+2*s.padding)*nowCount + s.padding - if nowCount == 0 { - y = height + s.padding - max = imgHeight - height = height + imgHeight + s.padding*2 - } else { - if imgHeight > max { - height = height + (imgHeight - max) - max = imgHeight - } - y = height - max - s.padding - } - } - - newspritesheet := image.NewRGBA(image.Rect(0, 0, width, height)) - - if spritesheet.Bounds().Dx() > 0 && spritesheet.Bounds().Dy() > 0 { - draw.Draw(newspritesheet, spritesheet.Bounds(), spritesheet, image.Point{}, draw.Src) - } - - draw.ApproxBiLinear.Scale(newspritesheet, image.Rect(x, y, x+imgWidth, y+imgHeight), img, img.Bounds(), draw.Src, nil) - spritesheet = newspritesheet - - s.Images = append(s.Images, Image{ - Name: info.Name, - Width: imgWidth, - Height: imgHeight, - X: x, - Y: y, - }) - fmt.Println(info.Name, imgWidth, imgHeight, x, y) - } - return spritesheet, nil + name string + infos []*ImageInfo + Root *SpriteNode } type ImageInfo struct { - Name, Path string + Name string + Path, Type string + Width, Height float64 + // image image.Image + Fit *SpriteNode } -func (i *ImageInfo) ReadInfo() (image.Image, error) { - if strings.HasSuffix(i.Path, ".png") { - pngFile, err := os.Open(i.Path) - if err != nil { - return nil, err - } - defer pngFile.Close() +type SpriteBuilder struct { + outPath string + sprites []Sprite +} - pngImg, err := png.Decode(pngFile) - if err != nil { - return nil, err - } +type SpriteNode struct { + X float64 + Y float64 + Width float64 + Height float64 + Used bool + Down *SpriteNode + Right *SpriteNode +} - return pngImg, nil +func NewSpriteBuilder(iconPath string, outPath string) (*SpriteBuilder, error) { + + basePath, err := os.Executable() + if err != nil { + return nil, errors.Errorf("could not get execute path: %v", err) + } + defaultOutPath := "style/sprite" + if outPath == "" { + outPath = defaultOutPath } - if strings.HasSuffix(i.Path, ".svg") { - svgFile, err := os.Open(i.Path) - if err != nil { - return nil, err - } - defer svgFile.Close() + if err := os.MkdirAll(filepath.Join(filepath.Dir(basePath), outPath), 0644); err != nil { + return nil, err + } - svgInfo, err := oksvg.ReadIconStream(svgFile, oksvg.WarnErrorMode) - if err != nil { - return nil, err + files, err := os.ReadDir(iconPath) + if err != nil { + panic(err) + } + + sb := &SpriteBuilder{ + outPath: filepath.Join(filepath.Dir(basePath), outPath), + sprites: []Sprite{}, + } + + for _, file := range files { + if !file.IsDir() { + continue } - svgImg := image.NewRGBA(image.Rect(int(svgInfo.ViewBox.X), int(svgInfo.ViewBox.Y), int(svgInfo.ViewBox.W), int(svgInfo.ViewBox.H))) - draw.Draw(svgImg, svgImg.Bounds(), &image.Uniform{color.Transparent}, image.Point{}, draw.Src) - svgInfo.SetTarget(0, 0, float64(svgInfo.ViewBox.W), float64(svgInfo.ViewBox.H)) - scaner := rasterx.NewScannerGV(int(svgInfo.ViewBox.W), int(svgInfo.ViewBox.H), svgImg, svgImg.Bounds()) - raster := rasterx.NewDasher(int(svgInfo.ViewBox.W), int(svgInfo.ViewBox.H), scaner) + subFiles, err := os.ReadDir(filepath.Join(iconPath, file.Name())) + if err != nil { + continue + } + + imageInfos := []*ImageInfo{} + for _, subFile := range subFiles { + if subFile.IsDir() { + continue + } + + filePath := filepath.Join(iconPath, file.Name(), subFile.Name()) + fileName := filepath.Base(filePath) + fileType := filepath.Ext(filePath) + + if fileType != ".svg" && fileType != ".png" { + continue + } + + imageInfo := ImageInfo{ + Name: strings.TrimSuffix(fileName, fileType), + Type: fileType, + Path: filePath, + } + imageInfos = append(imageInfos, &imageInfo) + } + + sb.sprites = append(sb.sprites, Sprite{ + name: file.Name(), + infos: imageInfos, + Root: nil, + }) + } + + return sb, nil +} + +func (sb *SpriteBuilder) Build() error { + for _, sprite := range sb.sprites { + + if _, err := os.Stat(filepath.Join(sb.outPath, sprite.name)); err != nil { + err = os.MkdirAll(filepath.Join(sb.outPath, sprite.name), 0644) + if err != nil { + return err + } + } + + sprite.Fit() + + if err := sprite.Save(sb.outPath, 1); err != nil { + return err + } + + if err := sprite.Save(sb.outPath, 2); err != nil { + return err + } + } + return nil +} + +func (s *Sprite) Fit() { + if len(s.infos) == 0 { + return + } + + // 初始化根节点,使用第一个块的尺寸 + firstBlock := s.infos[0] + firstBlock.ReadInfo() + s.Root = &SpriteNode{ + X: 0, + Y: 0, + Width: firstBlock.Width, + Height: firstBlock.Height, + } + + for _, info := range s.infos { + info.ReadInfo() + node := s.findNode(s.Root, info.Width, info.Height) + if node != nil { + // 找到合适的位置,分割节点 + fit := s.splitNode(node, info.Width, info.Height) + info.Fit = fit + } else { + // 没有找到合适位置,扩展容器 + fit := s.growNode(info.Width, info.Height) + info.Fit = fit + } + } +} + +// findNode 查找可以容纳指定尺寸的节点 +func (s *Sprite) findNode(root *SpriteNode, width, height float64) *SpriteNode { + if root.Used { + // 如果当前节点已使用,递归查找子节点 + if right := s.findNode(root.Right, width, height); right != nil { + return right + } + if down := s.findNode(root.Down, width, height); down != nil { + return down + } + return nil + } else if width <= root.Width && height <= root.Height { + // 当前节点可以容纳该块 + return root + } + return nil +} + +// splitNode 分割节点,创建新的子节点 +func (s *Sprite) splitNode(node *SpriteNode, width, height float64) *SpriteNode { + node.Used = true + node.Down = &SpriteNode{ + X: node.X, + Y: node.Y + height, + Width: node.Width, + Height: node.Height - height, + } + node.Right = &SpriteNode{ + X: node.X + width, + Y: node.Y, + Width: node.Width - width, + Height: height, + } + return node +} + +// growNode 扩展容器 +func (s *Sprite) growNode(width, height float64) *SpriteNode { + canGrowDown := width <= s.Root.Width + canGrowRight := height <= s.Root.Height + + shouldGrowRight := canGrowRight && (s.Root.Height >= (s.Root.Width + width)) // 保持方形,优先向右扩展 + shouldGrowDown := canGrowDown && (s.Root.Width >= (s.Root.Height + height)) // 保持方形,优先向下扩展 + + if shouldGrowRight { + return s.growRight(width, height) + } else if shouldGrowDown { + return s.growDown(width, height) + } else if canGrowRight { + return s.growRight(width, height) + } else if canGrowDown { + return s.growDown(width, height) + } + return nil // 应该确保初始根节点尺寸合理以避免这种情况 +} + +// growRight 向右扩展容器 +func (s *Sprite) growRight(width, height float64) *SpriteNode { + newRoot := &SpriteNode{ + Used: true, + X: 0, + Y: 0, + Width: s.Root.Width + width, + Height: s.Root.Height, + Down: s.Root, + Right: &SpriteNode{ + X: s.Root.Width, + Y: 0, + Width: width, + Height: s.Root.Height, + }, + } + s.Root = newRoot + if node := s.findNode(s.Root, width, height); node != nil { + return s.splitNode(node, width, height) + } + return nil +} + +// growDown 向下扩展容器 +func (s *Sprite) growDown(width, height float64) *SpriteNode { + newRoot := &SpriteNode{ + Used: true, + X: 0, + Y: 0, + Width: s.Root.Width, + Height: s.Root.Height + height, + Down: &SpriteNode{ + X: 0, + Y: s.Root.Height, + Width: s.Root.Width, + Height: height, + }, + Right: s.Root, + } + s.Root = newRoot + if node := s.findNode(s.Root, width, height); node != nil { + return s.splitNode(node, width, height) + } + return nil +} + +func (s *Sprite) Save(outPath string, pixelRatio int) error { + img := image.NewRGBA(image.Rect(0, 0, int(s.Root.Width)*pixelRatio, int(s.Root.Height)*pixelRatio)) + for _, info := range s.infos { + // fmt.Println(info.Name, info.Fit.X, info.Fit.Y, info.Fit.Width, info.Fit.Height) + // draw.Draw(img, info.image.Bounds().Add(image.Pt(int(info.Fit.X), int(info.Fit.Y))), info.image, image.Point{}, draw.Over) + + infoImg := info.ReadImg(pixelRatio) + + if infoImg == nil { + continue + } + draw.Draw(img, infoImg.Bounds().Add(image.Pt(int(info.Fit.X)*pixelRatio, int(info.Fit.Y)*pixelRatio)), infoImg, image.Point{}, draw.Over) + + // if pixelRatio != 1 { + // minX := info.image.Bounds().Min.X + // minY := info.image.Bounds().Min.Y + // maxX := info.image.Bounds().Max.X + // maxY := info.image.Bounds().Max.Y + // imgXRect := image.Rect(minX, minY, minX+(maxX-minX)*pixelRatio, minY+(maxY-minY)*pixelRatio) + // draw.ApproxBiLinear.Scale(img, imgXRect.Add(image.Pt(int(info.Fit.X)*pixelRatio, int(info.Fit.Y)*pixelRatio)), info.image, info.image.Bounds(), draw.Src, nil) + // } else { + // draw.Draw(img, info.image.Bounds().Add(image.Pt(int(info.Fit.X), int(info.Fit.Y))), info.image, image.Point{}, draw.Over) + // } + + } + + fileName := s.name + + if pixelRatio != 1 { + fileName = fmt.Sprintf("%s@%dx", s.name, pixelRatio) + } + + pngFilePath := filepath.Join(outPath, s.name, fileName+".png") + + var pngBuffer bytes.Buffer + err := png.Encode(&pngBuffer, img) + if err != nil { + return err + } + + err = os.WriteFile(pngFilePath, pngBuffer.Bytes(), 0644) + if err != nil { + return err + } + + jsonFilePath := filepath.Join(outPath, s.name, fileName+".json") + + jsonMap := make(map[string]map[string]interface{}) + for _, img := range s.infos { + jsonMap[img.Name] = map[string]interface{}{ + "width": img.Width * float64(pixelRatio), + "height": img.Height * float64(pixelRatio), + "pixelRatio": pixelRatio, + "x": img.Fit.X * float64(pixelRatio), + "y": img.Fit.Y * float64(pixelRatio), + } + } + + jsonBuffer, err := json.MarshalIndent(jsonMap, "", " ") + if err != nil { + return err + } + + err = os.WriteFile(jsonFilePath, jsonBuffer, 0644) + if err != nil { + return err + } + + return nil +} + +func (i *ImageInfo) ReadInfo() { + file, err := os.Open(i.Path) + if err != nil { + return + } + defer file.Close() + + if i.Type == ".png" { + pngImg, err := png.Decode(file) + if err != nil { + return + } + + width := pngImg.Bounds().Dx() + height := pngImg.Bounds().Dy() + + i.Width = float64(width) + i.Height = float64(height) + + // pngImgx := image.NewRGBA(image.Rect(0, 0, width, height)) + // draw.ApproxBiLinear.Scale(pngImgx, pngImgx.Bounds(), pngImg, pngImg.Bounds(), draw.Src, nil) + + // i.image = pngImgx + + return + } + + if i.Type == ".svg" { + svgInfo, err := oksvg.ReadIconStream(file, oksvg.WarnErrorMode) + if err != nil { + return + } + + // baseScale := 8.0 + + baseWidth := math.Ceil(svgInfo.ViewBox.W) + baseHeight := math.Ceil(svgInfo.ViewBox.H) + + // svgImg := image.NewRGBA(image.Rect(0, 0, int(baseWidth*baseScale), int(baseHeight*baseScale))) + // draw.Draw(svgImg, svgImg.Bounds(), image.Transparent, image.Point{}, draw.Src) + + // svgInfo.SetTarget(-svgInfo.ViewBox.X*baseScale, -svgInfo.ViewBox.Y*baseScale, svgInfo.ViewBox.W*baseScale, svgInfo.ViewBox.H*baseScale) + + // scaner := rasterx.NewScannerGV(int(baseWidth*baseScale), int(baseHeight*baseScale), svgImg, svgImg.Bounds()) + // raster := rasterx.NewDasher(int(baseWidth*baseScale), int(baseHeight*baseScale), scaner) + // svgInfo.Draw(raster, 1.0) + + i.Width = baseWidth + i.Height = baseHeight + + // svgImgx := image.NewRGBA(image.Rect(0, 0, int(i.Width), int(i.Height))) + // draw.ApproxBiLinear.Scale(svgImgx, svgImgx.Bounds(), svgImg, svgImg.Bounds(), draw.Src, nil) + + // i.image = svgImgx + return + + } +} + +func (i *ImageInfo) ReadImg(pixelRatio int) image.Image { + file, err := os.Open(i.Path) + if err != nil { + return nil + } + defer file.Close() + + if i.Type == ".png" { + pngImg, err := png.Decode(file) + if err != nil { + return nil + } + + width := pngImg.Bounds().Dx() * pixelRatio + height := pngImg.Bounds().Dy() * pixelRatio + + pngImgx := image.NewRGBA(image.Rect(0, 0, width, height)) + draw.ApproxBiLinear.Scale(pngImgx, pngImgx.Bounds(), pngImg, pngImg.Bounds(), draw.Src, nil) + + return pngImgx + } + + if i.Type == ".svg" { + svgInfo, err := oksvg.ReadIconStream(file, oksvg.WarnErrorMode) + if err != nil { + return nil + } + + baseScale := 8.0 + + baseWidth := math.Ceil(svgInfo.ViewBox.W) + baseHeight := math.Ceil(svgInfo.ViewBox.H) + + svgImg := image.NewRGBA(image.Rect(0, 0, int(baseWidth*baseScale), int(baseHeight*baseScale))) + draw.Draw(svgImg, svgImg.Bounds(), image.Transparent, image.Point{}, draw.Src) + + svgInfo.SetTarget(-svgInfo.ViewBox.X*baseScale, -svgInfo.ViewBox.Y*baseScale, svgInfo.ViewBox.W*baseScale, svgInfo.ViewBox.H*baseScale) + + scaner := rasterx.NewScannerGV(int(baseWidth*baseScale), int(baseHeight*baseScale), svgImg, svgImg.Bounds()) + raster := rasterx.NewDasher(int(baseWidth*baseScale), int(baseHeight*baseScale), scaner) svgInfo.Draw(raster, 1.0) - return svgImg, nil + svgImgx := image.NewRGBA(image.Rect(0, 0, int(baseWidth)*pixelRatio, int(baseHeight)*pixelRatio)) + draw.ApproxBiLinear.Scale(svgImgx, svgImgx.Bounds(), svgImg, svgImg.Bounds(), draw.Src, nil) + + return svgImgx } - return nil, nil + return nil } diff --git a/web/apis/feature.ts b/web/apis/feature.ts index e54473a..626ed19 100644 --- a/web/apis/feature.ts +++ b/web/apis/feature.ts @@ -11,7 +11,7 @@ export interface FeatureCreateParams {} export interface FeatureUpdateParams {} -export const list = async (params: FeatureListParams) => { +export const list = (params: FeatureListParams) => { return useHttp(base + "s", { method: "GET", params }); }; diff --git a/web/assets/css/main.css b/web/assets/css/main.css index 230ff7e..c4eec6c 100644 --- a/web/assets/css/main.css +++ b/web/assets/css/main.css @@ -1,3 +1,6 @@ +@import "tailwindcss"; +@plugin "daisyui"; + html, body, .main { diff --git a/web/components/maplibre/map.vue b/web/components/maplibre/map.vue index 9251d9c..e978f08 100644 --- a/web/components/maplibre/map.vue +++ b/web/components/maplibre/map.vue @@ -30,7 +30,7 @@ onMounted(async () => { container: mapContainer.value, // container id style: "https://demotiles.maplibre.org/style.json", // style URL center: [120.147376, 30.272934], // starting position [lng, lat] - zoom: 8, // starting zoom + zoom: 7, // starting zoom }); map.value.removeControl(map.value._controls[0]); const draw = new MapboxDraw({ @@ -58,47 +58,120 @@ onMounted(async () => { console.log(e); }); - const { data, status } = await feature.list({ page: 1, size: 10 }); - if (status.value !== "success") return; + map.value.on("load", () => { + handleData(); + }); +}); + +onUnmounted(() => { + if (map.value) map.value.remove(); +}); + +const featureResult = await feature.list({ page: 1, size: 10 }); + +const handleData = () => { + if (featureResult.status.value !== "success") return; let jsonData = turf.featureCollection( - data.value.data.map((x) => + featureResult.data.value.data.map((x) => turf.feature(x.geometry, { id: x.id, name: x.name, }) ) ); - console.log(jsonData); map.value.addSource("data", { type: "geojson", data: jsonData }); + // map.value.addLayer({ + // id: "data-symbol", + // type: "symbol", + // source: "data", + // paint: { + // "text-color": "#000", + // }, + // layout: { + // "text-field": ["get", "name"], + // "text-font": ["Open Sans Regular", "Arial Unicode MS Regular"], + // "text-size": 12, + // }, + // }); + // map.value.addLayer({ + // id: "data-fill", + // type: "fill", + // source: "data", + // paint: { + // "fill-color": "#000", + // "fill-opacity": 0.5, + // "fill-outline-color": "#FF0000", // 添加边界线颜色 + // }, + // }); + + map.value.addSource("world_cities", { + type: "vector", + // scheme: "xyz", + url: "/api/v1/mbtiles/world_cities", + // url: 'http://localhost:3000/world_cities', + // bounds: [-123.12359, -37.818085, 174.763027, 59.352706], + // maxzoom: 6, + // minzoom: 0, + // tiles: ["http://localhost:3001/api/v1/mbtiles/world_cities/{z}/{x}/{y}"], + }); map.value.addLayer({ - id: "data-symbol", + id: "world_cities", type: "symbol", - source: "data", - paint: { - "text-color": "#000", - }, + minzoom: 0, + maxzoom: 6, + source: "world_cities", + "source-layer": "cities", layout: { - "text-field": ["get", "name"], - "text-font": ["Open Sans Regular", "Arial Unicode MS Regular"], + "text-field": "{name}", + // "text-font": ["Open Sans Regular", "Arial Unicode MS Regular"], "text-size": 12, }, - }); - map.value.addLayer({ - id: "data-fill", - type: "fill", - source: "data", paint: { - "fill-color": "#000", - "fill-opacity": 0.5, - "fill-outline-color": "#FF0000", // 添加边界线颜色 + "text-color": "rgb(255,0,0)", }, }); -}); -onUnmounted(() => { - if (map.value) map.value.remove(); -}) + map.value.addSource("china-shortbread", { + type: "vector", + // url: "/api/v1/mbtiles/china-shortbread", + bounds: [73.41788, 14.27437, 134.8036, 53.65559], + maxzoom: 14, + minzoom: 0, + tiles: [ + "http://localhost:8888/api/v1/mbtiles/china-shortbread/{z}/{x}/{y}", + ], + }); + + // map.value.addLayer({ + // id: "china-shortbread", + // type: "symbol", + // bounds: [73.41788, 14.27437, 134.8036, 53.65559], + // maxzoom: 14, + // minzoom: 0, + // source: "china-shortbread", + // "source-layer": "place_labels", + // layout: { + // "text-field": "hello", + // // "text-font": ["Open Sans Regular", "Arial Unicode MS Regular"], + // "text-size": 12, + // }, + // paint: { + // "text-color": "#000", + // }, + // }); + + setTimeout(() => { + let source = map.value.getSource("world_cities"); + console.log(source); + + let chinashortbread = map.value.getSource("china-shortbread"); + console.log(chinashortbread); + + let maplibresource = map.value.getSource("maplibre"); + console.log(maplibresource); + }, 1000); +};