diff --git a/enginetest/queries/queries.go b/enginetest/queries/queries.go index fdfd07f00e..efd87af1ff 100644 --- a/enginetest/queries/queries.go +++ b/enginetest/queries/queries.go @@ -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}}}}}}, }, }, { @@ -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{ @@ -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}}}}}}, }, }, { @@ -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{ diff --git a/enginetest/scriptgen/setup/scripts/spatial b/enginetest/scriptgen/setup/scripts/spatial index ada93c137f..9440f89492 100644 --- a/enginetest/scriptgen/setup/scripts/spatial +++ b/enginetest/scriptgen/setup/scripts/spatial @@ -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 diff --git a/enginetest/scriptgen/setup/setup_data.sg.go b/enginetest/scriptgen/setup/setup_data.sg.go index 11d7a9c2bc..9572d1a1be 100644 --- a/enginetest/scriptgen/setup/setup_data.sg.go +++ b/enginetest/scriptgen/setup/setup_data.sg.go @@ -261,7 +261,7 @@ var Reserved_keywordsData = []SetupScript{{ var SpatialData = []SetupScript{{ `create table stringtogeojson_table (i bigint primary key, s blob)`, - `insert into stringtogeojson_table values (0, '{"type": "Point", "coordinates": [1,2]}'), (1, '{"type": "Point", "coordinates": [123.45,56.789]}'), (2, '{"type": "LineString", "coordinates": [[1,2],[3,4]]}'), (3, '{"type": "LineString", "coordinates": [[1.23,2.345],[3.56789,4.56]]}'), (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]]}')`, + `insert into stringtogeojson_table values (0, '{"type": "Point", "coordinates": [1,2]}'), (1, '{"type": "Point", "coordinates": [123.45,56.789]}'), (2, '{"type": "LineString", "coordinates": [[1,2],[3,4]]}'), (3, '{"type": "LineString", "coordinates": [[1.23,2.345],[3.56789,4.56]]}'), (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]]}'), (8, '{"type": "MultiLineString", "coordinates": [[[1.1,2.2],[3.3,4.4]],[[5.5,6.6],[7.7,8.8]]]}')`, `create table geometry_table (i bigint primary key, g geometry NOT NULL)`, `insert into geometry_table values (1, ST_GeomFromText('Point(1 2)')), (2, ST_SRID(ST_GeomFromText('Point(1 2)'), 4326)), (3, ST_GeomFromText('Linestring(1 2,3 4)')), (4, ST_SRID(ST_GeomFromText('Linestring(1 2,3 4)'), 4326)), (5, ST_GeomFromText('POLYGON((0 0,0 1,1 1,0 0))')), (6, ST_SRID(ST_GeomFromText('POLYGON((0 0,0 1,1 1,0 0))'), 4326)), (7, ST_GeomFromText('MultiPoint(1 2,3 4)')), (8, ST_SRID(ST_GeomFromText('MultiPoint(1 2,3 4)'), 4326)), (9, ST_GeomFromText('MULTILINESTRING((1 2,3 4))')), (10, ST_SRID(ST_GeomFromText('MULTILINESTRING((1 2,3 4))'), 4326))`, `create table point_table (i bigint primary key, p point NOT NULL);`, diff --git a/sql/expression/function/geojson.go b/sql/expression/function/geojson.go index 84ee2368f6..04423911ec 100644 --- a/sql/expression/function/geojson.go +++ b/sql/expression/function/geojson.go @@ -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) { @@ -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 } @@ -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()) } @@ -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) @@ -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") } @@ -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 { diff --git a/sql/expression/function/geojson_test.go b/sql/expression/function/geojson_test.go index 418c633f04..1c4387a812 100644 --- a/sql/expression/function/geojson_test.go +++ b/sql/expression/function/geojson_test.go @@ -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) @@ -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) @@ -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( @@ -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( diff --git a/sql/geometry.go b/sql/geometry.go index 0c9fa5fa5f..f9fb746b42 100644 --- a/sql/geometry.go +++ b/sql/geometry.go @@ -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) } @@ -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: @@ -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) } diff --git a/sql/geometry_test.go b/sql/geometry_test.go index f6e34de9d6..389c80c34c 100644 --- a/sql/geometry_test.go +++ b/sql/geometry_test.go @@ -101,10 +101,6 @@ func TestUnsupportedSpatialTypeByteArrayConversion(t *testing.T) { typeName: "MultiPolygon", hexValue: "0000000001060000000100000001030000000200000005000000000000000000000000000000000000000000000000000000000000000000084000000000000008400000000000000840000000000000084000000000000000000000000000000000000000000000000005000000000000000000F03F000000000000F03F000000000000F03F0000000000000040000000000000004000000000000000400000000000000040000000000000F03F000000000000F03F000000000000F03F", }, - { - typeName: "MultiLineString", - hexValue: "00000000010500000002000000010200000003000000000000000000F03F000000000000F03F00000000000000400000000000000040000000000000084000000000000008400102000000020000000000000000001040000000000000104000000000000014400000000000001440", - }, } for _, test := range unsupportedSpatialTypeTests { diff --git a/sql/multilinestring.go b/sql/multilinestring.go index f9d69729c5..30c757b368 100644 --- a/sql/multilinestring.go +++ b/sql/multilinestring.go @@ -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.