Skip to content

Commit

Permalink
geo: implement ST_Summary for Geography/Geometry
Browse files Browse the repository at this point in the history
Fixes #48405, #49049

Release note (sql change): Implemented the geometry based builtins `ST_Summary`.
  • Loading branch information
hueypark committed Jun 1, 2020
1 parent 187036c commit 6f83aa2
Show file tree
Hide file tree
Showing 5 changed files with 385 additions and 18 deletions.
20 changes: 20 additions & 0 deletions docs/generated/sql/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,26 @@ has no relationship with the commit order of concurrent transactions.</p>
</span></td></tr>
<tr><td><a name="st_startpoint"></a><code>st_startpoint(geometry: geometry) &rarr; geometry</code></td><td><span class="funcdesc"><p>Returns the first point of a geometry which has shape LineString. Returns NULL if the geometry is not a LineString.</p>
</span></td></tr>
<tr><td><a name="st_summary"></a><code>st_summary(geography: geography) &rarr; <a href="string.html">string</a></code></td><td><span class="funcdesc"><p>Returns a text summary of the contents of the geography.</p>
<p>Flags shown square brackets after the geometry type have the following meaning:</p>
<ul>
<li>M: has M coordinate</li>
<li>Z: has Z coordinate</li>
<li>B: has a cached bounding box</li>
<li>G: is geography</li>
<li>S: has spatial reference system</li>
</ul>
</span></td></tr>
<tr><td><a name="st_summary"></a><code>st_summary(geometry: geometry) &rarr; <a href="string.html">string</a></code></td><td><span class="funcdesc"><p>Returns a text summary of the contents of the geometry.</p>
<p>Flags shown square brackets after the geometry type have the following meaning:</p>
<ul>
<li>M: has M coordinate</li>
<li>Z: has Z coordinate</li>
<li>B: has a cached bounding box</li>
<li>G: is geography</li>
<li>S: has spatial reference system</li>
</ul>
</span></td></tr>
<tr><td><a name="st_touches"></a><code>st_touches(geometry_a: geometry, geometry_b: geometry) &rarr; <a href="bool.html">bool</a></code></td><td><span class="funcdesc"><p>Returns true if the only points in common between geometry_a and geometry_b are on the boundary. Note points do not touch other points.</p>
<p>This function utilizes the GEOS module.</p>
<p>This function will automatically use any available index.</p>
Expand Down
42 changes: 24 additions & 18 deletions pkg/geo/geo.go
Original file line number Diff line number Diff line change
Expand Up @@ -514,24 +514,9 @@ func spatialObjectFromGeom(t geom.T) (geopb.SpatialObject, error) {
if err != nil {
return geopb.SpatialObject{}, err
}
var shape geopb.Shape
switch t := t.(type) {
case *geom.Point:
shape = geopb.Shape_Point
case *geom.LineString:
shape = geopb.Shape_LineString
case *geom.Polygon:
shape = geopb.Shape_Polygon
case *geom.MultiPoint:
shape = geopb.Shape_MultiPoint
case *geom.MultiLineString:
shape = geopb.Shape_MultiLineString
case *geom.MultiPolygon:
shape = geopb.Shape_MultiPolygon
case *geom.GeometryCollection:
shape = geopb.Shape_GeometryCollection
default:
return geopb.SpatialObject{}, errors.Newf("unknown shape: %T", t)
shape, err := geopbShape(t)
if err != nil {
return geopb.SpatialObject{}, err
}
switch t.Layout() {
case geom.XY:
Expand All @@ -553,3 +538,24 @@ func spatialObjectFromGeom(t geom.T) (geopb.SpatialObject, error) {
BoundingBox: bbox,
}, nil
}

func geopbShape(t geom.T) (geopb.Shape, error) {
switch t := t.(type) {
case *geom.Point:
return geopb.Shape_Point, nil
case *geom.LineString:
return geopb.Shape_LineString, nil
case *geom.Polygon:
return geopb.Shape_Polygon, nil
case *geom.MultiPoint:
return geopb.Shape_MultiPoint, nil
case *geom.MultiLineString:
return geopb.Shape_MultiLineString, nil
case *geom.MultiPolygon:
return geopb.Shape_MultiPolygon, nil
case *geom.GeometryCollection:
return geopb.Shape_GeometryCollection, nil
default:
return geopb.Shape_Unset, errors.Newf("unknown shape: %T", t)
}
}
174 changes: 174 additions & 0 deletions pkg/geo/summary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright 2020 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package geo

import (
"fmt"
"strings"

"github.com/cockroachdb/cockroach/pkg/geo/geopb"
"github.com/cockroachdb/errors"
"github.com/twpayne/go-geom"
)

// Summary returns a text summary of the contents of the geometry type.
//
// Flags shown square brackets after the geometry type have the following meaning:
// M: has M coordinate
// Z: has Z coordinate
// B: has a cached bounding box
// G: is geography
// S: has spatial reference system
func Summary(t geom.T, shape geopb.Shape, isGeography bool) (string, error) {
return summary(t, shape, isGeography, 0)
}

func summary(t geom.T, shape geopb.Shape, isGeography bool, offset int) (sum string, err error) {
f, err := summaryFlag(t, isGeography)
if err != nil {
return "", err
}

sum += strings.Repeat(" ", offset)
switch t := t.(type) {
case *geom.Point:
return sum + fmt.Sprintf("%s[%s]", shape.String(), f), nil
case *geom.LineString:
return sum + fmt.Sprintf("%s[%s] with %d points", shape.String(), f, t.NumCoords()), nil
case *geom.Polygon:
numLinearRings := t.NumLinearRings()

sum += fmt.Sprintf("%s[%s] with %d ring", shape.String(), f, t.NumLinearRings())
if numLinearRings > 1 {
sum += "s"
}

for i := 0; i < numLinearRings; i++ {
ring := t.LinearRing(i)
sum += fmt.Sprintf("\n ring %d has %d points", i, ring.NumCoords())
}

return sum, nil
case *geom.MultiPoint:
numPoints := t.NumPoints()

sum += fmt.Sprintf("%s[%s] with %d element", shape.String(), f, numPoints)
if 1 < numPoints {
sum += "s"
}

for i := 0; i < numPoints; i++ {
point := t.Point(i)
line, err := summary(point, geopb.Shape_Point, isGeography, offset+2)
if err != nil {
return "", err
}

sum += "\n" + line
}

return sum, nil
case *geom.MultiLineString:
numLineStrings := t.NumLineStrings()

sum += fmt.Sprintf("%s[%s] with %d element", shape.String(), f, numLineStrings)
if 1 < numLineStrings {
sum += "s"
}

for i := 0; i < numLineStrings; i++ {
lineString := t.LineString(i)
line, err := summary(lineString, geopb.Shape_LineString, isGeography, offset+2)
if err != nil {
return "", err
}

sum += "\n" + line
}

return sum, nil
case *geom.MultiPolygon:
numPolygons := t.NumPolygons()

sum += fmt.Sprintf("%s[%s] with %d element", shape.String(), f, numPolygons)
if 1 < numPolygons {
sum += "s"
}

for i := 0; i < numPolygons; i++ {
polygon := t.Polygon(i)
line, err := summary(polygon, geopb.Shape_Polygon, isGeography, offset+2)
if err != nil {
return "", err
}

sum += "\n" + line
}

return sum, nil
case *geom.GeometryCollection:
numGeoms := t.NumGeoms()

sum += fmt.Sprintf("%s[%s] with %d element", shape.String(), f, numGeoms)
if 1 < numGeoms {
sum += "s"
}

for i := 0; i < numGeoms; i++ {
g := t.Geom(i)
gShape, err := geopbShape(g)
if err != nil {
return "", err
}

line, err := summary(g, gShape, isGeography, offset+2)
if err != nil {
return "", err
}

sum += "\n" + line
}

return sum, nil
default:
return "", errors.Newf("unspport geom type: %T", t)
}
}

func summaryFlag(t geom.T, isGeography bool) (f string, err error) {
layout := t.Layout()
if layout.MIndex() != -1 {
f += "M"
}

if layout.ZIndex() != -1 {
f += "Z"
}

bbox, err := BoundingBoxFromGeom(t)
if err != nil {
return "", err
}

if bbox != nil {
f += "B"
}

if geopb.SRID(t.SRID()) != geopb.UnknownSRID {
f += "S"
}

if isGeography {
f += "G"
}

return f, nil
}
108 changes: 108 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/geospatial
Original file line number Diff line number Diff line change
Expand Up @@ -1428,3 +1428,111 @@ ORDER BY id
1 4004 0
2 4326 4326
3 4004 3857

subtest st_summary

query T
SELECT ST_Summary('POINT(0 0)'::geometry)
----
Point[B]

query T
SELECT ST_Summary('SRID=4326;POINT(0 0)'::geometry)
----
Point[BS]

query T
SELECT ST_Summary('POINT(0 0 0)'::geometry)
----
Point[B]

query T
SELECT ST_Summary('POINT(0 0 0 0)'::geometry)
----
Point[B]

query T
SELECT ST_Summary('MULTIPOINT(0 0)'::geometry)
----
MultiPoint[B] with 1 element
Point[B]

query T
SELECT ST_Summary('SRID=4326;MULTIPOINT(0 0)'::geometry)
----
MultiPoint[BS] with 1 element
Point[B]

query T
SELECT ST_Summary('GEOMETRYCOLLECTION(MULTILINESTRING((0 0, 1 0),(2 0, 4 4)),MULTIPOINT(0 0))'::geometry)
----
GeometryCollection[B] with 2 elements
MultiLineString[B] with 2 elements
LineString[B] with 2 points
LineString[B] with 2 points
MultiPoint[B] with 1 element
Point[B]


query T
SELECT ST_Summary('SRID=4326;GEOMETRYCOLLECTION(MULTILINESTRING((0 0, 1 0),(2 0, 4 4)),MULTIPOINT(0 0))'::geometry)
----
GeometryCollection[BS] with 2 elements
MultiLineString[B] with 2 elements
LineString[B] with 2 points
LineString[B] with 2 points
MultiPoint[B] with 1 element
Point[B]

query T
SELECT ST_Summary('POINT(0 0)'::geography)
----
Point[BSG]

query T
SELECT ST_Summary('SRID=4326;POINT(0 0)'::geography)
----
Point[BSG]

query T
SELECT ST_Summary('POINT(0 0 0)'::geography)
----
Point[BSG]

query T
SELECT ST_Summary('POINT(0 0 0 0)'::geography)
----
Point[BSG]

query T
SELECT ST_Summary('MULTIPOINT(0 0)'::geography)
----
MultiPoint[BSG] with 1 element
Point[BG]

query T
SELECT ST_Summary('SRID=4326;MULTIPOINT(0 0)'::geography)
----
MultiPoint[BSG] with 1 element
Point[BG]

query T
SELECT ST_Summary('GEOMETRYCOLLECTION(MULTILINESTRING((0 0, 1 0),(2 0, 4 4)),MULTIPOINT(0 0))'::geography)
----
GeometryCollection[BSG] with 2 elements
MultiLineString[BG] with 2 elements
LineString[BG] with 2 points
LineString[BG] with 2 points
MultiPoint[BG] with 1 element
Point[BG]


query T
SELECT ST_Summary('SRID=4326;GEOMETRYCOLLECTION(MULTILINESTRING((0 0, 1 0),(2 0, 4 4)),MULTIPOINT(0 0))'::geography)
----
GeometryCollection[BSG] with 2 elements
MultiLineString[BG] with 2 elements
LineString[BG] with 2 points
LineString[BG] with 2 points
MultiPoint[BG] with 1 element
Point[BG]
Loading

0 comments on commit 6f83aa2

Please sign in to comment.