diff --git a/assets/i18n/active.en.toml b/assets/i18n/active.en.toml
index 45036a1e..a42656ec 100644
--- a/assets/i18n/active.en.toml
+++ b/assets/i18n/active.en.toml
@@ -92,7 +92,8 @@ Format = "Format"
# Collections/Collection page
ViewCollectionAs = "View collection as"
-Extent = "Geographic extent"
+GeographicExtent = "Geographic extent"
+TemporalExtent = "Temporal extent"
GoTo = "Go to the"
ViewIn = "View in the"
Browse = "Browse through the"
@@ -103,3 +104,4 @@ Geometry = "geometry"
Prev = "Previous"
Next = "Next"
Items = "items"
+ReferenceDate = "Date"
diff --git a/assets/i18n/active.nl.toml b/assets/i18n/active.nl.toml
index 434a948a..4a4a3ab2 100644
--- a/assets/i18n/active.nl.toml
+++ b/assets/i18n/active.nl.toml
@@ -98,7 +98,8 @@ Format = "Formaat"
# Collections/Collection page
ViewCollectionAs = "Bekijk collectie als"
-Extent = "Geografische begrenzing"
+GeographicExtent = "Geografische begrenzing"
+TemporalExtent = "Temporele begrenzing"
GoTo = "Ga naar de"
ViewIn = "Bekijk in de"
Browse = "Blader door de"
@@ -109,3 +110,4 @@ Geometry = "geometrie"
Prev = "Vorige"
Next = "Volgende"
Items = "items"
+ReferenceDate = "Peildatum"
diff --git a/engine/config.go b/engine/config.go
index 3678ae37..def140e0 100644
--- a/engine/config.go
+++ b/engine/config.go
@@ -83,7 +83,24 @@ func validate(config *Config) error {
errMessages = append(errMessages, valErr.Error()+"\n")
}
}
- return fmt.Errorf("invalid config provided:\n %v", errMessages)
+ return fmt.Errorf("invalid config provided:\n%v", errMessages)
+ }
+ // custom validations
+ if config.OgcAPI.Features != nil {
+ return validateCollectionsTemporalConfig(config.OgcAPI.Features.Collections)
+ }
+ return nil
+}
+
+func validateCollectionsTemporalConfig(collections GeoSpatialCollections) error {
+ var errMessages []string
+ for _, collection := range collections {
+ if collection.Metadata != nil && collection.Metadata.TemporalProperties != nil && collection.Metadata.Extent.Interval == nil {
+ errMessages = append(errMessages, fmt.Sprintf("validation failed for collection '%s'; field 'Extent.Interval' is required with field 'TemporalProperties'\n", collection.ID))
+ }
+ }
+ if len(errMessages) > 0 {
+ return fmt.Errorf("invalid config provided:\n%v", errMessages)
}
return nil
}
@@ -215,13 +232,14 @@ type GeoSpatialCollection struct {
}
type GeoSpatialCollectionMetadata struct {
- Title *string `yaml:"title"`
- Description *string `yaml:"description"`
- Thumbnail *string `yaml:"thumbnail"`
- Keywords []string `yaml:"keywords"`
- LastUpdated *string `yaml:"lastUpdated"`
- LastUpdatedBy string `yaml:"lastUpdatedBy"`
- Extent *Extent `yaml:"extent"`
+ Title *string `yaml:"title"`
+ Description *string `yaml:"description" validate:"required"`
+ Thumbnail *string `yaml:"thumbnail"`
+ Keywords []string `yaml:"keywords"`
+ LastUpdated *string `yaml:"lastUpdated"`
+ LastUpdatedBy string `yaml:"lastUpdatedBy"`
+ TemporalProperties *TemporalProperties `yaml:"temporalProperties" validate:"omitempty,required_with=Extent.Interval"`
+ Extent *Extent `yaml:"extent"`
}
type CollectionEntry3dGeoVolumes struct {
@@ -294,7 +312,7 @@ type OgcAPIFeatures struct {
Limit Limit `yaml:"limit"`
Datasources *Datasources `yaml:"datasources"` // optional since you can also define datasources at the collection level
Basemap string `yaml:"basemap" default:"OSM"`
- Collections GeoSpatialCollections `yaml:"collections" validate:"required"`
+ Collections GeoSpatialCollections `yaml:"collections" validate:"required,dive"`
// Whether GeoJSON/JSON-FG responses will be validated against the OpenAPI spec
// since it has significant performance impact when dealing with large JSON payloads.
@@ -464,8 +482,14 @@ type ZoomLevelRange struct {
}
type Extent struct {
- Srs string `yaml:"srs" validate:"required,startswith=EPSG:"`
- Bbox []string `yaml:"bbox"`
+ Srs string `yaml:"srs" validate:"required,startswith=EPSG:"`
+ Bbox []string `yaml:"bbox"`
+ Interval []string `yaml:"interval" validate:"omitempty,len=2"`
+}
+
+type TemporalProperties struct {
+ StartDate string `yaml:"startDate" validate:"required"`
+ EndDate string `yaml:"endDate" validate:"required"`
}
type License struct {
diff --git a/engine/templates/openapi/features.go.json b/engine/templates/openapi/features.go.json
index eeb8d39b..eb45f136 100644
--- a/engine/templates/openapi/features.go.json
+++ b/engine/templates/openapi/features.go.json
@@ -1036,7 +1036,7 @@
"datetime": {
"name": "datetime",
"in": "query",
- "description": "Either a date-time or an interval. Date and time expressions adhere to RFC 3339.\nIntervals may be bounded or half-bounded (double-dots at start or end).\n\nExamples:\n\n* A date-time: \"2018-02-12T23:20:50Z\"\n* A bounded interval: \"2018-02-12T00:00:00Z/2018-03-18T12:31:12Z\"\n* Half-bounded intervals: \"2018-02-12T00:00:00Z/..\" or \"../2018-03-18T12:31:12Z\"\n\nOnly features that have a temporal property that intersects the value of\n`datetime` are selected.\n\nIf a feature has multiple temporal properties, it is the decision of the\nserver whether only a single temporal property is used to determine\nthe extent or all relevant temporal properties.",
+ "description": "A date-time (intervals are currently not supported). Date and time expressions adhere to RFC 3339.\n\nExamples:\n\n* A date-time: \"2018-02-12T23:20:50Z\"\n\nOnly features that have a temporal property that intersects the value of\n`datetime` are selected.\n\nIf a feature has multiple temporal properties, it is the decision of the\nserver whether only a single temporal property is used to determine\nthe extent or all relevant temporal properties.",
"required": false,
"style": "form",
"explode": false,
diff --git a/examples/config_features_local.yaml b/examples/config_features_local.yaml
index e7b5c7ed..4a7f8813 100644
--- a/examples/config_features_local.yaml
+++ b/examples/config_features_local.yaml
@@ -67,6 +67,10 @@ ogcApi:
- Address
thumbnail: old.png
lastUpdated: "2030-01-02T12:00:00Z"
+ temporalProperties:
+ startDate: validfrom
+ endDate: validto
extent:
srs: EPSG:4326
bbox: [ "50.2129", "2.52713", "55.7212", "7.37403" ]
+ interval: [ "\"1970-01-01T00:00:00Z\"", "null" ]
diff --git a/examples/resources/addresses-crs84.gpkg b/examples/resources/addresses-crs84.gpkg
index bda24cfc..c57219aa 100644
Binary files a/examples/resources/addresses-crs84.gpkg and b/examples/resources/addresses-crs84.gpkg differ
diff --git a/examples/resources/addresses-etrs89.gpkg b/examples/resources/addresses-etrs89.gpkg
index cb15df2f..9cdd06c2 100644
Binary files a/examples/resources/addresses-etrs89.gpkg and b/examples/resources/addresses-etrs89.gpkg differ
diff --git a/examples/resources/addresses-rd.gpkg b/examples/resources/addresses-rd.gpkg
index d13c53c8..2370bf6b 100644
Binary files a/examples/resources/addresses-rd.gpkg and b/examples/resources/addresses-rd.gpkg differ
diff --git a/ogc/common/geospatial/templates/collection.go.html b/ogc/common/geospatial/templates/collection.go.html
index 70cc873c..d47e0efb 100644
--- a/ogc/common/geospatial/templates/collection.go.html
+++ b/ogc/common/geospatial/templates/collection.go.html
@@ -93,11 +93,19 @@
Features
{{ end }}
{{ if and .Params.Metadata .Params.Metadata.Extent }}
- {{ i18n "Extent" }}
+ {{ i18n "GeographicExtent" }}
({{ .Params.Metadata.Extent.Srs }}):
{{ .Params.Metadata.Extent.Bbox | join ", " }}
{{ end }}
+ {{ if and .Params.Metadata .Params.Metadata.Extent.Interval }}
+
+ {{ i18n "TemporalExtent" }}
+ (ISO-8601):
+ {{ toDate "2006-01-02T15:04:05Z" ((first .Params.Metadata.Extent.Interval) | replace "\"" "") | date "2006-01-02" }} /
+ {{ if not (contains "null" (last .Params.Metadata.Extent.Interval)) }}{{ toDate "2006-01-02T15:04:05Z" ((last .Params.Metadata.Extent.Interval) | replace "\"" "") | date "2006-01-02" }}{{ else }}..{{ end }}
+
+ {{ end }}
diff --git a/ogc/common/geospatial/templates/collection.go.json b/ogc/common/geospatial/templates/collection.go.json
index a50e772d..072f33fb 100644
--- a/ogc/common/geospatial/templates/collection.go.json
+++ b/ogc/common/geospatial/templates/collection.go.json
@@ -17,7 +17,11 @@
"spatial": {
"bbox": [ [ {{ .Params.Metadata.Extent.Bbox | join "," }} ] ],
"crs" : "http://www.opengis.net/def/crs/EPSG/0/{{ trimPrefix "EPSG:" .Params.Metadata.Extent.Srs }}"
- }
+ }{{ if and .Params.Metadata .Params.Metadata.Extent.Interval }},
+ "temporal": {
+ "interval": [ [ {{ .Params.Metadata.Extent.Interval | join ", " }} ] ],
+ "trs" : "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian"
+ }{{ end }}
},
{{ end }}
{{ if and .Config.OgcAPI.Features .Config.OgcAPI.Features.Collections }}
diff --git a/ogc/common/geospatial/templates/collections.go.html b/ogc/common/geospatial/templates/collections.go.html
index 2beb1c9b..b87347b1 100644
--- a/ogc/common/geospatial/templates/collections.go.html
+++ b/ogc/common/geospatial/templates/collections.go.html
@@ -55,11 +55,19 @@
- {{ i18n "Extent" }}
+ {{ i18n "GeographicExtent" }}
({{ $coll.Metadata.Extent.Srs }}):
{{ $coll.Metadata.Extent.Bbox | join ", " }}
{{ end }}
+ {{ if and $coll.Metadata $coll.Metadata.Extent.Interval }}
+
+ {{ i18n "TemporalExtent" }}
+ (ISO-8601):
+ {{ toDate "2006-01-02T15:04:05Z" ((first $coll.Metadata.Extent.Interval) | replace "\"" "") | date "2006-01-02" }} /
+ {{ if not (contains "null" (last $coll.Metadata.Extent.Interval)) }}{{ toDate "2006-01-02T15:04:05Z" ((last $coll.Metadata.Extent.Interval) | replace "\"" "") | date "2006-01-02" }}{{ else }}..{{ end }}
+
+ {{ end }}
{{ if and $coll.Metadata $coll.Metadata.Thumbnail }}
diff --git a/ogc/common/geospatial/templates/collections.go.json b/ogc/common/geospatial/templates/collections.go.json
index 298e96cd..e5108637 100644
--- a/ogc/common/geospatial/templates/collections.go.json
+++ b/ogc/common/geospatial/templates/collections.go.json
@@ -40,7 +40,11 @@
"spatial": {
"bbox": [ [ {{ $coll.Metadata.Extent.Bbox | join "," }} ] ],
"crs" : "http://www.opengis.net/def/crs/EPSG/0/{{ trimPrefix "EPSG:" $coll.Metadata.Extent.Srs }}"
- }
+ }{{ if and $coll.Metadata $coll.Metadata.Extent.Interval }},
+ "temporal": {
+ "interval": [ [ {{ $coll.Metadata.Extent.Interval | join ", " }} ] ],
+ "trs" : "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian"
+ }{{ end }}
}
{{ end }}
{{ if and $cfg.OgcAPI.Features $cfg.OgcAPI.Features.Collections }}
diff --git a/ogc/features/datasources/datasource.go b/ogc/features/datasources/datasource.go
index db53bb71..1303fcf7 100644
--- a/ogc/features/datasources/datasource.go
+++ b/ogc/features/datasources/datasource.go
@@ -2,6 +2,7 @@ package datasources
import (
"context"
+ "time"
"github.com/PDOK/gokoala/ogc/features/domain"
"github.com/go-spatial/geom"
@@ -43,6 +44,9 @@ type FeaturesCriteria struct {
// filtering by bounding box
Bbox *geom.Extent
+ // filtering by reference date
+ TemporalCriteria TemporalCriteria
+
// filtering by properties
PropertyFilters map[string]string
@@ -51,6 +55,15 @@ type FeaturesCriteria struct {
FilterLang string
}
+type TemporalCriteria struct {
+ // reference date
+ ReferenceDate time.Time
+
+ // startDate and endDate properties
+ StartDateProperty string
+ EndDateProperty string
+}
+
// FeatureTableMetadata abstraction to access metadata of a feature table (aka attribute table)
type FeatureTableMetadata interface {
diff --git a/ogc/features/datasources/geopackage/asserts.go b/ogc/features/datasources/geopackage/asserts.go
index c913cf57..be55e8c9 100644
--- a/ogc/features/datasources/geopackage/asserts.go
+++ b/ogc/features/datasources/geopackage/asserts.go
@@ -29,6 +29,14 @@ func assertIndexesExist(
return err
}
+ // assert temporal columns are indexed if configured
+ if coll.Metadata != nil && coll.Metadata.TemporalProperties != nil {
+ temporalBtreeColumns := strings.Join([]string{coll.Metadata.TemporalProperties.StartDate, coll.Metadata.TemporalProperties.EndDate}, ",")
+ if err := assertIndexExists(table.TableName, db, temporalBtreeColumns); err != nil {
+ return err
+ }
+ }
+
// assert the column for each property filter is indexed.
for _, propertyFilter := range coll.Features.Filters.Properties {
if err := assertIndexExists(table.TableName, db, propertyFilter.Name); err != nil {
diff --git a/ogc/features/datasources/geopackage/geopackage.go b/ogc/features/datasources/geopackage/geopackage.go
index 5a68682d..acd8244d 100644
--- a/ogc/features/datasources/geopackage/geopackage.go
+++ b/ogc/features/datasources/geopackage/geopackage.go
@@ -281,21 +281,23 @@ func (g *GeoPackage) makeFeaturesQuery(ctx context.Context, table *featureTable,
func (g *GeoPackage) makeDefaultQuery(table *featureTable, criteria datasources.FeaturesCriteria) (string, map[string]any) {
pfClause, pfNamedParams := propertyFiltersToSQL(criteria.PropertyFilters)
+ temporalClause, temporalNamedParams := temporalCriteriaToSQL(criteria.TemporalCriteria)
defaultQuery := fmt.Sprintf(`
with
- next as (select * from %[1]s where %[2]s >= :fid %[3]s order by %[2]s asc limit :limit + 1),
- prev as (select * from %[1]s where %[2]s < :fid %[3]s order by %[2]s desc limit :limit),
+ next as (select * from %[1]s where %[2]s >= :fid %[3]s %[4]s order by %[2]s asc limit :limit + 1),
+ prev as (select * from %[1]s where %[2]s < :fid %[3]s %[4]s order by %[2]s desc limit :limit),
nextprev as (select * from next union all select * from prev),
nextprevfeat as (select *, lag(%[2]s, :limit) over (order by %[2]s) as prevfid, lead(%[2]s, :limit) over (order by %[2]s) as nextfid from nextprev)
-select * from nextprevfeat where %[2]s >= :fid %[3]s limit :limit
-`, table.TableName, g.fidColumn, pfClause) // don't add user input here, use named params for user input!
+select * from nextprevfeat where %[2]s >= :fid %[3]s %[4]s limit :limit
+`, table.TableName, g.fidColumn, temporalClause, pfClause) // don't add user input here, use named params for user input!
namedParams := map[string]any{
"fid": criteria.Cursor.FID,
"limit": criteria.Limit,
}
maps.Copy(namedParams, pfNamedParams)
+ maps.Copy(namedParams, temporalNamedParams)
return defaultQuery, namedParams
}
@@ -313,6 +315,7 @@ func (g *GeoPackage) makeBboxQuery(table *featureTable, onlyFIDs bool, criteria
// whether to use the BTree index or the property filter index
btreeIndexHint = ""
}
+ temporalClause, temporalNamedParams := temporalCriteriaToSQL(criteria.TemporalCriteria)
bboxQuery := fmt.Sprintf(`
with
@@ -325,14 +328,14 @@ with
from %[1]s f inner join rtree_%[1]s_%[4]s rf on f.%[2]s = rf.id
where rf.minx <= :maxx and rf.maxx >= :minx and rf.miny <= :maxy and rf.maxy >= :miny
and st_intersects((select * from given_bbox), castautomagic(f.%[4]s)) = 1
- and f.%[2]s >= :fid %[6]s
+ and f.%[2]s >= :fid %[6]s %[7]s
order by f.%[2]s asc
limit (select iif(bbox_size == 'small', :limit + 1, 0) from bbox_size)),
next_bbox_btree as (select f.*
- from %[1]s f %[7]s
+ from %[1]s f %[8]s
where f.minx <= :maxx and f.maxx >= :minx and f.miny <= :maxy and f.maxy >= :miny
and st_intersects((select * from given_bbox), castautomagic(f.%[4]s)) = 1
- and f.%[2]s >= :fid %[6]s
+ and f.%[2]s >= :fid %[6]s %[7]s
order by f.%[2]s asc
limit (select iif(bbox_size == 'big', :limit + 1, 0) from bbox_size)),
next as (select * from next_bbox_rtree union all select * from next_bbox_btree),
@@ -340,22 +343,22 @@ with
from %[1]s f inner join rtree_%[1]s_%[4]s rf on f.%[2]s = rf.id
where rf.minx <= :maxx and rf.maxx >= :minx and rf.miny <= :maxy and rf.maxy >= :miny
and st_intersects((select * from given_bbox), castautomagic(f.%[4]s)) = 1
- and f.%[2]s < :fid %[6]s
+ and f.%[2]s < :fid %[6]s %[7]s
order by f.%[2]s desc
limit (select iif(bbox_size == 'small', :limit, 0) from bbox_size)),
prev_bbox_btree as (select f.*
- from %[1]s f %[7]s
+ from %[1]s f %[8]s
where f.minx <= :maxx and f.maxx >= :minx and f.miny <= :maxy and f.maxy >= :miny
and st_intersects((select * from given_bbox), castautomagic(f.%[4]s)) = 1
- and f.%[2]s < :fid %[6]s
+ and f.%[2]s < :fid %[6]s %[7]s
order by f.%[2]s desc
limit (select iif(bbox_size == 'big', :limit, 0) from bbox_size)),
prev as (select * from prev_bbox_rtree union all select * from prev_bbox_btree),
nextprev as (select * from next union all select * from prev),
nextprevfeat as (select *, lag(%[2]s, :limit) over (order by %[2]s) as prevfid, lead(%[2]s, :limit) over (order by %[2]s) as nextfid from nextprev)
-select %[5]s from nextprevfeat where %[2]s >= :fid %[6]s limit :limit
+select %[5]s from nextprevfeat where %[2]s >= :fid %[6]s %[7]s limit :limit
`, table.TableName, g.fidColumn, g.maxBBoxSizeToUseWithRTree, table.GeometryColumnName,
- selectClause, pfClause, btreeIndexHint) // don't add user input here, use named params for user input!
+ selectClause, temporalClause, pfClause, btreeIndexHint) // don't add user input here, use named params for user input!
bboxAsWKT, err := wkt.EncodeString(criteria.Bbox)
if err != nil {
@@ -371,6 +374,7 @@ select %[5]s from nextprevfeat where %[2]s >= :fid %[6]s limit :limit
"miny": criteria.Bbox.MinY(),
"bboxSrid": criteria.InputSRID}
maps.Copy(namedParams, pfNamedParams)
+ maps.Copy(namedParams, temporalNamedParams)
return bboxQuery, namedParams, nil
}
@@ -406,3 +410,14 @@ func propertyFiltersToSQL(pf map[string]string) (sql string, namedParams map[str
}
return sql, namedParams
}
+
+func temporalCriteriaToSQL(temporalCriteria datasources.TemporalCriteria) (sql string, namedParams map[string]any) {
+ namedParams = make(map[string]any)
+ if !temporalCriteria.ReferenceDate.IsZero() {
+ namedParams["referenceDate"] = temporalCriteria.ReferenceDate
+ startDate := temporalCriteria.StartDateProperty
+ endDate := temporalCriteria.EndDateProperty
+ sql = fmt.Sprintf(" and \"%[1]s\" <= :referenceDate and (\"%[2]s\" >= :referenceDate or \"%[2]s\" is null)", startDate, endDate)
+ }
+ return sql, namedParams
+}
diff --git a/ogc/features/html.go b/ogc/features/html.go
index 49c4fdc1..6d94993a 100644
--- a/ogc/features/html.go
+++ b/ogc/features/html.go
@@ -3,6 +3,7 @@ package features
import (
"net/http"
"strconv"
+ "time"
"github.com/PDOK/gokoala/engine"
"github.com/PDOK/gokoala/ogc/features/domain"
@@ -46,6 +47,7 @@ type featureCollectionPage struct {
PrevLink string
NextLink string
Limit int
+ ReferenceDate *time.Time
PropertyFilters map[string]string
}
@@ -59,8 +61,8 @@ type featurePage struct {
}
func (hf *htmlFeatures) features(w http.ResponseWriter, r *http.Request, collectionID string,
- cursor domain.Cursors, featuresURL featureCollectionURL, limit int, propertyFilters map[string]string,
- fc *domain.FeatureCollection) {
+ cursor domain.Cursors, featuresURL featureCollectionURL, limit int, referenceDate *time.Time,
+ propertyFilters map[string]string, fc *domain.FeatureCollection) {
collectionMetadata := collections[collectionID]
@@ -76,6 +78,10 @@ func (hf *htmlFeatures) features(w http.ResponseWriter, r *http.Request, collect
},
}...)
+ if referenceDate.IsZero() {
+ referenceDate = nil
+ }
+
pageContent := &featureCollectionPage{
*fc,
collectionID,
@@ -84,6 +90,7 @@ func (hf *htmlFeatures) features(w http.ResponseWriter, r *http.Request, collect
featuresURL.toPrevNextURL(collectionID, cursor.Prev, engine.FormatHTML),
featuresURL.toPrevNextURL(collectionID, cursor.Next, engine.FormatHTML),
limit,
+ referenceDate,
propertyFilters,
}
diff --git a/ogc/features/main.go b/ogc/features/main.go
index 7fd8d32b..5c705b59 100644
--- a/ogc/features/main.go
+++ b/ogc/features/main.go
@@ -63,6 +63,8 @@ func NewFeatures(e *engine.Engine) *Features {
}
// Features serve a FeatureCollection with the given collectionId
+//
+//nolint:cyclop
func (f *Features) Features() http.HandlerFunc {
cfg := f.engine.Config
@@ -80,7 +82,14 @@ func (f *Features) Features() http.HandlerFunc {
}
url := featureCollectionURL{*cfg.BaseURL.URL, r.URL.Query(), cfg.OgcAPI.Features.Limit,
cfg.OgcAPI.Features.PropertyFiltersForCollection(collectionID)}
- encodedCursor, limit, inputSRID, outputSRID, contentCrs, bbox, propertyFilters, err := url.parse()
+ encodedCursor, limit, inputSRID, outputSRID, contentCrs, bbox, referenceDate, propertyFilters, err := url.parse()
+ var temporalCriteria ds.TemporalCriteria
+ if collection := collections[collectionID]; collection != nil && collection.TemporalProperties != nil {
+ temporalCriteria = ds.TemporalCriteria{
+ ReferenceDate: referenceDate,
+ StartDateProperty: collection.TemporalProperties.StartDate,
+ EndDateProperty: collection.TemporalProperties.EndDate}
+ }
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
@@ -93,12 +102,13 @@ func (f *Features) Features() http.HandlerFunc {
// fast path
datasource := f.datasources[DatasourceKey{srid: outputSRID.GetOrDefault(), collectionID: collectionID}]
fc, newCursor, err = datasource.GetFeatures(r.Context(), collectionID, ds.FeaturesCriteria{
- Cursor: encodedCursor.Decode(url.checksum()),
- Limit: limit,
- InputSRID: inputSRID.GetOrDefault(),
- OutputSRID: outputSRID.GetOrDefault(),
- Bbox: bbox,
- PropertyFilters: propertyFilters,
+ Cursor: encodedCursor.Decode(url.checksum()),
+ Limit: limit,
+ InputSRID: inputSRID.GetOrDefault(),
+ OutputSRID: outputSRID.GetOrDefault(),
+ Bbox: bbox,
+ TemporalCriteria: temporalCriteria,
+ PropertyFilters: propertyFilters,
// Add filter, filter-lang
})
if err != nil {
@@ -110,12 +120,13 @@ func (f *Features) Features() http.HandlerFunc {
var fids []int64
datasource := f.datasources[DatasourceKey{srid: inputSRID.GetOrDefault(), collectionID: collectionID}]
fids, newCursor, err = datasource.GetFeatureIDs(r.Context(), collectionID, ds.FeaturesCriteria{
- Cursor: encodedCursor.Decode(url.checksum()),
- Limit: limit,
- InputSRID: inputSRID.GetOrDefault(),
- OutputSRID: outputSRID.GetOrDefault(),
- Bbox: bbox,
- PropertyFilters: propertyFilters,
+ Cursor: encodedCursor.Decode(url.checksum()),
+ Limit: limit,
+ InputSRID: inputSRID.GetOrDefault(),
+ OutputSRID: outputSRID.GetOrDefault(),
+ Bbox: bbox,
+ TemporalCriteria: temporalCriteria,
+ PropertyFilters: propertyFilters,
// Add filter, filter-lang
})
if err == nil && fids != nil {
@@ -133,7 +144,7 @@ func (f *Features) Features() http.HandlerFunc {
switch f.engine.CN.NegotiateFormat(r) {
case engine.FormatHTML:
- f.html.features(w, r, collectionID, newCursor, url, limit, propertyFilters, fc)
+ f.html.features(w, r, collectionID, newCursor, url, limit, &referenceDate, propertyFilters, fc)
case engine.FormatGeoJSON, engine.FormatJSON:
f.json.featuresAsGeoJSON(w, r, collectionID, newCursor, url, fc)
case engine.FormatJSONFG:
diff --git a/ogc/features/templates/features.go.html b/ogc/features/templates/features.go.html
index d5127940..68170eb0 100644
--- a/ogc/features/templates/features.go.html
+++ b/ogc/features/templates/features.go.html
@@ -9,7 +9,11 @@
const url = new URL(window.location.href);
url.searchParams.delete('cursor'); // when filters change, we can't continue pagination.
if (value) {
- url.searchParams.set(name, value);
+ if (name === 'datetime') {
+ url.searchParams.set(name, new Date(value).toISOString()); // input is %Y-%m-%d, but parameter value should be RFC3339
+ } else {
+ url.searchParams.set(name, value);
+ }
} else {
url.searchParams.delete(name);
}
@@ -54,6 +58,16 @@