Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MB-54131: Geoshape query decode optimization #14

Merged
merged 9 commits into from
Aug 30, 2023
13 changes: 7 additions & 6 deletions geojson/geojson_shapes_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ var jsoniter = jsoniterator.ConfigCompatibleWithStandardLibrary

// FilterGeoShapesOnRelation extracts the shapes in the document, apply
// the `relation` filter and confirms whether the shape in the document
// satisfies the given relation.
//
// satisfies the given relation.
Likith101 marked this conversation as resolved.
Show resolved Hide resolved
func FilterGeoShapesOnRelation(shape index.GeoJSON, targetShapeBytes []byte,
relation string, reader **bytes.Reader) (bool, error) {
relation string, reader **bytes.Reader, bufPool *[][]byte) (bool, error) {
Likith101 marked this conversation as resolved.
Show resolved Hide resolved

shapeInDoc, err := extractShapesFromBytes(targetShapeBytes, reader)
shapeInDoc, err := extractShapesFromBytes(targetShapeBytes, reader, bufPool)
if err != nil {
return false, err
}
Expand All @@ -43,7 +44,7 @@ func FilterGeoShapesOnRelation(shape index.GeoJSON, targetShapeBytes []byte,

// extractShapesFromBytes unmarshal the bytes to retrieve the
// embedded geojson shape.
func extractShapesFromBytes(targetShapeBytes []byte, r **bytes.Reader) (
func extractShapesFromBytes(targetShapeBytes []byte, r **bytes.Reader, bufPool *[][]byte) (
index.GeoJSON, error) {
if (*r) == nil {
*r = bytes.NewReader(targetShapeBytes[1:])
Expand Down Expand Up @@ -109,7 +110,7 @@ func extractShapesFromBytes(targetShapeBytes []byte, r **bytes.Reader) (
return mls, nil

case PolygonTypePrefix:
pgn := &Polygon{s2pgn: &s2.Polygon{}}
pgn := &Polygon{s2pgn: &s2.Polygon{BufPool: bufPool}}
err := pgn.s2pgn.Decode(*r)
if err != nil {
return nil, err
Expand Down Expand Up @@ -156,7 +157,7 @@ func extractShapesFromBytes(targetShapeBytes []byte, r **bytes.Reader) (
gc := &GeometryCollection{Shapes: make([]index.GeoJSON, numShapes)}

for i := int32(0); i < numShapes; i++ {
shape, err := extractShapesFromBytes(inputBytes[:lengths[i]], r)
shape, err := extractShapesFromBytes(inputBytes[:lengths[i]], r, nil)
if err != nil {
return nil, err
}
Expand Down
19 changes: 19 additions & 0 deletions s2/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,22 @@ func (d *decoder) readUvarint() (x uint64) {
x, d.err = binary.ReadUvarint(d.r)
return
}

func (d *decoder) readFloat64Array(size int, buffers *[][]byte) (*[]byte, int) {
if d.err != nil {
return nil, 0
}

var buf []byte

for _, buffer := range *buffers {
if len(buffer) <= size*8 {
buf = buffer
break
}
}

_, d.err = io.ReadFull(d.r, buf)

return &buf, len(buf) / 8
}
65 changes: 43 additions & 22 deletions s2/loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package s2

import (
"encoding/binary"
"fmt"
"io"
"math"
Expand Down Expand Up @@ -45,6 +46,7 @@ import (
type Loop struct {
vertices []Point

polygon *Polygon
// originInside keeps a precomputed value whether this loop contains the origin
// versus computing from the set of vertices every time.
originInside bool
Expand Down Expand Up @@ -413,12 +415,12 @@ func (l *Loop) BoundaryEqual(o *Loop) bool {
// -1 if it excludes the boundary of the other, and 0 if the boundaries of the two
// loops cross. Shared edges are handled as follows:
//
// If XY is a shared edge, define Reversed(XY) to be true if XY
// appears in opposite directions in both loops.
// Then this loop contains XY if and only if Reversed(XY) == the other loop is a hole.
// (Intuitively, this checks whether this loop contains a vanishingly small region
// extending from the boundary of the other toward the interior of the polygon to
// which the other belongs.)
// If XY is a shared edge, define Reversed(XY) to be true if XY
// appears in opposite directions in both loops.
// Then this loop contains XY if and only if Reversed(XY) == the other loop is a hole.
// (Intuitively, this checks whether this loop contains a vanishingly small region
// extending from the boundary of the other toward the interior of the polygon to
// which the other belongs.)
Likith101 marked this conversation as resolved.
Show resolved Hide resolved
//
// This function is used for testing containment and intersection of
// multi-loop polygons. Note that this method is not symmetric, since the
Expand Down Expand Up @@ -979,21 +981,23 @@ func (l *Loop) ContainsNested(other *Loop) bool {
// surface integral" means:
//
// (1) f(A,B,C) must be the integral of f if ABC is counterclockwise,
// and the integral of -f if ABC is clockwise.
//
// and the integral of -f if ABC is clockwise.
//
// (2) The result of this function is *either* the integral of f over the
// loop interior, or the integral of (-f) over the loop exterior.
//
// loop interior, or the integral of (-f) over the loop exterior.
//
// Note that there are at least two common situations where it easy to work
// around property (2) above:
//
// - If the integral of f over the entire sphere is zero, then it doesn't
// matter which case is returned because they are always equal.
// - If the integral of f over the entire sphere is zero, then it doesn't
// matter which case is returned because they are always equal.
//
// - If f is non-negative, then it is easy to detect when the integral over
// the loop exterior has been returned, and the integral over the loop
// interior can be obtained by adding the integral of f over the entire
// unit sphere (a constant) to the result.
// - If f is non-negative, then it is easy to detect when the integral over
// the loop exterior has been returned, and the integral over the loop
// interior can be obtained by adding the integral of f over the entire
// unit sphere (a constant) to the result.
//
Likith101 marked this conversation as resolved.
Show resolved Hide resolved
// Any changes to this method may need corresponding changes to surfaceIntegralPoint as well.
func (l *Loop) surfaceIntegralFloat64(f func(a, b, c Point) float64) float64 {
Expand Down Expand Up @@ -1287,11 +1291,26 @@ func (l *Loop) decode(d *decoder) {
return
}
l.vertices = make([]Point, nvertices)
for i := range l.vertices {
l.vertices[i].X = d.readFloat64()
l.vertices[i].Y = d.readFloat64()
l.vertices[i].Z = d.readFloat64()

pointsNeeded := int(nvertices) * 3
Likith101 marked this conversation as resolved.
Show resolved Hide resolved

i := 0

for pointsNeeded > 0 {
arr, pointsRead := d.readFloat64Array(pointsNeeded, l.polygon.BufPool)
if pointsRead == 0 {
break
}
pointsNeeded = pointsNeeded - pointsRead
for j := 0; j < int(pointsRead/3); j++ {
Likith101 marked this conversation as resolved.
Show resolved Hide resolved
l.vertices[i+j].X = math.Float64frombits(binary.LittleEndian.Uint64((*arr)[8*(j*3) : 8*(j*3+1)]))
l.vertices[i+j].Y = math.Float64frombits(binary.LittleEndian.Uint64((*arr)[8*(j*3+1) : 8*(j*3+2)]))
l.vertices[i+j].Z = math.Float64frombits(binary.LittleEndian.Uint64((*arr)[8*(j*3+2) : 8*(j*3+3)]))
}

i = i + int(pointsRead/3)
}

l.index = NewShapeIndex()
l.originInside = d.readBool()
l.depth = int(d.readUint32())
Expand Down Expand Up @@ -1748,10 +1767,12 @@ func (i *intersectsRelation) wedgesCross(a0, ab1, a2, b0, b2 Point) bool {
// so we return crossingTargetDontCare for both crossing targets.
//
// Aside: A possible early exit condition could be based on the following.
// If A contains a point of both B and ~B, then A intersects Boundary(B).
// If ~A contains a point of both B and ~B, then ~A intersects Boundary(B).
// So if the intersections of {A, ~A} with {B, ~B} are all non-empty,
// the return value is 0, i.e., Boundary(A) intersects Boundary(B).
//
// If A contains a point of both B and ~B, then A intersects Boundary(B).
// If ~A contains a point of both B and ~B, then ~A intersects Boundary(B).
// So if the intersections of {A, ~A} with {B, ~B} are all non-empty,
// the return value is 0, i.e., Boundary(A) intersects Boundary(B).
//
Likith101 marked this conversation as resolved.
Show resolved Hide resolved
// Unfortunately it isn't worth detecting this situation because by the
// time we have seen a point in all four intersection regions, we are also
// guaranteed to have seen at least one pair of crossing edges.
Expand Down
19 changes: 11 additions & 8 deletions s2/polygon.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,16 @@ import (
//
// Polygons have the following restrictions:
//
// - Loops may not cross, i.e. the boundary of a loop may not intersect
// both the interior and exterior of any other loop.
// - Loops may not cross, i.e. the boundary of a loop may not intersect
// both the interior and exterior of any other loop.
//
// - Loops may not share edges, i.e. if a loop contains an edge AB, then
// no other loop may contain AB or BA.
// - Loops may not share edges, i.e. if a loop contains an edge AB, then
// no other loop may contain AB or BA.
//
// - Loops may share vertices, however no vertex may appear twice in a
// single loop (see Loop).
// - Loops may share vertices, however no vertex may appear twice in a
// single loop (see Loop).
//
// - No loop may be empty. The full loop may appear only in the full polygon.
// - No loop may be empty. The full loop may appear only in the full polygon.
Likith101 marked this conversation as resolved.
Show resolved Hide resolved
type Polygon struct {
loops []*Loop

Expand Down Expand Up @@ -77,6 +77,8 @@ type Polygon struct {
// preceding loops in the polygon. This field is used for polygons that
// have a large number of loops, and may be empty for polygons with few loops.
cumulativeEdges []int

BufPool *[][]byte
Likith101 marked this conversation as resolved.
Show resolved Hide resolved
}

// PolygonFromLoops constructs a polygon from the given set of loops. The polygon
Expand Down Expand Up @@ -1133,7 +1135,7 @@ func (p *Polygon) Decode(r io.Reader) error {
const maxEncodedLoops = 10000000

func (p *Polygon) decode(d *decoder) {
*p = Polygon{}
*p = Polygon{BufPool: p.BufPool}
d.readUint8() // Ignore irrelevant serialized owns_loops_ value.

p.hasHoles = d.readBool()
Expand All @@ -1151,6 +1153,7 @@ func (p *Polygon) decode(d *decoder) {
p.loops = make([]*Loop, nloops)
for i := range p.loops {
p.loops[i] = new(Loop)
p.loops[i].polygon = p
p.loops[i].decode(d)
p.numVertices += len(p.loops[i].vertices)
}
Expand Down