Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
bcd6c35
Update builder_snapper files.
rsned May 4, 2025
3ab23c5
Define the builder options type and its two helper methods.
rsned May 10, 2025
1d63448
add header text
rsned May 10, 2025
09f7532
Add builders layer interface.
rsned May 10, 2025
51ac9e3
Add the base builder type with its extensive comment block.
rsned May 10, 2025
a95103b
Add builder graph options and related enums.
rsned May 10, 2025
0b2af37
Add initial builder graph type and related enums.
rsned May 10, 2025
af0ce50
Add more types to graph options.
rsned May 10, 2025
6084658
Add another batch of builder internal variables and update init.
rsned May 10, 2025
0b5e252
change polylinetype to package private like the other builder enums.
rsned May 11, 2025
6435633
Add remaining builder datastructure elements and finish init().
rsned May 11, 2025
45220f6
update comments and move edge_type to graph_options next to the other…
rsned May 18, 2025
38bd88b
minor enum shuffling
rsned May 18, 2025
03f216d
Add S2Builder Graph's EdgeProcessor class to Go with unit tests.
rsned May 18, 2025
4269fec
fix some staticcheck lint errors.
rsned May 18, 2025
d5f81d3
Update edgeProcessor and add a few more test cases to cover more bran…
rsned May 19, 2025
2ce78ad
simplify test merge input ids setup.
rsned May 19, 2025
e2d1225
Merge branch 'master' into builder-foundations
rsned May 19, 2025
c295223
Rename edge->graphEdge throughout, fix error exit case.
rsned May 28, 2025
463a9e0
Merge branch 'builder-foundations' of ssh://github.com/rsned/geo into…
rsned May 29, 2025
7b462a4
Add all of the test cases for graphEdgeProcessor's Run from C++
rsned May 29, 2025
a2bb0fe
Roll options back into the file of the same name.
rsned May 29, 2025
d817c66
Move graphEdgeProcessor and tests into builder_graph.go and tests.
rsned May 29, 2025
b68663f
Add comments to remaining member variables in Builder and Graph.
rsned May 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add another batch of builder internal variables and update init.
  • Loading branch information
rsned committed May 10, 2025
commit 60846585f5114da04dc094ecc46ae3ec6a14997a
128 changes: 128 additions & 0 deletions s2/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@

package s2

import (
"math"

"github.com/golang/geo/s1"
)

const (
// maxEdgeDeviationRatio is set so that MaxEdgeDeviation will be large enough
// compared to snapRadius such that edge splitting is rare.
Expand Down Expand Up @@ -63,6 +69,29 @@ const (
edgeTypeUndirected
)

// isFullPolygonPredicate is an interface for determining if Polygons are
// full or not. For output layers that represent polygons, there is an ambiguity
// inherent in spherical geometry that does not exist in planar geometry.
// Namely, if a polygon has no edges, does it represent the empty polygon
// (containing no points) or the full polygon (containing all points)? This
// ambiguity also occurs for polygons that consist only of degeneracies, e.g.
// a degenerate loop with only two edges could be either a degenerate shell in
// the empty polygon or a degenerate hole in the full polygon.
//
// To resolve this ambiguity, an IsFullPolygonPredicate may be specified for
// each output layer (see AddIsFullPolygonPredicate below). If the output
// after snapping consists only of degenerate edges and/or sibling pairs
// (including the case where there are no edges at all), then the layer
// implementation calls the given predicate to determine whether the polygon
// is empty or full except for those degeneracies. The predicate is given
// an S2Builder::Graph containing the output edges, but note that in general
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change :: references to Go style.

// the predicate must also have knowledge of the input geometry in order to
// determine the correct result.
//
// This predicate is only needed by layers that are assembled into polygons.
// It is not used by other layer types.
type isFullPolygonPredicate func(g *graph) (bool, error)

// builder is a tool for assembling polygonal geometry from edges. Here are
// some of the things it is designed for:
//
Expand Down Expand Up @@ -155,9 +184,108 @@ const (
// TODO(rsned): Make the type public when Builder is ready.
type builder struct {
opts *builderOptions
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recognize that the C++ and Java implementations also have a mutable Options member, but this seems like a weird use of the builder pattern. Someone could change state of the options part way through using the Builder, which seems like it would make reasoning about the semantic guarantees more difficult.

It also seems kind of odd that there's a builderOptions type, but also a lot of option-like fields in the builder itself. Does the builder need to hang on to the builderOptions that was used in init, or are the derived fields sufficient?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be only a couple places that read from it within Builder so making it non-mutable seems reasonable.

The primary use case from C++ is passing in the default options, or creating an options with an explicit snap function a la "S2Builder builder{S2Builder::Options(S2CellIdSnapFunction(snap_level))};"


// The maximum distance (inclusive) that a vertex can move when snapped,
// equal to options.SnapFunction().SnapRadius()).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

opts.snapFunction

siteSnapRadiusCA s1.ChordAngle
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love the CA prefixes on all these, though I see there's at least one case where there's a ChrodAngle and an Angle with the same base name.


// The maximum distance (inclusive) that an edge can move when snapping to a
// snap site. It can be slightly larger than the site snap radius when
// edges are being split at crossings.
edgeSnapRadiusCA s1.ChordAngle

// True if we need to check that snapping has not changed the input topology
// around any vertex (i.e. Voronoi site). Normally this is only necessary for
// forced vertices, but if the snap radius is very small (e.g., zero) and
// split_crossing_edges() is true then we need to do this for all vertices.
// In all other situations, any snapped edge that crosses a vertex will also
// be closer than min_edge_vertex_separation() to that vertex, which will
// cause us to add a separation site anyway.
checkAllSiteCrossings bool

maxEdgeDeviation s1.Angle
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason some fields aren't commented?

I don't see some of these in the C++ version, but they've got comments in the Java one you can copy.

edgeSiteQueryRadiusCA s1.ChordAngle
minEdgeLengthToSplitCA s1.ChordAngle

minSiteSeparation s1.Angle
minSiteSeparationCA s1.ChordAngle
minEdgeSiteSeparationCA s1.ChordAngle
minEdgeSiteSeparationCALimit s1.ChordAngle

maxAdjacentSiteSeparationCA s1.ChordAngle

// The squared sine of the edge snap radius. This is equivalent to the snap
// radius (squared) for distances measured through the interior of the
// sphere to the plane containing an edge. This value is used only when
// interpolating new points along edges (see GetSeparationSite).
edgeSnapRadiusSin2 float64

// True if snapping was requested. This is true if either snapRadius() is
// positive, or splitCrossingEdges() is true (which implicitly requests
// snapping to ensure that both crossing edges are snapped to the
// intersection point).
snappingRequested bool

// Initially false, and set to true when it is discovered that at least one
// input vertex or edge does not meet the output guarantees (e.g., that
// vertices are separated by at least snapFunction.minVertexSeparation).
snappingNeeded bool
}

// init initializes this instance with the given options.
func (b *builder) init(opts *builderOptions) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it desirable to be able to call Init on an existing builder, or would a constructor function be sufficient?

(S2Builder.java doesn't have an init function.)

b.opts = opts

snapFunc := opts.snapFunction
sr := snapFunc.SnapRadius()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this could be inlined. (It's a separate variable assignment in C++/Java because DCHECK/checkArgument are called on it.)


// Cap the snap radius to the limit.
if sr > maxSnapRadius {
sr = maxSnapRadius
}

// Convert the snap radius to an ChordAngle. This is the "true snap
// radius" used when evaluating exact predicates.
b.siteSnapRadiusCA = s1.ChordAngleFromAngle(sr)

// When intersectionTolerance is non-zero we need to use a larger snap
// radius for edges than for vertices to ensure that both edges are snapped
// to the edge intersection location. This is because the computed
// intersection point is not exact; it may be up to intersectionTolerance
// away from its true position. The computed intersection point might then
// be snapped to some other vertex up to SnapRadius away. So to ensure
// that both edges are snapped to a common vertex, we need to increase the
// snap radius for edges to at least the sum of these two values (calculated
// conservatively).
edgeSnapRadius := opts.edgeSnapRadius()
b.edgeSnapRadiusCA = roundUp(edgeSnapRadius)
b.snappingRequested = (edgeSnapRadius > 0)

// Compute the maximum distance that a vertex can be separated from an
// edge while still affecting how that edge is snapped.
b.maxEdgeDeviation = opts.maxEdgeDeviation()
b.edgeSiteQueryRadiusCA = s1.ChordAngleFromAngle(b.maxEdgeDeviation +
snapFunc.MinEdgeVertexSeparation())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: break after equals, or put the whole thing on one line.


// Compute the maximum edge length such that even if both endpoints move by
// the maximum distance allowed (i.e., edge_snap_radius), the center of the
// edge will still move by less than max_edge_deviation(). This saves us a
// lot of work since then we don't need to check the actual deviation.
if !b.snappingRequested {
b.minEdgeLengthToSplitCA = s1.InfChordAngle()
} else {
// This value varies between 30 and 50 degrees depending on
// the snap radius.
b.minEdgeLengthToSplitCA = s1.ChordAngleFromAngle(s1.Angle(2 *
math.Acos(math.Sin(edgeSnapRadius.Radians())/
math.Sin(b.maxEdgeDeviation.Radians()))))
}

// TODO(rsned): Continue adding to init
}

// roundUp rounds the given angle up by the max error and returns it as a chord angle.
func roundUp(a s1.Angle) s1.ChordAngle {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe roundAngleUp

ca := s1.ChordAngleFromAngle(a)
return ca.Expanded(ca.MaxAngleError())
}