Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: adds configurable query parameters to tile endpoints #795

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions atlas/atlas.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/go-spatial/geom/slippy"
"github.com/go-spatial/tegola"
"github.com/go-spatial/tegola/cache"
"github.com/go-spatial/tegola/config"
"github.com/go-spatial/tegola/internal/observer"
"github.com/go-spatial/tegola/observability"
)
Expand Down Expand Up @@ -78,6 +79,10 @@ type Atlas struct {
// holds a reference to the observer backend
observer observability.Interface

// holds a reference to configured query parameters for maps
// key = map name
params map[string][]config.QueryParameter

// publishBuildInfo indicates if we should publish the build info on change of observer
// this is set by calling PublishBuildInfo, which will publish
// the build info on the observer and insure changes to observer
Expand Down Expand Up @@ -213,6 +218,36 @@ func (a *Atlas) AddMap(m Map) {
a.maps[m.Name] = m
}

// AddParams adds the given query parameters to the atlas params map
// keyed by the map name, with upper-cased tokens
func (a *Atlas) AddParams(name string, params []config.QueryParameter) {
if a == nil {
defaultAtlas.AddParams(name, params)
return
}
if a.params == nil {
a.params = make(map[string][]config.QueryParameter)
}
a.params[name] = config.QueryParameters(params).Clean()
}

// GetParams returns any configured query parameters for the given
// map by name
func (a *Atlas) GetParams(name string) []config.QueryParameter {
if a == nil {
return defaultAtlas.GetParams(name)
}
return a.params[name]
}

// HasParams returns true if the given map by name has configured query parameters
func (a *Atlas) HasParams(name string) bool {
if a == nil {
return defaultAtlas.HasParams(name)
}
return len(a.params[name]) > 0
}

// GetCache returns the registered cache if one is registered, otherwise nil
func (a *Atlas) GetCache() cache.Interface {
if a == nil {
Expand Down
4 changes: 2 additions & 2 deletions atlas/layer.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ type Layer struct {
// default tags to include when encoding the layer. provider tags take precedence
DefaultTags map[string]interface{}
GeomType geom.Geometry
// DontSimplify indicates wheather feature simplification should be applied.
// DontSimplify indicates whether feature simplification should be applied.
// We use a negative in the name so the default is to simplify
DontSimplify bool
// DontClip indicates wheather feature clipping should be applied.
// DontClip indicates whether feature clipping should be applied.
// We use a negative in the name so the default is to clip
DontClip bool
}
Expand Down
8 changes: 6 additions & 2 deletions atlas/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (
"github.com/go-spatial/tegola/provider/debug"
)

// NewMap creates a new map with the necessary default values
// NewWebMercatorMap creates a new map with the necessary default values
func NewWebMercatorMap(name string) Map {
return Map{
Name: name,
Expand All @@ -40,6 +40,7 @@ func NewWebMercatorMap(name string) Map {
}
}

// Map defines a Web Mercator map
type Map struct {
Name string
// Contains an attribution to be displayed when the map is shown to a user.
Expand Down Expand Up @@ -348,7 +349,10 @@ func (m Map) encodeMVTTile(ctx context.Context, tile *slippy.Tile) ([]byte, erro
}

// add layers to our tile
mvtTile.AddLayers(mvtLayers...)
err := mvtTile.AddLayers(mvtLayers...)
if err != nil {
return nil, err
}

// generate the MVT tile
vtile, err := mvtTile.VTile(ctx)
Expand Down
5 changes: 5 additions & 0 deletions cmd/internal/register/maps.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ func Maps(a *atlas.Atlas, maps []config.Map, providers map[string]provider.Tiler
}
newMap.Layers = append(newMap.Layers, layer)
}

if len(m.Parameters) > 0 {
a.AddParams(string(m.Name), m.Parameters)
}

a.AddMap(newMap)
}
return nil
Expand Down
6 changes: 3 additions & 3 deletions cmd/tegola/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ var (
// parsed config
conf config.Config

// require cache
// RequireCache in this instance
RequireCache bool
)

Expand All @@ -42,11 +42,11 @@ var RootCmd = &cobra.Command{
Use: "tegola",
Short: "tegola is a vector tile server",
Long: fmt.Sprintf(`tegola is a vector tile server
Version: %v`, build.Version),
Version: %v`, build.Version),
PersistentPreRunE: rootCmdValidatePersistent,
}

func rootCmdValidatePersistent(cmd *cobra.Command, args []string) (err error) {
func rootCmdValidatePersistent(cmd *cobra.Command, _ []string) (err error) {
requireCache := RequireCache || cachecmd.RequireCache
cmdName := cmd.CalledAs()
switch cmdName {
Expand Down
139 changes: 126 additions & 13 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,45 @@ import (
"github.com/go-spatial/tegola/provider"
)

const (
BboxToken = "!BBOX!"
ZoomToken = "!ZOOM!"
XToken = "!X!"
YToken = "!Y!"
ZToken = "!Z!"
ScaleDenominatorToken = "!SCALE_DENOMINATOR!"
PixelWidthToken = "!PIXEL_WIDTH!"
PixelHeightToken = "!PIXEL_HEIGHT!"
IdFieldToken = "!ID_FIELD!"
GeomFieldToken = "!GEOM_FIELD!"
GeomTypeToken = "!GEOM_TYPE!"
)

// ReservedTokens for query injection
var ReservedTokens = []string{
BboxToken,
ZoomToken,
XToken,
YToken,
ZToken,
ScaleDenominatorToken,
PixelWidthToken,
PixelHeightToken,
IdFieldToken,
GeomFieldToken,
GeomTypeToken,
}

// IsReservedToken returns true if the specified token is reserved
func IsReservedToken(token string) bool {
for _, t := range ReservedTokens {
if token == t {
return true
}
}
return false
}

var blacklistHeaders = []string{"content-encoding", "content-length", "content-type"}

// Config represents a tegola config file.
Expand Down Expand Up @@ -52,12 +91,55 @@ type Webserver struct {

// 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"`
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"`
Parameters []QueryParameter `toml:"params"`
TileBuffer *env.Int `toml:"tile_buffer"`
}

// ValidateParams ensures configured params don't conflict with existing
// query tokens or have overlapping names
func (m Map) ValidateParams() error {
if len(m.Parameters) == 0 {
return nil
}

var usedNames, usedTokens []string

for _, param := range m.Parameters {
if IsReservedToken(param.Token) {
return ErrParamTokenReserved{
MapName: string(m.Name),
Parameter: param,
}
}

for _, name := range usedNames {
if name == param.Name {
return ErrParamNameDuplicate{
MapName: string(m.Name),
Parameter: param,
}
}
}

for _, token := range usedTokens {
if token == param.Token {
return ErrParamTokenDuplicate{
MapName: string(m.Name),
Parameter: param,
}
}
}

usedNames = append(usedNames, param.Name)
usedTokens = append(usedTokens, param.Token)
}

return nil
}

// MapLayer represents a the config for a layer in a map
Expand All @@ -69,10 +151,10 @@ type MapLayer struct {
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.
// DontSimplify indicates whether 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.
// DontClip indicates whether feature clipping should be applied.
// We use a negative in the name so the default is to clipping
DontClip env.Bool `toml:"dont_clip"`
}
Expand All @@ -98,6 +180,31 @@ func (ml MapLayer) GetName() (string, error) {
return name, err
}

// QueryParameter represents an HTTP query parameter specified for use with
// a given map instance.
type QueryParameter struct {
Name string `toml:"name"`
Token string `toml:"token"`
Default string `toml:"default"`
}

// QueryParameters is an array of QueryParameter
type QueryParameters []QueryParameter

// Clean will return the list of parameters with all upper-cased token names
func (params QueryParameters) Clean() QueryParameters {
var cleanParams []QueryParameter
for _, param := range params {
cleanParams = append(cleanParams, QueryParameter{
Name: param.Name,
Token: strings.ToUpper(param.Token),
Default: param.Default,
})
}

return cleanParams
}

// Validate checks the config for issues
func (c *Config) Validate() error {

Expand Down Expand Up @@ -141,6 +248,12 @@ func (c *Config) Validate() error {
// map of layers to providers
mapLayers := map[string]map[string]MapLayer{}
for mapKey, m := range c.Maps {

// validate any declared query parameters
if err := m.ValidateParams(); err != nil {
return err
}

if _, ok := mapLayers[string(m.Name)]; !ok {
mapLayers[string(m.Name)] = map[string]MapLayer{}
}
Expand All @@ -149,19 +262,19 @@ func (c *Config) Validate() error {
// we can only have the same provider for all layers.
// This allow us to track what the first found provider
// is.
provider := ""
currProvider := ""
isMVTProvider := false
for layerKey, l := range m.Layers {
pname, _, err := l.ProviderLayerName()
if err != nil {
return err
}

if provider == "" {
if currProvider == "" {
// This is the first provider we found.
// For MVTProviders all others need to be the same, so store it
// so we can check later
provider = pname
currProvider = pname
}

isMvt, doesExists := mvtproviders[pname]
Expand All @@ -178,13 +291,13 @@ func (c *Config) Validate() error {
isMVTProvider = isMVTProvider || isMvt

// only need to do this check if we are dealing with MVTProviders
if isMVTProvider && pname != provider {
if isMVTProvider && pname != currProvider {
// for mvt_providers we can only have the same provider
// for all layers
// check to see
if mvtproviders[pname] || isMVTProvider {
return ErrMVTDifferentProviders{
Original: provider,
Original: currProvider,
Current: pname,
}
}
Expand Down
Loading