forked from go-spatial/tegola
-
Notifications
You must be signed in to change notification settings - Fork 0
/
config.go
239 lines (207 loc) · 6.84 KB
/
config.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
232
233
234
235
236
237
238
239
// Package config loads and understands the tegola config format.
package config
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
"github.com/BurntSushi/toml"
"github.com/go-spatial/tegola"
"github.com/go-spatial/tegola/internal/env"
"github.com/go-spatial/tegola/internal/log"
)
var blacklistHeaders = []string{"content-encoding", "content-length", "content-type"}
// Config represents a tegola config file.
type Config struct {
// the tile buffer to use
TileBuffer *env.Int `toml:"tile_buffer"`
// LocationName is the file name or http server that the config was read from.
// If this is an empty string, it means that the location was unknown. This is the case if
// the Parse() function is used directly.
LocationName string
Webserver Webserver `toml:"webserver"`
Cache env.Dict `toml:"cache"`
// Map of providers.
Providers []env.Dict `toml:"providers"`
Maps []Map `toml:"maps"`
}
type Webserver struct {
HostName env.String `toml:"hostname"`
Port env.String `toml:"port"`
URIPrefix env.String `toml:"uri_prefix"`
Headers env.Dict `toml:"headers"`
SSLCert env.String `toml:"ssl_cert"`
SSLKey env.String `toml:"ssl_key"`
}
// A Map represents a map in the Tegola Config file.
type Map struct {
Name env.String `toml:"name"`
Attribution env.String `toml:"attribution"`
Bounds []env.Float `toml:"bounds"`
Center [3]env.Float `toml:"center"`
Layers []MapLayer `toml:"layers"`
TileBuffer *env.Int `toml:"tile_buffer"`
}
type MapLayer struct {
// Name is optional. If it's not defined the name of the ProviderLayer will be used.
// Name can also be used to group multiple ProviderLayers under the same namespace.
Name env.String `toml:"name"`
ProviderLayer env.String `toml:"provider_layer"`
MinZoom *env.Uint `toml:"min_zoom"`
MaxZoom *env.Uint `toml:"max_zoom"`
DefaultTags interface{} `toml:"default_tags"`
// DontSimplify indicates wheather feature simplification should be applied.
// We use a negative in the name so the default is to simplify
DontSimplify env.Bool `toml:"dont_simplify"`
// DontClip indicates wheather feature clipping should be applied.
// We use a negative in the name so the default is to clipping
DontClip env.Bool `toml:"dont_clip"`
}
// ProviderLayerName returns the provider and layer names
func (ml MapLayer) ProviderLayerName() (string, string, error) {
// split the provider layer (syntax is provider.layer)
plParts := strings.Split(string(ml.ProviderLayer), ".")
if len(plParts) != 2 {
return "", "", ErrInvalidProviderLayerName{ProviderLayerName: string(ml.ProviderLayer)}
}
return plParts[0], plParts[1], nil
}
// GetName will return the user-defined Layer name from the config,
// or if not defined will return the name of the layer associated with the provider
func (ml MapLayer) GetName() (string, error) {
if ml.Name != "" {
return string(ml.Name), nil
}
_, name, err := ml.ProviderLayerName()
return name, err
}
// checks the config for issues
func (c *Config) Validate() error {
// check for map layer name / zoom collisions
// map of layers to providers
mapLayers := map[string]map[string]MapLayer{}
for mapKey, m := range c.Maps {
if _, ok := mapLayers[string(m.Name)]; !ok {
mapLayers[string(m.Name)] = map[string]MapLayer{}
}
for layerKey, l := range m.Layers {
name, err := l.GetName()
if err != nil {
return err
}
// MaxZoom default
if l.MaxZoom == nil {
ph := env.Uint(tegola.MaxZ)
// set in iterated value
l.MaxZoom = &ph
// set in underlying config struct
c.Maps[mapKey].Layers[layerKey].MaxZoom = &ph
}
// MinZoom default
if l.MinZoom == nil {
ph := env.Uint(0)
// set in iterated value
l.MinZoom = &ph
// set in underlying config struct
c.Maps[mapKey].Layers[layerKey].MinZoom = &ph
}
// check if we already have this layer
if val, ok := mapLayers[string(m.Name)][name]; ok {
// we have a hit. check for zoom range overlap
if uint(*val.MinZoom) <= uint(*l.MaxZoom) && uint(*l.MinZoom) <= uint(*val.MaxZoom) {
return ErrOverlappingLayerZooms{
ProviderLayer1: string(val.ProviderLayer),
ProviderLayer2: string(l.ProviderLayer),
}
}
continue
}
// add the MapLayer to our map
mapLayers[string(m.Name)][name] = l
}
}
// check for blacklisted headers
for k := range c.Webserver.Headers {
for _, v := range blacklistHeaders {
if v == strings.ToLower(k) {
return ErrInvalidHeader{Header: k}
}
}
}
// check if webserver.uri_prefix is set and if so
// confirm it starts with a forward slash "/"
if string(c.Webserver.URIPrefix) != "" {
uriPrefix := string(c.Webserver.URIPrefix)
if string(uriPrefix[0]) != "/" {
return ErrInvalidURIPrefix(uriPrefix)
}
}
return nil
}
// ConfigTileBuffers handles setting the tile buffer for a Map
func (c *Config) ConfigureTileBuffers() {
// range our configured maps
for mapKey, m := range c.Maps {
// if there is a tile buffer config for this map, use it
if m.TileBuffer != nil {
c.Maps[mapKey].TileBuffer = m.TileBuffer
continue
}
// if there is a global tile buffer config, use it
if c.TileBuffer != nil {
c.Maps[mapKey].TileBuffer = c.TileBuffer
continue
}
// tile buffer is not configured, use default
c.Maps[mapKey].TileBuffer = env.IntPtr(env.Int(tegola.DefaultTileBuffer))
}
}
// Parse will parse the Tegola config file provided by the io.Reader.
func Parse(reader io.Reader, location string) (conf Config, err error) {
// decode conf file, don't care about the meta data.
_, err = toml.DecodeReader(reader, &conf)
conf.LocationName = location
conf.ConfigureTileBuffers()
return conf, err
}
// Load will load and parse the config file from the given location.
func Load(location string) (conf Config, err error) {
var reader io.Reader
// check for http prefix
if strings.HasPrefix(location, "http") {
log.Infof("loading remote config (%v)", location)
// setup http client with a timeout
var httpClient = &http.Client{
Timeout: time.Second * 10,
}
// make the http request
res, err := httpClient.Get(location)
if err != nil {
return conf, fmt.Errorf("error fetching remote config file (%v): %v ", location, err)
}
// set the reader to the response body
reader = res.Body
} else {
log.Infof("loading local config (%v)", location)
// check the conf file exists
if _, err := os.Stat(location); os.IsNotExist(err) {
return conf, fmt.Errorf("config file at location (%v) not found!", location)
}
// open the confi file
reader, err = os.Open(location)
if err != nil {
return conf, fmt.Errorf("error opening local config file (%v): %v ", location, err)
}
}
return Parse(reader, location)
}
func LoadAndValidate(filename string) (cfg Config, err error) {
cfg, err = Load(filename)
if err != nil {
return cfg, err
}
// validate our config
return cfg, cfg.Validate()
}