Skip to content

Commit 4eff49e

Browse files
broot5drinckes
andauthored
Add Support for Mapbox Vector Tiles in the grid server (#680)
* Replace deprecated package go.geojson with orb Replace deprecated package io/ioutil with os * Add support for Mapbox Vector Tile in tile_server * Add tests for MVT tile generation in gridserver * Update tile_server README to include Mapbox Vector Tile support Format tile_server README * Specify Mapbox Vector Tile version and update test data * Updated README to explicitly state Mapbox Vector Tile version as 2.1. * Modified MVT layer version to 2. * Updated test data files to match the new layer version. --------- Co-authored-by: Doug Rinckes <[email protected]>
1 parent f12fed5 commit 4eff49e

File tree

13 files changed

+136
-24
lines changed

13 files changed

+136
-24
lines changed

tile_server/README.md

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,44 @@
22

33
This code provides a Go server to handle
44
[Tile Map Service](https://en.wikipedia.org/wiki/Tile_Map_Service) requests. It
5-
is able to respond with [GeoJSON](https://geojson.org) or image tiles, either of
5+
is able to respond with [GeoJSON](https://geojson.org), image tiles or
6+
[Mapbox Vector Tiles](https://github.com/mapbox/vector-tile-spec) (version 2.1), any of
67
which can be added as an overlay to a map.
78

89
## Limitations
910

10-
1. This server does not implement any GetCapabilities methods.
11-
1. A fixed image tile size of 256x256 pixels is used.
11+
1. This server does not implement any GetCapabilities methods.
12+
1. A fixed image tile size of 256x256 pixels is used.
1213

1314
## Tile Requests
1415

1516
The server responds to tile requests. These send a zoom level, and x and y tile
16-
numbers. The request URL determines whether the response should be GeoJSON or an
17-
image.
17+
numbers. The request URL determines whether the response should be GeoJSON, an
18+
image, or a Mapbox Vector Tile.
1819

1920
The format of the requests is:
2021

2122
```
2223
//hostname:port/grid/[tilespec]/z/x/y.[format]?[options]
2324
```
2425

25-
* `tilespec` must be either `wms` or `tms`. The only difference in these is
26+
* `tilespec` must be either `wms` or `tms`. The only difference in these is
2627
that the Y tiles are numbered from north to south (`wms`) or from south to
2728
north (`tms`).
28-
* `format` must be either `json` for a GeoJSON FeatureCollection, or `png`
29-
for a PNG image tile.
30-
* The optional parameters are:
31-
* `linecol`: this defines the RGBA colour to use for lines in the PNG
29+
* `format` must be either `json` for a GeoJSON FeatureCollection, `png`
30+
for a PNG image tile, or `mvt` for a Mapbox Vector Tile.
31+
* The optional parameters are:
32+
* `linecol`: this defines the RGBA colour to use for lines in the PNG
3233
tiles.
33-
* `labelcol`: this defines the RGBA colour to use for the labels in the
34+
* `labelcol`: this defines the RGBA colour to use for the labels in the
3435
PNG tiles.
35-
* `zoomadjust`: this is added to the map zoom value, to cause the returned
36-
grid to be finer or coarser. This affects both GeoJSON and image tiles.
37-
* `projection`: this can be used to change the map projection from the
36+
* `zoomadjust`: this is added to the map zoom value, to cause the returned
37+
grid to be finer or coarser. This affects both GeoJSON, image tiles,
38+
and Mapbox Vector Tile.
39+
* `projection`: this can be used to change the map projection from the
3840
default, spherical mercator, to geodetic. Valid values are:
39-
* `mercator` or `epsg:3857`: selects spherical mercator (default)
40-
* `geodetic` or `epsg:4326`: selects geodetic projection
41+
* `mercator` or `epsg:3857`: selects spherical mercator (default)
42+
* `geodetic` or `epsg:4326`: selects geodetic projection
4143

4244
An example request could be:
4345

@@ -147,18 +149,18 @@ go test ./tile_server/gridserver -v --logtostderr
147149

148150
The following other projects need to be installed:
149151

150-
[GeoJSON](https://github.com/paulmach/go.geojson) provides the definition for
151-
the GeoJSON response objects. Install with:
152+
[orb](https://github.com/paulmach/orb) provides the definition for
153+
the GeoJSON and Mapbox Vector Tile response objects. Install with:
152154

153155
```
154-
go get github.com/paulmach/go.geojson
156+
go get github.com/paulmach/orb
155157
```
156158

157159
[Freetype](https://github.com/golang/freetype) is used for the labels in the PNG
158160
tiles. Install with:
159161

160162
```
161-
$ go get github.com/golang/freetype
163+
go get github.com/golang/freetype
162164
```
163165

164166
[Open Location Code](https://github.com/open-location-code/) generates the codes

tile_server/gridserver/go.mod

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,8 @@ require (
1111
golang.org/x/image v0.18.0
1212
)
1313

14-
require go.mongodb.org/mongo-driver v1.11.4 // indirect
14+
require (
15+
github.com/gogo/protobuf v1.3.2 // indirect
16+
github.com/paulmach/protoscan v0.2.1 // indirect
17+
go.mongodb.org/mongo-driver v1.11.4 // indirect
18+
)

tile_server/gridserver/go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
22
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
33
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
45
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
56
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
67
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
@@ -23,6 +24,7 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
2324
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
2425
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
2526
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
27+
github.com/paulmach/protoscan v0.2.1 h1:rM0FpcTjUMvPUNk2BhPJrreDKetq43ChnL+x1sRg8O8=
2628
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
2729
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
2830
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -123,6 +125,7 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
123125
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
124126
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
125127
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
128+
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
126129
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
127130
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
128131
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

tile_server/gridserver/gridserver.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
const (
1717
outputJSON = "json"
1818
outputPNG = "png"
19+
outputMVT = "mvt"
1920
tileNumberingWMS = "wms"
2021
tileNumberingTMS = "tms"
2122
lineColorOption = "linecol"
@@ -25,7 +26,7 @@ const (
2526
)
2627

2728
var (
28-
pathSpec = regexp.MustCompile(fmt.Sprintf(`^/grid/(%s|%s)/(\d+)/(\d+)/(\d+)\.(%s|%s)`, tileNumberingWMS, tileNumberingTMS, outputJSON, outputPNG))
29+
pathSpec = regexp.MustCompile(fmt.Sprintf(`^/grid/(%s|%s)/(\d+)/(\d+)/(\d+)\.(%s|%s|%s)`, tileNumberingWMS, tileNumberingTMS, outputJSON, outputPNG, outputMVT))
2930
)
3031

3132
// LatLng represents a latitude and longitude in degrees.
@@ -71,6 +72,8 @@ func Parse(r *http.Request) (*TileRef, error) {
7172
opts.Format = JSONTile
7273
} else if g[5] == outputPNG {
7374
opts.Format = ImageTile
75+
} else if g[5] == outputMVT {
76+
opts.Format = VectorTile
7477
} else {
7578
return nil, fmt.Errorf("Tile output type not specified: %v", g[5])
7679
}

tile_server/gridserver/mvt.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package gridserver
2+
3+
import (
4+
log "github.com/golang/glog"
5+
"github.com/paulmach/orb/encoding/mvt"
6+
"github.com/paulmach/orb/maptile"
7+
)
8+
9+
const (
10+
layerName = "grid"
11+
layerVersion = 2
12+
layerExtent = 4096
13+
)
14+
15+
// MVT returns a Mapbox Vector Tile (MVT) marshalled as bytes.
16+
func (t *TileRef) MVT() ([]byte, error) {
17+
log.Infof("Producing mvt for tile z/x/y %v/%v/%v (%s)", t.Z, t.X, t.Y, t.Path())
18+
gj, err := t.GeoJSON()
19+
if err != nil {
20+
return nil, err
21+
}
22+
23+
layer := &mvt.Layer{
24+
Name: layerName,
25+
Version: layerVersion,
26+
Extent: layerExtent,
27+
Features: gj.Features,
28+
}
29+
30+
// Since GeoJSON stores geometries in latitude and longitude (WGS84),
31+
// we only need to project the coordinates if the desired output projection is Mercator.
32+
if t.Options.Projection.String() == "mercator" {
33+
// Convert TMS coordinates to WMS coordinates
34+
wmsY := (1 << uint(t.Z)) - t.Y - 1
35+
layer.ProjectToTile(maptile.New(uint32(t.X), uint32(wmsY), maptile.Zoom(t.Z)))
36+
}
37+
38+
data, err := mvt.Marshal(mvt.Layers{layer})
39+
if err != nil {
40+
return nil, err
41+
}
42+
return data, nil
43+
}

tile_server/gridserver/mvt_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package gridserver
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
)
7+
8+
func TestMVT(t *testing.T) {
9+
var tests = []struct {
10+
x, y, z int
11+
opts *TileOptions
12+
testFile string
13+
}{
14+
{x: 17, y: 19, z: 5, testFile: testDataPath + "5_17_19.mvt"},
15+
{
16+
x: 17, y: 19, z: 5,
17+
opts: &TileOptions{Format: VectorTile, LineColor: lineColor, LabelColor: labelColor, Projection: NewMercatorTMS(), ZoomAdjust: 2},
18+
testFile: testDataPath + "5_17_19_zoom_2.mvt",
19+
},
20+
{
21+
x: 1098232, y: 1362659, z: 21,
22+
opts: &TileOptions{Format: VectorTile, LineColor: lineColor, LabelColor: labelColor, Projection: NewMercatorTMS(), ZoomAdjust: 0},
23+
testFile: testDataPath + "21_1098232_1362659.mvt",
24+
},
25+
{
26+
x: 17, y: 19, z: 5,
27+
opts: &TileOptions{Format: VectorTile, LineColor: lineColor, LabelColor: labelColor, Projection: NewGeodeticTMS(), ZoomAdjust: 0},
28+
testFile: testDataPath + "5_17_19_geodetic.mvt",
29+
},
30+
{
31+
x: 1098232, y: 1362659, z: 21,
32+
opts: &TileOptions{Format: VectorTile, LineColor: lineColor, LabelColor: labelColor, Projection: NewGeodeticTMS(), ZoomAdjust: 0},
33+
testFile: testDataPath + "21_1098232_1362659_geodetic.mvt",
34+
},
35+
}
36+
for n, td := range tests {
37+
want := readTestData(td.testFile)
38+
tr := MakeTileRef(td.x, td.y, td.z, td.opts)
39+
got, err := tr.MVT()
40+
if err != nil {
41+
t.Errorf("Test %d: MVT failed: %v", n, err)
42+
}
43+
if !bytes.Equal(got, want) {
44+
t.Errorf("Test %d: got MVT != want MVT", n)
45+
}
46+
}
47+
}
Binary file not shown.
Binary file not shown.
131 Bytes
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)