@xyo-network/quadkey 2.36.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +165 -0
- package/README.md +80 -0
- package/dist/cjs/Quadkey.d.ts +66 -0
- package/dist/cjs/Quadkey.d.ts.map +1 -0
- package/dist/cjs/Quadkey.js +282 -0
- package/dist/cjs/Quadkey.js.map +1 -0
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +5 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/utils.d.ts +5 -0
- package/dist/cjs/utils.d.ts.map +1 -0
- package/dist/cjs/utils.js +44 -0
- package/dist/cjs/utils.js.map +1 -0
- package/dist/docs.json +2282 -0
- package/dist/esm/Quadkey.d.ts +66 -0
- package/dist/esm/Quadkey.d.ts.map +1 -0
- package/dist/esm/Quadkey.js +274 -0
- package/dist/esm/Quadkey.js.map +1 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/utils.d.ts +5 -0
- package/dist/esm/utils.d.ts.map +1 -0
- package/dist/esm/utils.js +38 -0
- package/dist/esm/utils.js.map +1 -0
- package/package.json +57 -0
- package/src/Quadkey.ts +335 -0
- package/src/index.ts +1 -0
- package/src/utils.ts +40 -0
package/src/Quadkey.ts
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { BigNumber } from '@xylabs/bignumber'
|
|
2
|
+
import { Buffer } from '@xylabs/buffer'
|
|
3
|
+
import { assertEx } from '@xylabs/sdk-js'
|
|
4
|
+
import {
|
|
5
|
+
GeoJson,
|
|
6
|
+
MercatorBoundingBox,
|
|
7
|
+
MercatorTile,
|
|
8
|
+
tileFromPoint,
|
|
9
|
+
tileFromQuadkey,
|
|
10
|
+
tilesFromBoundingBox,
|
|
11
|
+
tileToBoundingBox,
|
|
12
|
+
tileToQuadkey,
|
|
13
|
+
} from '@xyo-network/sdk-xyo-js'
|
|
14
|
+
import { LngLat, LngLatLike } from 'mapbox-gl'
|
|
15
|
+
|
|
16
|
+
import { bitShiftLeft, bitShiftRight, padHex } from './utils'
|
|
17
|
+
|
|
18
|
+
export * from './utils'
|
|
19
|
+
|
|
20
|
+
const MAX_ZOOM = 124
|
|
21
|
+
|
|
22
|
+
export const isQuadkey = (obj: { type: string }) => obj?.type === Quadkey.type
|
|
23
|
+
|
|
24
|
+
const RelativeDirectionConstantLookup: Record<string, number> = {
|
|
25
|
+
e: 1,
|
|
26
|
+
n: -2,
|
|
27
|
+
s: 2,
|
|
28
|
+
w: -1,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class Quadkey {
|
|
32
|
+
public type = Quadkey.type
|
|
33
|
+
|
|
34
|
+
private key = Buffer.alloc(32)
|
|
35
|
+
|
|
36
|
+
constructor(key = Buffer.alloc(32)) {
|
|
37
|
+
key.copy(this.key, this.key.length - key.length)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public equals(obj: Quadkey): boolean {
|
|
41
|
+
return obj.toBase4HashLabel() == this.toBase4HashLabel()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static root = new Quadkey()
|
|
45
|
+
|
|
46
|
+
static type = 'Quadkey'
|
|
47
|
+
|
|
48
|
+
static fromBase4String(value?: string) {
|
|
49
|
+
if (value === 'fhr' || value === '') {
|
|
50
|
+
return Quadkey.root
|
|
51
|
+
}
|
|
52
|
+
if (value && value.length && value.length > 0) {
|
|
53
|
+
const quadkey = new Quadkey(Buffer.from(padHex(new BigNumber(value, 4).toString(16)), 'hex')).setZoom(value.length)
|
|
54
|
+
return quadkey.valid() ? quadkey : undefined
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static fromBase10String(value: string) {
|
|
59
|
+
return new Quadkey(Buffer.from(padHex(new BigNumber(value, 10).toString(16)), 'hex'))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static fromBase16String(value: string) {
|
|
63
|
+
const valueToUse = value.startsWith('0x') ? value.slice(2) : value
|
|
64
|
+
return new Quadkey(Buffer.from(padHex(valueToUse), 'hex'))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
static fromBuffer(value: Buffer) {
|
|
68
|
+
return Quadkey.fromBase16String(value.toString('hex'))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static fromString(zoom: number, id: string, base = 10) {
|
|
72
|
+
switch (base) {
|
|
73
|
+
case 10:
|
|
74
|
+
return Quadkey.fromBase10String(id)?.setZoom(zoom)
|
|
75
|
+
case 16:
|
|
76
|
+
return Quadkey.fromBase16String(id).setZoom(zoom)
|
|
77
|
+
default:
|
|
78
|
+
throw Error(`Invalid base [${base}]`)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public static from(zoom: number, id: Buffer) {
|
|
83
|
+
return new Quadkey().setId(id).setZoom(zoom)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public static fromLngLat(point: LngLatLike, zoom: number) {
|
|
87
|
+
const tile = tileFromPoint(LngLat.convert(point), zoom)
|
|
88
|
+
const quadkeyString = tileToQuadkey(tile)
|
|
89
|
+
return Quadkey.fromBase4String(quadkeyString)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public static fromTile(tile: MercatorTile) {
|
|
93
|
+
return Quadkey.fromBase4String(tileToQuadkey(tile))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public isInBoundingBox(boundingBox: MercatorBoundingBox) {
|
|
97
|
+
const tileBoundingBox = tileToBoundingBox(this.toTile())
|
|
98
|
+
return (
|
|
99
|
+
boundingBox.contains(tileBoundingBox.getNorthEast()) ||
|
|
100
|
+
boundingBox.contains(tileBoundingBox.getNorthWest()) ||
|
|
101
|
+
boundingBox.contains(tileBoundingBox.getSouthEast()) ||
|
|
102
|
+
boundingBox.contains(tileBoundingBox.getSouthWest())
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
public static fromBoundingBox(bbox: MercatorBoundingBox, zoom: number) {
|
|
107
|
+
const tiles = tilesFromBoundingBox(bbox, Math.floor(zoom))
|
|
108
|
+
const result: Quadkey[] = []
|
|
109
|
+
for (const tile of tiles) {
|
|
110
|
+
result.push(assertEx(Quadkey.fromTile(tile), 'Bad Quadkey'))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return result
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public getGridBoundingBox(size: number) {
|
|
117
|
+
const hash = this.toBase4Hash()
|
|
118
|
+
let index = 0
|
|
119
|
+
let left = 0
|
|
120
|
+
let top = 0
|
|
121
|
+
let blockSize = size
|
|
122
|
+
while (index < hash.length) {
|
|
123
|
+
blockSize >>= 1
|
|
124
|
+
switch (hash[index]) {
|
|
125
|
+
case '1':
|
|
126
|
+
left += blockSize
|
|
127
|
+
break
|
|
128
|
+
case '2':
|
|
129
|
+
top += blockSize
|
|
130
|
+
break
|
|
131
|
+
case '3':
|
|
132
|
+
left += blockSize
|
|
133
|
+
top += blockSize
|
|
134
|
+
break
|
|
135
|
+
}
|
|
136
|
+
index++
|
|
137
|
+
}
|
|
138
|
+
if (blockSize < 2) {
|
|
139
|
+
blockSize = 2
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
height: blockSize,
|
|
143
|
+
left,
|
|
144
|
+
top,
|
|
145
|
+
width: blockSize,
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
public setKey(zoom: number, id: Buffer) {
|
|
150
|
+
id.copy(this.key, this.key.length - id.length)
|
|
151
|
+
this.key.writeUInt8(zoom, 0)
|
|
152
|
+
return this
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
public setZoom(zoom: number) {
|
|
156
|
+
assertEx(zoom < MAX_ZOOM, `Invalid zoom [${zoom}] max=${MAX_ZOOM}`)
|
|
157
|
+
this.setKey(zoom, this.id())
|
|
158
|
+
return this
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
public parent() {
|
|
162
|
+
if (this.zoom() > 0) {
|
|
163
|
+
return new Quadkey().setId(bitShiftRight(bitShiftRight(this.id()))).setZoom(this.zoom() - 1)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
public relative(direction: string) {
|
|
168
|
+
const directionConstant = assertEx(RelativeDirectionConstantLookup[direction], 'Invalid direction')
|
|
169
|
+
let quadkey = this.toBase4Hash()
|
|
170
|
+
if (quadkey.length === 0) {
|
|
171
|
+
return this
|
|
172
|
+
}
|
|
173
|
+
let index = quadkey.length - 1
|
|
174
|
+
while (index >= 0) {
|
|
175
|
+
let number = parseInt(quadkey.charAt(index))
|
|
176
|
+
number += directionConstant
|
|
177
|
+
if (number > 3) {
|
|
178
|
+
number -= 4
|
|
179
|
+
quadkey = quadkey.substring(0, index) + number.toString() + quadkey.substring(index + 1)
|
|
180
|
+
index--
|
|
181
|
+
} else if (number < 0) {
|
|
182
|
+
number += 4
|
|
183
|
+
quadkey = quadkey.substring(0, index) + number.toString() + quadkey.substring(index + 1)
|
|
184
|
+
index--
|
|
185
|
+
} else {
|
|
186
|
+
index = -1
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return Quadkey.fromBase4String(quadkey)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
public childrenByZoom(zoom: number) {
|
|
193
|
+
// if we are limiting by zoom, and we are already at that limit, just return this quadkey
|
|
194
|
+
if (zoom && zoom === this.zoom()) {
|
|
195
|
+
return [this]
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// recursively get children
|
|
199
|
+
let deepResult: Quadkey[] = []
|
|
200
|
+
for (const quadkey of this.children()) {
|
|
201
|
+
deepResult = deepResult.concat(quadkey.childrenByZoom(zoom))
|
|
202
|
+
}
|
|
203
|
+
return deepResult
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
public children() {
|
|
207
|
+
assertEx(this.zoom() < MAX_ZOOM - 1, 'Can not get children of bottom tiles')
|
|
208
|
+
const result: Quadkey[] = []
|
|
209
|
+
const shiftedId = bitShiftLeft(bitShiftLeft(this.id()))
|
|
210
|
+
for (let i = 0; i < 4; i++) {
|
|
211
|
+
const currentLastByte = shiftedId.readUInt8(shiftedId.length - 1)
|
|
212
|
+
shiftedId.writeUInt8((currentLastByte & 0xfc) | i, shiftedId.length - 1)
|
|
213
|
+
result.push(new Quadkey().setId(shiftedId).setZoom(this.zoom() + 1))
|
|
214
|
+
}
|
|
215
|
+
return result
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
public siblings() {
|
|
219
|
+
const siblings = assertEx(this.parent()?.children(), `siblings: parentChildren ${this.toBase4Hash()}`)
|
|
220
|
+
const filteredeSiblinngs = siblings.filter((quadkey) => this.compareTo(quadkey) !== 0)
|
|
221
|
+
assertEx(filteredeSiblinngs.length === 3, `siblings: expected 3 [${filteredeSiblinngs.length}]`)
|
|
222
|
+
return filteredeSiblinngs
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
public clone() {
|
|
226
|
+
return Quadkey.fromBase10String(this.toBase10String())
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
public zoom() {
|
|
230
|
+
return this.toBuffer().readUInt8(0)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private _geoJson?: GeoJson
|
|
234
|
+
|
|
235
|
+
public geoJson() {
|
|
236
|
+
this._geoJson = this._geoJson ?? new GeoJson(this.toBase4Hash())
|
|
237
|
+
return this._geoJson
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
public setId(id: Buffer) {
|
|
241
|
+
this.setKey(this.zoom(), id)
|
|
242
|
+
return this
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
public toTile(): MercatorTile {
|
|
246
|
+
return tileFromQuadkey(this.toBase4Hash())
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
public toBoundingBox(): MercatorBoundingBox {
|
|
250
|
+
return tileToBoundingBox(this.toTile())
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
public getGridLocation() {
|
|
254
|
+
const tileData = tileFromQuadkey(this.toBase4Hash())
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
col: 2 ** tileData[2] - tileData[1] - 1,
|
|
258
|
+
row: tileData[0],
|
|
259
|
+
zoom: tileData[2],
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
public valid() {
|
|
264
|
+
const zoom = this.zoom()
|
|
265
|
+
const shift = (MAX_ZOOM - zoom) * 2
|
|
266
|
+
const id = this.id()
|
|
267
|
+
let testId = id
|
|
268
|
+
for (let i = 0; i < shift; i++) {
|
|
269
|
+
testId = bitShiftLeft(testId)
|
|
270
|
+
}
|
|
271
|
+
for (let i = 0; i < shift; i++) {
|
|
272
|
+
testId = bitShiftRight(testId)
|
|
273
|
+
}
|
|
274
|
+
return testId.compare(id) === 0
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
public id() {
|
|
278
|
+
return this.toBuffer().slice(1)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
public toBuffer() {
|
|
282
|
+
return this.key
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
public toBigNumber() {
|
|
286
|
+
return new BigNumber(`${this.key.toString('hex')}`, 'hex')
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
public toString() {
|
|
290
|
+
return `0x${padHex(this.toBigNumber().toString(16))}`
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
public toJSON(): string {
|
|
294
|
+
return this.toBase4HashLabel()
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
public toBase10String() {
|
|
298
|
+
return this.toBigNumber().toString(10)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
public toBase4HashLabel() {
|
|
302
|
+
const hash = this.toBase4Hash()
|
|
303
|
+
return hash.length === 0 ? 'fhr' : hash
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
public toBase4Hash() {
|
|
307
|
+
const bn = new BigNumber(this.id().toString('hex'), 16)
|
|
308
|
+
const zoom = this.zoom()
|
|
309
|
+
if (zoom === 0) {
|
|
310
|
+
return ''
|
|
311
|
+
}
|
|
312
|
+
let quadkeySimple = bn.toString(4)
|
|
313
|
+
while (quadkeySimple.length < zoom) {
|
|
314
|
+
quadkeySimple = `0${quadkeySimple}`
|
|
315
|
+
}
|
|
316
|
+
return quadkeySimple
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
public toHex() {
|
|
320
|
+
return `0x${this.key.toString('hex')}`
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
public toShortString() {
|
|
324
|
+
const buffer = this.toBuffer()
|
|
325
|
+
const part1 = buffer.slice(0, 2)
|
|
326
|
+
const part2 = buffer.slice(buffer.length - 2, buffer.length)
|
|
327
|
+
return `${part1.toString('hex')}...${part2.toString('hex')}`
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
public compareTo(quadkey: Quadkey) {
|
|
331
|
+
return this.toBigNumber().cmp(quadkey.toBigNumber())
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
public static Zero = Quadkey.from(0, Buffer.alloc(31, 0))
|
|
335
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Quadkey'
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Buffer } from 'buffer/'
|
|
2
|
+
|
|
3
|
+
export const padHex = (hex: string, byteCount?: number) => {
|
|
4
|
+
let result = hex
|
|
5
|
+
if (hex.length % 2 !== 0) {
|
|
6
|
+
result = `0${hex}`
|
|
7
|
+
}
|
|
8
|
+
if (byteCount) {
|
|
9
|
+
while (result.length / 2 < byteCount) {
|
|
10
|
+
result = `00${result}`
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return result
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const bitShiftLeft = (buffer: Buffer) => {
|
|
17
|
+
const shifted = Buffer.alloc(buffer.length)
|
|
18
|
+
const last = buffer.length - 1
|
|
19
|
+
for (let index = 0; index < last; index++) {
|
|
20
|
+
shifted[index] = buffer[index] << 1
|
|
21
|
+
if (buffer[index + 1] & 0x80) {
|
|
22
|
+
shifted[index] += 0x01
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
shifted[last] = buffer[last] << 1
|
|
26
|
+
return shifted
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const bitShiftRight = (buffer: Buffer) => {
|
|
30
|
+
const shifted = Buffer.alloc(buffer.length)
|
|
31
|
+
const last = buffer.length - 1
|
|
32
|
+
for (let index = last; index > 0; index--) {
|
|
33
|
+
shifted[index] = buffer[index] >> 1
|
|
34
|
+
if (buffer[index - 1] & 0x01) {
|
|
35
|
+
shifted[index] += 0x80
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
shifted[0] = buffer[0] >> 1
|
|
39
|
+
return shifted
|
|
40
|
+
}
|