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  }