diff --git a/common/consts/consts.go b/common/consts/consts.go index deb83c1..86b9a6c 100644 --- a/common/consts/consts.go +++ b/common/consts/consts.go @@ -17,3 +17,8 @@ const ( // InnerIDAltitudekeyIndex altitudekeyはinnerID[1] InnerIDAltitudekeyIndex = 1 ) + +// ZBaseOffsetForNegativeFIndex +// +// 元の最大高さを保ったまま正方向と負方向でfインデックスを半数ずつ導入するためのzBaseOffset +const ZBaseOffsetForNegativeFIndex = 1 << (ZOriginValue - 1) diff --git a/integrate/check_spatial_id_overlap.go b/detector/check_spatial_id_overlap.go similarity index 55% rename from integrate/check_spatial_id_overlap.go rename to detector/check_spatial_id_overlap.go index 73ca713..c6b1d69 100644 --- a/integrate/check_spatial_id_overlap.go +++ b/detector/check_spatial_id_overlap.go @@ -1,7 +1,12 @@ -package integrate +package detector import ( "fmt" + "github.com/trajectoryjp/multidimensional-radix-tree/src/tree" + "github.com/trajectoryjp/spatial_id_go/v4/common/consts" + "github.com/trajectoryjp/spatial_id_go/v4/common/errors" + "github.com/trajectoryjp/spatial_id_go/v4/integrate" + "github.com/trajectoryjp/spatial_id_go/v4/transform" "strconv" "strings" ) @@ -27,36 +32,9 @@ import ( // 以下の条件に当てはまる場合、エラーインスタンスが返却される。ただしこのときbool値にfalseが返却される。 // 空間IDフォーマット不正:空間IDのフォーマットに違反する値が"重複判定対象空間ID"に入力されていた場合。 func CheckSpatialIdsOverlap(spatialId1 string, spatialId2 string) (bool, error) { - // ズームレベルの取り出し - arr1 := strings.Split(spatialId1, "/") - arr2 := strings.Split(spatialId2, "/") - if len(arr1) != 4 || len(arr2) != 4 { - // 不正形式 - return false, fmt.Errorf("invalid format. spatialId1: %v, spatialId2: %v", spatialId1, spatialId2) - } - zoom1, _ := strconv.Atoi(arr1[0]) - zoom2, _ := strconv.Atoi(arr2[0]) - - // zoomレベルで場合分け - // zoomレベルが等しいとき、変換なし - if zoom1 > zoom2 { - // spatialId2のzoomレベルをzoom1に合わせるよう変換 - convertedSpatialId, err := ChangeSpatialIdsZoom([]string{spatialId1}, int64(zoom2)) - if err != nil { - return false, err - } - spatialId1 = convertedSpatialId[0] - } else if zoom1 < zoom2 { - // spatialId1のzoomレベルをzoom2に合わせるよう変換 - convertedSpatialId, err := ChangeSpatialIdsZoom([]string{spatialId2}, int64(zoom1)) - if err != nil { - return false, err - } - spatialId2 = convertedSpatialId[0] - } - - return spatialId1 == spatialId2, nil - + ids1 := []string{spatialId1} + ids2 := []string{spatialId2} + return CheckSpatialIdsArrayOverlap(ids1, ids2) } // CheckSpatialIdsArrayOverlap 2つの空間ID重複の判定関数 @@ -71,35 +49,100 @@ func CheckSpatialIdsOverlap(spatialId1 string, spatialId2 string) (bool, error) // // spatialIds1, spatialIds2: 重複判定対象の空間ID列。ズームレベルが異なっている入力も許容。 // +// ただし高度は16,777,216未満 〜 -16,777,216m以上に制限される(この範囲で高度変換を行う) +// // 戻り値: // // bool: // 重複の有無が返却される。true: 重複あり false: 重複なし // // error: -// 以下の条件に当てはまる場合、エラーインスタンスが返却される。ただしこのときbool値にfalseが返却される。 +// 以下の条件に当てはまる場合、エラーインスタンスが返却される。このときbool値はfalseで返却される。 // 空間IDフォーマット不正:空間IDのフォーマットに違反する値が"重複判定対象空間ID"に入力されていた場合。 +// 空間ID高度範囲外:高度範囲外の空間IDが入力されていた場合。 func CheckSpatialIdsArrayOverlap(spatialIds1 []string, spatialIds2 []string) (bool, error) { - // spatialIds1から各要素の取り出し - for _, spatialId1 := range spatialIds1 { - // spatialIds2から各要素の取り出し - for _, spatialId2 := range spatialIds2 { - // 取り出した要素の比較 - result, err := CheckSpatialIdsOverlap(spatialId1, spatialId2) - if err != nil { - // エラー発生時、falseとerrorを返却 - return false, err - } - if result { - // 重複判定時、trueとnilを返却 - return result, nil - } + // f,x,yで3次元分の2分木 + tr := tree.CreateTree(tree.Create3DTable()) + // spatialIds1から各要素をradix treeに格納 + for indexSpatialId1, spatialId1 := range spatialIds1 { + zoom1, f1, x1, y1, err := getSpatialIdAttrs(spatialId1) + if err != nil { + return false, fmt.Errorf("%w @spatialId1[%v]", err, indexSpatialId1) + } + // 高度インデックスをオフセット変換のみ実行して自然数にする + // minAltitudeKey == maxAltitudeKeyになるため結果は片方のみ利用する + convertedFIndex, _, errAltConversion := transform.ConvertZToMinMaxAltitudekey(int64(f1), int64(zoom1), int64(zoom1), consts.ZOriginValue, consts.ZBaseOffsetForNegativeFIndex) + if convertedFIndex < 0 { + return false, errors.NewSpatialIdError(errors.InputValueErrorCode, fmt.Sprintf("input f-index %v is out of altitude range @spatialId1[%v] = %v", f1, indexSpatialId1, spatialId1)) + } + if errAltConversion != nil { + return false, fmt.Errorf("%w @spatialId1[%v] = %v", errAltConversion, indexSpatialId1, spatialId1) + } + index1 := tree.Indexs{convertedFIndex, int64(x1), int64(y1)} + tr.Append(index1, tree.ZoomSetLevel(zoom1), spatialId1) + } + // spatialIds2から各要素の取り出し + for indexSpatialId2, spatialId2 := range spatialIds2 { + zoom2, f2, x2, y2, err := getSpatialIdAttrs(spatialId2) + if err != nil { + return false, fmt.Errorf("%w @spatialId2[%v]", err, indexSpatialId2) + } + // 取り出した要素の比較 + // 高度インデックスをオフセット変換のみ実行して自然数にする + // minAltitudeKey == maxAltitudeKeyになるため結果は片方のみ利用する + convertedFIndex2, _, errAltConversion := transform.ConvertZToMinMaxAltitudekey(int64(f2), int64(zoom2), int64(zoom2), consts.ZOriginValue, consts.ZBaseOffsetForNegativeFIndex) + if convertedFIndex2 < 0 { + return false, errors.NewSpatialIdError(errors.InputValueErrorCode, fmt.Sprintf("input f-index %v is out of altitude range @spatialId2[%v] = %v", f2, indexSpatialId2, spatialId2)) + } + if errAltConversion != nil { + return false, fmt.Errorf("%w @spatialId2[%v] = %v", errAltConversion, indexSpatialId2, spatialId2) + } + result := tr.IsOverlap(tree.Indexs{convertedFIndex2, int64(x2), int64(y2)}, tree.ZoomSetLevel(zoom2)) + if result { + // 重複判定時、trueとnilを返却 + return result, nil } } return false, nil } +// getSpatialIdAttrs 空間IDフォーマットチェック関数 +// +// 入力された空間IDの各成分を格納した配列を返却する。 +// 以下の条件に当てはまる場合はフォーマット違反となりエラーインスタンスが返却される。 +// +// ・空間IDの各成分に数値が入力されていない場合 +// ・区切り文字の数が3つで無い場合 +// +// 引数: +// +// spatialId:空間ID +// +// 戻り値: +// +// 空間IDに含まれる精度(ズームレベル), F, X, Y成分を格納した以下のtuple +// (精度(ズームレベル), F成分, X成分, Y成分) +// 入力された拡張空間IDのフォーマットが不正な場合、戻り値を0にしエラーインスタンスを返却。 +func getSpatialIdAttrs(spatialId string) (int, int, int, int, error) { + // 分割 + spatialIdAttributes := strings.Split(spatialId, consts.SpatialIDDelimiter) + if len(spatialIdAttributes) != 4 { + // 不正形式(要素数) + return 0, 0, 0, 0, errors.NewSpatialIdError(errors.InputValueErrorCode, fmt.Sprintf("spatialId: %v", spatialId)) + } + var errNumberConversion error + zoom, errNumberConversion := strconv.Atoi(spatialIdAttributes[0]) + f, errNumberConversion := strconv.Atoi(spatialIdAttributes[1]) + x, errNumberConversion := strconv.Atoi(spatialIdAttributes[2]) + y, errNumberConversion := strconv.Atoi(spatialIdAttributes[3]) + // 不正形式(数値) + if errNumberConversion != nil { + return 0, 0, 0, 0, errors.NewSpatialIdError(errors.InputValueErrorCode, fmt.Sprintf("spatialId: %v", spatialId)) + } + return zoom, f, x, y, nil +} + // CheckExtendedSpatialIdsOverlap 2つの拡張空間IDの重複の判定関数 // // 比較対象として入力された2つの拡張空間IDのズームレベルを変換して揃え、重複の判定を行う。 @@ -148,12 +191,12 @@ func CheckExtendedSpatialIdsOverlap(extendedSpatialId1 string, extendedSpatialId } // 変換後のZoomレベルを指定して変換 - extendedSpatialIds1, err := ChangeExtendedSpatialIdsZoom([]string{extendedSpatialId1}, int64(targetHorizontalZoom), int64(targetVerticalZoom)) + extendedSpatialIds1, err := integrate.ChangeExtendedSpatialIdsZoom([]string{extendedSpatialId1}, int64(targetHorizontalZoom), int64(targetVerticalZoom)) if err != nil { // 変換エラー return false, err } - extendedSpatialIds2, err := ChangeExtendedSpatialIdsZoom([]string{extendedSpatialId2}, int64(targetHorizontalZoom), int64(targetVerticalZoom)) + extendedSpatialIds2, err := integrate.ChangeExtendedSpatialIdsZoom([]string{extendedSpatialId2}, int64(targetHorizontalZoom), int64(targetVerticalZoom)) if err != nil { // 変換エラー return false, err diff --git a/integrate/check_spatial_id_overlap_test.go b/detector/check_spatial_id_overlap_test.go similarity index 56% rename from integrate/check_spatial_id_overlap_test.go rename to detector/check_spatial_id_overlap_test.go index dd5b6f4..f37d46c 100644 --- a/integrate/check_spatial_id_overlap_test.go +++ b/detector/check_spatial_id_overlap_test.go @@ -1,4 +1,4 @@ -package integrate +package detector import ( "reflect" @@ -77,7 +77,7 @@ func TestCheckSpatialIdsOverlap03(t *testing.T) { // 期待値 expectValue := false - expectError := "invalid format. spatialId1: , spatialId2: 16/0/58198/25804" + expectError := "InputValueError,入力チェックエラー,spatialId: @spatialId1[0]" if !reflect.DeepEqual(resultValue, expectValue) { t.Errorf("空間ID - 期待値:%v, 取得値:%v", expectValue, resultValue) @@ -96,30 +96,282 @@ func TestCheckSpatialIdsOverlap03(t *testing.T) { // + 試験データ // - パターン1: // 比較対象の空間ID:{"25/0/29803148/13212522/777"},{"16/0/58198/25804"} +// - パターン2: +// 比較対象の空間ID:{"25/0/29803148/13212522"},{"16/0/58198/25804/777"} // // + 確認内容 // - 入力値から入力チェックエラーを取得できること func TestCheckSpatialIdsOverlap04(t *testing.T) { - //入力値 - spatialId1 := "25/0/29803148/13212522/777" - spatialId2 := "16/0/58198/25804" + testCases := []struct { + spatialId1 string + spatialId2 string + expectValue bool + expectError string + }{ + { + //入力値 + spatialId1: "25/0/29803148/13212522/777", + spatialId2: "16/0/58198/25804", + // 期待値 + expectValue: false, + expectError: "InputValueError,入力チェックエラー,spatialId: 25/0/29803148/13212522/777 @spatialId1[0]", + }, + { + //入力値 + spatialId1: "25/0/29803148/13212522", + spatialId2: "16/0/58198/25804/777", + // 期待値 + expectValue: false, + expectError: "InputValueError,入力チェックエラー,spatialId: 16/0/58198/25804/777 @spatialId2[0]", + }, + } + for _, testCase := range testCases { + resultValue, resultErr := CheckSpatialIdsOverlap(testCase.spatialId1, testCase.spatialId2) + + if !reflect.DeepEqual(resultValue, testCase.expectValue) { + t.Errorf("空間ID - 期待値:%v, 取得値:%v", testCase.expectValue, resultValue) + } + if resultErr.Error() != testCase.expectError { + // 戻り値のエラーインスタンスが期待値と異なる場合Errorをログに出力 + t.Errorf("error - 期待値:%s, 取得値:%s\n", testCase.expectError, resultErr.Error()) + } + } + + t.Log("テスト終了") +} + +// TestCheckSpatialIdsOverlap05 空間IDの重複確認関数 正常系動作確認 +// +// + 試験データ +// - パターン1: +// 比較対象の空間ID:{"13/1/7274/3225"}, {"16/8/58198/25804"} +// +// + 確認内容 +// - fインデックスの包含関係があることを確認できること +func TestCheckSpatialIdsOverlap05(t *testing.T) { + // 入力値 + spatialId1 := "13/1/7274/3225" + spatialId2 := "16/8/58198/25804" + resultValue, resultErr := CheckSpatialIdsOverlap(spatialId1, spatialId2) + + // 期待値 + expectValue := true + + if !reflect.DeepEqual(resultValue, expectValue) { + t.Errorf("空間ID - 期待値:%v, 取得値:%v", expectValue, resultValue) + } + if resultErr != nil { + // 戻り値のエラーインスタンスが期待値と異なる場合Errorをログに出力 + t.Errorf("error - 期待値:nil, 取得値:%s", resultErr) + } + + t.Log("テスト終了") +} + +// TestCheckSpatialIdsOverlap06 空間IDの重複確認関数 正常系動作確認 +// +// + 試験データ +// - パターン1: +// 比較対象の空間ID:{"13/1/7274/3225"}, {"16/17/58198/25804"} +// +// + 確認内容 +// - fインデックスの包含関係がないことを確認できること +func TestCheckSpatialIdsOverlap06(t *testing.T) { + // 入力値 + spatialId1 := "13/1/7274/3225" + spatialId2 := "16/17/58198/25804" resultValue, resultErr := CheckSpatialIdsOverlap(spatialId1, spatialId2) // 期待値 expectValue := false - expectError := "invalid format. spatialId1: 25/0/29803148/13212522/777, spatialId2: 16/0/58198/25804" if !reflect.DeepEqual(resultValue, expectValue) { t.Errorf("空間ID - 期待値:%v, 取得値:%v", expectValue, resultValue) } - if resultErr.Error() != expectError { + if resultErr != nil { // 戻り値のエラーインスタンスが期待値と異なる場合Errorをログに出力 - t.Errorf("error - 期待値:%s, 取得値:%s\n", expectError, resultErr.Error()) + t.Errorf("error - 期待値:nil, 取得値:%s", resultErr) + } + + t.Log("テスト終了") +} + +// TestCheckSpatialIdsOverlap07 空間IDの重複確認関数 正常系動作確認 +// +// + 試験データ +// - パターン1: +// 比較対象の空間ID:{"13/-1/7274/3225"}, {"16/-8/58198/25804"} +// +// + 確認内容 +// - fインデックスが負数の場合においても入力値から包含関係があることを確認できること +func TestCheckSpatialIdsOverlap07(t *testing.T) { + // 入力値 + spatialId1 := "13/-1/7274/3225" + spatialId2 := "16/-8/58198/25804" + resultValue, resultErr := CheckSpatialIdsOverlap(spatialId1, spatialId2) + + // 期待値 + expectValue := true + + if !reflect.DeepEqual(resultValue, expectValue) { + t.Errorf("空間ID - 期待値:%v, 取得値:%v", expectValue, resultValue) + } + if resultErr != nil { + // 戻り値のエラーインスタンスが期待値と異なる場合Errorをログに出力 + t.Errorf("error - 期待値:nil, 取得値:%s", resultErr) + } + + t.Log("テスト終了") +} + +// TestCheckSpatialIdsOverlap08 空間IDの重複確認関数 正常系動作確認 +// +// + 試験データ +// - パターン1: +// 比較対象の空間ID:{"13/-1/7274/3225"}, {"16/-10/58198/25804"} +// +// + 確認内容 +// - fインデックスに関する包含関係がないことを確認できること +func TestCheckSpatialIdsOverlap08(t *testing.T) { + // 入力値 + spatialId1 := "13/-1/7274/3225" + spatialId2 := "16/-10/58198/25804" + resultValue, resultErr := CheckSpatialIdsOverlap(spatialId1, spatialId2) + + // 期待値 + expectValue := false + + if !reflect.DeepEqual(resultValue, expectValue) { + t.Errorf("空間ID - 期待値:%v, 取得値:%v", expectValue, resultValue) + } + if resultErr != nil { + // 戻り値のエラーインスタンスが期待値と異なる場合Errorをログに出力 + t.Errorf("error - 期待値:nil, 取得値:%s", resultErr) } t.Log("テスト終了") } +// TestCheckSpatialIdsOverlap09 空間IDの重複確認関数 空間ID高度変換失敗 +// +// 試験詳細: +// + 試験データ +// - パターン1(入力高度範囲外エラー): +// 比較対象の空間ID:{"25/16777216/0/3225"},{"18/1/58198/25804"} +// - パターン2(入力高度範囲外エラー): +// 比較対象の空間ID:{"18/1/58198/25804"},{"25/16777216/0/3225"} +// - パターン3(入力高度範囲外エラー): +// 比較対象の空間ID:{"25/-16777217/0/3225"},{"16/8/58198/25804"} +// - パターン4(入力高度範囲外エラー): +// 比較対象の空間ID:{"18/1/58198/25804"},{"25/-16777217/0/3225"} +// +// + 確認内容 +// - 入力値から指定した入力チェックエラー、変換エラーを取得できること +func TestCheckSpatialIdsOverlap09(t *testing.T) { + testCases := []struct { + spatialId1 string + spatialId2 string + expectValue bool + expectError string + }{ + { + // 入力空間IDのfインデックスが不正 + //入力値 + spatialId1: "25/16777216/0/3225", + spatialId2: "18/1/58198/25804", + // 期待値 + expectValue: false, + expectError: "InputValueError,入力チェックエラー,output index does not exist with given outputZoom, zBaseExponent, and zBaseOffset @spatialId1[0] = 25/16777216/0/3225", + }, + { + // 入力空間IDのfインデックスが不正 + //入力値 + spatialId1: "18/1/58198/25804", + spatialId2: "25/16777216/0/3225", + // 期待値 + expectValue: false, + expectError: "InputValueError,入力チェックエラー,output index does not exist with given outputZoom, zBaseExponent, and zBaseOffset @spatialId2[0] = 25/16777216/0/3225", + }, + { + // 入力可能な高度インデックス範囲を超えている(下限より小さい) + //入力値 + spatialId1: "25/-16777217/0/3225", + spatialId2: "18/1/58198/25804", + // 期待値 + expectValue: false, + // 高度変換が負数を許容しないため高度変換エラーになる + expectError: "InputValueError,入力チェックエラー,output index does not exist with given outputZoom, zBaseExponent, and zBaseOffset @spatialId1[0] = 25/-16777217/0/3225", + }, + { + // 入力可能な高度インデックス範囲を超えている(下限より小さい) + //入力値 + spatialId1: "18/1/58198/25804", + spatialId2: "25/-16777217/0/3225", + // 期待値 + expectValue: false, + // 高度変換が負数を許容しないため高度変換エラーになる + expectError: "InputValueError,入力チェックエラー,output index does not exist with given outputZoom, zBaseExponent, and zBaseOffset @spatialId2[0] = 25/-16777217/0/3225", + }, + } + for _, testCase := range testCases { + resultValue, err := CheckSpatialIdsOverlap(testCase.spatialId1, testCase.spatialId2) + var resultErr string + if err != nil { + resultErr = err.Error() + } + + if !reflect.DeepEqual(resultValue, testCase.expectValue) { + t.Errorf("空間ID - 期待値:%v, 取得値:%v", testCase.expectValue, resultValue) + } + if resultErr != testCase.expectError { + // 戻り値のエラーインスタンスが期待値と異なる場合Errorをログに出力 + t.Errorf("error - 期待値:%s, 取得値:%s\n", testCase.expectError, resultErr) + } + } + + t.Log("テスト終了") +} + +// BenchmarkCheckSpatialIdsOverlap01 空間IDの重複確認関数 包含関係あり +// +// + 試験データ +// - パターン1: +// 比較対象の空間ID:{"13/0/7274/3225"}, {"16/0/58198/25804"}(包含関係あり) +// +// + 確認内容 +// - 入力値の先頭に包含関係があった場合の処理速度 +func BenchmarkCheckSpatialIdsOverlap01(b *testing.B) { + // 入力値 + spatialId1 := "13/0/7274/3225" + spatialId2 := "16/0/58198/25804" + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = CheckSpatialIdsOverlap(spatialId1, spatialId2) + } + b.StopTimer() + b.Log("テスト終了") +} + +// BenchmarkCheckSpatialIdsOverlap02 空間IDの重複確認関数 包含関係なし +// +// + 試験データ +// - パターン1: +// 比較対象の空間ID:{"13/0/7275/3226"}, {"16/0/58198/25804"}(包含関係なし) +// +// + 確認内容 +// - 入力値に包含関係がなかった場合の処理速度 +func BenchmarkCheckSpatialIdsOverlap02(b *testing.B) { + // 入力値 + spatialId1 := "13/0/7275/3226" + spatialId2 := "16/0/58198/25804" + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = CheckSpatialIdsOverlap(spatialId1, spatialId2) + } + b.StopTimer() + b.Log("テスト終了") +} + // TestCheckSpatialIdsArrayOverlap01 空間ID列の重複確認関数 正常系動作確認 // // + 試験データ @@ -192,7 +444,7 @@ func TestCheckSpatialIdsArrayOverlap03(t *testing.T) { // 期待値 expectValue := false - expectError := "invalid format. spatialId1: , spatialId2: 16/0/58198/25803" + expectError := "InputValueError,入力チェックエラー,spatialId: @spatialId1[0]" if !reflect.DeepEqual(resultValue, expectValue) { t.Errorf("空間ID - 期待値:%v, 取得値:%v", expectValue, resultValue) @@ -222,7 +474,7 @@ func TestCheckSpatialIdsArrayOverlap04(t *testing.T) { // 期待値 expectValue := false - expectError := "invalid format. spatialId1: 25/0/29803148/13212522/777, spatialId2: 16/0/58198/25803" + expectError := "InputValueError,入力チェックエラー,spatialId: 25/0/29803148/13212522/777 @spatialId1[0]" if !reflect.DeepEqual(resultValue, expectValue) { t.Errorf("空間ID - 期待値:%v, 取得値:%v", expectValue, resultValue) @@ -235,6 +487,54 @@ func TestCheckSpatialIdsArrayOverlap04(t *testing.T) { t.Log("テスト終了") } +// BenchmarkCheckSpatialIdsArrayOverlap01 空間ID列の重複確認関数 空間IDの重複確認関数 配列ベンチマーク(包含関係あり) +// +// + 試験データ +// - パターン1(包含関係あり): +// 比較対象の空間ID列:[100]{"13/0/7274/3225"}, [100]{"16/0/58198/25804"} +// +// + 確認内容 +// - 入力値に包含関係があった場合の処理速度 +func BenchmarkCheckSpatialIdsArrayOverlap01(b *testing.B) { + // 入力値 + spatialIds1 := []string{} + spatialIds2 := []string{} + for i := 0; i < 100; i++ { + spatialIds1 = append(spatialIds1, "13/0/7274/3225") + spatialIds2 = append(spatialIds2, "16/0/58198/25804") + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = CheckSpatialIdsArrayOverlap(spatialIds1, spatialIds2) + } + b.StopTimer() + b.Log("テスト終了") +} + +// BenchmarkCheckSpatialIdsArrayOverlap02 空間ID列の重複確認関数 空間IDの重複確認関数 配列ベンチマーク(包含関係なし) +// +// + 試験データ +// - パターン1(包含関係なし): +// 比較対象の空間ID列:[100]{"13/0/7275/3226"}, [100]{"16/0/58198/25804"} +// +// + 確認内容 +// - 入力値に包含関係がない場合の処理速度 +func BenchmarkCheckSpatialIdsArrayOverlap02(b *testing.B) { + // 入力値 + spatialIds1 := []string{} + spatialIds2 := []string{} + for i := 0; i < 100; i++ { + spatialIds1 = append(spatialIds1, "13/0/7275/3226") + spatialIds2 = append(spatialIds2, "16/0/58198/25804") + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = CheckSpatialIdsArrayOverlap(spatialIds1, spatialIds2) + } + b.StopTimer() + b.Log("テスト終了") +} + // TestCheckExtendedSpatialIdsOverlap01 拡張空間IDの重複確認関数 正常系動作確認 // // 試験詳細: @@ -357,6 +657,26 @@ func TestCheckExtendedSpatialIdsOverlap04(t *testing.T) { t.Log("テスト終了") } +// BenchmarkCheckExtendedSpatialIdsOverlap01 空間IDの重複確認関数 自然数fインデックスベンチマーク +// +// + 試験データ +// - パターン1: +// 比較対象の空間ID:{"16/58206/25805/16/1"}, {"15/29104/12902/15/0"}(包含関係あり) +// +// + 確認内容 +// - 入力値に包含関係があった場合の処理速度 +func BenchmarkCheckExtendedSpatialIdsOverlap01(b *testing.B) { + // 入力値 + spatialId1 := "16/58209/25805/16/1" + spatialId2 := "15/29104/12902/15/0" + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = CheckExtendedSpatialIdsOverlap(spatialId1, spatialId2) + } + b.StopTimer() + b.Log("テスト終了") +} + // TestCheckExtendedSpatialIdsArrayOverlap01 拡張空間IDの重複確認関数 正常系動作確認 // // 試験詳細: @@ -478,3 +798,27 @@ func TestCheckExtendedSpatialIdsArrayOverlap04(t *testing.T) { t.Log("テスト終了") } + +// BenchmarkCheckExtendedSpatialIdsArrayOverlap01 空間ID列の重複確認関数 空間IDの重複確認関数 配列ベンチマーク +// +// + 試験データ +// - パターン1(包含関係あり): +// 比較対象の空間ID列:[10]{"16/58206/25805/16/1"}, [10]{"15/29104/12902/15/0"} +// +// + 確認内容 +// - 入力値に包含関係があった場合の処理速度 +func BenchmarkCheckExtendedSpatialIdsArrayOverlap01(b *testing.B) { + // 入力値 + spatialIds1 := []string{"16/58206/25805/16/1"} + spatialIds2 := []string{"15/29104/12902/15/0"} + for i := 0; i < 9; i++ { + spatialIds1 = append(spatialIds1, "16/58206/25805/16/1") + spatialIds2 = append(spatialIds2, "15/29104/12902/15/0") + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = CheckExtendedSpatialIdsArrayOverlap(spatialIds1, spatialIds2) + } + b.StopTimer() + b.Log("テスト終了") +} diff --git a/examples/detectOverlap/.gitignore b/examples/detectOverlap/.gitignore new file mode 100644 index 0000000..c627000 --- /dev/null +++ b/examples/detectOverlap/.gitignore @@ -0,0 +1 @@ +*.prof diff --git a/examples/detectOverlap/go.mod b/examples/detectOverlap/go.mod new file mode 100644 index 0000000..b6b1fd6 --- /dev/null +++ b/examples/detectOverlap/go.mod @@ -0,0 +1,15 @@ +module github.com/trajectoryjp/spatial_id_go/v4/testoverlap + +go 1.22.5 + +require github.com/trajectoryjp/spatial_id_go/v4 v4.0.0-20240813075132-0b8c6d4fdae5 + +require ( + github.com/go-gl/mathgl v1.1.0 // indirect + github.com/trajectoryjp/closest_go v1.0.1 // indirect + github.com/trajectoryjp/geodesy_go v1.0.1 // indirect + github.com/trajectoryjp/multidimensional-radix-tree/src v0.0.0-20240725143308-c304415df20d // indirect + github.com/wroge/wgs84 v1.1.7 // indirect + golang.org/x/image v0.7.0 // indirect + gonum.org/v1/gonum v0.13.0 // indirect +) diff --git a/examples/detectOverlap/go.sum b/examples/detectOverlap/go.sum new file mode 100644 index 0000000..1fbf175 --- /dev/null +++ b/examples/detectOverlap/go.sum @@ -0,0 +1,52 @@ +github.com/go-gl/mathgl v1.1.0 h1:0lzZ+rntPX3/oGrDzYGdowSLC2ky8Osirvf5uAwfIEA= +github.com/go-gl/mathgl v1.1.0/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ= +github.com/trajectoryjp/closest_go v1.0.1 h1:dvshtZzPDwhGDhzn5SVZxlgNnqr/gLp4uIpGDIEOHaU= +github.com/trajectoryjp/closest_go v1.0.1/go.mod h1:+iTxW7/pOi0dIqo2/GcqGqWh/fAj2gMjDIiZ+BFDXIc= +github.com/trajectoryjp/geodesy_go v1.0.1 h1:FvvnA3kPcPoXOjn6cS27R9aiCLyszOarFuZRVlQRG2s= +github.com/trajectoryjp/geodesy_go v1.0.1/go.mod h1:eJuX+ds+t3MKxHG9dhobqNjUbUsYlpTT9XDO0mbc0LU= +github.com/trajectoryjp/multidimensional-radix-tree/src v0.0.0-20240725143308-c304415df20d h1:K2EBr4rnKcca/DjrMI779x2nJ1U3UuFyZsFPh/uR52I= +github.com/trajectoryjp/multidimensional-radix-tree/src v0.0.0-20240725143308-c304415df20d/go.mod h1:dDSriNaS7E5bvwmmXxF62rqb1FfgzgB7IDdMEsm8O9A= +github.com/trajectoryjp/spatial_id_go/v4 v4.0.0-20240813075132-0b8c6d4fdae5 h1:MkiRbXMpb8DY4jdBk8PK8mlI9nTM8dItFRYjqDkbR0M= +github.com/trajectoryjp/spatial_id_go/v4 v4.0.0-20240813075132-0b8c6d4fdae5/go.mod h1:zpAxqh+UyVGxJbqF3R9QCW9SnZ7/GJnJ1T9kiQ4jA2E= +github.com/wroge/wgs84 v1.1.7 h1:8WVUUrpjysYxrn0ssWX7z90SOUKCuHt9NQ5tg9ovjIY= +github.com/wroge/wgs84 v1.1.7/go.mod h1:mc1F8ubW03DO4zaf/006cmhaiMlfvbKmqVAcPuAtsNA= +github.com/xieyuschen/deepcopy v1.0.1 h1:nTCnKprCOdibz8WXWlMZzULIlpzZX0ZzKjz8HlGd/Nk= +github.com/xieyuschen/deepcopy v1.0.1/go.mod h1:smzaXhQZuuehOzevwMMLzvM7gBslB4VPdgJbwdyIDSA= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw= +golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM= +gonum.org/v1/gonum v0.13.0/go.mod h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU= diff --git a/examples/detectOverlap/main.go b/examples/detectOverlap/main.go new file mode 100644 index 0000000..6b3259d --- /dev/null +++ b/examples/detectOverlap/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "github.com/trajectoryjp/spatial_id_go/v4/detector" + _ "net/http/pprof" + "os" + "runtime/pprof" + "time" +) + +const maxCases = 10 + +func main() { + // profile + f, err := os.Create("cpu.prof") + if err != nil { + panic(err) + } + defer func(f *os.File) { + _ = f.Close() + }(f) + // 入力値 + spatialIdCount := maxCases + fmt.Println("inputCount,id1Len,id2Len,truePoint,duration(ns),detection result") + _ = pprof.StartCPUProfile(f) + // caseCount < spatialIdCountまで: caseCountで重複する配列データ生成 + // caseCount => spatialIdCount: 重複なしデータ生成 + for caseCount := 0; caseCount < spatialIdCount+1; caseCount++ { + spatialIds1, spatialIds2 := makeTargets(caseCount, spatialIdCount, []string{}, []string{}) + start := time.Now() + result, _ := detector.CheckSpatialIdsArrayOverlap(spatialIds1, spatialIds2) + end := time.Now() + duration := end.Sub(start) + fmt.Printf("%v,%v,%v,%v,%v,%v\n", spatialIdCount, len(spatialIds1), len(spatialIds2), caseCount, duration.Nanoseconds(), result) + } + pprof.StopCPUProfile() +} + +func makeTargets(truePoint int, spatialIdCount int, spatialIds1 []string, spatialIds2 []string) ([]string, []string) { + for i := 0; i < spatialIdCount; i++ { + if i == truePoint { + spatialIds1 = append(spatialIds1, "13/0/7274/3225") + spatialIds2 = append(spatialIds2, "16/0/58198/25804") + } else { + spatialIds1 = append(spatialIds1, "13/0/7275/3226") + spatialIds2 = append(spatialIds2, "16/0/58198/25804") + } + } + return spatialIds1, spatialIds2 +} diff --git a/go.mod b/go.mod index 14ee2fa..9d27dad 100644 --- a/go.mod +++ b/go.mod @@ -21,4 +21,5 @@ require ( require ( github.com/stretchr/testify v1.9.0 github.com/trajectoryjp/closest_go v1.0.2 + github.com/trajectoryjp/multidimensional-radix-tree/src v0.0.0-20240725143308-c304415df20d ) diff --git a/go.sum b/go.sum index b48bf0a..14d5017 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,10 @@ github.com/trajectoryjp/closest_go v1.0.2 h1:dWm5u43/xsP7IKMzrUO+NEbXnS4GBIPXj7o github.com/trajectoryjp/closest_go v1.0.2/go.mod h1:+iTxW7/pOi0dIqo2/GcqGqWh/fAj2gMjDIiZ+BFDXIc= github.com/trajectoryjp/geodesy_go v1.0.2 h1:wh0RcZDZOQ6Q38evg6YJJ8gAC6+GKSVJgEFI4yusKZE= github.com/trajectoryjp/geodesy_go v1.0.2/go.mod h1:eJuX+ds+t3MKxHG9dhobqNjUbUsYlpTT9XDO0mbc0LU= +github.com/trajectoryjp/multidimensional-radix-tree/src v0.0.0-20240723082158-f3916a674d95 h1:M+XVwjY8llZ0bRaGHI3V0GKktgehxZI/GxoZ91f1qhA= +github.com/trajectoryjp/multidimensional-radix-tree/src v0.0.0-20240723082158-f3916a674d95/go.mod h1:dDSriNaS7E5bvwmmXxF62rqb1FfgzgB7IDdMEsm8O9A= +github.com/trajectoryjp/multidimensional-radix-tree/src v0.0.0-20240725143308-c304415df20d h1:K2EBr4rnKcca/DjrMI779x2nJ1U3UuFyZsFPh/uR52I= +github.com/trajectoryjp/multidimensional-radix-tree/src v0.0.0-20240725143308-c304415df20d/go.mod h1:dDSriNaS7E5bvwmmXxF62rqb1FfgzgB7IDdMEsm8O9A= github.com/wroge/wgs84 v1.1.7 h1:8WVUUrpjysYxrn0ssWX7z90SOUKCuHt9NQ5tg9ovjIY= github.com/wroge/wgs84 v1.1.7/go.mod h1:mc1F8ubW03DO4zaf/006cmhaiMlfvbKmqVAcPuAtsNA= github.com/xieyuschen/deepcopy v1.0.1 h1:nTCnKprCOdibz8WXWlMZzULIlpzZX0ZzKjz8HlGd/Nk= diff --git a/transform/convert_quadkey_and_Vertical_id.go b/transform/convert_quadkey_and_Vertical_id.go index c63b2a1..5700c07 100644 --- a/transform/convert_quadkey_and_Vertical_id.go +++ b/transform/convert_quadkey_and_Vertical_id.go @@ -384,9 +384,9 @@ func ConvertExtendedSpatialIDsToQuadkeysAndAltitudekeys(extendedSpatialIDs []str for _, idString := range extendedSpatialIDs { quadkeys := []int64{} - currentID, error := object.NewExtendedSpatialID(idString) - if error != nil { - return nil, error + currentID, err := object.NewExtendedSpatialID(idString) + if err != nil { + return nil, err } // check zoom of currentID @@ -1050,13 +1050,9 @@ func ConvertZToMinMaxAltitudekey(inputIndex int64, inputZoom int64, outputZoom i func convertZToMinAltitudekey(inputIndex int64, inputZoom int64, outputZoom int64, zBaseExponent int64, zBaseOffset int64) (int64, error) { // 1. check that the input index exists in the input system - inputResolution := common.CalculateArithmeticShift(1, inputZoom) - - maxInputIndex := inputResolution - 1 - minInputIndex := -inputResolution - - if inputIndex > maxInputIndex || inputIndex < minInputIndex { - return 0, errors.NewSpatialIdError(errors.InputValueErrorCode, "input index does not exist") + err, ok := validateIndexExists(inputIndex, inputZoom, true) + if !ok { + return 0, err } // 2. Calculate outputIndex @@ -1065,12 +1061,9 @@ func convertZToMinAltitudekey(inputIndex int64, inputZoom int64, outputZoom int6 outputIndex = common.CalculateArithmeticShift(outputIndex, (outputZoom - zBaseExponent)) // 3. Check to make sure outputIndex exists in the output system - outputResolution := common.CalculateArithmeticShift(1, outputZoom) - - maxOutputIndex := outputResolution - 1 - minOutputIndex := int64(0) + _, ok = validateIndexExists(outputIndex, outputZoom, false) - if outputIndex > maxOutputIndex || outputIndex < minOutputIndex { + if !ok { return 0, errors.NewSpatialIdError(errors.InputValueErrorCode, "output index does not exist with given outputZoom, zBaseExponent, and zBaseOffset") } @@ -1078,6 +1071,46 @@ func convertZToMinAltitudekey(inputIndex int64, inputZoom int64, outputZoom int6 } +// validateIndexExists 指定した(拡張)空間IDインデックスが指定ズームレベルにおいて存在するか確認する +// +// 引数 : +// +// inputIndex : 確認対象の(拡張)空間IDのインデックス +// +// inputZoom : 変換対象の(拡張)空間IDのズームレベル(zインデックス) +// +// minValueIsNegative : 最小インデックス範囲が0未満である(fインデックス)場合true, そうでない(x,yインデックス)場合false +// +// trueの場合最大インデックス値を符号反転したものを最小インデックス範囲として扱う +// +// falseの場合最小インデックス範囲を0として扱う +// +// 戻り値 : +// +// インデックスが存在すればtrue,しなければfalse +// +// 戻り値(エラー) : +// +// 戻り値がfalseの場合、同時に以下の内容のエラーインスタンスが返却される。 +// 入力インデックス不正 :inputIndexにそのズームレベル(inputZoom)で存在しないインデックス値が入力されていた場合。 +// 出力インデックス不正 :変換後のインデックスが入力ズームレベル(inputZoom)で存在しないインデックス値になった場合。 +func validateIndexExists(inputIndex int64, inputZoom int64, minValueIsNegative bool) (error, bool) { + inputResolution := common.CalculateArithmeticShift(1, inputZoom) + + maxInputIndex := inputResolution - 1 + var minInputIndex int64 + if minValueIsNegative { + minInputIndex = -inputResolution + } else { + minInputIndex = 0 + } + + if inputIndex > maxInputIndex || inputIndex < minInputIndex { + return errors.NewSpatialIdError(errors.InputValueErrorCode, "input index does not exist"), false + } + return nil, true +} + // 高さのbit形式のインデックスを計算する。 // // 引数 :