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 }