Skip to content

Commit 6860ba6

Browse files
committed
feat(server): add tiles chiitiler handler
1 parent 45348ec commit 6860ba6

23 files changed

+29358
-35
lines changed

Diff for: server/config.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ type Config struct {
9494
CityGML_PackerTimeout uint `pp:",omitempty"`
9595
Flow_BaseURL string `pp:",omitempty"`
9696
Flow_Token string `pp:",omitempty"`
97+
CHIITILER_URL string `pp:",omitempty"`
9798
}
9899

99100
func NewConfig() (*Config, error) {
@@ -217,7 +218,9 @@ func (c *Config) DataCatalog() datacatalog.Config {
217218

218219
func (c *Config) Tiles() tiles.Config {
219220
return tiles.Config{
220-
CMS: c.plateauCMS(),
221+
CMS: c.plateauCMS(),
222+
Host: c.Host + "/tiles",
223+
ChiitilerURL: c.CHIITILER_URL,
221224
}
222225
}
223226

Diff for: server/service.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ func Tiles(conf *Config) (*Service, error) {
187187
return err
188188
}
189189

190-
h.Route(g.Group(""))
190+
h.Route(g.Group("/tiles"))
191191
h.Init(context.Background())
192192
return nil
193193
},

Diff for: server/tiles/chiitiler.go

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package tiles
2+
3+
import (
4+
_ "embed"
5+
"fmt"
6+
"net/http"
7+
8+
"github.com/labstack/echo/v4"
9+
"github.com/reearth/reearthx/log"
10+
)
11+
12+
// to generate style JSON files, see /tools/tile-style-json
13+
14+
//go:embed darkStyle.json
15+
var DarkStyle []byte
16+
17+
//go:embed lightStyle.json
18+
var LightStyle []byte
19+
20+
var styles = map[string][]byte{
21+
"dark-map": DarkStyle,
22+
"light-map": LightStyle,
23+
}
24+
25+
// chiitilerHandler handles requests for chiitiler with style
26+
func (h *Handler) chiitilerHandler(c echo.Context) error {
27+
if h.host == nil || h.chiitilerURL == nil {
28+
return c.JSON(http.StatusNotFound, map[string]string{"error": "not found"})
29+
}
30+
31+
ctx := c.Request().Context()
32+
style := c.Param("id")
33+
_, ok := styles[style]
34+
if !ok {
35+
return c.JSON(http.StatusNotFound, map[string]string{"error": "not found"})
36+
}
37+
38+
z := c.Param("z")
39+
x := c.Param("x")
40+
y := c.Param("y")
41+
42+
styleURL := *h.host
43+
styleURL.Path = fmt.Sprintf("/styles/%s.json", style)
44+
u := *h.chiitilerURL
45+
u.Path = fmt.Sprintf("/tiles/%s/%s/%s", z, x, y)
46+
q := u.Query()
47+
q.Add("url", styleURL.String())
48+
u.RawQuery = q.Encode()
49+
us := u.String()
50+
51+
log.Debugfc(ctx, "tiles: chiitiler: %s", us)
52+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, us, nil)
53+
if err != nil {
54+
log.Errorfc(ctx, "tiles: failed to create request: %v", err)
55+
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "internal server error"})
56+
}
57+
58+
resp, err := h.http.Do(req)
59+
if err != nil {
60+
log.Errorfc(ctx, "tiles: failed to request chiitiler: %v", err)
61+
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "internal server error"})
62+
}
63+
64+
defer resp.Body.Close()
65+
return c.Stream(resp.StatusCode, resp.Header.Get("Content-Type"), resp.Body)
66+
}
67+
68+
func styleHandler(c echo.Context) error {
69+
style := c.Param("id")
70+
s, ok := styles[style]
71+
if !ok {
72+
return c.JSON(http.StatusNotFound, map[string]string{"error": "not found"})
73+
}
74+
75+
return c.Blob(http.StatusOK, "application/json", s)
76+
}

Diff for: server/tiles/darkStyle.json

+1
Large diffs are not rendered by default.

Diff for: server/tiles/handler.go

+37-10
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,18 @@ const modelKey = "tiles"
2121
type Config struct {
2222
CMS plateaucms.Config
2323
CacheControl string
24+
Host string
25+
ChiitilerURL string
2426
}
2527

2628
type Handler struct {
27-
pcms *plateaucms.CMS
28-
http *http.Client
29-
lock sync.RWMutex
30-
tiles Tiles
31-
conf Config
29+
pcms *plateaucms.CMS
30+
http *http.Client
31+
lock sync.RWMutex
32+
host *url.URL
33+
chiitilerURL *url.URL
34+
tiles Tiles
35+
conf Config
3236
}
3337

3438
func New(conf Config) (*Handler, error) {
@@ -37,9 +41,27 @@ func New(conf Config) (*Handler, error) {
3741
return nil, fmt.Errorf("failed to create plateau cms: %w", err)
3842
}
3943

44+
var host, chiitilerURL *url.URL
45+
46+
if conf.Host != "" {
47+
host, err = url.Parse(conf.Host)
48+
if err != nil {
49+
return nil, fmt.Errorf("failed to parse host: %w", err)
50+
}
51+
}
52+
53+
if conf.ChiitilerURL != "" {
54+
chiitilerURL, err = url.Parse(conf.ChiitilerURL)
55+
if err != nil {
56+
return nil, fmt.Errorf("failed to parse chiitiler url: %w", err)
57+
}
58+
}
59+
4060
return &Handler{
41-
pcms: pcms,
42-
conf: conf,
61+
pcms: pcms,
62+
conf: conf,
63+
host: host,
64+
chiitilerURL: chiitilerURL,
4365
http: &http.Client{
4466
Timeout: 10 * time.Second,
4567
},
@@ -66,8 +88,9 @@ func (h *Handler) Init(ctx context.Context) {
6688
}
6789

6890
func (h *Handler) Route(g *echo.Group) {
69-
g.GET("/tiles/:id/:z/:x/:y", h.GetTile)
70-
g.POST("/tiles/update", h.UpdateCache)
91+
g.GET("/:id/:z/:x/:y", h.GetTile)
92+
g.GET("/styles/:id.json", styleHandler)
93+
g.POST("/update", h.UpdateCache)
7194
}
7295

7396
func (h *Handler) UpdateCache(c echo.Context) error {
@@ -77,8 +100,12 @@ func (h *Handler) UpdateCache(c echo.Context) error {
77100
}
78101

79102
func (h *Handler) GetTile(c echo.Context) error {
80-
ctx := c.Request().Context()
81103
id := c.Param("id")
104+
if _, ok := styles[id]; ok {
105+
return h.chiitilerHandler(c)
106+
}
107+
108+
ctx := c.Request().Context()
82109
z := c.Param("z")
83110
x := c.Param("x")
84111
y := c.Param("y")

Diff for: server/tiles/handler_test.go

+45-23
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package tiles
33
import (
44
"net/http"
55
"net/http/httptest"
6+
"net/url"
67
"testing"
78

89
"github.com/jarcoal/httpmock"
@@ -15,7 +16,12 @@ func TestHanlder_GetTile(t *testing.T) {
1516
httpmock.Activate()
1617
defer httpmock.DeactivateAndReset()
1718

18-
httpmock.RegisterResponder("GET", "https://example.com/1/2/3.png", httpmock.NewStringResponder(200, "image"))
19+
httpmock.RegisterResponder("GET", "https://example.com/1/2/3.png",
20+
httpmock.NewStringResponder(200, "image"))
21+
httpmock.RegisterResponderWithQuery(
22+
"GET", "https://ciitiler.example.com/tiles/1/2/3.png",
23+
map[string]string{"url": "https://example.com/styles/light-map.json"},
24+
httpmock.NewStringResponder(200, "ciitiler"))
1925

2026
h := &Handler{
2127
tiles: Tiles{
@@ -26,28 +32,44 @@ func TestHanlder_GetTile(t *testing.T) {
2632
},
2733
},
2834
},
29-
http: http.DefaultClient,
35+
host: lo.Must(url.Parse("https://example.com")),
36+
chiitilerURL: lo.Must(url.Parse("https://ciitiler.example.com")),
37+
http: http.DefaultClient,
3038
}
3139

32-
// 200
33-
req := httptest.NewRequest("GET", "/test/1/2/3.png", nil)
34-
w := httptest.NewRecorder()
35-
c := echo.New().NewContext(req, w)
36-
c.SetParamNames("id", "z", "x", "y")
37-
c.SetParamValues("test", "1", "2", "3.png")
38-
39-
assert.NoError(t, h.GetTile(c))
40-
assert.Equal(t, 200, w.Code)
41-
assert.Equal(t, "image", w.Body.String())
42-
43-
// 404
44-
req = httptest.NewRequest("GET", "/test2/1/2/3.png", nil)
45-
w = httptest.NewRecorder()
46-
c = echo.New().NewContext(req, w)
47-
c.SetParamNames("id", "z", "x", "y")
48-
c.SetParamValues("test2", "1", "2", "3.png")
49-
50-
assert.NoError(t, h.GetTile(c))
51-
assert.Equal(t, 404, w.Code)
52-
assert.Equal(t, `{"error":"not found"}`+"\n", w.Body.String())
40+
t.Run("200", func(t *testing.T) {
41+
req := httptest.NewRequest("GET", "/test/1/2/3.png", nil)
42+
w := httptest.NewRecorder()
43+
c := echo.New().NewContext(req, w)
44+
c.SetParamNames("id", "z", "x", "y")
45+
c.SetParamValues("test", "1", "2", "3.png")
46+
47+
assert.NoError(t, h.GetTile(c))
48+
assert.Equal(t, 200, w.Code)
49+
assert.Equal(t, "image", w.Body.String())
50+
})
51+
52+
t.Run("200 ciitiler", func(t *testing.T) {
53+
req := httptest.NewRequest("GET", "/light-map/1/2/3.png", nil)
54+
w := httptest.NewRecorder()
55+
c := echo.New().NewContext(req, w)
56+
c.SetParamNames("id", "z", "x", "y")
57+
c.SetParamValues("light-map", "1", "2", "3.png")
58+
59+
assert.NoError(t, h.GetTile(c))
60+
assert.Equal(t, 200, w.Code)
61+
assert.Equal(t, "ciitiler", w.Body.String())
62+
})
63+
64+
t.Run("404", func(t *testing.T) {
65+
req := httptest.NewRequest("GET", "/test2/1/2/3.png", nil)
66+
w := httptest.NewRecorder()
67+
c := echo.New().NewContext(req, w)
68+
c.SetParamNames("id", "z", "x", "y")
69+
c.SetParamValues("test2", "1", "2", "3.png")
70+
71+
assert.NoError(t, h.GetTile(c))
72+
assert.Equal(t, 404, w.Code)
73+
assert.Equal(t, `{"error":"not found"}`+"\n", w.Body.String())
74+
})
5375
}

Diff for: server/tiles/lightStyle.json

+1
Large diffs are not rendered by default.

Diff for: tools/tile-style-json/.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/*.json
2+
!package.json
3+
!package-lock.json

Diff for: tools/tile-style-json/README.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# tile-style-json
2+
3+
A small Node.js script to write Mapbox style JSON for tiles.
4+
5+
```sh
6+
npm i
7+
npm start
8+
# you will get lightStyle.json and darkStyle.json
9+
```

Diff for: tools/tile-style-json/index.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import * as styles from "./vector-map-style/styles";
2+
import { writeFileSync } from "fs";
3+
4+
Object.keys(styles).forEach((key) => {
5+
const style = styles[key];
6+
writeFileSync(`${key}.json`, JSON.stringify(style));
7+
console.log(`Wrote ${key}.json`);
8+
});

0 commit comments

Comments
 (0)