-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfeature.go
More file actions
148 lines (136 loc) · 5.08 KB
/
Copy pathfeature.go
File metadata and controls
148 lines (136 loc) · 5.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// OGC MovingFeature assembly in the tier. The engine returns the canonical
// MEOS-function outputs (asMFJSON text, the STBOX accessors, the trajectory
// GeoJSON) plus the plain feature columns; the OGC Feature / FeatureCollection
// envelope is assembled here, once, for every backend. No temporal work runs
// in the tier — only JSON framing, which is not a MEOS concern.
package main
import (
"encoding/json"
"strconv"
)
type crsName struct {
Type string `json:"type"`
Properties map[string]string `json:"properties"`
}
type trsLink struct {
Type string `json:"type"`
Properties map[string]string `json:"properties"`
}
type ogcLink struct {
Rel string `json:"rel"`
Href string `json:"href"`
}
// mfFeature is the OGC API – Moving Features "MovingFeature" object. Field order
// follows the MF-JSON shape; the temporalGeometry / geometry / properties are
// spliced verbatim from the engine (already-serialized JSON).
type mfFeature struct {
Type string `json:"type"`
ID string `json:"id"`
Properties json.RawMessage `json:"properties"`
CRS crsName `json:"crs"`
TRS trsLink `json:"trs"`
Bbox []float64 `json:"bbox"`
Time []string `json:"time"`
TemporalGeometry json.RawMessage `json:"temporalGeometry"`
Geometry json.RawMessage `json:"geometry,omitempty"`
Links []ogcLink `json:"links"`
}
// exportFeature is the lighter NDJSON lakehouse-feed Feature: identity,
// properties and the temporalGeometry, without the OGC hypermedia envelope.
type exportFeature struct {
Type string `json:"type"`
ID string `json:"id"`
Properties json.RawMessage `json:"properties"`
TemporalGeometry json.RawMessage `json:"temporalGeometry"`
}
func buildExportFeature(id int64, props json.RawMessage, tgeom []byte) ([]byte, error) {
return json.Marshal(exportFeature{
Type: "Feature", ID: strconv.FormatInt(id, 10),
Properties: props,
TemporalGeometry: json.RawMessage(ogcify(string(tgeom))),
})
}
func scanExportRow(rows Rows, generic bool) (id int64, props json.RawMessage, tgeom []byte, err error) {
var tg string
if generic {
var pt string
err = rows.Scan(&id, &pt, &tg)
props = json.RawMessage(pt)
} else {
var mmsi *int64
var name *string
err = rows.Scan(&id, &mmsi, &name, &tg)
props = typedProps(mmsi, name)
}
tgeom = []byte(tg)
return
}
func crsURN(srid int) crsName {
return crsName{Type: "Name", Properties: map[string]string{
"name": "urn:ogc:def:crs:EPSG::" + strconv.Itoa(srid)}}
}
var trsGregorian = trsLink{Type: "Link", Properties: map[string]string{
"type": "ogcdef", "href": "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian"}}
// buildFeature assembles one MovingFeature. tgeom is the asMFJSON text and geom
// the trajectory GeoJSON (nil to omit); both are rewritten by ogcify to the OGC
// interpolation/crs vocabulary. props is the already-serialized properties JSON.
func buildFeature(id int64, props json.RawMessage, srid int, bbox []float64, tmin, tmax string, tgeom, geom []byte, cid string) ([]byte, error) {
f := mfFeature{
Type: "Feature", ID: strconv.FormatInt(id, 10),
Properties: props,
CRS: crsURN(srid),
TRS: trsGregorian,
Bbox: bbox,
Time: []string{tmin, tmax},
TemporalGeometry: json.RawMessage(ogcify(string(tgeom))),
Links: []ogcLink{{Rel: "self", Href: "/collections/" + cid + "/items/" + strconv.FormatInt(id, 10)}},
}
if geom != nil {
f.Geometry = json.RawMessage(ogcify(string(geom)))
}
return json.Marshal(f)
}
// propSel is the feature row's property projection: the stored properties text
// for generic collections, the typed (mmsi, name) columns for ships (assembled
// in the tier by typedProps, not a per-engine json builder in SQL).
func propSel(generic bool) string {
if generic {
return "CAST(coalesce(properties,CAST('{}' AS jsonb)) AS text)"
}
return "mmsi, name"
}
// scanFeatureRow reads one row of the feature projection (id, properties,
// bbox xmin..ymax, tmin, tmax, asMFJSON) for either schema mode.
func scanFeatureRow(rows Rows, generic bool) (id int64, props json.RawMessage, bbox []float64, tmin, tmax string, tgeom []byte, err error) {
bbox = make([]float64, 4)
var tg string
if generic {
var pt string
err = rows.Scan(&id, &pt, &bbox[0], &bbox[1], &bbox[2], &bbox[3], &tmin, &tmax, &tg)
props = json.RawMessage(pt)
} else {
var mmsi *int64
var name *string
err = rows.Scan(&id, &mmsi, &name, &bbox[0], &bbox[1], &bbox[2], &bbox[3], &tmin, &tmax, &tg)
props = typedProps(mmsi, name)
}
tgeom = []byte(tg)
return
}
// typedProps builds the properties object for the typed ships schema in the
// tier (engine-agnostic), instead of a per-engine json builder in SQL.
func typedProps(mmsi *int64, name *string) json.RawMessage {
m := map[string]any{}
if mmsi != nil {
m["mmsi"] = *mmsi
} else {
m["mmsi"] = nil
}
if name != nil {
m["name"] = *name
} else {
m["name"] = nil
}
b, _ := json.Marshal(m)
return b
}