diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8ca42df..e7fed68 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.19' + go-version: '1.22' - name: Build run: go build -v ./... diff --git a/common/consts/consts.go b/common/consts/consts.go index 61f564c..deb83c1 100644 --- a/common/consts/consts.go +++ b/common/consts/consts.go @@ -4,5 +4,16 @@ package consts const GeoCrs = 4326 // GeoCrs 空間IDで利用する地理座標系 const OrthCrs = 3857 // OrthCrs 直交座標系のEPSGコード const SpatialIDDelimiter = "/" // SpatialIDDelimiter 空間IDの区切り文字 +const ZOriginValue = 25 // ZOriginValue 空間IDにおけるボクセルの高さが1mとなるズームレベル const Minima = 1e-10 // Minima 浮動小数点誤差 + +const MaxTileXYZZoom = 35 // MaxTileXYZZoom TileXYZにおける最大ズームレベル(水平、垂直共通) + +// FromExtendedSpatialIDToQuadkeyAndAltitudekey.innerIDの並び順 +const ( + // InnerIDQuadkeyIndex quadkeyはinnerID[0] + InnerIDQuadkeyIndex = 0 + // InnerIDAltitudekeyIndex altitudekeyはinnerID[1] + InnerIDAltitudekeyIndex = 1 +) diff --git a/common/object/id_object.go b/common/object/id_object.go index b28664b..0b634b5 100644 --- a/common/object/id_object.go +++ b/common/object/id_object.go @@ -1,6 +1,12 @@ // 拡張空間IDパッケージ package object +import ( + "fmt" + "github.com/trajectoryjp/spatial_id_go/v4/common/consts" + "github.com/trajectoryjp/spatial_id_go/v4/common/errors" +) + // FromExtendedSpatialIDToQuadkeyAndAltitudekey 拡張空間IDから変換したquadkeyとaltitudekeyの組み合わせを管理する構造体 type FromExtendedSpatialIDToQuadkeyAndAltitudekey struct { // quadkeyの精度 @@ -507,3 +513,191 @@ func (qh QuadkeyAndVerticalID) MaxHeight() float64 { func (qh QuadkeyAndVerticalID) MinHeight() float64 { return qh.minHeight } + +// TileXYZ 水平方向TileKey(x,y)と垂直方向TileKey(z)の組み合わせを管理する構造体 +type TileXYZ struct { + // 水平精度 [0, consts.MaxTileXYZZoom]の整数 + hZoom int64 + // x 水平方向key x軸 + x int64 + // y 水平方向key y軸 + y int64 + // 垂直精度 [0, consts.MaxTileXYZZoom]の整数 + vZoom int64 + // 垂直方向key + z int64 +} + +// NewTileXYZ TileXYZ初期化関数 +// +// quadkeyに数値以外が含まれていた場合エラーとなる。 +// +// 引数: +// +// hZoom: 水平ズームレベル +// x: 水平方向xインデックス +// y: 水平方向yインデックス +// vZoom: 垂直ズームレベル +// z: 高さ方向のインデックス +// +// 戻り値: +// +// 初期化したTileXYZオブジェクト +// +// 戻り値(エラー): +// +// 以下の条件に当てはまる場合、エラーインスタンスが返却される。 +// ズームレベル不正:hZoomまたはvZoomに[0, consts.MaxTileXYZZoom]以外の数値が含まれていた場合 +func NewTileXYZ(hZoom int64, x int64, y int64, vZoom int64, z int64) (*TileXYZ, error) { + tile := &TileXYZ{} + + // 水平精度設定 + err := tile.SetHZoom(hZoom) + if err != nil { + return nil, err + } + + // X設定 + tile.SetX(x) + // Y設定 + tile.SetY(y) + + // 垂直精度設定 + err = tile.SetVZoom(vZoom) + if err != nil { + return nil, err + } + + // 高さのインデックス設定 + tile.SetZ(z) + + return tile, nil +} + +// SetHZoom 水平精度設定関数 +// +// TileXYZオブジェクトのhZoomを引数の入力値に設定する。 +// +// input 引数: +// +// hZoom:水平精度 +// +// 戻り値(エラー): +// +// 以下の条件に当てはまる場合、エラーインスタンスが返却される。 +// ズームレベル不正:hZoomに[0, consts.MaxTileXYZZoom]以外の数値が含まれていた場合 +func (a *TileXYZ) SetHZoom(hZoom int64) error { + if !(hZoom <= consts.MaxTileXYZZoom) { + return errors.NewSpatialIdError(errors.InputValueErrorCode, fmt.Sprintf("hZoom must be in 0-%v, but got %v", consts.MaxTileXYZZoom, hZoom)) + } + a.hZoom = hZoom + return nil +} + +// SetX xインデックス設定関数 +// +// TileXYZオブジェクトのxを引数の入力値に設定する。 +// +// input 引数: +// +// x:xインデックス値 +func (a *TileXYZ) SetX(x int64) { + a.x = x +} + +// SetY yインデックス設定関数 +// +// TileXYZオブジェクトのyを引数の入力値に設定する。 +// +// input 引数: +// +// y:yインデックス値 +func (a *TileXYZ) SetY(y int64) { + a.y = y +} + +// SetVZoom 垂直精度設定関数 +// +// TileXYZオブジェクトのvZoomを引数の入力値に設定する。 +// +// input 引数: +// +// vZoom:垂直精度 +// +// 戻り値(エラー): +// +// 以下の条件に当てはまる場合、エラーインスタンスが返却される。 +// ズームレベル不正:はvZoomに0-35以外の数値が含まれていた場合 +func (a *TileXYZ) SetVZoom(vZoom int64) error { + if !(vZoom <= consts.MaxTileXYZZoom) { + return errors.NewSpatialIdError(errors.InputValueErrorCode, fmt.Sprintf("vZoom must be in 0-%v, but got %v", consts.MaxTileXYZZoom, vZoom)) + } + a.vZoom = vZoom + return nil +} + +// SetZ zインデックス設定関数 +// +// TileXYZオブジェクトのzを引数の入力値に設定する。 +// +// input 引数: +// +// z:zインデックス値 +func (a *TileXYZ) SetZ(z int64) { + a.z = z +} + +// HZoom HZoom設定値取得関数 +// +// TileXYZオブジェクトに設定されているhZoomの値を取得する。 +// +// output 戻り値: +// +// TileXYZオブジェクトに設定されているHZoomの値 +func (a *TileXYZ) HZoom() int64 { + return a.hZoom +} + +// X x設定値取得関数 +// +// TileXYZオブジェクトに設定されているxの値を取得する。 +// +// output 戻り値: +// +// TileXYZオブジェクトに設定されているxの値 +func (a *TileXYZ) X() int64 { + return a.x +} + +// Y y設定値取得関数 +// +// TileXYZオブジェクトに設定されているyの値を取得する。 +// +// output 戻り値: +// +// TileXYZオブジェクトに設定されているyの値 +func (a *TileXYZ) Y() int64 { + return a.y +} + +// VZoom vZoom設定値取得関数 +// +// TileXYZオブジェクトに設定されているvZoomの値を取得する。 +// +// output 戻り値: +// +// TileXYZオブジェクトに設定されているvZoomの値 +func (a *TileXYZ) VZoom() int64 { + return a.vZoom +} + +// Z z設定値取得関数 +// +// TileXYZオブジェクトに設定されているzの値を取得する。 +// +// output 戻り値: +// +// TileXYZオブジェクトに設定されているzの値 +func (a *TileXYZ) Z() int64 { + return a.z +} diff --git a/documentation/convesion.md b/documentation/convesion.md new file mode 100644 index 0000000..96cbb1c --- /dev/null +++ b/documentation/convesion.md @@ -0,0 +1,374 @@ +# 空間ID変換関数 + +`transform/convert_quadkey_and_Vertical_id.go`内にある各種変換関数の利用方法を記載 + +## 変換関数一覧 + +TileXYZ/空間ID/拡張空間ID相互変換における変換関数の対応関係: + +| 変換元(縦軸)\変換先(横軸) | 空間ID | 拡張空間ID | TileXYZ | +|-----------------|------------------------------------------|-----------------------------------------|---------| +| 空間ID | - | なし | なし | +| 拡張空間ID | `ConvertExtendedSpatialIDToSpatialIDs()` | - | なし | +| TileXYZ | `ConvertTileXYZsToSpatialIDs()` | `ConvertTileXYZsToExtendedSpatialIDs()` | - | + +高度(一次元インデックス)変換関数: + +| 変換元 | 変換先 | 対応関数 | +|--------------|--------------|---------------------------------| +| 一次元インデックス | 一次元変換先インデックス | `ConvertZToMinMaxAltitudekey()` | +| 一次元変換先インデックス | 一次元インデックス | `ConvertAltitudekeyToMinMaxZ()` | + +廃止/非推奨予定の関数: + +| 変換元 | 変換先 | 関数 | +|--------|--------|--------------------------------------------------------| +| 拡張空間ID | Key | `ConvertExtendedSpatialIDsToQuadkeysAndAltitudekeys()` | +| 拡張空間ID | Key | `ConvertExtendedSpatialIDsToQuadkeysAndVerticalIDs()` | +| Key | 拡張空間ID | `ConvertQuadkeysAndVerticalIDsToExtendedSpatialIDs()` | +| Key | 空間ID | `ConvertQuadkeysAndVerticalIDsToSpatialIDs()` | +| 空間ID | Key | `ConvertSpatialIDsToQuadkeysAndVerticalIDs()` | + +## TileXYZ -> 空間ID変換 + +3Dタイルキー空間でボクセルデータを扱っている場合、`transform`パッケージの関数を用いて(拡張)空間IDへ変換することが可能 + +以下のコードブロックの全文は`ExampleConvertTileXYZsToSpatialIDs`([transform/convert_quadkey_and_Vertical_id_test.go](../transform/convert_quadkey_and_Vertical_id_test.go))で実行可能 + +次のような3Dタイルキー空間データがある時を考える + +```go +inputData := []struct { + hZoom uint16 + x int64 + y int64 + vZoom uint16 + z int64 +}{ + { + 22, + 85263, + 65423, + 23, + 0, + }, + { + 22, + 85263, + 65423, + 23, + 1, + }, +} +``` + +この3Dタイルキー空間データを本ライブラリでパースする方法はいくつかあるが、ここでは`object.TileXYZ`として扱うため以下を実行する + +```go +var inputXYZ []*object.TileXYZ +for _, in := range inputData { + tile, err := object.NewTileXYZ(in.hZoom, in.x, in.y, in.vZoom, in.z) + if err != nil { + panic(err) + } + inputXYZ = append(inputXYZ, tile) +} +``` + +作成したTileXYZデータを空間IDへ変換するには、`ConvertTileXYZsToSpatialIDs`を用いる + +```go +outputData, err := transform.ConvertTileXYZsToSpatialIDs( + inputXYZ, + 25, + -1, + 23, +) +if err != nil { + panic(err) +} +``` + +この関数の引数は、以下の並びである +``` +request []*object.TileXYZ, zBaseExponent uint16, zBaseOffset int64, extendedSpatialIdVZoom uint16 +``` + +- `request`には先ほど作成したTileXYZデータを渡す +- `zBaseExponent`と`zBaseOffset`は、内部で利用する3Dタイルキー->拡張空間ID変換([`#ConvertTileXYZsToExtendedSpatialIDs`](#converttilexyzstoextendedspatialids))のために3Dタイルキー空間系と空間ID系にある高度インデックスの基準情報を渡す + - `zBaseExponent`は基準となるズームレベルを指定する: 具体的には、ボクセルのサイズが1mとなる際のズームレベル + - `zBaseOffset`は、基準となるインデックス番号を指定する: 空間ID系で0番に位置する3Dタイルキー空間系インデックス番号 + - この際のズームレベルは`zBaseExponent`を用いるものとする +- `extendedSpatialIdVZoom`は、内部で利用する拡張空間ID変換([`#ConvertTileXYZsToExtendedSpatialIDs`](#converttilexyzstoextendedspatialids))時に出力するズームレベルを指定する + +例で示している`zBaseExponent=25`,`zBaseOffset=-1`は、下図のような関係である + +![fig.1](./image/tilekey-standard-SpatialID.png) +
fig1. ConvertTileXYZsToSpatialIDs入力の高度基準
+ +変換関数の結果としては、空間IDの文字列配列が得られる + +``` +{ + "23/0/170526/130846", + "23/0/170527/130846", + "23/0/170526/130847", + "23/0/170527/130847", + "23/1/170526/130846", + "23/1/170526/130847", + "23/1/170527/130846", + "23/1/170527/130847", + "23/2/170526/130846", + "23/2/170526/130847", + "23/2/170527/130846", + "23/2/170527/130847", +} +``` + +この配列が元となった3Dタイルキー空間データ数(2個)に対して12個に増えている理由は大きく分けて2つある: + +1. 内部の拡張空間ID変換において、高度基準の指定によりボクセル数が増加している + - 詳細は[`#ConvertTileXYZsToExtendedSpatialIDs`](#converttilexyzstoextendedspatialids)を確認 + - 例では`zBaseOffset`が2のべき乗ではない(`-1`)ため3個に増加 +2. 内部の拡張空間ID->空間ID変換において、垂直ズームレベルと水平ズームレベルを統合するためにボクセル分割を行っている + - 詳細は[`#ConvertExtenededSpatialIDToExtendedSpatialIDs`](#convertextendedspatialidtospatialids)を確認 + - 例では`TileXYZ.hZoom`より`extendedSpatialIdVZoom`の方が1大きいため、1.で増加した3つに対し4倍されて12個に増加 + +1.で増加したボクセル数は2.の増加量に影響(乗算で増加)する +メモリ利用量の観点から増加量を抑えたい場合、次のような条件を設定する: + +- `zBaseExponent`を25以下にする +- `zBaseOffset`は2のべき乗にする +- 入力データの水平ズームレベルと同じまたはそれに近い`extendedSpatialIdVZoom`を設定する + +### ConvertTileXYZsToExtendedSpatialIDs + +TileXYZ空間のボクセルを拡張空間IDに変換する + +変換時、`TileXYZ.x`,`TileXYZ.y`は元の値が利用されるのみである +しかし`TileXYZ.z`は一次元逆変換(`ConvertAltitudekeyToMinMaxZ()`)によって元の値に近い垂直方向拡張空間インデックスに変換される + +これは`TileXYZ.z`と拡張空間ID垂直インデックスの間ではボクセルのインデックス付番や0番の位置の基準を変更されているためである + +この基準とデータを`ConvertTileXYZsToExtendedSpatialIDs`の入力として扱う + +```go +[]TileXYZ{ + { + hZoom: 20 + x: 85263 + y: 65423 + vZoom: 23 + z: 0 + } +}, +zBaseExponent 25, +zBaseOffset 8, +outputVZoom 23 +``` + +ここでパラメータには次のような値を利用する(fig.2) + +- `[]TileXYZ` 以下のデータを含む`TileXYZ`のスライス + - `hZoom` 水平方向ズームレベル + - `x` 水平方向xキー + - `y` 水平方向yキー + - `vZoom` 垂直方向ズームレベル + - `z` 垂直方向zキー +- `zBaseExponent` 高度キー1つの実際の高さが1mになるズームレベル +- `zBaseOffset` 空間ID垂直インデックス0番に対応する高度キー + - ここでは高度キー0番のズームレベルを`zBaseExponent`で扱う +- `outputVZoom`出力拡張空間IDの垂直ズームレベル + +![fig.2](./image/tilekey-standard.png) +
fig2. ConvertTileXYZsToExtendedSpatialIDs入力の高度基準
+ +出力は拡張空間ID文字列になる +上記の入力は以下の文字列配列として出力される + +``` +["20/85263/65423/23/-2"] +``` + +上記の例では入力と出力は1:1であるが、この変換では1:Nに変換されることがある + +その条件は次のどちらか + +1. `vZoom`が`zBaseExponent`または25(これは空間IDの高度基準における`zBaseExponent`である)より大きい場合 +2. `zBaseOffset`が2のべき乗でない場合 + +また、変換の前後で`TileXYZ.z`にそのズームレベルで存在しないインデックスが現れた場合エラーとなる +この際変換は失敗となり、変換後のデータはnilとなる + +#### 変換例 + +##### 1. 入力TileXYZのzが出力拡張空間ID垂直インデックスに対応する場合 + +入力 + +``` +[]TileXYZ{ + { + hZoom : 20 + x: 85263 + y: 65423 + vZoom: 23 + z: 0 + } +}, +zBaseExponent: 25, +zBaseOffset: 8, +outputVZoom: 23 +``` + +出力 + +``` +extendedSpatialIDs :["20/85263/65423/23/-2"] +``` + +##### 2. vZoomが25より大きい場合 + +入力 + +``` +[]TileXYZ{ + { + hZoom : 20 + x: 85263 + y: 65423 + vZoom: 26 + z: 3 + } +}, +zBaseExponent: 25, +zBaseOffset: -2, +outputVZoom: 26 + +``` + +出力 + +``` +extendedSpatialIDs: ["20/85263/65423/26/7", "20/85263/65423/26/7] +``` + +##### 3. zBaseOffsetが2のべき乗でない場合 + +入力 + +``` +[]TileXYZ{ + { + hZoom : 20 + x: 85263 + y: 65423 + vZoom: 23 + z: 0 + } +}, +zBaseExponent: 25, +zBaseOffset: 7, +outputVZoom: 23 +``` + +出力 + +``` +extendedSpatialIDs: ["20/85263/65423/23/-2", "20/85263/65423/23/-1"] +``` + +### ConvertExtendedSpatialIDToSpatialIDs + +1つの拡張空間IDの構造体を渡すとその拡張空間IDのボクセル形状を維持した空間ID列へ変換する +この結果の文字列配列を出力として得ることができる + +出力空間ID配列の長さは拡張空間IDの水平ズームレベルと垂直ズームレベルの差に依存する + +このズームレベル差を`zDiff`とすると出力空間ID数`outputSize`は次のようになる + +1. 水平ズームレベルより垂直ズームレベルの方が大きい場合 + +`outputSize`は`zDiff`の4のべき乗 + +```py +outputSize = 4 ** abs(zDiff) +``` + +2. 垂直ズームレベルより水平ズームレベルの方が大きい場合 + +`outputSize`は`zDiff`の2のべき乗 + +```py +outputSize = 2 ** abs(zDiff) +``` + +これは下図で示すように、入力拡張空間IDの形状を維持するためにボクセル分割を行なっているためである + +![fig.3](./image/spatialid-volel-splitting.png) +
fig3. ConvertExtendedSpatialIDToSpatialIDs実行時のボクセル分割
+ +`zDiff=0`の際、ボクセル分割は発生せず出力空間ID数は1つになる +そのため、メモリ使用量の観点からはなるべく水平ズームレベルと垂直ズームレベルの差が少ない(=`zDiff`が0に近い)拡張空間IDを用いることを推奨する + +#### 使用例 + +##### 1. 水平ズームレベルの方が低い場合 + +入力 + +``` +ExtendedSpatialID{ + hZoom: 6, + x: 24, + y: 49, + vZoom: 7, + z: 0, +} +``` + +出力 + +``` +[]string{"7/0/48/98", "7/0/48/99", "7/0/49/98", "7/0/49/99"} +``` + +##### 2. 垂直ズームレベルの方が低い場合 + +入力 + +``` +ExtendedSpatialID{ + hZoom: 7, + x: 24, + y: 53, + vZoom: 6, + z: 24, +} +``` + +出力 + +``` +[]string{"7/48/24/53", "7/49/24/53"} +``` + +##### 3. 水平ズームレベル、垂直ズームレベルに差がない場合 + +入力 + +``` +ExtendedSpatialID{ + hZoom: 6, + x: 24, + y: 49, + vZoom: 6, + z: 0, +} +``` + +出力 + +``` +[]string{"6/0/24/49"} +``` diff --git a/documentation/image/spatialid-volel-splitting.png b/documentation/image/spatialid-volel-splitting.png new file mode 100644 index 0000000..a7fb627 Binary files /dev/null and b/documentation/image/spatialid-volel-splitting.png differ diff --git a/documentation/image/tilekey-standard-SpatialID.png b/documentation/image/tilekey-standard-SpatialID.png new file mode 100644 index 0000000..35784b2 Binary files /dev/null and b/documentation/image/tilekey-standard-SpatialID.png differ diff --git a/documentation/image/tilekey-standard.png b/documentation/image/tilekey-standard.png new file mode 100644 index 0000000..210974a Binary files /dev/null and b/documentation/image/tilekey-standard.png differ diff --git a/examples/checkZoom/go.mod b/examples/checkZoom/go.mod index a53b37e..d9d5fd2 100644 --- a/examples/checkZoom/go.mod +++ b/examples/checkZoom/go.mod @@ -1,10 +1,12 @@ module checkZoom -go 1.20 +go 1.22 + +toolchain go1.22.6 require github.com/trajectoryjp/spatial_id_go/v4 v4.0.0 require ( github.com/wroge/wgs84 v1.1.7 // indirect - gonum.org/v1/gonum v0.13.0 // indirect + gonum.org/v1/gonum v0.15.1 // indirect ) diff --git a/examples/checkZoom/go.sum b/examples/checkZoom/go.sum index 6d2c600..f495db7 100644 --- a/examples/checkZoom/go.sum +++ b/examples/checkZoom/go.sum @@ -1,7 +1,8 @@ -github.com/trajectoryjp/spatial_id_go/v2 v2.0.0 h1:AZkQkIrsOV6IYFPheoDkTiRYSdoARCw0HQe5cfhWci8= -github.com/trajectoryjp/spatial_id_go/v2 v2.0.0/go.mod h1:XEDPqIuIatdNOE08aoZ+jQKf3YPMS57WA2pbrD/3GTE= +github.com/trajectoryjp/spatial_id_go/v4 v4.0.0 h1:EV8B81DORXjQtLO59xYZw+ML6c+23PfhZd1e4DZdUdo= +github.com/trajectoryjp/spatial_id_go/v4 v4.0.0/go.mod h1:Q5epEMx4BXEvPwe/8Ffc6RsHI6IPZYAPJVwgZhZi66k= github.com/wroge/wgs84 v1.1.7 h1:8WVUUrpjysYxrn0ssWX7z90SOUKCuHt9NQ5tg9ovjIY= github.com/wroge/wgs84 v1.1.7/go.mod h1:mc1F8ubW03DO4zaf/006cmhaiMlfvbKmqVAcPuAtsNA= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= -gonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM= -gonum.org/v1/gonum v0.13.0/go.mod h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= +gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= diff --git a/go.mod b/go.mod index 9b54e31..14ee2fa 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,24 @@ module github.com/trajectoryjp/spatial_id_go/v4 -go 1.19 +go 1.22 + +toolchain go1.22.6 require ( github.com/go-gl/mathgl v1.1.0 - github.com/trajectoryjp/geodesy_go v1.0.1 + github.com/trajectoryjp/geodesy_go v1.0.2 github.com/wroge/wgs84 v1.1.7 - gonum.org/v1/gonum v0.13.0 + gonum.org/v1/gonum v0.15.1 ) -require golang.org/x/image v0.7.0 // indirect +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/image v0.19.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) -require github.com/trajectoryjp/closest_go v1.0.1 +require ( + github.com/stretchr/testify v1.9.0 + github.com/trajectoryjp/closest_go v1.0.2 +) diff --git a/go.sum b/go.sum index 331219d..b48bf0a 100644 --- a/go.sum +++ b/go.sum @@ -1,46 +1,28 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/trajectoryjp/closest_go v1.0.2 h1:dWm5u43/xsP7IKMzrUO+NEbXnS4GBIPXj7onHWG3imQ= +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/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/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= +github.com/xieyuschen/deepcopy v1.0.1/go.mod h1:smzaXhQZuuehOzevwMMLzvM7gBslB4VPdgJbwdyIDSA= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= 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/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ= +golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys= 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= +gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= +gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/integrate/change_zoom.go b/integrate/change_zoom.go index 8c81f11..6689d83 100644 --- a/integrate/change_zoom.go +++ b/integrate/change_zoom.go @@ -214,6 +214,28 @@ func HorizontalZoom( // 変換後の拡張空間ID格納用のスライス horizontalIDs := []string{} + minXparam, minYparam, maxXparam, maxYparam := HorizontalZoomMinMax(inputZoom, xIndex, yIndex, outputZoom) + + // 変換後の拡張空間IDを定義 + for y := minYparam; y <= maxYparam; y++ { + for x := minXparam; x <= maxXparam; x++ { + horizontalIDs = append( + horizontalIDs, + strconv.FormatInt(outputZoom, 10)+ + "/"+strconv.FormatInt(x, 10)+ + "/"+strconv.FormatInt(y, 10)) + } + } + return horizontalIDs +} + +// HorizontalZoomMinMax 拡張空間IDの水平方向の精度変換関数 +// +// HorizontalZoom() の返却値が最小x,yインデックスと最大x,yインデックスになったもの +// 詳細は HorizontalZoom のドキュメントを参照 +// +// 戻り値の順序:(最小xインデックス, 最小yインデックス, 最大xインデックス, 最大yインデックス) +func HorizontalZoomMinMax(inputZoom int64, xIndex int64, yIndex int64, outputZoom int64) (int64, int64, int64, int64) { // 精度の差異を取得 hZoomDiff := outputZoom - inputZoom @@ -248,18 +270,7 @@ func HorizontalZoom( maxXparam = minXparam maxYparam = minYparam } - - // 変換後の拡張空間IDを定義 - for y := minYparam; y <= maxYparam; y++ { - for x := minXparam; x <= maxXparam; x++ { - horizontalIDs = append( - horizontalIDs, - strconv.FormatInt(outputZoom, 10)+ - "/"+strconv.FormatInt(x, 10)+ - "/"+strconv.FormatInt(y, 10)) - } - } - return horizontalIDs + return minXparam, minYparam, maxXparam, maxYparam } // VerticalZoom 拡張空間IDの垂直方向の精度変換関数 diff --git a/transform/convert_quadkey_and_Vertical_id.go b/transform/convert_quadkey_and_Vertical_id.go index 81c7269..b6a0de8 100644 --- a/transform/convert_quadkey_and_Vertical_id.go +++ b/transform/convert_quadkey_and_Vertical_id.go @@ -2,10 +2,13 @@ package transform import ( + "fmt" "math" "strconv" "strings" + "github.com/trajectoryjp/spatial_id_go/v4/common/consts" + "github.com/trajectoryjp/spatial_id_go/v4/common" "github.com/trajectoryjp/spatial_id_go/v4/common/errors" "github.com/trajectoryjp/spatial_id_go/v4/common/object" @@ -15,8 +18,7 @@ import ( // 宣言 var ( - alt25 = math.Pow(2, 25) - zOriginValue int64 = 25 + alt25 = math.Pow(2, 25) ) // ConvertQuadkeysAndVerticalIDsToExtendedSpatialIDs 内部形式IDを拡張空間IDに変換する。 @@ -380,7 +382,6 @@ func ConvertExtendedSpatialIDsToQuadkeysAndAltitudekeys(extendedSpatialIDs []str duplicate := map[[2]int64]interface{}{} for _, idString := range extendedSpatialIDs { - var altitudeKeys []int64 quadkeys := []int64{} currentID, error := object.NewExtendedSpatialID(idString) @@ -401,15 +402,15 @@ func ConvertExtendedSpatialIDsToQuadkeysAndAltitudekeys(extendedSpatialIDs []str } // B. convert vertical IDs to fit Output Vertical Zoom Level - altitudeKeys, error = convertZToAltitudekey(currentID.Z(), currentID.VZoom(), outputAltitudekeyZoom, zBaseExponent, zBaseOffset) - if error != nil { - return nil, error + minAltitudeKey, maxAltitudeKey, err := ConvertZToMinMaxAltitudekey(currentID.Z(), currentID.VZoom(), outputAltitudekeyZoom, zBaseExponent, zBaseOffset) + if err != nil { + return nil, err } // 水平方向と垂直方向の組み合わせを作成する idList := [][2]int64{} for _, quadkey := range quadkeys { - for _, altitudeKey := range altitudeKeys { + for altitudeKey := minAltitudeKey; altitudeKey <= maxAltitudeKey; altitudeKey++ { newID := [2]int64{quadkey, altitudeKey} if _, ok := duplicate[newID]; ok { continue @@ -438,6 +439,328 @@ func ConvertExtendedSpatialIDsToQuadkeysAndAltitudekeys(extendedSpatialIDs []str return extendedSpatialIDToQuadkeyAndAltitudekey, nil } +// ConvertExtendedSpatialIDToSpatialIDs 拡張空間IDを空間IDへ変換する +// +// 拡張空間IDの水平精度と垂直精度は変換によって高い方に揃えられる。 +// このため、出力空間ID配列の長さは拡張空間IDの水平ズームレベルと垂直ズームレベルの差に依存する +// +// 引数 : +// +// extendedSpatialID : 変換対象の拡張空間ID構造体 +// +// 戻り値 : +// +// 変換後の空間IDのスライス +// +// 補足事項: +// +// 入力拡張空間IDの垂直精度と水平精度の間で差がある場合、差が1増えるごとに4(垂直精度の方が低い場合) +// または2(垂直精度の方が低い場合)のべき乗で出力空間ID数が増加する。 +// そのため、精度差が大きすぎると変換後の空間ID数は大幅に増大する。 +// 動作環境によってはメモリ不足となる可能性があるため、注意すること。 +// +// 変換利用例: +// 1. 水平精度の方が低い場合 +// 入力 +// ExtendedSpatialID{ +// hZoom: 6, +// x: 24, +// y: 49, +// vZoom: 7, +// z: 0, +// } +// +// 出力 +// []string{"7/0/48/98", "7/0/48/99", "7/0/49/98", "7/0/49/99"} +// +// 2. 垂直精度の方が低い場合 +// 入力 +// ExtendedSpatialID{ +// hZoom: 7, +// x: 24, +// y: 53, +// vZoom: 6, +// z: 24, +// } +// +// 出力 +// []string{"7/48/24/53", "7/49/24/53"} +// +// 3. 水平精度、垂直精度に差がない場合 +// 入力 +// ExtendedSpatialID{ +// hZoom: 6, +// x: 24, +// y: 49, +// vZoom: 6, +// z: 0, +// } +// +// 出力 +// []string{"6/0/24/49"} +func ConvertExtendedSpatialIDToSpatialIDs(extendedSpatialID *object.ExtendedSpatialID) []string { + spatialIds := []string{} + // 精度が高い方へズームレベルを上げる + switch { + case extendedSpatialID.HZoom() < extendedSpatialID.VZoom(): + targetZoomLevel := extendedSpatialID.VZoom() + xMin, yMin, xMax, yMax := integrate.HorizontalZoomMinMax(extendedSpatialID.HZoom(), extendedSpatialID.X(), extendedSpatialID.Y(), targetZoomLevel) + for x := xMin; x <= xMax; x++ { + for y := yMin; y <= yMax; y++ { + spatialId := strconv.FormatInt(targetZoomLevel, 10) + consts.SpatialIDDelimiter + strconv.FormatInt(extendedSpatialID.Z(), 10) + consts.SpatialIDDelimiter + strconv.FormatInt(x, 10) + consts.SpatialIDDelimiter + strconv.FormatInt(y, 10) + spatialIds = append(spatialIds, spatialId) + } + } + case extendedSpatialID.HZoom() > extendedSpatialID.VZoom(): + targetZoomLevel := extendedSpatialID.HZoom() + verticalIds := integrate.VerticalZoom(extendedSpatialID.VZoom(), extendedSpatialID.Z(), targetZoomLevel) + for _, verticalId := range verticalIds { + // "z/f" + "x/y" + spatialId := verticalId + consts.SpatialIDDelimiter + strconv.FormatInt(extendedSpatialID.X(), 10) + consts.SpatialIDDelimiter + strconv.FormatInt(extendedSpatialID.Y(), 10) + spatialIds = append(spatialIds, spatialId) + } + // ズームレベルが等しい場合は直接変換可 + case extendedSpatialID.HZoom() == extendedSpatialID.VZoom(): + spatialId := strconv.FormatInt(extendedSpatialID.HZoom(), 10) + consts.SpatialIDDelimiter + strconv.FormatInt(extendedSpatialID.Z(), 10) + consts.SpatialIDDelimiter + strconv.FormatInt(extendedSpatialID.X(), 10) + consts.SpatialIDDelimiter + strconv.FormatInt(extendedSpatialID.Y(), 10) + spatialIds = append(spatialIds, spatialId) + } + return spatialIds +} + +// ConvertTileXYZsToExtendedSpatialIDs +// TileXYZ形式から拡張空間ID形式へ変換する +// +// TileXYZのx,yは同一の意味のまま拡張空間IDのX,Yに変換される。 +// このため出力の水平精度は入力のものが用いられる(ただし精度チェックは行われる)。 +// TileXYZのzから拡張空間ID垂直インデックス(f)へは高度変換が用いられ変化する。 +// 引数 : +// +// request : 変換対象のTileXYZ構造体のスライス +// zBaseExponent: TileXYZのzの高さが1mとなるズームレベル +// zBaseOffset: ズームレベルがzBaseExponentのとき高度0mにおけるTileXYZのz +// outputVZoom : 入力値が変換後の拡張空間IDの高さの精度となる。拡張空間IDの精度の閾値である 0 ~ 35 の整数値を指定可能。 +// +// 戻り値 : +// +// 変換後の拡張空間IDのスライス +// +// 戻り値(エラー) : +// +// 以下の条件に当てはまる場合、エラーインスタンスが返却される。 +// 精度閾値超過(出力精度):出力の水平方向精度、または高さ方向の精度に 0 ~ 35 の整数値以外が入力されていた場合。 +// z高度範囲外:変換前のzがその垂直ズームレベルにおける高度範囲外である場合。 +// 拡張空間ID高度範囲外:変換後の拡張空間ID高度がその垂直ズームレベルにおける高度範囲外である場合。 +// +// 補足事項: +// +// 高さについて: +// TileXYZ形式のzと拡張空間ID形式垂直インデックスは高度基準が異なる(同じにすることも可能) +// 引数のzBaseExponentとzBaseOffsetで高度基準を定義する +// TileXYZ内のデータもしくは引数が次の状態のとき、入力TileXYZ数に対して出力拡張空間ID垂直インデックス数が増加する +// - vZoomがzBaseExponentまたは25(空間IDの高度基準におけるzBaseExponent)より大きい場合 +// - zBaseOffsetが2のべき乗でない場合 +// +// 変換利用例: +// +// 1. 入力TileXYZのzが出力拡張空間ID垂直インデックスに対応する場合 +// +// []TileXYZ{ +// { +// hZoom : 20 +// x 85263 +// y 65423 +// vZoom 23 +// z 0 +// } +// }, +// zBaseExponent 25, +// zBaseOffset 8, +// outputVZoom 23 +// +// extendedSpatialIDs :["20/85263/65423/23/-2"] +// +// 2. vZoomが25より大きい場合 +// +// []TileXYZ{ +// { +// hZoom : 20 +// x 85263 +// y 65423 +// vZoom 26 +// z 3 +// } +// }, +// zBaseExponent 25, +// zBaseOffset -2, +// outputVZoom 26 +// +// extendedSpatialIDs :["20/85263/65423/26/7", "20/85263/65423/26/7] +// +// 3. zBaseOffsetが2のべき乗でない場合 +// +// []TileXYZ{ +// { +// hZoom : 20 +// x 85263 +// y 65423 +// vZoom 23 +// z 0 +// } +// }, +// zBaseExponent 25, +// zBaseOffset 7, +// outputVZoom 23 +// +// extendedSpatialIDs :["20/85263/65423/23/-2", "20/85263/65423/23/-1"] +func ConvertTileXYZsToExtendedSpatialIDs(request []*object.TileXYZ, zBaseExponent int64, zBaseOffset int64, outputVZoom int64) ([]object.ExtendedSpatialID, error) { + + extendedSpatialIDsMap := make(map[int64]object.ExtendedSpatialID) + + for _, r := range request { + if !extendedSpatialIDCheckZoom(r.HZoom(), outputVZoom) { + return nil, errors.NewSpatialIdError(errors.InputValueErrorCode, fmt.Sprintf("extendSpatialID zoom level must be in 0-35: hZoom=%v, vZoom=%v", r.HZoom(), outputVZoom)) + } + + zMin, zMax, err := ConvertAltitudekeyToMinMaxZ(r.Z(), r.VZoom(), outputVZoom, zBaseExponent, zBaseOffset) + if err != nil { + return nil, err + } + + for z := zMin; z <= zMax; z++ { + // 重複排除 + if _, exists := extendedSpatialIDsMap[z]; exists { + continue + } + extendedSpatialID := new(object.ExtendedSpatialID) + extendedSpatialID.SetX(r.X()) + extendedSpatialID.SetY(r.Y()) + extendedSpatialID.SetZ(z) + extendedSpatialID.SetZoom(r.HZoom(), outputVZoom) + extendedSpatialIDsMap[z] = *extendedSpatialID + } + } + extendedSpatialIDs := make([]object.ExtendedSpatialID, 0, len(extendedSpatialIDsMap)) + for _, extendedSpatialID := range extendedSpatialIDsMap { + extendedSpatialIDs = append(extendedSpatialIDs, extendedSpatialID) + } + + return extendedSpatialIDs, nil +} + +// ConvertTileXYZsToSpatialIDs +// TileXYZ形式から空間ID形式へ変換する +// ConvertTileXYZsToExtendedSpatialIDs と ConvertExtendedSpatialIDToSpatialIDs の組み合わせであるため、詳細はそちらを参照 +// +// TileXYZのx,yは水平精度と垂直精度のうち高い方の空間IDのX,Yに変換される。 +// TileXYZのZから拡張空間ID垂直インデックス(f)へは高度変換が用いられ変化する。 +// +// 引数 : +// +// request : 変換対象のTileXYZ構造体のスライス +// zBaseExponent: zの高さが1mとなるズームレベル +// zBaseOffset: ズームレベルがzBaseExponentのとき高度0mにおけるvZoom +// extendedSpatialIdVZoom : 拡張空間ID変換中の拡張空間IDの高さの精度指定。空間ID変換時、水平精度の方が高い場合この値は使われない。拡張空間IDの精度の閾値である 0 ~ 35 の整数値を指定可能。 +// +// 戻り値 : +// +// 変換後の空間IDのスライス +// +// 戻り値(エラー) : +// +// 以下の条件に当てはまる場合、エラーインスタンスが返却される。 +// 精度閾値超過(出力精度):出力の水平方向精度、または高さ方向の精度に 0 ~ 35 の整数値以外が入力されていた場合。 +// z高度範囲外:変換前のzがその垂直ズームレベルにおける高度範囲外である場合。 +// 拡張空間ID高度範囲外:拡張空間ID変換時の拡張空間ID高度がその垂直ズームレベルにおける高度範囲外である場合。 +// +// 補足事項: +// +// 入力のoutputVZoomは次の2項目と関係しており、出力空間ID数に影響を与える +// 1. TileXYZの垂直精度, zBaseExponent, zBaseOffset: 条件により拡張空間ID変換後の拡張空間ID数が増加する(詳しくは ConvertTileXYZsToExtendedSpatialIDs の例を参照) +// 2. TileXYZの水平精度の差: 差が1増えるごとに1で出力された拡張空間ID数の4のべき乗で出力空間ID数が増加する。 +// そのため、これら2項目の値によっては変換後の空間ID数は大幅に増大する。 +// 動作環境によってはメモリ不足となる可能性があるため、注意すること。 +func ConvertTileXYZsToSpatialIDs(request []*object.TileXYZ, zBaseExponent int64, zBaseOffset int64, extendedSpatialIdVZoom int64) ([]string, error) { + var outputData []string + outputExtendedSpatialIds, err := ConvertTileXYZsToExtendedSpatialIDs( + request, zBaseExponent, zBaseOffset, extendedSpatialIdVZoom, + ) + if err != nil { + return nil, err + } + for _, out := range outputExtendedSpatialIds { + spatialIds := ConvertExtendedSpatialIDToSpatialIDs(&out) + outputData = append(outputData, spatialIds...) + } + return outputData, nil +} + +// ConvertAltitudekeyToMinMaxZ altitudekeyを(拡張)空間IDのz成分に高度変換する。 +// +// 変換前と変換後の精度差によって出力されるaltitudekeyの個数は増減する。 +// +// 引数 : +// +// altitudekey : 変換前のaltitudekeyの値 +// +// altitudekeyZoom : 変換前のaltitudekeyの精度指定 +// +// outputZoom : 変換対象の拡張空間ID垂直精度の指定 +// +// zBaseExponent : 変換対象の拡張空間IDの高さが1mとなるズームレベル +// +// zBaseOffset : ズームレベルがzBaseExponentのとき、高度0mにおける拡張空間IDの垂直インデックス値 +// +// 戻り値 : +// +// 変換後のaltitudekeyの最小値と最大値 +// +// 戻り値の順序:(最小altitudekey, 最大altitudekey) +// +// 戻り値(エラー) : +// +// 以下の条件に当てはまる場合、エラーインスタンスが返却される。 +// 入力インデックス不正 :inputIndexにそのズームレベル(inputZoom)で存在しないインデックス値が入力されていた場合。 +// 出力インデックス不正 :出力altitudekeyが出力ズームレベル(outputZoom)で存在しないインデックス値になった場合。 +func ConvertAltitudekeyToMinMaxZ(altitudekey int64, altitudekeyZoomLevel int64, outputZoom int64, zBaseExponent int64, zBaseOffset int64) (int64, int64, error) { + // 1. check that the input index exists in the input system + inputResolution := common.CalculateArithmeticShift(1, altitudekeyZoomLevel) + + maxInputIndex := inputResolution - 1 + minInputIndex := int64(0) + + if altitudekey > maxInputIndex || altitudekey < minInputIndex { + return 0, 0, errors.NewSpatialIdError(errors.InputValueErrorCode, "input index does not exist") + } + + // 2. Calculate internal index + zoomDifference := zBaseExponent - altitudekeyZoomLevel + + internalMinIndex := common.CalculateArithmeticShift(altitudekey, zoomDifference) + internalMaxIndex := internalMinIndex + if zoomDifference > 0 { + internalMaxIndex = common.CalculateArithmeticShift(altitudekey+1, zoomDifference) - 1 + } + // 3. Calculate outputMinIndex + outputZoomDifference := outputZoom - consts.ZOriginValue + outputMinIndex := common.CalculateArithmeticShift(internalMinIndex-zBaseOffset, outputZoomDifference) + outputMaxIndex := common.CalculateArithmeticShift(internalMaxIndex-zBaseOffset, outputZoomDifference) + if outputZoomDifference > 0 { + outputMaxIndex = common.CalculateArithmeticShift(internalMaxIndex-zBaseOffset+1, outputZoomDifference) - 1 + } + + // 4. Check to make sure outputMinIndex exists in the output system + outputResolution := common.CalculateArithmeticShift(1, outputZoom) + + maxOutputIndex := outputResolution - 1 + minOutputIndex := -outputResolution + + if outputMaxIndex > maxOutputIndex || outputMinIndex < minOutputIndex { + return 0, 0, errors.NewSpatialIdError(errors.InputValueErrorCode, "output index does not exist with given outputZoom, zBaseExponent, and zBaseOffset") + } + + return outputMinIndex, outputMaxIndex, nil +} + // ConvertSpatialIDsToQuadkeysAndVerticalIDs 空間IDを内部形式IDに変換する。 // // 最高高度、最低高度の両方が同じ値の場合、変換後の高さ方向のIDを空間IDインデックス形式とする。 @@ -672,21 +995,41 @@ func convertVerticallIDToBit(vZoom int64, vIndex int64, outputZoom int64, maxHei } -func convertZToAltitudekey(inputIndex int64, inputZoom int64, outputZoom int64, zBaseExponent int64, zBaseOffset int64) ([]int64, error) { - - var ( - outputIndexes []int64 - error error - ) +// ConvertZToMinMaxAltitudekey (拡張)空間IDのz成分をaltitudekeyに高度変換する。 +// +// 変換前と変換後の精度差によって出力されるaltitudekeyの個数は増減する。 +// +// 引数 : +// +// inputIndex : 変換対象の(拡張)空間IDのz成分(fインデックス) +// +// inputZoom : 変換対象の(拡張)空間IDのズームレベル(zインデックス) +// +// outputZoom : 変換後のaltitudekeyの精度指定 +// +// zBaseExponent : 変換後のaltitudekeyの高さが1mとなるズームレベル +// +// zBaseOffset : ズームレベルがzBaseExponentのとき、高度0mにおけるaltitudekeyのインデックス値 +// +// 戻り値 : +// +// 変換後のaltitudekeyのスライス +// +// 戻り値(エラー) : +// +// 以下の条件に当てはまる場合、エラーインスタンスが返却される。 +// 入力インデックス不正 :inputIndexにそのズームレベル(inputZoom)で存在しないインデックス値が入力されていた場合。 +// 出力インデックス不正 :出力altitudekeyが出力ズームレベル(outputZoom)で存在しないインデックス値になった場合。 +func ConvertZToMinMaxAltitudekey(inputIndex int64, inputZoom int64, outputZoom int64, zBaseExponent int64, zBaseOffset int64) (minAltitudeKey int64, maxAltitudeKey int64, err error) { // determine the upper and lower index bounds to search for matches in height solution space - lowerBound, error := convertZToMinAltitudekey(inputIndex, inputZoom, outputZoom, zBaseExponent, zBaseOffset) - if error != nil { - return nil, error + lowerBound, err := convertZToMinAltitudekey(inputIndex, inputZoom, outputZoom, zBaseExponent, zBaseOffset) + if err != nil { + return 0, 0, err } - upperBound, error := convertZToMinAltitudekey(inputIndex+1, inputZoom, outputZoom, zBaseExponent, zBaseOffset) - if error != nil { - return nil, error + upperBound, err := convertZToMinAltitudekey(inputIndex+1, inputZoom, outputZoom, zBaseExponent, zBaseOffset) + if err != nil { + return 0, 0, err } // Determine the vertical index/indices to return. @@ -694,15 +1037,14 @@ func convertZToAltitudekey(inputIndex int64, inputZoom int64, outputZoom int64, // mathematically the altitude associated with the lower bounds will always satisfy the solution set. // b) cycle through indices from lowerBounds+1 to upperBounds with i to find any possible additional indexes // that satisfy the solution set. - outputIndexes = append(outputIndexes, lowerBound) - - for i := lowerBound + 1; i < upperBound; i++ { - - outputIndexes = append(outputIndexes, int64(i)) + // but only output (minimum key, maximum key) as (lowerBound, upperBound - 1) + minAltitudeKey = lowerBound + maxAltitudeKey = upperBound - 1 + if minAltitudeKey > maxAltitudeKey { + return minAltitudeKey, minAltitudeKey, nil + } else { + return minAltitudeKey, maxAltitudeKey, nil } - - return outputIndexes, nil - } func convertZToMinAltitudekey(inputIndex int64, inputZoom int64, outputZoom int64, zBaseExponent int64, zBaseOffset int64) (int64, error) { @@ -718,7 +1060,7 @@ func convertZToMinAltitudekey(inputIndex int64, inputZoom int64, outputZoom int6 } // 2. Calculate outputIndex - outputIndex := common.CalculateArithmeticShift(inputIndex, -(inputZoom - zOriginValue)) + outputIndex := common.CalculateArithmeticShift(inputIndex, -(inputZoom - consts.ZOriginValue)) outputIndex += zBaseOffset outputIndex = common.CalculateArithmeticShift(outputIndex, (outputZoom - zBaseExponent)) diff --git a/transform/convert_quadkey_and_Vertical_id_test.go b/transform/convert_quadkey_and_Vertical_id_test.go index d655697..16a0875 100755 --- a/transform/convert_quadkey_and_Vertical_id_test.go +++ b/transform/convert_quadkey_and_Vertical_id_test.go @@ -2,11 +2,16 @@ package transform import ( + "fmt" "reflect" "sort" "strconv" + "strings" "testing" + "github.com/trajectoryjp/spatial_id_go/v4/common/consts" + + "github.com/stretchr/testify/assert" "github.com/trajectoryjp/spatial_id_go/v4/common/errors" "github.com/trajectoryjp/spatial_id_go/v4/common/object" ) @@ -376,6 +381,489 @@ func TestConvertExtendedSpatialIDsToQuadkeysAndAltitudekeys_5(t *testing.T) { } } +func newTileXYZ(t *testing.T, hZoom int64, x int64, y int64, vZoom int64, z int64) *object.TileXYZ { + t.Helper() + xyz, err := object.NewTileXYZ(hZoom, x, y, vZoom, z) + if err != nil { + t.Fatal(err) + } + return xyz +} + +func newExtendedSpatialID(t *testing.T, id string) *object.ExtendedSpatialID { + t.Helper() + extendedSpatialId, err := object.NewExtendedSpatialID(id) + if err != nil { + t.Fatal(err) + } + return extendedSpatialId +} + +func TestConvertTileXYZsToSpatialIDs(t *testing.T) { + type argSet struct { + tile []*object.TileXYZ + zBaseExponent int64 + zBaseOffset int64 + outputVZoom int64 + } + testCases := []struct { + expected []string + request argSet + }{ + { + // z/f/x/y + []string{"23/-2/85263/65423"}, + argSet{ + []*object.TileXYZ{newTileXYZ( + t, + 23, + 85263, + 65423, + 23, + 0, + )}, + 23, + 8, + 23, + }, + }, + { + []string{"25/1/170526/130846", "25/1/170526/130847", "25/1/170527/130846", "25/1/170527/130847"}, + argSet{ + []*object.TileXYZ{newTileXYZ( + t, + 24, + 85263, + 65423, + 25, + 3, + )}, + 25, + 2, + 25, + }, + }, + { + []string{"4/0/63/23", "4/1/63/23"}, + argSet{ + []*object.TileXYZ{newTileXYZ( + t, + 4, + 63, + 23, + 3, + 3, + )}, + 3, + 2, + 3, + }, + }, + { + []string{"23/-2/85263/65423", "23/-1/85263/65423"}, + argSet{ + []*object.TileXYZ{newTileXYZ( + t, + 23, + 85263, + 65423, + 23, + 0, + )}, + 25, + 7, + 23, + }, + }, + { + []string{"26/6/85263/65423", "26/7/85263/65423"}, + argSet{ + []*object.TileXYZ{newTileXYZ( + t, + 26, + 85263, + 65423, + 26, + 3, + )}, + 25, + -2, + 26, + }, + }, + { + []string{ + "23/0/170526/130846", + "23/1/170526/130846", + "23/2/170526/130846", + "23/0/170526/130847", + "23/1/170526/130847", + "23/2/170526/130847", + "23/0/170527/130846", + "23/1/170527/130846", + "23/2/170527/130846", + "23/0/170527/130847", + "23/1/170527/130847", + "23/2/170527/130847", + }, + argSet{ + []*object.TileXYZ{ + newTileXYZ( + t, + 22, + 85263, + 65423, + 23, + 0, + ), + newTileXYZ( + t, + 22, + 85263, + 65423, + 23, + 1, + )}, + 25, + -1, + 23, + }, + }, + } + for _, testCase := range testCases { + result, err := ConvertTileXYZsToSpatialIDs(testCase.request.tile, testCase.request.zBaseExponent, testCase.request.zBaseOffset, testCase.request.outputVZoom) + if err != nil { + t.Fatal(err) + } + if assert.ElementsMatch(t, testCase.expected, result) == false { + // t.Error(result): + t.Errorf("expected: %v result: %v", testCase.expected, result) + } else { + t.Log("Success", result) + } + } + +} + +func ExampleConvertTileXYZsToSpatialIDs() { + inputData := []struct { + hZoom int64 + x int64 + y int64 + vZoom int64 + z int64 + }{ + { + 22, + 85263, + 65423, + 23, + 0, + }, + { + 22, + 85263, + 65423, + 23, + 1, + }, + } + var inputXYZ []*object.TileXYZ + for _, in := range inputData { + tile, err := object.NewTileXYZ(in.hZoom, in.x, in.y, in.vZoom, in.z) + if err != nil { + panic(err) + } + inputXYZ = append(inputXYZ, tile) + } + outputData, err := ConvertTileXYZsToSpatialIDs( + inputXYZ, + 25, + -1, + 23, + ) + if err != nil { + panic(err) + } + for _, out := range outputData { + fmt.Println(out) + } + // Unordered output: + // 23/0/170526/130846 + // 23/0/170527/130846 + // 23/0/170526/130847 + // 23/0/170527/130847 + // 23/1/170526/130846 + // 23/1/170526/130847 + // 23/1/170527/130846 + // 23/1/170527/130847 + // 23/2/170526/130846 + // 23/2/170526/130847 + // 23/2/170527/130846 + // 23/2/170527/130847 +} + +func TestConvertTileXYZsToExtendedSpatialIDs(t *testing.T) { + type argSet struct { + tile []*object.TileXYZ + zBaseExponent int64 + zBaseOffset int64 + outputVZoom int64 + } + testCases := []struct { + expected []string + request argSet + }{ + { + // hZoom/x/y/vZoom/z + []string{"20/85263/65423/23/-2"}, + argSet{ + []*object.TileXYZ{newTileXYZ( + t, + 20, + 85263, + 65423, + 23, + 0, + )}, + 23, + 8, + 23, + }, + }, + { + []string{"20/85263/65423/25/1"}, + argSet{ + []*object.TileXYZ{newTileXYZ( + t, + 20, + 85263, + 65423, + 25, + 3, + )}, + 25, + 2, + 25, + }, + }, + { + []string{"20/85263/65423/3/0"}, + argSet{ + []*object.TileXYZ{newTileXYZ( + t, + 20, + 85263, + 65423, + 3, + 3, + )}, + 3, + 2, + 3, + }, + }, + { + []string{"20/85263/65423/23/-2"}, + argSet{ + []*object.TileXYZ{newTileXYZ( + t, + 20, + 85263, + 65423, + 23, + 0, + )}, + 25, + 8, + 23, + }, + }, + { + []string{"20/85263/65423/23/-2", "20/85263/65423/23/-1"}, + argSet{ + []*object.TileXYZ{newTileXYZ( + t, + 20, + 85263, + 65423, + 23, + 0, + )}, + 25, + 7, + 23, + }, + }, + { + []string{"20/85263/65423/26/6", "20/85263/65423/26/7"}, + argSet{ + []*object.TileXYZ{newTileXYZ( + t, + 20, + 85263, + 65423, + 26, + 3, + )}, + 25, + -2, + 26, + }, + }, + { + []string{"20/85263/65423/23/0", "20/85263/65423/23/1", "20/85263/65423/23/2"}, + argSet{ + []*object.TileXYZ{ + newTileXYZ( + t, + 20, + 85263, + 65423, + 23, + 0, + ), + newTileXYZ( + t, + 20, + 85263, + 65423, + 23, + 1, + )}, + 25, + -1, + 23, + }, + }, + } + for _, testCase := range testCases { + expectedData := []object.ExtendedSpatialID{} + for i := 0; i < len(testCase.expected); i++ { + extendedSpatialId := newExtendedSpatialID(t, testCase.expected[i]) + expectedData = append(expectedData, *extendedSpatialId) + } + result, err := ConvertTileXYZsToExtendedSpatialIDs(testCase.request.tile, testCase.request.zBaseExponent, testCase.request.zBaseOffset, testCase.request.outputVZoom) + if err != nil { + t.Fatal(err) + } + if assert.ElementsMatch(t, expectedData, result) == false { + // t.Error(result): + t.Errorf("expected: %v result: %v", expectedData, result) + } else { + t.Log("Success", result) + } + } + +} + +func TestErrorConvertTileXYZsToExtendedSpatialIDs(t *testing.T) { + type argSet struct { + tile []*object.TileXYZ + zBaseExponent int64 + zBaseOffset int64 + outputVZoom int64 + } + testCases := []struct { + expected string + request argSet + }{ + { + "input index does not exist", + argSet{ + []*object.TileXYZ{newTileXYZ( + t, + 20, + 85263, + 65423, + 25, + (1<