github.com/PDOK/gokoala@v0.50.6/internal/ogc/tiles/main.go (about)

     1  package tiles
     2  
     3  import (
     4  	"log"
     5  	"net/http"
     6  	"net/url"
     7  	"strings"
     8  
     9  	"github.com/PDOK/gokoala/internal/engine"
    10  	"github.com/PDOK/gokoala/internal/ogc/common/geospatial"
    11  	"github.com/go-chi/chi/v5"
    12  )
    13  
    14  const (
    15  	templatesDir            = "internal/ogc/tiles/templates/"
    16  	tilesPath               = "/tiles"
    17  	tilesLocalPath          = "tiles/"
    18  	tileMatrixSetsPath      = "/tileMatrixSets"
    19  	tileMatrixSetsLocalPath = "tileMatrixSets/"
    20  	defaultTilesTmpl        = "{tms}/{z}/{x}/{y}." + engine.FormatMVTAlternative
    21  )
    22  
    23  type Tiles struct {
    24  	engine *engine.Engine
    25  }
    26  
    27  func NewTiles(e *engine.Engine) *Tiles {
    28  	tilesBreadcrumbs := []engine.Breadcrumb{
    29  		{
    30  			Name: "Tiles",
    31  			Path: "tiles",
    32  		},
    33  	}
    34  	tileMatrixSetsBreadcrumbs := []engine.Breadcrumb{
    35  		{
    36  			Name: "Tile Matrix Sets",
    37  			Path: "tileMatrixSets",
    38  		},
    39  	}
    40  
    41  	e.RenderTemplates(tilesPath,
    42  		tilesBreadcrumbs,
    43  		engine.NewTemplateKey(templatesDir+"tiles.go.json"),
    44  		engine.NewTemplateKey(templatesDir+"tiles.go.html"))
    45  	e.RenderTemplates(tileMatrixSetsPath,
    46  		tileMatrixSetsBreadcrumbs,
    47  		engine.NewTemplateKey(templatesDir+"tileMatrixSets.go.json"),
    48  		engine.NewTemplateKey(templatesDir+"tileMatrixSets.go.html"))
    49  
    50  	renderTemplatesForSrs(e, "EuropeanETRS89_LAEAQuad", tilesBreadcrumbs, tileMatrixSetsBreadcrumbs)
    51  	renderTemplatesForSrs(e, "NetherlandsRDNewQuad", tilesBreadcrumbs, tileMatrixSetsBreadcrumbs)
    52  	renderTemplatesForSrs(e, "WebMercatorQuad", tilesBreadcrumbs, tileMatrixSetsBreadcrumbs)
    53  
    54  	_, err := url.ParseRequestURI(e.Config.OgcAPI.Tiles.TileServer.String())
    55  	if err != nil {
    56  		log.Fatalf("invalid tileserver url provided: %v", err)
    57  	}
    58  	tiles := &Tiles{
    59  		engine: e,
    60  	}
    61  
    62  	e.Router.Get(tileMatrixSetsPath, tiles.TileMatrixSets())
    63  	e.Router.Get(tileMatrixSetsPath+"/{tileMatrixSetId}", tiles.TileMatrixSet())
    64  	e.Router.Get(tilesPath, tiles.TilesetsList())
    65  	e.Router.Get(tilesPath+"/{tileMatrixSetId}", tiles.Tileset())
    66  	e.Router.Head(tilesPath+"/{tileMatrixSetId}/{tileMatrix}/{tileRow}/{tileCol}", tiles.Tile())
    67  	e.Router.Get(tilesPath+"/{tileMatrixSetId}/{tileMatrix}/{tileRow}/{tileCol}", tiles.Tile())
    68  	e.Router.Get(geospatial.CollectionsPath+"/{collectionId}/tiles", tiles.TilesCollection())
    69  
    70  	return tiles
    71  }
    72  
    73  func renderTemplatesForSrs(e *engine.Engine, srs string, tilesBreadcrumbs []engine.Breadcrumb, tileMatrixSetsBreadcrumbs []engine.Breadcrumb) {
    74  	tilesSrsBreadcrumbs := tilesBreadcrumbs
    75  	tilesSrsBreadcrumbs = append(tilesSrsBreadcrumbs, []engine.Breadcrumb{
    76  		{
    77  			Name: srs,
    78  			Path: tilesLocalPath + srs,
    79  		},
    80  	}...)
    81  	tileMatrixSetsSrsBreadcrumbs := tileMatrixSetsBreadcrumbs
    82  	tileMatrixSetsSrsBreadcrumbs = append(tileMatrixSetsSrsBreadcrumbs, []engine.Breadcrumb{
    83  		{
    84  			Name: srs,
    85  			Path: tileMatrixSetsLocalPath + srs,
    86  		},
    87  	}...)
    88  
    89  	e.RenderTemplates(tileMatrixSetsPath+"/"+srs,
    90  		tileMatrixSetsSrsBreadcrumbs,
    91  		engine.NewTemplateKey(templatesDir+tileMatrixSetsLocalPath+srs+".go.json"),
    92  		engine.NewTemplateKey(templatesDir+tileMatrixSetsLocalPath+srs+".go.html"))
    93  
    94  	e.RenderTemplates(tilesPath+"/"+srs,
    95  		tilesSrsBreadcrumbs,
    96  		engine.NewTemplateKey(templatesDir+tilesLocalPath+srs+".go.json"),
    97  		engine.NewTemplateKey(templatesDir+tilesLocalPath+srs+".go.html"))
    98  
    99  	e.RenderTemplates(tilesPath+"/"+srs,
   100  		tilesSrsBreadcrumbs,
   101  		engine.NewTemplateKey(templatesDir+tilesLocalPath+srs+".go.tilejson"))
   102  }
   103  
   104  func (t *Tiles) TileMatrixSets() http.HandlerFunc {
   105  	return func(w http.ResponseWriter, r *http.Request) {
   106  		key := engine.NewTemplateKeyWithLanguage(templatesDir+"tileMatrixSets.go."+t.engine.CN.NegotiateFormat(r), t.engine.CN.NegotiateLanguage(w, r))
   107  		t.engine.ServePage(w, r, key)
   108  	}
   109  }
   110  
   111  func (t *Tiles) TileMatrixSet() http.HandlerFunc {
   112  	return func(w http.ResponseWriter, r *http.Request) {
   113  		tileMatrixSetID := chi.URLParam(r, "tileMatrixSetId")
   114  		key := engine.NewTemplateKeyWithLanguage(templatesDir+tileMatrixSetsLocalPath+tileMatrixSetID+".go."+t.engine.CN.NegotiateFormat(r), t.engine.CN.NegotiateLanguage(w, r))
   115  		t.engine.ServePage(w, r, key)
   116  	}
   117  }
   118  
   119  func (t *Tiles) TilesetsList() http.HandlerFunc {
   120  	return func(w http.ResponseWriter, r *http.Request) {
   121  		key := engine.NewTemplateKeyWithLanguage(templatesDir+"tiles.go."+t.engine.CN.NegotiateFormat(r), t.engine.CN.NegotiateLanguage(w, r))
   122  		t.engine.ServePage(w, r, key)
   123  	}
   124  }
   125  
   126  func (t *Tiles) Tileset() http.HandlerFunc {
   127  	return func(w http.ResponseWriter, r *http.Request) {
   128  		tileMatrixSetID := chi.URLParam(r, "tileMatrixSetId")
   129  		key := engine.NewTemplateKeyWithLanguage(templatesDir+tilesLocalPath+tileMatrixSetID+".go."+t.engine.CN.NegotiateFormat(r), t.engine.CN.NegotiateLanguage(w, r))
   130  		t.engine.ServePage(w, r, key)
   131  	}
   132  }
   133  
   134  // Tile reverse proxy to Azure Blob, assumes blob bucket/container is public
   135  func (t *Tiles) Tile() http.HandlerFunc {
   136  	return func(w http.ResponseWriter, r *http.Request) {
   137  		tileMatrixSetID := chi.URLParam(r, "tileMatrixSetId")
   138  		tileMatrix := chi.URLParam(r, "tileMatrix")
   139  		tileRow := chi.URLParam(r, "tileRow")
   140  		tileCol := chi.URLParam(r, "tileCol")
   141  
   142  		// We support content negotiation using Accept header and ?f= param, but also
   143  		// using the .pbf extension. This is for backwards compatibility.
   144  		if !strings.HasSuffix(tileCol, ".pbf") {
   145  			// if no format is specified, default to mvt
   146  			if format := strings.Replace(t.engine.CN.NegotiateFormat(r), engine.FormatJSON, engine.FormatMVT, 1); format != engine.FormatMVT && format != engine.FormatMVTAlternative {
   147  				engine.RenderProblem(engine.ProblemBadRequest, w, "Specify tile format. Currently only Mapbox Vector Tiles (?f=mvt) tiles are supported")
   148  				return
   149  			}
   150  		} else {
   151  			tileCol = tileCol[:len(tileCol)-4] // remove .pbf extension
   152  		}
   153  
   154  		// ogc spec is (default) z/row/col but tileserver is z/col/row (z/x/y)
   155  		replacer := strings.NewReplacer("{tms}", tileMatrixSetID, "{z}", tileMatrix, "{x}", tileCol, "{y}", tileRow)
   156  		tilesTmpl := defaultTilesTmpl
   157  		if t.engine.Config.OgcAPI.Tiles.URITemplateTiles != nil {
   158  			tilesTmpl = *t.engine.Config.OgcAPI.Tiles.URITemplateTiles
   159  		}
   160  		path, _ := url.JoinPath("/", replacer.Replace(tilesTmpl))
   161  
   162  		target, err := url.Parse(t.engine.Config.OgcAPI.Tiles.TileServer.String() + path)
   163  		if err != nil {
   164  			log.Printf("invalid target url, can't proxy tiles: %v", err)
   165  			engine.RenderProblem(engine.ProblemServerError, w)
   166  			return
   167  		}
   168  		t.engine.ReverseProxy(w, r, target, true, engine.MediaTypeMVT)
   169  	}
   170  }
   171  
   172  func (t *Tiles) TilesCollection(_ ...any) http.HandlerFunc {
   173  	return func(_ http.ResponseWriter, r *http.Request) {
   174  		collectionID := chi.URLParam(r, "collectionId")
   175  
   176  		// TODO: not implemented, since we don't (yet) support tile collections
   177  		log.Printf("TODO: return tiles for collection %s", collectionID)
   178  	}
   179  }