forked from go-spatial/tegola
-
Notifications
You must be signed in to change notification settings - Fork 0
/
handle_zxy.go
231 lines (195 loc) · 5.57 KB
/
handle_zxy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
//Package server implements the http frontend
package server
import (
"fmt"
"log"
"net/http"
"strconv"
"strings"
"sync"
"github.com/golang/protobuf/proto"
"github.com/terranodo/tegola"
"github.com/terranodo/tegola/basic"
"github.com/terranodo/tegola/mvt"
)
const (
// MaxTileSize is 500k. Currently just throws a warning when tile
// is larger than MaxTileSize
MaxTileSize = 500000
// MaxZoom will not render tile beyond this zoom level
MaxZoom = 20
)
// URI scheme: /maps/:map_name/:z/:x/:y
// map_name - map name in the config file
// z, x, y - tile coordinates as described in the Slippy Map Tilenames specification
// z - zoom level
// x - row
// y - column
func handleZXY(w http.ResponseWriter, r *http.Request) {
// check http verb
switch r.Method {
// preflight check for CORS request
case "OPTIONS":
// TODO: how configurable do we want the CORS policy to be?
// set CORS header
w.Header().Add("Access-Control-Allow-Origin", "*")
// options call does not have a body
w.Write(nil)
return
// tile request
case "GET":
// pop off URI prefix
uri := r.URL.Path[len("/maps/"):]
// break apart our URI
uriParts := strings.Split(uri, "/")
// check that we have the correct number of arguments in our URI
if len(uriParts) != 4 {
http.Error(w, "uri requires four params: /:map_id/:z/:x/:y", http.StatusBadRequest)
return
}
// lookup our map layers
layers, ok := maps[uriParts[0]]
if !ok {
log.Printf("map (%v) not configured. check your config file", uriParts[0])
http.Error(w, "no map configured: "+uriParts[0], http.StatusBadRequest)
return
}
// trim the "y" param in the url in case it has an extension
yparts := strings.Split(uriParts[3], ".")
uriParts[3] = yparts[0]
// parse our URL vals to ints
z, err := strconv.Atoi(uriParts[1])
if err != nil {
http.Error(w, "invalid z value: "+uriParts[1], http.StatusBadRequest)
return
}
x, err := strconv.Atoi(uriParts[2])
if err != nil {
http.Error(w, "invalid x value: "+uriParts[2], http.StatusBadRequest)
return
}
y, err := strconv.Atoi(uriParts[3])
if err != nil {
http.Error(w, "invalid y value: "+uriParts[3], http.StatusBadRequest)
return
}
// new tile
tile := tegola.Tile{
Z: z,
X: x,
Y: y,
}
// generate a tile
var mvtTile mvt.Tile
// check that our request is below max zoom and we have layers to render
if tile.Z <= MaxZoom && len(layers) != 0 {
// wait group for concurrent layer fetching
var wg sync.WaitGroup
// filter down the layers we need for this zoom
ls := layers.FilterByZoom(tile.Z)
// layer stack
mvtLayers := make([]*mvt.Layer, len(ls))
// iterate our layers
for i, l := range ls {
// incriment our waitgroup
wg.Add(1)
// go routine for rendering the layer
go func(i int, l Layer) {
// on completion let the wait group know
defer wg.Done()
// fetch layer from data provider
mvtLayer, err := l.Provider.MVTLayer(l.Name, tile, l.DefaultTags)
if err != nil {
log.Printf("Error Getting MVTLayer: %v", err)
http.Error(w, fmt.Sprintf("Error Getting MVTLayer: %v", err.Error()), http.StatusBadRequest)
return
}
// add the layer to the slice position
mvtLayers[i] = mvtLayer
}(i, l)
}
// wait for the waitgroup to finish
wg.Wait()
// add layers to our tile
mvtTile.AddLayers(mvtLayers...)
}
// check for the debug query string
debug := r.URL.Query().Get("debug")
if debug == "true" {
// add debug layer
debugLayer := debugLayer(tile)
mvtTile.AddLayers(debugLayer)
}
// generate our vector tile
vtile, err := mvtTile.VTile(tile.BoundingBox())
if err != nil {
log.Printf("Error Getting VTile: %v", err)
http.Error(w, fmt.Sprintf("Error Getting VTile: %v", err.Error()), http.StatusBadRequest)
return
}
// marshal our tile into a protocol buffer
var pbyte []byte
pbyte, err = proto.Marshal(vtile)
if err != nil {
log.Printf("Error marshalling tile: %v", err)
http.Error(w, "Error marshalling tile", http.StatusInternalServerError)
return
}
// TODO: how configurable do we want the CORS policy to be?
// set CORS header
w.Header().Add("Access-Control-Allow-Origin", "*")
// mimetype for protocol buffers
w.Header().Add("Content-Type", "application/x-protobuf")
w.Write(pbyte)
// check for tile size warnings
if len(pbyte) > MaxTileSize {
log.Printf("tile z:%v, x:%v, y:%v is rather large - %v", tile.Z, tile.X, tile.Y, len(pbyte))
}
// log the request
L.Log(logItem{
X: tile.X,
Y: tile.Y,
Z: tile.Z,
RequestIP: r.RemoteAddr,
})
default:
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
}
// creates a debug layer with z/x/y encoded as a point
func debugLayer(tile tegola.Tile) *mvt.Layer {
// get tile bounding box
ext := tile.BoundingBox()
// create a new layer and name it
layer := mvt.Layer{
Name: "debug",
}
xlen := ext.Maxx - ext.Minx
ylen := ext.Maxy - ext.Miny
// tile outlines
outline := mvt.Feature{
Tags: map[string]interface{}{
"type": "debug_outline",
},
Geometry: &basic.Line{ // tile outline
basic.Point{ext.Minx, ext.Miny},
basic.Point{ext.Maxx, ext.Miny},
basic.Point{ext.Maxx, ext.Maxy},
basic.Point{ext.Minx, ext.Maxy},
},
}
// new feature
zxy := mvt.Feature{
Tags: map[string]interface{}{
"type": "debug_text",
"zxy": fmt.Sprintf("Z:%v, X:%v, Y:%v", tile.Z, tile.X, tile.Y),
},
Geometry: &basic.Point{ // middle of the tile
ext.Minx + (xlen / 2),
ext.Miny + (ylen / 2),
},
}
layer.AddFeatures(outline, zxy)
return &layer
}