Skip to content

Commit

Permalink
Merge pull request #1323 from dolthub/james/mline3
Browse files Browse the repository at this point in the history
add support for `MultiLineString` pt. 3
  • Loading branch information
jycor authored Oct 13, 2022
2 parents 87b28c9 + 05ebe52 commit 2395a57
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 19 deletions.
16 changes: 16 additions & 0 deletions enginetest/queries/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ var SpatialQueryTests = []QueryTest{
{sql.Polygon{SRID: 4326, Lines: []sql.LineString{{SRID: 4326, Points: []sql.Point{{SRID: 4326, X: 0, Y: 0}, {SRID: 4326, X: 1, Y: 1}, {SRID: 4326, X: 2, Y: 2}, {SRID: 4326, X: 0, Y: 0}}}}}},
{sql.MultiPoint{SRID: 4326, Points: []sql.Point{{SRID: 4326, X: 1, Y: 2}, {SRID: 4326, X: 3, Y: 4}}}},
{sql.MultiPoint{SRID: 4326, Points: []sql.Point{{SRID: 4326, X: 1.23, Y: 2.345}, {SRID: 4326, X: 3.56789, Y: 4.56}}}},
{sql.MultiLineString{SRID: 4326, Lines: []sql.LineString{{SRID: 4326, Points: []sql.Point{{SRID: 4326, X: 1.1, Y: 2.2}, {SRID: 4326, X: 3.3, Y: 4.4}}}, {SRID: 4326, Points: []sql.Point{{SRID: 4326, X: 5.5, Y: 6.6}, {SRID: 4326, X: 7.7, Y: 8.8}}}}}},
},
},
{
Expand Down Expand Up @@ -276,6 +277,13 @@ var SpatialQueryTests = []QueryTest{
{sql.JSONDocument{Val: map[string]interface{}{"type": "MultiPoint", "coordinates": [][2]float64{{1, 2}, {3, 4}, {5, 6}}}}},
},
},
{
Query: `SELECT ST_ASGEOJSON(l) from mline_table`,
Expected: []sql.Row{
{sql.JSONDocument{Val: map[string]interface{}{"type": "MultiLineString", "coordinates": [][][2]float64{{{1, 2}, {3, 4}}}}}},
{sql.JSONDocument{Val: map[string]interface{}{"type": "MultiLineString", "coordinates": [][][2]float64{{{1, 2}, {3, 4}, {5, 6}}}}}},
},
},
{
Query: `SELECT ST_ASGEOJSON(ST_GEOMFROMGEOJSON(s)) from stringtogeojson_table`,
Expected: []sql.Row{
Expand All @@ -287,6 +295,7 @@ var SpatialQueryTests = []QueryTest{
{sql.JSONDocument{Val: map[string]interface{}{"type": "Polygon", "coordinates": [][][2]float64{{{0, 0}, {1, 1}, {2, 2}, {0, 0}}}}}},
{sql.JSONDocument{Val: map[string]interface{}{"type": "MultiPoint", "coordinates": [][2]float64{{1, 2}, {3, 4}}}}},
{sql.JSONDocument{Val: map[string]interface{}{"type": "MultiPoint", "coordinates": [][2]float64{{1.23, 2.345}, {3.56789, 4.56}}}}},
{sql.JSONDocument{Val: map[string]interface{}{"type": "MultiLineString", "coordinates": [][][2]float64{{{1.1, 2.2}, {3.3, 4.4}}, {{5.5, 6.6}, {7.7, 8.8}}}}}},
},
},
{
Expand Down Expand Up @@ -316,6 +325,13 @@ var SpatialQueryTests = []QueryTest{
{sql.MultiPoint{SRID: 4326, Points: []sql.Point{{SRID: 4326, X: 1, Y: 2}, {SRID: 4326, X: 3, Y: 4}, {SRID: 4326, X: 5, Y: 6}}}},
},
},
{
Query: `SELECT ST_GEOMFROMGEOJSON(ST_ASGEOJSON(l)) from mline_table`,
Expected: []sql.Row{
{sql.MultiLineString{SRID: 4326, Lines: []sql.LineString{{SRID: 4326, Points: []sql.Point{{SRID: 4326, X: 1, Y: 2}, {SRID: 4326, X: 3, Y: 4}}}}}},
{sql.MultiLineString{SRID: 4326, Lines: []sql.LineString{{SRID: 4326, Points: []sql.Point{{SRID: 4326, X: 1, Y: 2}, {SRID: 4326, X: 3, Y: 4}, {SRID: 4326, X: 5, Y: 6}}}}}},
},
},
{
Query: `SELECT ST_DIMENSION(p) from point_table`,
Expected: []sql.Row{
Expand Down
3 changes: 2 additions & 1 deletion enginetest/scriptgen/setup/scripts/spatial
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ insert into stringtogeojson_table values
(4, '{"type": "Polygon", "coordinates": [[[1.1,2.2],[3.3,4.4],[5.5,6.6],[1.1,2.2]]]}'),
(5, '{"type": "Polygon", "coordinates": [[[0,0],[1,1],[2,2],[0,0]]]}'),
(6, '{"type": "MultiPoint", "coordinates": [[1,2],[3,4]]}'),
(7, '{"type": "MultiPoint", "coordinates": [[1.23,2.345],[3.56789,4.56]]}')
(7, '{"type": "MultiPoint", "coordinates": [[1.23,2.345],[3.56789,4.56]]}'),
(8, '{"type": "MultiLineString", "coordinates": [[[1.1,2.2],[3.3,4.4]],[[5.5,6.6],[7.7,8.8]]]}')
----

exec
Expand Down
2 changes: 1 addition & 1 deletion enginetest/scriptgen/setup/setup_data.sg.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 43 additions & 1 deletion sql/expression/function/geojson.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ func MPointToSlice(p sql.MultiPoint) [][2]float64 {
return arr
}

func MLineToSlice(p sql.MultiLineString) [][][2]float64 {
arr := make([][][2]float64, len(p.Lines))
for i, l := range p.Lines {
arr[i] = LineToSlice(l)
}
return arr
}

func FindBBox(v interface{}) [4]float64 {
var res [4]float64
switch v := v.(type) {
Expand Down Expand Up @@ -115,6 +123,15 @@ func FindBBox(v interface{}) [4]float64 {
res[2] = math.Max(res[2], tmp[2])
res[3] = math.Max(res[3], tmp[3])
}
case sql.MultiLineString:
res = [4]float64{math.MaxFloat64, math.MaxFloat64, math.SmallestNonzeroFloat64, math.SmallestNonzeroFloat64}
for _, l := range v.Lines {
tmp := FindBBox(l)
res[0] = math.Min(res[0], tmp[0])
res[1] = math.Min(res[1], tmp[1])
res[2] = math.Max(res[2], tmp[2])
res[3] = math.Max(res[3], tmp[3])
}
}
return res
}
Expand Down Expand Up @@ -186,6 +203,9 @@ func (g *AsGeoJSON) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
case sql.MultiPoint:
obj["type"] = "MultiPoint"
obj["coordinates"] = MPointToSlice(v)
case sql.MultiLineString:
obj["type"] = "MultiLineString"
obj["coordinates"] = MLineToSlice(v)
default:
return nil, ErrInvalidArgumentType.New(g.FunctionName())
}
Expand Down Expand Up @@ -391,6 +411,26 @@ func SliceToMPoint(coords interface{}) (interface{}, error) {
return sql.MultiPoint{SRID: sql.GeoSpatialSRID, Points: points}, nil
}

func SliceToMLine(coords interface{}) (interface{}, error) {
// coords must be a slice of slices of at least 2 slices of 2 float64
cs, ok := coords.([]interface{})
if !ok {
return nil, errors.New("member 'coordinates' must be of type 'array'")
}
if len(cs) == 0 {
return nil, errors.New("not enough lines")
}
lines := make([]sql.LineString, len(cs))
for i, c := range cs {
l, err := SliceToLine(c)
if err != nil {
return nil, err
}
lines[i] = l.(sql.LineString)
}
return sql.MultiLineString{SRID: sql.GeoSpatialSRID, Lines: lines}, nil
}

// Eval implements the sql.Expression interface.
func (g *GeomFromGeoJSON) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
val, err := g.ChildExpressions[0].Eval(ctx, row)
Expand Down Expand Up @@ -438,6 +478,8 @@ func (g *GeomFromGeoJSON) Eval(ctx *sql.Context, row sql.Row) (interface{}, erro
res, err = SliceToPoly(coords)
case "MultiPoint":
res, err = SliceToMPoint(coords)
case "MultiLineString":
res, err = SliceToMLine(coords)
default:
return nil, errors.New("member 'type' is wrong")
}
Expand Down Expand Up @@ -473,7 +515,7 @@ func (g *GeomFromGeoJSON) Eval(ctx *sql.Context, row sql.Row) (interface{}, erro
return nil, errors.New("unsupported number of coordinate dimensions")
}
}
case "Polygon":
case "Polygon", "MultiLineString":
for _, a := range obj["coordinates"].([]interface{}) {
for _, b := range a.([]interface{}) {
if len(b.([]interface{})) > 2 {
Expand Down
39 changes: 35 additions & 4 deletions sql/expression/function/geojson_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,16 @@ func TestAsGeoJSON(t *testing.T) {
require.NoError(err)
require.Equal(sql.JSONDocument{Val: map[string]interface{}{"coordinates": [][2]float64{{1, 2}, {3, 4}}, "type": "LineString"}}, v)
})
t.Run("convert linestring to geojson", func(t *testing.T) {
t.Run("convert polygon to geojson", func(t *testing.T) {
require := require.New(t)
f, err := NewAsGeoJSON(expression.NewLiteral(sql.Polygon{Lines: []sql.LineString{{Points: []sql.Point{{X: 0, Y: 0}, {X: 1, Y: 0}, {X: 1, Y: 1}, {X: 0, Y: 0}}}}}, sql.PolygonType{}))
require.NoError(err)

v, err := f.Eval(sql.NewEmptyContext(), nil)
require.NoError(err)
require.Equal(sql.JSONDocument{Val: map[string]interface{}{"coordinates": [][][2]float64{{{0, 0}, {1, 0}, {1, 1}, {0, 0}}}, "type": "Polygon"}}, v)
})
t.Run("convert multipoint to geojson", func(t *testing.T) {
require := require.New(t)
f, err := NewAsGeoJSON(expression.NewLiteral(sql.MultiPoint{Points: []sql.Point{{X: 1, Y: 2}, {X: 3, Y: 4}}}, sql.MultiPointType{}))
require.NoError(err)
Expand All @@ -51,14 +60,14 @@ func TestAsGeoJSON(t *testing.T) {
require.NoError(err)
require.Equal(sql.JSONDocument{Val: map[string]interface{}{"coordinates": [][2]float64{{1, 2}, {3, 4}}, "type": "MultiPoint"}}, v)
})
t.Run("convert polygon to geojson", func(t *testing.T) {
t.Run("convert multilinestring to geojson", func(t *testing.T) {
require := require.New(t)
f, err := NewAsGeoJSON(expression.NewLiteral(sql.Polygon{Lines: []sql.LineString{{Points: []sql.Point{{X: 0, Y: 0}, {X: 1, Y: 0}, {X: 1, Y: 1}, {X: 0, Y: 0}}}}}, sql.PolygonType{}))
f, err := NewAsGeoJSON(expression.NewLiteral(sql.MultiLineString{Lines: []sql.LineString{{Points: []sql.Point{{X: 1, Y: 2}, {X: 3, Y: 4}}}}}, sql.MultiLineStringType{}))
require.NoError(err)

v, err := f.Eval(sql.NewEmptyContext(), nil)
require.NoError(err)
require.Equal(sql.JSONDocument{Val: map[string]interface{}{"coordinates": [][][2]float64{{{0, 0}, {1, 0}, {1, 1}, {0, 0}}}, "type": "Polygon"}}, v)
require.Equal(sql.JSONDocument{Val: map[string]interface{}{"coordinates": [][][2]float64{{{1, 2}, {3, 4}}}, "type": "MultiLineString"}}, v)
})
t.Run("convert point with floats to geojson", func(t *testing.T) {
require := require.New(t)
Expand Down Expand Up @@ -145,6 +154,19 @@ func TestAsGeoJSON(t *testing.T) {
require.NoError(err)
require.Equal(sql.JSONDocument{Val: map[string]interface{}{"coordinates": [][2]float64{{100, 2}, {1, 200}}, "type": "MultiPoint", "bbox": [4]float64{1, 2, 100, 200}}}, v)
})
t.Run("convert multilinestring with bounding box", func(t *testing.T) {
require := require.New(t)
f, err := NewAsGeoJSON(
expression.NewLiteral(sql.MultiLineString{Lines: []sql.LineString{{Points: []sql.Point{{X: 1, Y: 2}, {X: 3, Y: 4}}}}}, sql.MultiLineStringType{}),
expression.NewLiteral(2, sql.Int64),
expression.NewLiteral(1, sql.Int64),
)
require.NoError(err)

v, err := f.Eval(sql.NewEmptyContext(), nil)
require.NoError(err)
require.Equal(sql.JSONDocument{Val: map[string]interface{}{"coordinates": [][][2]float64{{{1, 2}, {3, 4}}}, "type": "MultiLineString", "bbox": [4]float64{1, 2, 3, 4}}}, v)
})
t.Run("convert point with srid 0 and flag 2", func(t *testing.T) {
require := require.New(t)
f, err := NewAsGeoJSON(
Expand Down Expand Up @@ -286,6 +308,15 @@ func TestGeomFromGeoJSON(t *testing.T) {
require.NoError(err)
require.Equal(sql.MultiPoint{SRID: 4326, Points: []sql.Point{{4326, 1, 2}, {4326, 3, 4}}}, v)
})
t.Run("convert multilinestring from geojson", func(t *testing.T) {
require := require.New(t)
f, err := NewGeomFromGeoJSON(expression.NewLiteral(`{"type":"MultiLineString", "coordinates":[[[0,0],[1,1],[0,1],[0,0]]]}`, sql.Blob))
require.NoError(err)

v, err := f.Eval(sql.NewEmptyContext(), nil)
require.NoError(err)
require.Equal(sql.MultiLineString{SRID: 4326, Lines: []sql.LineString{{4326, []sql.Point{{4326, 0, 0}, {4326, 1, 1}, {4326, 0, 1}, {4326, 0, 0}}}}}, v)
})
t.Run("reject dimensions greater than 2 with flag 1", func(t *testing.T) {
require := require.New(t)
f, err := NewGeomFromGeoJSON(
Expand Down
14 changes: 7 additions & 7 deletions sql/geometry.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ func (t GeometryType) Compare(a any, b any) (int, error) {
return LineStringType{}.Compare(inner, b)
case Polygon:
return PolygonType{}.Compare(inner, b)
case MultiPoint:
return MultiPointType{}.Compare(inner, b)
case MultiLineString:
return MultiLineStringType{}.Compare(inner, b)
default:
return 0, ErrNotGeometry.New(a)
}
Expand Down Expand Up @@ -334,7 +338,7 @@ func (t GeometryType) Convert(v interface{}) (interface{}, error) {
case WKBMultiPointID:
geom, err = DeserializeMPoint(val, isBig, srid)
case WKBMultiLineID:
return nil, ErrUnsupportedGISType.New("MultiLineString", hex.EncodeToString(val))
geom, err = DeserializeMLine(val, isBig, srid)
case WKBMultiPolyID:
return nil, ErrUnsupportedGISType.New("MultiPolygon", hex.EncodeToString(val))
case WKBGeomCollectionID:
Expand Down Expand Up @@ -431,12 +435,8 @@ func (t GeometryType) MatchSRID(v interface{}) error {
// if matched with SRID value of row value
var srid uint32
switch val := v.(type) {
case Point:
srid = val.SRID
case LineString:
srid = val.SRID
case Polygon:
srid = val.SRID
case GeometryValue:
srid = val.GetSRID()
default:
return ErrNotGeometry.New(v)
}
Expand Down
4 changes: 0 additions & 4 deletions sql/geometry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,6 @@ func TestUnsupportedSpatialTypeByteArrayConversion(t *testing.T) {
typeName: "MultiPolygon",
hexValue: "0000000001060000000100000001030000000200000005000000000000000000000000000000000000000000000000000000000000000000084000000000000008400000000000000840000000000000084000000000000000000000000000000000000000000000000005000000000000000000F03F000000000000F03F000000000000F03F0000000000000040000000000000004000000000000000400000000000000040000000000000F03F000000000000F03F000000000000F03F",
},
{
typeName: "MultiLineString",
hexValue: "00000000010500000002000000010200000003000000000000000000F03F000000000000F03F00000000000000400000000000000040000000000000084000000000000008400102000000020000000000000000001040000000000000104000000000000014400000000000001440",
},
}

for _, test := range unsupportedSpatialTypeTests {
Expand Down
2 changes: 1 addition & 1 deletion sql/multilinestring.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ func (t MultiLineStringType) ValueType() reflect.Type {

// Zero implements Type interface.
func (t MultiLineStringType) Zero() interface{} {
return MultiLineString{Lines: []LineString{{Points: []Point{{}, {}}}}}
return MultiLineString{Lines: []LineString{LineStringType{}.Zero().(LineString)}}
}

// GetSpatialTypeSRID implements SpatialColumnType interface.
Expand Down

0 comments on commit 2395a57

Please sign in to comment.