Skip to content

Commit

Permalink
add overlaps to SpatialID and SpatialIDDetector
Browse files Browse the repository at this point in the history
  • Loading branch information
Strorkis committed Feb 12, 2025
1 parent 59eb499 commit d24fb27
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 20 deletions.
66 changes: 46 additions & 20 deletions spatial_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package spatialID

import (
"math"
"slices"
"strconv"
"strings"

Expand Down Expand Up @@ -100,7 +99,7 @@ func MergeSpatialIDs (spatialIDs []*SpatialID) []*SpatialID {
return mergedSpatialIDs
}

func CompareSpatialIDs (a, b *SpatialID) int {
func CompareSpatialIDs(a, b *SpatialID) int {
if a.GetZ() < b.GetZ() {
return -1
} else if a.GetZ() > b.GetZ() {
Expand Down Expand Up @@ -130,10 +129,10 @@ func CompareSpatialIDs (a, b *SpatialID) int {

// SpatialID 空間IDクラス
type SpatialID struct {
z int8 // 精度
f int64 // 高さID
x int64 // 経度ID
y int64 // 緯度ID
z int8 // 精度
f int64 // 高さID
x int64 // 経度ID
y int64 // 緯度ID
}

func NewSpatialIDFromString(string string) (*SpatialID, error) {
Expand Down Expand Up @@ -167,7 +166,7 @@ func NewSpatialIDFromGeodetic(geodetic coordinates.Geodetic, z int8) (*SpatialID
max := float64(int(1) << z)

// 経度方向のインデックスの計算
x := math.Floor(max * math.Mod(*geodetic.Longitude() + 180.0, 360.0))
x := math.Floor(max * math.Mod(*geodetic.Longitude()+180.0, 360.0))

radianLatitude := mathematics.RadianPerDegree * *geodetic.Latitude()

Expand Down Expand Up @@ -205,7 +204,7 @@ func NewSpatialID(
func (id *SpatialID) SetX(x int64) {
limit := int64(1 << id.GetZ())

id.x = x%limit
id.x = x % limit
if id.x < 0 {
id.x += limit
}
Expand Down Expand Up @@ -257,34 +256,61 @@ func (id SpatialID) GetY() int64 {

func (id SpatialID) String() string {
return strconv.FormatInt(int64(id.GetZ()), 10) + delimiter +
strconv.FormatInt(id.GetF(), 10) + delimiter +
strconv.FormatInt(id.GetX(), 10) + delimiter +
strconv.FormatInt(id.GetY(), 10)
strconv.FormatInt(id.GetF(), 10) + delimiter +
strconv.FormatInt(id.GetX(), 10) + delimiter +
strconv.FormatInt(id.GetY(), 10)
}

func (id SpatialID) NewParent(number int8) (*SpatialID, error) {
return NewSpatialID(
id.GetZ()-number,
id.GetF() >> number,
id.GetX() >> number,
id.GetY() >> number,
id.GetF()>>number,
id.GetX()>>number,
id.GetY()>>number,
)
}

func (id SpatialID) NewMinChild(number int8) (*SpatialID, error) {
return NewSpatialID(
id.GetZ()+number,
id.GetF() << number,
id.GetX() << number,
id.GetY() << number,
id.GetF()<<number,
id.GetX()<<number,
id.GetY()<<number,
)
}

func (id SpatialID) NewMaxChild(number int8) (*SpatialID, error) {
return NewSpatialID(
id.GetZ()+number,
(id.GetF() + 1) << number - 1,
(id.GetX() + 1) << number - 1,
(id.GetY() + 1) << number - 1,
(id.GetF()+1)<<number-1,
(id.GetX()+1)<<number-1,
(id.GetY()+1)<<number-1,
)
}

func (id SpatialID) GetZoomedDownTo(afterZ int8) (*SpatialID, error) {
return id.NewParent(id.z - afterZ)
}

func (id *SpatialID) Overlaps(targetId *SpatialID) bool {
// zは0以上であることが保証されているため、エラーハンドリングしない
if id.GetZ() < targetId.GetZ() {
targetId, _ = targetId.GetZoomedDownTo(id.GetZ())
} else if id.GetZ() > targetId.GetZ() {
id, _ = id.GetZoomedDownTo(targetId.GetZ())
}
return *id == *targetId
}

type SpatialIDs []*SpatialID

func (ids SpatialIDs) Overlaps(targetIDs SpatialIDs) bool {
for _, id := range ids {
for _, targetId := range targetIDs {
if id.Overlaps(targetId) {
return true
}
}
}
return false
}
68 changes: 68 additions & 0 deletions spatial_id_detector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package spatialID

import (
radixtree "github.com/trajectoryjp/multidimensional-radix-tree/src/tree"
)

type SpatialIDDetector interface {
IsOverlap(ids SpatialIDs) bool
}

type SpatialIDGreedyDetector struct {
ids SpatialIDs
}

func NewSpatialIDGreedyDetector(ids SpatialIDs) SpatialIDDetector {
return &SpatialIDGreedyDetector{ids}
}

func (detector *SpatialIDGreedyDetector) IsOverlap(targetIDs SpatialIDs) bool {
return detector.ids.Overlaps(targetIDs)
}

type SpatialIDTreeDetector struct {
positiveTree radixtree.TreeInterface
negativeTree radixtree.TreeInterface
}

func NewSpatialIDTreeDetector(ids SpatialIDs) SpatialIDDetector {
var positiveTree radixtree.TreeInterface
var negativeTree radixtree.TreeInterface
for _, id := range ids {
if id.GetF() >= 0 {
if positiveTree == nil {
positiveTree = radixtree.CreateTree(radixtree.Create3DTable())
}
treeIndex := radixtree.Indexs{id.GetF(), id.GetX(), id.GetY()}
positiveTree.Append(treeIndex, radixtree.ZoomSetLevel(id.GetZ()), struct{}{})
} else {
if negativeTree == nil {
negativeTree = radixtree.CreateTree(radixtree.Create3DTable())
}
treeIndex := radixtree.Indexs{^id.GetF(), id.GetX(), id.GetY()}
negativeTree.Append(treeIndex, radixtree.ZoomSetLevel(id.GetZ()), struct{}{})
}
}
return &SpatialIDTreeDetector{positiveTree, negativeTree}
}

func (tree *SpatialIDTreeDetector) IsOverlap(targetIds SpatialIDs) bool {
for _, id := range targetIds {
var isOverlap bool
if id.GetF() >= 0 {
if tree.positiveTree != nil {
treeIndex := radixtree.Indexs{id.GetF(), id.GetX(), id.GetY()}
isOverlap = tree.positiveTree.IsOverlap(treeIndex, radixtree.ZoomSetLevel(id.GetZ()))
}
} else {
if tree.negativeTree != nil {
treeIndex := radixtree.Indexs{^id.GetF(), id.GetX(), id.GetY()}
isOverlap = tree.negativeTree.IsOverlap(treeIndex, radixtree.ZoomSetLevel(id.GetZ()))
}
}
if isOverlap {
return true
}
}
return false
}
75 changes: 75 additions & 0 deletions spatial_id_detector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package spatialID

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func TestSpatialIDDetector(t *testing.T) {
testCases := []struct {
spatialIDs SpatialIDs
targetSpatialIDs SpatialIDs
expectedResult bool
}{
// ズームレベル最小、インデックス最小値/最大値
{
spatialIDs: SpatialIDs{{0, 0, 0, 0}},
targetSpatialIDs: SpatialIDs{{0, 0, 0, 0}},
expectedResult: true,
},
// ズームレベル最大、インデックス最小値
{
spatialIDs: SpatialIDs{{MaxZ, 0, 0, 0}},
targetSpatialIDs: SpatialIDs{{MaxZ, 0, 0, 0}},
expectedResult: true,
},
// ズームレベル最大、インデックス最大値
{
spatialIDs: SpatialIDs{{MaxZ, int64(1)<<MaxZ - 1, int64(1)<<MaxZ - 1, int64(1)<<MaxZ - 1}},
targetSpatialIDs: SpatialIDs{{MaxZ, int64(1)<<MaxZ - 1, int64(1)<<MaxZ - 1, int64(1)<<MaxZ - 1}},
expectedResult: true,
},
// ズームレベル違い
{
spatialIDs: SpatialIDs{{22, 0, 0, 0}},
targetSpatialIDs: SpatialIDs{{23, 1, 1, 1}},
expectedResult: true,
},
{
spatialIDs: SpatialIDs{{24, 2, 2, 2}},
targetSpatialIDs: SpatialIDs{{23, 1, 1, 1}},
expectedResult: true,
},
{
spatialIDs: SpatialIDs{{24, 3, 3, 3}},
targetSpatialIDs: SpatialIDs{{23, 1, 1, 1}},
expectedResult: true,
},
{
spatialIDs: SpatialIDs{{22, 1, 1, 1}, {23, 0, 0, 0}, {23, 2, 2, 2}, {24, 0, 0, 0}, {24, 1, 1, 1}},
targetSpatialIDs: SpatialIDs{{23, 1, 1, 1}},
expectedResult: false,
},
}

for testCaseIndex, testCase := range testCases {
for swapIndex := range 2 {
for detectorIndex, newSpatialIDDetector := range []func(SpatialIDs) SpatialIDDetector{
NewSpatialIDGreedyDetector,
NewSpatialIDTreeDetector,
} {
spatialIDDetector := newSpatialIDDetector(testCase.spatialIDs)
actualResult := spatialIDDetector.IsOverlap(testCase.targetSpatialIDs)
assert.Equal(
t,
testCase.expectedResult,
actualResult,
fmt.Sprintf("testCaseIndex: %d, swapIndex: %d, detectorIndex: %d", testCaseIndex, swapIndex, detectorIndex),
)
}
testCase.spatialIDs, testCase.targetSpatialIDs = testCase.targetSpatialIDs, testCase.spatialIDs
}
}
}

0 comments on commit d24fb27

Please sign in to comment.