Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 77 additions & 3 deletions ats_schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,9 +409,8 @@ func TestATSSchemaBundleMatchesOGC(t *testing.T) {
// reason is the standard's, not the tier's: `temporalPrimitiveValue` declares
// `datetimes` an array of `minItems: 2` beside `values` as a single scalar
// (`oneOf` number/string/boolean), so no document carrying a value per instant
// satisfies it; and its `interpolation` enum reads Discrete/Step/Linear/Regression
// where the temporal-geometry half of the same document names Stepwise. This
// asserts the cell that has one authority, which is the type token.
// satisfies it. This asserts the cells that are satisfiable, of which the type
// token is one.
func TestATSSchemaTemporalPropertyType(t *testing.T) {
f, err := os.Open(ogcBundlePath)
if err != nil {
Expand Down Expand Up @@ -465,3 +464,78 @@ func TestATSSchemaTemporalPropertyType(t *testing.T) {
}
}
}

// The interpolation the tier writes is one the standard names.
//
// ⛔ BOTH ADMITTED SETS ARE READ OUT OF THE VENDORED DOCUMENT. Part 1 constrains
// an interpolation in exactly two places and they are not the same list:
// `motionCurve` (what a temporal geometry takes, through a $ref that a walk over
// `temporalPrimitiveGeometry` alone does not see) and `temporalPrimitiveValue`.
// The step function is `Step` in both, and the word `Stepwise` — the older
// MF-JSON encoding extension's spelling — appears nowhere in the document.
func TestATSSchemaInterpolationToken(t *testing.T) {
raw, err := os.ReadFile(ogcBundlePath)
if err != nil {
t.Fatal(err)
}
if n := bytes.Count(raw, []byte(`"Stepwise"`)); n != 0 {
t.Errorf(`the vendored document names "Stepwise" %d times; the tier writes "Step" because it named it 0`, n)
}
var doc struct {
Components struct {
Schemas struct {
MotionCurve struct {
OneOf []struct {
Enum []string `json:"enum"`
} `json:"oneOf"`
} `json:"motionCurve"`
TemporalPrimitiveValue struct {
Properties struct {
Interpolation struct {
Enum []string `json:"enum"`
} `json:"interpolation"`
} `json:"properties"`
} `json:"temporalPrimitiveValue"`
} `json:"schemas"`
} `json:"components"`
}
if err := json.Unmarshal(raw, &doc); err != nil {
t.Fatalf("reading %s: %v", ogcBundlePath, err)
}
var curve []string
for _, b := range doc.Components.Schemas.MotionCurve.OneOf {
curve = append(curve, b.Enum...)
}
value := doc.Components.Schemas.TemporalPrimitiveValue.Properties.Interpolation.Enum
if len(curve) == 0 || len(value) == 0 {
t.Fatal("the vendored document declares no interpolation enum, so this test would assert nothing")
}
in := func(set []string, s string) bool {
for _, a := range set {
if a == s {
return true
}
}
return false
}
// What the tier writes: MobilityDB's own token, carried through ogcify.
const geometry = `{"type":"MovingPoint","interpolation":"Step"}`
const valueSeq = `{"interpolation":"Step","values":[1,2]}`
for _, c := range []struct {
what, doc string
admitted []string
}{
{"a temporal geometry", geometry, curve},
{"a temporal property value", valueSeq, value},
} {
var m map[string]any
if err := json.Unmarshal([]byte(ogcify(c.doc)), &m); err != nil {
t.Fatalf("%s: ogcify produced no JSON: %v", c.what, err)
}
got, _ := m["interpolation"].(string)
if !in(c.admitted, got) {
t.Errorf("%s is written with interpolation %q, which the standard does not name; it admits %v",
c.what, got, c.admitted)
}
}
}
19 changes: 15 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,18 @@ var (
)

// OGC <-> MobilityDB conventions (assessed against live MobilityDB):
// MobilityDB rejects "Stepwise" and uses "Step"; crs is "EPSG:<n>" vs the URN.
var ogc2mdbInterp = map[string]string{"Linear": "Linear", "Stepwise": "Step", "Discrete": "Discrete"}
// crs is "EPSG:<n>" vs the URN.
//
// The step function is "Step" on both sides. OGC API - Moving Features Part 1
// names it so in each of the two places it constrains an interpolation —
// `motionCurve` (Discrete/Step/Linear/Quadratic/Cubic, what a temporal geometry
// takes) and `temporalPrimitiveValue` (Discrete/Step/Linear/Regression) — and
// the word "Stepwise" appears nowhere in that standard. "Stepwise" belongs to
// the older MF-JSON encoding extension, and is accepted on input for a client
// that still writes it.
var ogc2mdbInterp = map[string]string{
"Linear": "Linear", "Step": "Step", "Discrete": "Discrete", "Stepwise": "Step",
}
var epsgName = regexp.MustCompile(`"name":\s*"EPSG:(\d+)"`)
var epsgURN = regexp.MustCompile(`EPSG:+(\d+)`)

Expand All @@ -55,9 +65,10 @@ var hourOnlyOffset = regexp.MustCompile(`(\d\d:\d\d:\d\d(?:\.\d+)?)([+-]\d\d)(["
// rfc3339Tz completes hour-only timezone offsets so datetimes are RFC 3339.
func rfc3339Tz(s string) string { return hourOnlyOffset.ReplaceAllString(s, `$1$2:00$3`) }

// ogcify turns what MobilityDB writes into what the standard names. The
// interpolation needs nothing: MobilityDB's "Step" is already the token both
// `motionCurve` and `temporalPrimitiveValue` enumerate.
func ogcify(s string) string {
s = strings.ReplaceAll(s, `"interpolation": "Step"`, `"interpolation": "Stepwise"`)
s = strings.ReplaceAll(s, `"interpolation":"Step"`, `"interpolation":"Stepwise"`)
s = rfc3339Tz(s)
return epsgName.ReplaceAllString(s, `"name":"urn:ogc:def:crs:EPSG::$1"`)
}
Expand Down
2 changes: 1 addition & 1 deletion samples/temporal-geometry-velocity.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"2026-01-01T08:25:00+00:00",
"2026-01-01T08:45:00+00:00"
],
"interpolation": "Stepwise",
"interpolation": "Step",
"lower_inc": true,
"upper_inc": true,
"values": [
Expand Down
2 changes: 1 addition & 1 deletion samples/temporal-properties.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,6 @@
"type": "TText"
}
],
"timeStamp": "2026-08-29T17:08:43Z"
"timeStamp": "2026-08-29T17:29:23Z"
}

2 changes: 1 addition & 1 deletion samples/temporal-property.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"2026-01-01T08:00:00+00:00",
"2026-01-01T08:45:00+00:00"
],
"interpolation": "Stepwise",
"interpolation": "Step",
"lower_inc": true,
"upper_inc": true,
"values": [
Expand Down
Loading