From a0f274afe1d0499c79336636e38bd5954ab04522 Mon Sep 17 00:00:00 2001 From: Richard Kettelerij Date: Fri, 20 Sep 2024 10:06:18 +0200 Subject: [PATCH] feat(oaf): Add support to return feature properties in a specific order + take relations into account. --- internal/ogc/features/domain/mapper.go | 7 ++-- internal/ogc/features/domain/props.go | 47 +++++++++++++++++++------- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/internal/ogc/features/domain/mapper.go b/internal/ogc/features/domain/mapper.go index b24b346e..9fcc16dc 100644 --- a/internal/ogc/features/domain/mapper.go +++ b/internal/ogc/features/domain/mapper.go @@ -58,7 +58,7 @@ func MapRowsToFeatures(rows *sqlx.Rows, fidColumn string, externalFidColumn stri return result, nil, err } - orderProps := propConfig != nil && propConfig.PropertiesInSpecificOrder + propertiesOrder := propConfig != nil && propConfig.PropertiesInSpecificOrder firstRow := true var prevNextID *PrevNextFID for rows.Next() { @@ -66,7 +66,7 @@ func MapRowsToFeatures(rows *sqlx.Rows, fidColumn string, externalFidColumn stri if values, err = rows.SliceScan(); err != nil { return result, nil, err } - feature := &Feature{Properties: NewFeatureProperties(orderProps)} + feature := &Feature{Properties: NewFeatureProperties(propertiesOrder)} np, err := mapColumnsToFeature(firstRow, feature, columns, values, fidColumn, externalFidColumn, geomColumn, mapGeom, mapRel) if err != nil { @@ -173,7 +173,8 @@ func mapExternalFid(columns []string, values []any, externalFidColumn string, fe // it as a relation to another feature. newColumnName, newColumnValue := mapRel(columnName, columnValue, externalFidColumn) if newColumnName != "" { - feature.Properties.Set(newColumnName, newColumnValue) + columnNameWithoutExternalFID := strings.ReplaceAll(columnName, externalFidColumn, "") + feature.Properties.SetRelation(newColumnName, newColumnValue, columnNameWithoutExternalFID) feature.Properties.Delete(columnName) } } diff --git a/internal/ogc/features/domain/props.go b/internal/ogc/features/domain/props.go index 86502b0b..05719dd8 100644 --- a/internal/ogc/features/domain/props.go +++ b/internal/ogc/features/domain/props.go @@ -2,14 +2,15 @@ package domain import ( "slices" + "strings" "github.com/PDOK/gokoala/internal/engine/util" perfjson "github.com/goccy/go-json" orderedmap "github.com/wk8/go-ordered-map/v2" ) -// FeatureProperties the properties of a GeoJSON Feature -// Either unordered (= default, and has the best performance) or in a specific order when configured as such. +// FeatureProperties the properties of a GeoJSON Feature. Properties are either unordered +// (default, and has the best performance!) or ordered in a specific way as described in the config. type FeatureProperties struct { unordered map[string]any ordered orderedmap.OrderedMap[string, any] @@ -34,20 +35,11 @@ func NewFeaturePropertiesWithData(order bool, data map[string]any) FeatureProper func (p *FeatureProperties) MarshalJSON() ([]byte, error) { if p.unordered != nil { // properties are allowed to contain anything, including for example XML/GML. - b, e := perfjson.MarshalWithOption(p.unordered, perfjson.DisableHTMLEscape()) - return b, e + return perfjson.MarshalWithOption(p.unordered, perfjson.DisableHTMLEscape()) } return p.ordered.MarshalJSON() } -func (p *FeatureProperties) Set(key string, value any) { - if p.unordered != nil { - p.unordered[key] = value - } else { - p.ordered.Set(key, value) - } -} - func (p *FeatureProperties) Value(key string) any { if p.unordered != nil { return p.unordered[key] @@ -63,6 +55,37 @@ func (p *FeatureProperties) Delete(key string) { } } +func (p *FeatureProperties) Set(key string, value any) { + if p.unordered != nil { + p.unordered[key] = value + } else { + p.ordered.Set(key, value) + } +} + +func (p *FeatureProperties) SetRelation(key string, value any, existingKeyPrefix string) { + p.Set(key, value) + p.moveKeyBeforePrefix(key, existingKeyPrefix) +} + +// moveKeyBeforePrefix best-effort algorithm to place the feature relation BEFORE any similarly named keys. +// For example, places "building.href" before "building_fk" or "building_fid". +func (p *FeatureProperties) moveKeyBeforePrefix(key string, keyPrefix string) { + if p.unordered != nil { + return + } + var existingKey string + for pair := p.ordered.Oldest(); pair != nil; pair = pair.Next() { + if strings.HasPrefix(pair.Key, keyPrefix) { + existingKey = pair.Key + break + } + } + if existingKey != "" { + _ = p.ordered.MoveBefore(key, existingKey) + } +} + // Keys of the Feature properties. // // Note: In the future we might replace this with Go 1.23 iterators (range-over-func) however at the moment this