Skip to content

Commit

Permalink
Merge pull request #267 from PDOK/PDOK/ets_validator_fixes
Browse files Browse the repository at this point in the history
fix: for OGC API Tiles validator findings [merge in januari]
  • Loading branch information
kad-korpem authored Jan 14, 2025
2 parents 28417e0 + 3a804c1 commit 5a79f30
Show file tree
Hide file tree
Showing 14 changed files with 1,381 additions and 23 deletions.
16 changes: 14 additions & 2 deletions .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ jobs:
--name gokoala \
gokoala:local --config-file /examples/config_all.yaml
# E2E Test
# E2E Test (Cypress)
- name: E2E Test => Cypress
uses: cypress-io/github-action@v6
with:
working-directory: ./tests
browser: chrome

# E2E Test
# E2E Test (Features conformance)
- name: E2E Test => OGC API Features Conformance Validation
run: |
sleep 5
Expand All @@ -53,6 +53,18 @@ jobs:
--exitOnFail \
--prettyPrint
# E2E Test (Tiles conformance)
- name: E2E Test => OGC API Tiles Conformance Validation
run: |
sleep 5
docker run --net=host -t -v "$(pwd):/mnt" \
docker.io/pdok/ets-ogcapi-tiles10-docker:latest \
http://localhost:8080 \
--generateHtmlReport true \
--outputDir /mnt/output \
--exitOnFail \
--prettyPrint
- name: Stop GoKoala test instance
run: |
docker stop gokoala
2 changes: 1 addition & 1 deletion cmd/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func Test_newRouter(t *testing.T) {
log.Print(recorder.Body.String()) // to ease debugging
switch {
case strings.HasSuffix(tt.apiCall, "json"):
assert.JSONEq(t, recorder.Body.String(), string(expectedBody))
assert.JSONEq(t, string(expectedBody), recorder.Body.String())
case strings.HasSuffix(tt.apiCall, "html") || strings.HasSuffix(tt.apiCall, "xml"):
assert.Contains(t, normalize(recorder.Body.String()), normalize(string(expectedBody)))
default:
Expand Down
8 changes: 8 additions & 0 deletions examples/config_all.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ ogcApi:
zoomLevelRange:
start: 14
end: 14
- srs: EPSG:3857
zoomLevelRange:
start: 17
end: 17
collections:
# geodata tiles (collection-level tiles)
- id: addresses # same collection as the geovolumes/features
Expand All @@ -86,6 +90,10 @@ ogcApi:
zoomLevelRange:
start: 14
end: 14
- srs: EPSG:3857
zoomLevelRange:
start: 17
end: 17

features:
datasources:
Expand Down
1 change: 1 addition & 0 deletions internal/engine/contentnegotiation.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ func newContentNegotiation(availableLanguages []config.Language) *ContentNegotia
contenttype.NewMediaType(MediaTypeMVT),
contenttype.NewMediaType(MediaTypeMapboxStyle),
contenttype.NewMediaType(MediaTypeSLD),
contenttype.NewMediaType(MediaTypeOpenAPI),
}

formatsByMediaType := map[string]string{
Expand Down
16 changes: 8 additions & 8 deletions internal/engine/templates/openapi/tiles.go.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"Tiling Schemes"
],
"summary": "Retrieve the list of available tiling schemes (tile matrix sets)",
"operationId": "getTileMatrixSetsList",
"operationId": "tileMatrixSets.dataset.vector.getTileSetsList",
"parameters": [
{
"$ref": "#/components/parameters/f-metadata"
Expand All @@ -43,7 +43,7 @@
"Tiling Schemes"
],
"summary": "Retrieve the definition of the specified tiling scheme (tile matrix set)",
"operationId": "getTileMatrixSet",
"operationId": "tileMatrixSets.dataset.vector.getTileSet",
"parameters": [
{
"$ref": "#/components/parameters/tileMatrixSetId"
Expand All @@ -67,7 +67,7 @@
"Vector Tiles"
],
"summary": "Retrieve a list of available vector tilesets for the whole dataset",
"operationId": "{{ $coll.ID }}.getVectorTiles",
"operationId": "{{ $coll.ID }}.collection.vector.getTileSetsList",
"parameters": [
{
"$ref": "#/components/parameters/f-metadata"
Expand All @@ -87,7 +87,7 @@
"Vector Tiles"
],
"summary": "Retrieve the vector tileset metadata for the whole dataset and the specified tiling scheme (tile matrix set)",
"operationId": "{{ $coll.ID }}.getVectorTilesMetadata",
"operationId": "{{ $coll.ID }}.collection.vector.getTileSet",
"parameters": [
{
"$ref": "#/components/parameters/tileMatrixSetId"
Expand All @@ -110,7 +110,7 @@
"Vector Tiles"
],
"summary": "Retrieve a vector tile from the dataset.",
"operationId": "{{ $coll.ID }}.getTile",
"operationId": "{{ $coll.ID }}.collection.vector.getTile",
"parameters": [
{
"$ref": "#/components/parameters/tileMatrix"
Expand Down Expand Up @@ -147,7 +147,7 @@
"Vector Tiles"
],
"summary": "Retrieve a list of available vector tilesets for the specified collection",
"operationId": "getVectorTiles",
"operationId": "tiles.dataset.vector.getTileSetsList",
"parameters": [
{
"$ref": "#/components/parameters/f-metadata"
Expand All @@ -167,7 +167,7 @@
"Vector Tiles"
],
"summary": "Retrieve the vector tileset metadata for the specified collection and tiling scheme (tile matrix set)",
"operationId": "getVectorTilesMetadata",
"operationId": "tiles.dataset.vector.getTileSet",
"parameters": [
{
"$ref": "#/components/parameters/tileMatrixSetId"
Expand All @@ -190,7 +190,7 @@
"Vector Tiles"
],
"summary": "Retrieve a vector tile from a collection",
"operationId": "getTile",
"operationId": "tiles.dataset.vector.getTile",
"parameters": [
{
"$ref": "#/components/parameters/tileMatrix"
Expand Down
8 changes: 7 additions & 1 deletion internal/engine/testdata/expected_sitemap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
<url>
<loc>http://localhost:8080/tiles/EuropeanETRS89_LAEAQuad?f=html</loc>
</url>
<url>
<loc>http://localhost:8080/tiles/WebMercatorQuad?f=html</loc>
</url>
<url>
<loc>http://localhost:8080/tileMatrixSets?f=html</loc>
</url>
Expand All @@ -44,7 +47,10 @@
<url>
<loc>http://localhost:8080/tileMatrixSets/EuropeanETRS89_LAEAQuad?f=html</loc>
</url>
<url>
<loc>http://localhost:8080/tileMatrixSets/WebMercatorQuad?f=html</loc>
</url>
<url>
<loc>http://localhost:8080/styles?f=html</loc>
</url>
</urlset>
</urlset>
102 changes: 101 additions & 1 deletion internal/ogc/tiles/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@ package tiles
import (
"errors"
"fmt"
"log"
"net/http"
"net/url"
"os"
"strconv"
"strings"

"github.com/PDOK/gokoala/config"
"github.com/PDOK/gokoala/internal/engine"
"github.com/PDOK/gokoala/internal/engine/util"
g "github.com/PDOK/gokoala/internal/ogc/common/geospatial"
"github.com/go-chi/chi/v5"
"gopkg.in/yaml.v3"
)

const (
Expand All @@ -23,6 +27,7 @@ const (
defaultTilesTmpl = "{tms}/{z}/{x}/{y}." + engine.FormatMVTAlternative
collectionsCrumb = "collections/"
tilesCrumbTitle = "Tiles"
tmsLimitsDir = "internal/ogc/tiles/tileMatrixSetLimits/"
)

var (
Expand Down Expand Up @@ -58,12 +63,24 @@ type templateData struct {
}

type Tiles struct {
engine *engine.Engine
engine *engine.Engine
tileMatrixSetLimits map[string]map[int]TileMatrixSetLimits
}

type TileMatrixSetLimits struct {
MinCol int `yaml:"minCol" json:"minCol"`
MaxCol int `yaml:"maxCol" json:"maxCol"`
MinRow int `yaml:"minRow" json:"minRow"`
MaxRow int `yaml:"maxRow" json:"maxRow"`
}

func NewTiles(e *engine.Engine) *Tiles {
tiles := &Tiles{engine: e}

// TileMatrixSetLimits
supportedProjections := e.Config.OgcAPI.Tiles.GetProjections()
tiles.tileMatrixSetLimits = readTileMatrixSetLimits(supportedProjections)

// TileMatrixSets
renderTileMatrixTemplates(e)
e.Router.Get(tileMatrixSetsPath, tiles.TileMatrixSets())
Expand Down Expand Up @@ -163,6 +180,23 @@ func (t *Tiles) Tile(tilesConfig config.Tiles) http.HandlerFunc {
engine.RenderProblemAndLog(engine.ProblemBadRequest, w, err, err.Error())
return
}
tm, tr, tc, err := parseTileParams(tileMatrix, tileRow, tileCol)
if err != nil {
engine.RenderProblemAndLog(engine.ProblemBadRequest, w, err, strings.ReplaceAll(err.Error(), "strconv.Atoi: ", ""))
return
}

if _, ok := t.tileMatrixSetLimits[tileMatrixSetID]; !ok {
// unknown tileMatrixSet
err = fmt.Errorf("unknown tileMatrixSet '%s'", tileMatrixSetID)
engine.RenderProblemAndLog(engine.ProblemBadRequest, w, err, err.Error())
return
}
err = checkTileMatrixSetLimits(t.tileMatrixSetLimits, tileMatrixSetID, tm, tr, tc)
if err != nil {
engine.RenderProblemAndLog(engine.ProblemNotFound, w, err, err.Error())
return
}

target, err := createTilesURL(tileMatrixSetID, tileMatrix, tileCol, tileRow, tilesConfig)
if err != nil {
Expand All @@ -186,6 +220,23 @@ func (t *Tiles) TileForCollection(tilesConfigByCollection map[string]config.Tile
engine.RenderProblemAndLog(engine.ProblemBadRequest, w, err, err.Error())
return
}
tm, tr, tc, err := parseTileParams(tileMatrix, tileRow, tileCol)
if err != nil {
engine.RenderProblemAndLog(engine.ProblemBadRequest, w, err, strings.ReplaceAll(err.Error(), "strconv.Atoi: ", ""))
return
}

if _, ok := t.tileMatrixSetLimits[tileMatrixSetID]; !ok {
// unknown tileMatrixSet
err = fmt.Errorf("unknown tileMatrixSet '%s'", tileMatrixSetID)
engine.RenderProblemAndLog(engine.ProblemBadRequest, w, err, err.Error())
return
}
err = checkTileMatrixSetLimits(t.tileMatrixSetLimits, tileMatrixSetID, tm, tr, tc)
if err != nil {
engine.RenderProblemAndLog(engine.ProblemNotFound, w, err, err.Error())
return
}

tilesConfig, ok := tilesConfigByCollection[collectionID]
if !ok {
Expand Down Expand Up @@ -300,6 +351,13 @@ func renderTilesTemplates(e *engine.Engine, collection *config.GeoSpatialCollect
},
}...)
path = g.CollectionsPath + "/" + collectionID + tilesPath + "/" + projection
} else {
projectionBreadcrumbs = append(projectionBreadcrumbs, []engine.Breadcrumb{
{
Name: projection,
Path: path,
},
}...)
}
e.RenderTemplatesWithParams(path,
data,
Expand All @@ -319,3 +377,45 @@ func getCollectionTitle(collectionID string, metadata *config.GeoSpatialCollecti
}
return collectionID
}

func readTileMatrixSetLimits(supportedProjections []config.SupportedSrs) map[string]map[int]TileMatrixSetLimits {
tileMatrixSetLimits := make(map[string]map[int]TileMatrixSetLimits)
for _, supportedSrs := range supportedProjections {
tileMatrixSetID := config.AllTileProjections[supportedSrs.Srs]
yamlFile, err := os.ReadFile(tmsLimitsDir + tileMatrixSetID + ".yaml")
if err != nil {
log.Fatalf("unable to read file %s", tileMatrixSetID+".yaml")
}
tmsLimits := make(map[int]TileMatrixSetLimits)
err = yaml.Unmarshal(yamlFile, &tmsLimits)
if err != nil {
log.Fatalf("cannot unmarshal yaml: %v", err)
}
// keep only the zoomlevels supported
for tm := range tmsLimits {
if tm < supportedSrs.ZoomLevelRange.Start || tm > supportedSrs.ZoomLevelRange.End {
delete(tmsLimits, tm)
}
}
tileMatrixSetLimits[tileMatrixSetID] = tmsLimits
}
return tileMatrixSetLimits
}

func parseTileParams(tileMatrix, tileRow, tileCol string) (int, int, int, error) {
tm, tmErr := strconv.Atoi(tileMatrix)
tr, trErr := strconv.Atoi(tileRow)
tc, tcErr := strconv.Atoi(tileCol)
return tm, tr, tc, errors.Join(tmErr, trErr, tcErr)
}

func checkTileMatrixSetLimits(tileMatrixSetLimits map[string]map[int]TileMatrixSetLimits, tileMatrixSetID string, tileMatrix, tileRow, tileCol int) error {
if limits, ok := tileMatrixSetLimits[tileMatrixSetID][tileMatrix]; !ok {
// tileMatrix out of supported range
return fmt.Errorf("tileMatrix %d is out of range", tileMatrix)
} else if tileRow < limits.MinRow || tileRow > limits.MaxRow || tileCol < limits.MinCol || tileCol > limits.MaxCol {
// tileRow and/or tileCol out of supported range
return fmt.Errorf("tileRow/tileCol %d/%d is out of range", tileRow, tileCol)
}
return nil
}
Loading

0 comments on commit 5a79f30

Please sign in to comment.