github.com/PDOK/gokoala@v0.50.6/internal/ogc/features/domain/mapper.go (about) 1 package domain 2 3 import ( 4 "fmt" 5 "time" 6 7 "github.com/go-spatial/geom" 8 "github.com/go-spatial/geom/encoding/geojson" 9 "github.com/jmoiron/sqlx" 10 ) 11 12 // MapRowsToFeatureIDs datasource agnostic mapper from SQL rows set feature IDs, including prev/next feature ID 13 func MapRowsToFeatureIDs(rows *sqlx.Rows) (featureIDs []int64, prevNextID *PrevNextFID, err error) { 14 firstRow := true 15 for rows.Next() { 16 var values []any 17 if values, err = rows.SliceScan(); err != nil { 18 return nil, nil, err 19 } 20 if len(values) != 3 { 21 return nil, nil, fmt.Errorf("expected 3 columns containing the feature id, "+ 22 "the previous feature id and the next feature id. Got: %v", values) 23 } 24 featureID := values[0].(int64) 25 featureIDs = append(featureIDs, featureID) 26 if firstRow { 27 prev := int64(0) 28 if values[1] != nil { 29 prev = values[1].(int64) 30 } 31 next := int64(0) 32 if values[2] != nil { 33 next = values[2].(int64) 34 } 35 prevNextID = &PrevNextFID{Prev: prev, Next: next} 36 firstRow = false 37 } 38 } 39 return 40 } 41 42 // MapRowsToFeatures datasource agnostic mapper from SQL rows/result set to Features domain model 43 func MapRowsToFeatures(rows *sqlx.Rows, fidColumn string, geomColumn string, 44 geomMapper func([]byte) (geom.Geometry, error)) ([]*Feature, *PrevNextFID, error) { 45 46 result := make([]*Feature, 0) 47 columns, err := rows.Columns() 48 if err != nil { 49 return result, nil, err 50 } 51 52 firstRow := true 53 var prevNextID *PrevNextFID 54 for rows.Next() { 55 var values []any 56 if values, err = rows.SliceScan(); err != nil { 57 return result, nil, err 58 } 59 60 feature := &Feature{Feature: geojson.Feature{Properties: make(map[string]any)}} 61 np, err := mapColumnsToFeature(firstRow, feature, columns, values, fidColumn, geomColumn, geomMapper) 62 if err != nil { 63 return result, nil, err 64 } else if firstRow { 65 prevNextID = np 66 firstRow = false 67 } 68 result = append(result, feature) 69 } 70 return result, prevNextID, nil 71 } 72 73 //nolint:cyclop,funlen 74 func mapColumnsToFeature(firstRow bool, feature *Feature, columns []string, values []any, 75 fidColumn string, geomColumn string, geomMapper func([]byte) (geom.Geometry, error)) (*PrevNextFID, error) { 76 77 prevNextID := PrevNextFID{} 78 for i, columnName := range columns { 79 columnValue := values[i] 80 81 switch columnName { 82 case fidColumn: 83 feature.ID = columnValue.(int64) 84 85 case geomColumn: 86 if columnValue == nil { 87 feature.Properties[columnName] = nil 88 continue 89 } 90 rawGeom, ok := columnValue.([]byte) 91 if !ok { 92 return nil, fmt.Errorf("failed to read geometry from %s column in datasource", geomColumn) 93 } 94 mappedGeom, err := geomMapper(rawGeom) 95 if err != nil { 96 return nil, fmt.Errorf("failed to map/decode geometry from datasource, error: %w", err) 97 } 98 feature.Geometry = geojson.Geometry{Geometry: mappedGeom} 99 100 case "minx", "miny", "maxx", "maxy", "min_zoom", "max_zoom": 101 // Skip these columns used for bounding box and zoom filtering 102 continue 103 104 case "prevfid": 105 // Only the first row in the result set contains the previous feature id 106 if firstRow && columnValue != nil { 107 prevNextID.Prev = columnValue.(int64) 108 } 109 110 case "nextfid": 111 // Only the first row in the result set contains the next feature id 112 if firstRow && columnValue != nil { 113 prevNextID.Next = columnValue.(int64) 114 } 115 116 default: 117 if columnValue == nil { 118 feature.Properties[columnName] = nil 119 continue 120 } 121 // Grab any non-nil, non-id, non-bounding box, & non-geometry column as a tag 122 switch v := columnValue.(type) { 123 case []uint8: 124 asBytes := make([]byte, len(v)) 125 copy(asBytes, v) 126 feature.Properties[columnName] = string(asBytes) 127 case int64: 128 feature.Properties[columnName] = v 129 case float64: 130 feature.Properties[columnName] = v 131 case time.Time: 132 feature.Properties[columnName] = v 133 case string: 134 feature.Properties[columnName] = v 135 case bool: 136 feature.Properties[columnName] = v 137 default: 138 return nil, fmt.Errorf("unexpected type for sqlite column data: %v: %T", columns[i], v) 139 } 140 } 141 } 142 return &prevNextID, nil 143 }