Skip to content

Commit

Permalink
Merge pull request #100 from PDOK/cleanup
Browse files Browse the repository at this point in the history
Cleanup
  • Loading branch information
rkettelerij authored Jan 11, 2024
2 parents 4c929fe + 5cc4c24 commit 77cee31
Show file tree
Hide file tree
Showing 17 changed files with 82 additions and 166 deletions.
13 changes: 0 additions & 13 deletions engine/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,6 @@ func (c *Config) AllCollections() GeoSpatialCollections {
if c.OgcAPI.Features != nil {
result = append(result, c.OgcAPI.Features.Collections...)
}
if c.OgcAPI.Maps != nil {
result = append(result, c.OgcAPI.Maps.Collections...)
}
return result
}

Expand Down Expand Up @@ -147,7 +144,6 @@ type OgcAPI struct {
Tiles *OgcAPITiles `yaml:"tiles" validate:"required_with=Styles"`
Styles *OgcAPIStyles `yaml:"styles"`
Features *OgcAPIFeatures `yaml:"features"`
Maps *OgcAPIMaps `yaml:"maps"`
Processes *OgcAPIProcesses `yaml:"processes"`
}

Expand Down Expand Up @@ -206,7 +202,6 @@ type GeoSpatialCollection struct {
GeoVolumes *CollectionEntry3dGeoVolumes `yaml:",inline"`
Tiles *CollectionEntryTiles `yaml:",inline"`
Features *CollectionEntryFeatures `yaml:",inline"`
Maps *CollectionEntryMaps `yaml:",inline"`
}

type GeoSpatialCollectionMetadata struct {
Expand Down Expand Up @@ -265,10 +260,6 @@ type CollectionEntryFeatures struct {
} `yaml:"filters"`
}

type CollectionEntryMaps struct {
// placeholder
}

type OgcAPI3dGeoVolumes struct {
TileServer YAMLURL `yaml:"tileServer" validate:"required,url"`
Collections GeoSpatialCollections `yaml:"collections"`
Expand Down Expand Up @@ -329,10 +320,6 @@ func (oaf OgcAPIFeatures) PropertyFiltersForCollection(collectionID string) []Pr
return []PropertyFilter{}
}

type OgcAPIMaps struct {
Collections GeoSpatialCollections `yaml:"collections"`
}

type OgcAPIProcesses struct {
SupportsDismiss bool `yaml:"supportsDismiss"`
SupportsCallback bool `yaml:"supportsCallback"`
Expand Down
33 changes: 7 additions & 26 deletions engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (

"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
)

const (
Expand Down Expand Up @@ -67,6 +66,13 @@ func NewEngineWithConfig(config *Config, openAPIFile string, enableTrailingSlash
CN: contentNegotiation,
Router: router,
}

if config.Resources != nil {
newResourcesEndpoint(engine) // Resources endpoint to serve static assets
}
router.Get("/health", func(w http.ResponseWriter, r *http.Request) {
SafeWrite(w.Write, []byte("OK")) // Health endpoint
})
return engine
}

Expand Down Expand Up @@ -338,28 +344,3 @@ func SafeWrite(write func([]byte) (int, error), body []byte) {
log.Printf("failed to write response: %v", err)
}
}

func newRouter(version string, enableTrailingSlash bool, enableCORS bool) *chi.Mux {
router := chi.NewRouter()
router.Use(middleware.RealIP) // should be first middleware
router.Use(middleware.Logger) // log to console
router.Use(middleware.Recoverer) // catch panics and turn into 500s
router.Use(middleware.GetHead) // support HEAD requests https://docs.ogc.org/is/17-069r4/17-069r4.html#_http_1_1
if enableTrailingSlash {
router.Use(middleware.StripSlashes)
}
if enableCORS {
router.Use(cors.Handler(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{http.MethodGet, http.MethodHead, http.MethodOptions},
AllowedHeaders: []string{HeaderRequestedWith},
ExposedHeaders: []string{HeaderContentCrs, HeaderLink},
AllowCredentials: false,
MaxAge: int((time.Hour * 24).Seconds()),
}))
}
// implements https://gitdocumentatie.logius.nl/publicatie/api/adr/#api-57
router.Use(middleware.SetHeader(HeaderAPIVersion, version))
router.Use(middleware.Compress(5, CompressibleMediaTypes...)) // enable gzip responses
return router
}
2 changes: 1 addition & 1 deletion engine/resources_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type ResourcesEndpoint struct {
engine *Engine
}

func NewResourcesEndpoint(e *Engine) *ResourcesEndpoint {
func newResourcesEndpoint(e *Engine) *ResourcesEndpoint {
resources := &ResourcesEndpoint{
engine: e,
}
Expand Down
35 changes: 35 additions & 0 deletions engine/router.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package engine

import (
"net/http"
"time"

"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
)

func newRouter(version string, enableTrailingSlash bool, enableCORS bool) *chi.Mux {
router := chi.NewRouter()
router.Use(middleware.RealIP) // should be first middleware
router.Use(middleware.Logger) // log to console
router.Use(middleware.Recoverer) // catch panics and turn into 500s
router.Use(middleware.GetHead) // support HEAD requests https://docs.ogc.org/is/17-069r4/17-069r4.html#_http_1_1
if enableTrailingSlash {
router.Use(middleware.StripSlashes)
}
if enableCORS {
router.Use(cors.Handler(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{http.MethodGet, http.MethodHead, http.MethodOptions},
AllowedHeaders: []string{HeaderRequestedWith},
ExposedHeaders: []string{HeaderContentCrs, HeaderLink},
AllowCredentials: false,
MaxAge: int((time.Hour * 24).Seconds()),
}))
}
// implements https://gitdocumentatie.logius.nl/publicatie/api/adr/#api-57
router.Use(middleware.SetHeader(HeaderAPIVersion, version))
router.Use(middleware.Compress(5, CompressibleMediaTypes...)) // enable gzip responses
return router
}
10 changes: 0 additions & 10 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package main
import (
"log"
"net"
"net/http"
"os"
"strconv"

Expand Down Expand Up @@ -135,13 +134,4 @@ func setupOGCBuildingBlocks(engine *eng.Engine) {
if engine.Config.OgcAPI.Processes != nil {
processes.NewProcesses(engine)
}

// Resources endpoint to serve static assets
if engine.Config.Resources != nil {
eng.NewResourcesEndpoint(engine)
}
// Health endpoint
engine.Router.Get("/health", func(w http.ResponseWriter, r *http.Request) {
eng.SafeWrite(w.Write, []byte("OK"))
})
}
3 changes: 2 additions & 1 deletion ogc/common/geospatial/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ OpenAPI

Responses:
- Expand the `collections` and `collection` [templates](./templates).
- Implement collection support in the given OGC building block code by implementing the `CollectionContent` interface.
- Implement an endpoint in your specific OGC API building block to serve the CONTENTS of a collection
(e.g. `/collection/{collectionId}/tiles`)

Testing:
- Add unit tests
Expand Down
26 changes: 10 additions & 16 deletions ogc/common/geospatial/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ type Collections struct {
engine *engine.Engine
}

// NewCollections enables support for OGC APIs that organize data in the concept of collections.
// A collection, also known as a geospatial data resource, is a common way to organize data in various OGC APIs.
func NewCollections(e *engine.Engine) *Collections {
if e.Config.HasCollections() {
collectionsBreadcrumbs := []engine.Breadcrumb{
Expand Down Expand Up @@ -68,6 +70,14 @@ func (c *Collections) Collections() http.HandlerFunc {
}
}

// Collection provides METADATA about a specific collection. To get the CONTENTS of a collection each OGC API
// building block must provide a separate/specific endpoint.
//
// For example in:
// - OGC API Features you would have: /collections/{collectionId}/items
// - OGC API Tiles could have: /collections/{collectionId}/tiles
// - OGC API Maps could have: /collections/{collectionId}/maps
// - OGC API 3d GeoVolumes would have: /collections/{collectionId}/3dtiles
func (c *Collections) Collection() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
collectionID := chi.URLParam(r, "collectionId")
Expand All @@ -76,19 +86,3 @@ func (c *Collections) Collection() http.HandlerFunc {
c.engine.ServePage(w, r, key)
}
}

// CollectionSupport a collection, also known as a geospatial data resource, is a common way to organize
// data in various OGC APIs.
type CollectionSupport interface {

// CollectionContent While the generic /collections/{collectionId} endpoint provides METADATA about
// a collection this endpoint should provide the CONTENTS of a collection.
//
// For example in:
// - OGC API Features you would have: /collections/{collectionId}/items
// - OGC API Tiles could have: /collections/{collectionId}/tiles
// - OGC API Maps could have: /collections/{collectionId}/maps
// - OGC API 3d GeoVolumes could have: /collections/{collectionId}/3dtiles
// etc.
CollectionContent(args ...any) http.HandlerFunc
}
11 changes: 0 additions & 11 deletions ogc/common/geospatial/templates/collection.go.html
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,6 @@ <h5 class="card-title">Features</h5>

{{ end }}
{{ end }}

{{ if and .Config.OgcAPI.Maps .Config.OgcAPI.Maps.Collections }}
{{ if .Config.OgcAPI.Maps.Collections.ContainsID .Params.ID }}
<li class="list-group-item">
<h5 class="card-title">Maps</h5>
<ul>
<li>TODO (placeholder)</li>
</ul>
</li>
{{ end }}
{{ end }}
</ul>
<!-- end specific part per OGC spec -->

Expand Down
9 changes: 0 additions & 9 deletions ogc/common/geospatial/templates/collection.go.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,6 @@
"href" : "{{ .Config.BaseURL }}/collections/{{ .Params.ID }}/items?f=html"
}
{{ end }}
{{ if and .Config.OgcAPI.Maps .Config.OgcAPI.Maps.Collections }}
,
{
"rel" : "items",
"type" : "application/json",
"title" : "The JSON representation of the {{ .Params.ID }} raster map tileset served from this endpoint",
"href" : "{{ .Config.BaseURL }}/collections/{{ .Params.ID }}/map/tiles?f=json"
},
{{ end }}
]
{{ if and .Config.OgcAPI.GeoVolumes .Config.OgcAPI.GeoVolumes.Collections }}
,
Expand Down
11 changes: 0 additions & 11 deletions ogc/common/geospatial/templates/collections.go.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,17 +124,6 @@
{{/* placeholder for more links*/}}
{{end}}
{{end}}
{{ if and $cfg.OgcAPI.Maps $cfg.OgcAPI.Maps.Collections }}
{{ if $cfg.OgcAPI.Maps.Collections.ContainsID $coll.ID }}
,{
"rel" : "items",
"type" : "application/json",
"title" : "The JSON representation of the {{ $coll.ID }} raster map tileset served from this endpoint",
"href" : "{{ $baseUrl }}/collections/{{ $coll.ID }}/map/tiles?f=json"
}
{{/* placeholder for more links*/}}
{{end}}
{{end}}
],
"content" : [
{{ if and $cfg.OgcAPI.GeoVolumes $cfg.OgcAPI.GeoVolumes.Collections }}
Expand Down
18 changes: 9 additions & 9 deletions ogc/features/datasources/geopackage/geopackage.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,6 @@ func (ft featureTable) ColumnsWithDataType() map[string]string {
return ft.ColumnsWithDateType
}

func (g *GeoPackage) GetFeatureTableMetadata(collection string) (datasources.FeatureTableMetadata, error) {
val, ok := g.featureTableByCollectionID[collection]
if !ok {
return nil, fmt.Errorf("no metadata for %s", collection)
}
return val, nil
}

type GeoPackage struct {
backend geoPackageBackend

Expand Down Expand Up @@ -259,6 +251,14 @@ func (g *GeoPackage) GetFeature(ctx context.Context, collection string, featureI
return features[0], nil
}

func (g *GeoPackage) GetFeatureTableMetadata(collection string) (datasources.FeatureTableMetadata, error) {
val, ok := g.featureTableByCollectionID[collection]
if !ok {
return nil, fmt.Errorf("no metadata for %s", collection)
}
return val, nil
}

// Build specific features queries based on the given options.
// Make sure to use SQL bind variables and return named params: https://jmoiron.github.io/sqlx/#namedParams
func (g *GeoPackage) makeFeaturesQuery(table *featureTable, onlyFIDs bool, criteria datasources.FeaturesCriteria) (string, map[string]any, error) {
Expand Down Expand Up @@ -380,7 +380,7 @@ func propertyFiltersToSQL(pf map[string]string) (sql string, namedParams map[str
position++
namedParam := fmt.Sprintf("pf%d", position)
// column name in double quotes in case it is a reserved keyword
// also: we don't currently support LIKE since wildcard searches don't using the index
// also: we don't currently support LIKE since wildcard searches don't use the index
sql += fmt.Sprintf(" and \"%s\" = :%s", k, namedParam)
namedParams[namedParam] = v
}
Expand Down
18 changes: 7 additions & 11 deletions ogc/features/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ const (
)

var (
collections map[string]*engine.GeoSpatialCollectionMetadata
collections map[string]*engine.GeoSpatialCollectionMetadata
emptyFeatureCollection = &domain.FeatureCollection{Features: make([]*domain.Feature, 0)}
)

type DatasourceKey struct {
Expand Down Expand Up @@ -56,13 +57,13 @@ func NewFeatures(e *engine.Engine) *Features {
json: newJSONFeatures(e),
}

e.Router.Get(geospatial.CollectionsPath+"/{collectionId}/items", f.CollectionContent())
e.Router.Get(geospatial.CollectionsPath+"/{collectionId}/items", f.Features())
e.Router.Get(geospatial.CollectionsPath+"/{collectionId}/items/{featureId}", f.Feature())
return f
}

// CollectionContent serve a FeatureCollection with the given collectionId
func (f *Features) CollectionContent(_ ...any) http.HandlerFunc {
// Features serve a FeatureCollection with the given collectionId
func (f *Features) Features() http.HandlerFunc {
cfg := f.engine.Config

return func(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -129,11 +130,7 @@ func (f *Features) CollectionContent(_ ...any) http.HandlerFunc {
}
}
if fc == nil {
log.Printf("no results found for collection '%s' with params: %s",
collectionID, r.URL.Query().Encode())
fc = &domain.FeatureCollection{
Features: make([]*domain.Feature, 0),
}
fc = emptyFeatureCollection
}

switch f.engine.CN.NegotiateFormat(r) {
Expand Down Expand Up @@ -187,8 +184,7 @@ func (f *Features) Feature() http.HandlerFunc {
return
}
if feat == nil {
log.Printf("no result found for collection '%s' and feature id: %d",
collectionID, featureID)
log.Printf("no result found for feature id: %d in collection '%s'", featureID, collectionID)
http.NotFound(w, r)
return
}
Expand Down
2 changes: 1 addition & 1 deletion ogc/features/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ func TestFeatures_CollectionContent(t *testing.T) {

newEngine := engine.NewEngine(tt.fields.configFile, "", false, true)
features := NewFeatures(newEngine)
handler := features.CollectionContent()
handler := features.Features()
handler.ServeHTTP(rr, req)

assert.Equal(t, tt.fields.contentCrs, rr.Header().Get("Content-Crs"))
Expand Down
Loading

0 comments on commit 77cee31

Please sign in to comment.