Skip to content

Add PointIndex and ClosestPointQuery - #274

Open
fmeurisse wants to merge 3 commits into
golang:masterfrom
fmeurisse:point_index
Open

Add PointIndex and ClosestPointQuery#274
fmeurisse wants to merge 3 commits into
golang:masterfrom
fmeurisse:point_index

Conversation

@fmeurisse

Copy link
Copy Markdown

Summary

  • Adds PointIndex[Data], a generic index of points sorted by leaf CellID, with Add, Remove, Clear, and NumPoints operations.
  • Adds PointIndexIterator[Data] with the standard S2 iterator interface: Begin, Finish, Next, Prev, Seek, LocatePoint, and LocateCellID.
  • Updates README.md to mark S2PointIndex as implemented.

Implementation note / question

The current implementation stores entries in a plain sorted slice. Add and Remove maintain sort order by shifting elements, making each mutation O(n). This matches the build-once, query-many usage pattern described in the doc comment,
but is not suitable for large datasets or frequently mutated indexes.

The C++ S2 library uses std::map (a red-black tree) for this type, which gives O(log n) mutations. Would it make sense to introduce github.com/google/btree as a dependency to back this index with a proper tree structure? That would
bring mutations down to O(log n) at the cost of adding a new module dependency.

Happy to rework the implementation if the project is open to that dependency, or to keep the current approach and document the limitation more prominently if not.

@fmeurisse fmeurisse closed this May 29, 2026
@fmeurisse fmeurisse reopened this May 29, 2026
  Points are stored in a plain sorted slice keyed by leaf CellID.
  Add and Remove maintain sort order by shifting elements, so each
  mutation is O(n). This implementation is not a btree and is therefore
  not suitable for large datasets or frequently mutated indexes; it is
  designed for the build-once, query-many pattern.
@jmr

jmr commented May 29, 2026

Copy link
Copy Markdown
Collaborator

The C++ S2 library uses std::map (a red-black tree) for this type, which gives O(log n) mutations.

It uses absl::btree_multimap.

https://github.com/google/s2geometry/blob/282b35ba495a7ba762802ad8367abb8988cd0db8/src/s2/s2point_index.h#L37

Would it make sense to introduce github.com/google/btree as a dependency to back this index with a proper tree structure?

That's archived. https://github.com/google/btree

Can you do some research on other b-tree options?

@fmeurisse

Copy link
Copy Markdown
Author

That's archived. https://github.com/google/btree

Can you do some research on other b-tree options?

With a quick search, there don’t seem to be many B-tree implementations in Go. I only found two GitHub repositories from the same author, Josh Baker (Tidwall):

  • tidwall/btree
  • tidwall/btype (very recent)

The tidwall/btree project is a fairly complete Go B-tree implementation with support for generics, ordered maps/sets, iterators, bulk loading, and copy-on-write features.

@jmr

jmr commented May 30, 2026

Copy link
Copy Markdown
Collaborator
  • tidwall/btree

This seems fine.

  • tidwall/btype (very recent)

I would give this more burn-in time.

@jmr jmr left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Probably some more comments later.

Comment thread s2/closest_point_query.go
@@ -0,0 +1,496 @@
// Copyright 2015 Google Inc. All rights reserved.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Copyright 2025-2026 Google LLC. All rights reserved.

Comment thread s2/point_index.go
// this after copying an iterator (it2 = *it) and before calling Next() or
// Prev() across a CellID boundary, to decouple the copy's cursor from the
// original.
func (it *PointIndexIterator[Data]) Refresh() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Refresh seems a bit weird. What if we just don't support iterator copying?

Are you implementing this because you want to use it or because you want to implement something?

If the first, will no copy still work for you?

There's also BTreeG which supports duplicate keys. That could work.

query.opts.distanceLimit = s1.ChordAngleFromAngle(s1.Angle(frac) * queryRadius)
}
if randomUniformInt(2) != 0 {
maxErrFrac := 1e-4 + math.Exp(randomUniformFloat64(0, 1)*math.Log(1.0))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Log(1.0) is zero. If #279 gets merged before yours, use that. If not, maxErrFrac := math.Exp(randomUniformFloat64(math.Log(1e-4), 0)).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

#279 has been merged, so use randomLogUniformFloat64.

Comment thread s2/point_index.go
func (it *PointIndexIterator[Data]) LocatePoint(target Point) bool {
id := cellIDFromPoint(target)
it.Seek(id)
if !it.Done() && it.CellID().RangeMin() <= id {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We only have leaf cells, so this can be simplifed. Comment why it's different than C++, though.

Comment thread s2/point_index.go
return true
}
// Already at the first entry; restore the cursor so Next() still works.
it.iter.Seek(it.currentID)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do we need this? Check what btree iterator does and add a comment.

@jmr jmr changed the title add PointIndex and PointIndexIterator Add PointIndex and ClosestPointQuery Jun 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants