Skip to content

Commit

Permalink
feat(server): implement geo field's position (#1176)
Browse files Browse the repository at this point in the history
* wip: position value

* refactor to value

* refactor 2

* wip: unit tests

* fix: mapToFloat64

* add Test_propertyPosition_ToValue

* add propertyPosition to registry

* add more unit tests

* fix: float32 to float64 conversion

* add decimal numbers to unit tests

* fix: bad import in list_test
  • Loading branch information
nourbalaha authored Jun 14, 2024
1 parent 3dffe8b commit 2ae9729
Show file tree
Hide file tree
Showing 4 changed files with 383 additions and 1 deletion.
2 changes: 1 addition & 1 deletion server/pkg/item/view/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package view
import (
"testing"

"github.com/go-playground/assert/v2"
"github.com/reearth/reearth-cms/server/pkg/id"
"github.com/stretchr/testify/assert"
)

func TestList_Ordered(t *testing.T) {
Expand Down
201 changes: 201 additions & 0 deletions server/pkg/value/position.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package value

import (
"encoding/json"
"slices"
"strconv"

"github.com/samber/lo"
)

const TypePoint Type = "point"
// const TypeLineString Type = "lineString"
// const TypePolygon Type = "polygon"

type propertyPosition struct{}

type Position = []float64

func (p *propertyPosition) ToValue(i any) (any, bool) {
if i == nil {
return nil, true
}

switch v := i.(type) {
case []float64:
return v, true
case []float32:
return mapFloat32ToFloat64(v)
case []int:
return mapIntegersToFloat64(v), true
case []int8:
return mapIntegersToFloat64(v), true
case []int16:
return mapIntegersToFloat64(v), true
case []int32:
return mapIntegersToFloat64(v), true
case []int64:
return mapIntegersToFloat64(v), true
case []uint:
return mapIntegersToFloat64(v), true
case []uint8:
return mapIntegersToFloat64(v), true
case []uint16:
return mapIntegersToFloat64(v), true
case []uint32:
return mapIntegersToFloat64(v), true
case []uint64:
return mapIntegersToFloat64(v), true
case []uintptr:
return mapIntegersToFloat64(v), true
case []json.Number:
return mapJSONNumbersToFloat64(v)
case []string:
return mapStringsToFloat64(v)
default:
return nil, false
}
}

func mapIntegersToFloat64[T any](v []T) []float64 {
return lo.Map(v, func(n T, _ int) float64 {
return intToFloat64(n)
})
}

func intToFloat64(v any) float64 {
switch val := v.(type) {
case int:
return float64(val)
case int8:
return float64(val)
case int16:
return float64(val)
case int32:
return float64(val)
case int64:
return float64(val)
case uint:
return float64(val)
case uint8:
return float64(val)
case uint16:
return float64(val)
case uint32:
return float64(val)
case uint64:
return float64(val)
case uintptr:
return float64(val)
default:
return 0
}
}

func mapStringsToFloat64(v []string) ([]float64, bool) {
var err error
s := lo.Map(v, func(s string, _ int) float64 {
vv, err2 := strconv.ParseFloat(s, 64)
if err2 != nil {
err = err2
return 0
}
return vv
})
if err != nil {
return nil, false
}
return s, true
}

func mapJSONNumbersToFloat64(v []json.Number) ([]float64, bool) {
var err error
s := lo.Map(v, func(n json.Number, _ int) float64 {
vv, err2 := n.Float64()
if err2 != nil {
err = err2
return 0
}
return vv
})
if err != nil {
return nil, false
}
return s, true
}

func mapFloat32ToFloat64(v []float32) ([]float64, bool) {
var err error
s := lo.Map(v, func(n float32, _ int) float64 {
ss := strconv.FormatFloat(float64(n), 'f', -1, 32)
vv, err2 := strconv.ParseFloat(ss, 64)
if err2 != nil {
err = err2
return 0
}
return vv
})
if err != nil {
return nil, false
}
return s, true
}

func (*propertyPosition) ToInterface(v any) (any, bool) {
return v, true
}

func (*propertyPosition) Validate(i any) bool {
v, ok := i.(Position)
if !ok {
return false
}
return len(v) >= 2
}

func (*propertyPosition) Equal(v, w any) bool {
vv := v.(Position)
ww := w.(Position)
if len(vv) != len(ww) {
return false
}
return slices.Equal(vv, ww)
}

func (*propertyPosition) IsEmpty(i any) bool {
if i == nil {
return true
}
v, ok := i.(Position)
if !ok {
return true
}
return len(v) == 0
}

func (v *Value) ValuePosition() (vv Position, ok bool) {
if v == nil {
return
}
vv, ok = v.v.(Position)
if !ok {
return nil, false
}
if len(vv) > 3 {
return vv[:3], true // TODO: need to think about his case
}
return
}

func (m *Multiple) ValuesPosition() (vv []Position, ok bool) {
if m == nil {
return
}
vv = lo.FilterMap(m.v, func(v *Value, _ int) (Position, bool) {
return v.ValuePosition()
})
if len(vv) != len(m.v) {
return nil, false
}
return vv, true
}
180 changes: 180 additions & 0 deletions server/pkg/value/position_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package value

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
)

func Test_propertyPosition_ToValue(t *testing.T) {
tests := []struct {
name string
arg any
want1 any
want2 bool
}{
{
name: "nil",
arg: nil,
want1: nil,
want2: true,
},
{
name: "string",
arg: []string{"1.12345", "2.12345"},
want1: []float64{1.12345, 2.12345},
want2: true,
},
{
name: "json.Number",
arg: []json.Number{"1.12345", "2.12345"},
want1: []float64{1.12345, 2.12345},
want2: true,
},
{
name: "float64",
arg: []float64{1.12345, 2.12345},
want1: []float64{1.12345, 2.12345},
want2: true,
},
{
name: "float32",
arg: []float32{1.1234567, 2.12345},
want1: []float64{1.1234567, 2.12345},
want2: true,
},
{
name: "int",
arg: []int{1, 2},
want1: []float64{1.0, 2.0},
want2: true,
},
{
name: "int8",
arg: []int8{1, 2},
want1: []float64{1.0, 2.0},
want2: true,
},
{
name: "int16",
arg: []int16{1, 2},
want1: []float64{1.0, 2.0},
want2: true,
},
{
name: "int32",
arg: []int32{1, 2},
want1: []float64{1.0, 2.0},
want2: true,
},
{
name: "int64",
arg: []int64{1, 2},
want1: []float64{1.0, 2.0},
want2: true,
},
{
name: "uint",
arg: []uint{1, 2},
want1: []float64{1.0, 2.0},
want2: true,
},
{
name: "uint8",
arg: []uint8{1, 2},
want1: []float64{1.0, 2.0},
want2: true,
},
{
name: "uint16",
arg: []uint16{1, 2},
want1: []float64{1.0, 2.0},
want2: true,
},
{
name: "uint32",
arg: []uint32{1, 2},
want1: []float64{1.0, 2.0},
want2: true,
},
{
name: "uint64",
arg: []uint64{1, 2},
want1: []float64{1.0, 2.0},
want2: true,
},
{
name: "uintptr",
arg: []uintptr{1, 2},
want1: []float64{1.0, 2.0},
want2: true,
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
p := &propertyPosition{}
got1, got2 := p.ToValue(tt.arg)
assert.Equal(t, tt.want1, got1)
assert.Equal(t, tt.want2, got2)
})
}
}

func Test_propertyPosition_ToInterface(t *testing.T) {
v := []float64{1.1, 2.1, 3.1}
tt, ok := (&propertyPosition{}).ToInterface(v)
assert.Equal(t, v, tt)
assert.Equal(t, true, ok)
}

func Test_propertyPosition_IsEmpty(t *testing.T) {
assert.True(t, (&propertyPosition{}).IsEmpty([]float64{}))
assert.False(t, (&propertyPosition{}).IsEmpty([]float64{1.1, 2.1, 3.1}))
}

func Test_propertyPosition_Validate(t *testing.T) {
assert.True(t, (&propertyPosition{}).Validate([]float64{1.1, 2.1, 3.1}))
assert.False(t, (&propertyPosition{}).Validate([]float64{1.1}))
assert.False(t, (&propertyPosition{}).Validate([]int{1, 2, 3}))
assert.False(t, (&propertyPosition{}).Validate([]string{"1", "2", "3"}))
assert.False(t, (&propertyPosition{}).Validate(1))
}

func Test_propertyPosition_Equal(t *testing.T) {
ps := &propertyPosition{}
assert.True(t, ps.Equal(Position{1.1, 2.1, 3.1}, Position{1.1, 2.1, 3.1}))
ps1 := &propertyPosition{}
assert.False(t, ps1.Equal(Position{1.1, 2.1, 3.1}, Position{1.1, 2.1}))
}

func TestValue_ValuePosition(t *testing.T) {
var v *Value
got, ok := v.ValuePosition()
assert.Equal(t, []float64(nil), got)
assert.Equal(t, false, ok)

v = &Value{
v: []float64{1.1, 2.1, 3.1},
}
got, ok = v.ValuePosition()
assert.Equal(t, []float64{1.1, 2.1, 3.1}, got)
assert.Equal(t, true, ok)
}

func TestMultiple_ValuesPosition(t *testing.T) {
var m *Multiple
got, ok := m.ValuesPosition()
var expected []Position
assert.Equal(t, expected, got)
assert.False(t, ok)

m = NewMultiple(TypePoint, []any{Position{1.1, 2.1, 3.1}, Position{1.1, 2.1, 3.1}, Position{1.1, 2.1, 3.1}})
expected = []Position{{1.1, 2.1, 3.1}, {1.1, 2.1, 3.1}, {1.1, 2.1, 3.1}}
got, ok = m.ValuesPosition()
assert.Equal(t, expected, got)
assert.True(t, ok)
}
Loading

0 comments on commit 2ae9729

Please sign in to comment.