@truelies/osm-dybuf 0.4.3 → 0.4.6
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/README.md +77 -30
- package/collectible_types.d.ts +22 -0
- package/collectible_types.mjs +48 -0
- package/grid_index.d.ts +46 -19
- package/grid_index.mjs +133 -24
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -92,13 +92,17 @@ import {
|
|
|
92
92
|
GridUnit,
|
|
93
93
|
Gridex,
|
|
94
94
|
INT_COORD_SCALE,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
CANONICAL_CODE_LEVEL,
|
|
96
|
+
gridexAtLonLat,
|
|
97
|
+
gridCodeAtLonLat,
|
|
98
|
+
gridCodeComponentsAtLonLat,
|
|
99
|
+
lonCodeFromLondex,
|
|
100
|
+
latCodeFromLatdex,
|
|
101
|
+
londexPrefixCodeFromLonCode,
|
|
102
|
+
lonLatCodeRangeForGridex,
|
|
103
|
+
encodeAxisCode,
|
|
104
|
+
decodeAxisCode,
|
|
105
|
+
lonCodeMagnitudeBits,
|
|
102
106
|
} from "@truelies/osm-dybuf/grid_index";
|
|
103
107
|
|
|
104
108
|
const unit = new GridUnit(8); // level 8
|
|
@@ -109,17 +113,27 @@ const [minLon, minLat, maxLon, maxLat] = unit.boundsOfGrid(cell);
|
|
|
109
113
|
const children = unit.toLowerLevelGridexes(cell); // level 9, 4 cells
|
|
110
114
|
const parent = new GridUnit(9).toUpperLevelGridex(children[0]); // back to level 8
|
|
111
115
|
|
|
112
|
-
//
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
console.log(
|
|
116
|
+
// GPS -> gridex / lonCode / latCode / gridCode
|
|
117
|
+
const gridex = gridexAtLonLat(16, 121.56, 25.03);
|
|
118
|
+
const parts = gridCodeComponentsAtLonLat(16, 121.56, 25.03);
|
|
119
|
+
console.log(gridex.londex, gridex.latdex);
|
|
120
|
+
console.log(parts.lonCode, parts.latCode, parts.gridCode);
|
|
121
|
+
|
|
122
|
+
// gridex <-> code
|
|
123
|
+
const lonCode = lonCodeFromLondex(16, -123);
|
|
124
|
+
const latCode = latCodeFromLatdex(16, 456);
|
|
125
|
+
console.log(lonCode, latCode);
|
|
126
|
+
console.log(gridCodeAtLonLat(16, 121.56, 25.03));
|
|
127
|
+
|
|
128
|
+
// canonical prefix / range for coarser level query
|
|
129
|
+
const lonPrefix = londexPrefixCodeFromLonCode(15, lonCode, CANONICAL_CODE_LEVEL);
|
|
130
|
+
const range = lonLatCodeRangeForGridex(15, -3, 4, CANONICAL_CODE_LEVEL);
|
|
131
|
+
console.log(lonPrefix, range.lonCode.from, range.lonCode.to);
|
|
132
|
+
|
|
133
|
+
// axis code roundtrip
|
|
134
|
+
const lonBits = lonCodeMagnitudeBits(CANONICAL_CODE_LEVEL);
|
|
135
|
+
const axisCode = encodeAxisCode(-123, lonBits);
|
|
136
|
+
console.log(decodeAxisCode(axisCode, lonBits)); // -123
|
|
123
137
|
```
|
|
124
138
|
|
|
125
139
|
- `toLowerLevelGridexes(gridex)`:
|
|
@@ -128,15 +142,48 @@ console.log(decodeSignedGridIndex(lonCode, GRID16_LON_MAG_BITS)); // -123
|
|
|
128
142
|
- `toUpperLevelGridex(gridex)`:
|
|
129
143
|
- returns the parent cell at `level - 1`
|
|
130
144
|
- throws when current unit is level `0`
|
|
131
|
-
- `
|
|
132
|
-
- converts GPS directly to
|
|
133
|
-
|
|
134
|
-
- `
|
|
135
|
-
|
|
136
|
-
-
|
|
137
|
-
- `
|
|
145
|
+
- `gridexAtLonLat(level, lon, lat)`:
|
|
146
|
+
- converts GPS directly to a `Gridex`
|
|
147
|
+
- `gridCodeComponentsAtLonLat(level, lon, lat)`:
|
|
148
|
+
- returns `{ gridex, lonCode, latCode, gridCode }`
|
|
149
|
+
- `lonCodeFromLondex(level, londex)` / `latCodeFromLatdex(level, latdex)`:
|
|
150
|
+
- converts axis indices to canonical axis codes
|
|
151
|
+
- `londexPrefixCodeFromLonCode(targetLevel, lonCode, sourceLevel?)`:
|
|
152
|
+
- trims a finer `lonCode` into a coarser-level prefix view
|
|
153
|
+
- `lonLatCodeRangeForGridex(level, londex, latdex, sourceLevel?)`:
|
|
154
|
+
- expands any `lv <= sourceLevel` cell into the corresponding canonical axis-code range
|
|
155
|
+
- `gridCodeAtLonLat(level, lon, lat)` / `gridCodeFromGridex(level, gridex)`:
|
|
156
|
+
- returns the packed `gridCode`
|
|
157
|
+
- `encodeAxisCode(index, magnitudeBits)` / `decodeAxisCode(code, magnitudeBits)`:
|
|
138
158
|
- encodes grid index as `sign bit + (abs(index) - 1)`
|
|
139
|
-
|
|
159
|
+
|
|
160
|
+
### Collectible types
|
|
161
|
+
|
|
162
|
+
`collectible_types.mjs` exposes stable numeric collectible type IDs for Firestore / frontend use:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import {
|
|
166
|
+
COLLECTIBLE_TYPE_IDS,
|
|
167
|
+
COLLECTIBLE_TYPES_BY_ID,
|
|
168
|
+
TELEPORT_SUBTYPE_IDS,
|
|
169
|
+
} from "@truelies/osm-dybuf/collectible_types";
|
|
170
|
+
|
|
171
|
+
console.log(COLLECTIBLE_TYPE_IDS.teleport); // 1
|
|
172
|
+
console.log(TELEPORT_SUBTYPE_IDS.rail); // 1
|
|
173
|
+
console.log(COLLECTIBLE_TYPES_BY_ID[2].nameZh); // "聖晶石"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Current built-in IDs:
|
|
177
|
+
|
|
178
|
+
- `1`: 傳送點
|
|
179
|
+
- `2`: 聖晶石
|
|
180
|
+
|
|
181
|
+
Current teleport subtypes:
|
|
182
|
+
|
|
183
|
+
- `1`: 鐵路
|
|
184
|
+
- `2`: 陸路
|
|
185
|
+
- `3`: 海路
|
|
186
|
+
- `4`: 空路
|
|
140
187
|
|
|
141
188
|
## Local Development
|
|
142
189
|
|
|
@@ -150,9 +197,9 @@ Unit test example (`tests/grid_index.test.mjs`) covers:
|
|
|
150
197
|
- `gridexesAt` near axis (no zero index cell)
|
|
151
198
|
- `toLowerLevelGridexes` and `toUpperLevelGridex` roundtrip
|
|
152
199
|
- level boundary errors (`lv0` has no upper level, `lv16` has no lower level)
|
|
153
|
-
- canonical
|
|
154
|
-
- GPS -> `
|
|
155
|
-
- `lv15` cell ->
|
|
200
|
+
- canonical axis-code encode/decode roundtrip
|
|
201
|
+
- GPS -> `gridex / lonCode / latCode / gridCode`
|
|
202
|
+
- `lv15` cell -> canonical code range expansion correctness
|
|
156
203
|
|
|
157
204
|
- `npm pack`: build a tarball so other repos can install via `npm install ./path/to/osm-dybuf-*.tgz`
|
|
158
205
|
- `npm link`: link the package locally (`npm link` here, then `npm link @truelies/osm-dybuf` in the consumer repo)
|
|
@@ -174,4 +221,4 @@ Unit test example (`tests/grid_index.test.mjs`) covers:
|
|
|
174
221
|
- `region_numeric_codes.json`: latest region ID table (generated via `scripts/dump_region_codes.py`)
|
|
175
222
|
- `inspect_dybuf.mjs`: CLI tool that uses the package locally
|
|
176
223
|
|
|
177
|
-
When schema IDs or DyBuf layouts change in the exporter, remember to bump the version here and re-publish/install so frontends and tools stay in sync. Version `0.4.3` aligns the package with the current `lv0~13` export range, the current frontend feature set (`water_wetland`, `building`, derived `label_*`, no exported `place_label`), the numeric `featureId` parser contract, and the
|
|
224
|
+
When schema IDs or DyBuf layouts change in the exporter, remember to bump the version here and re-publish/install so frontends and tools stay in sync. Version `0.4.3` aligns the package with the current `lv0~13` export range, the current frontend feature set (`water_wetland`, `building`, derived `label_*`, no exported `place_label`), the numeric `featureId` parser contract, and the canonical grid conversion helpers shared by Firestore / Redis / frontend indexing.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const COLLECTIBLE_TYPE_IDS: Readonly<{
|
|
2
|
+
teleport: 1;
|
|
3
|
+
sacredCrystal: 2;
|
|
4
|
+
}>;
|
|
5
|
+
|
|
6
|
+
export interface CollectibleTypeMeta {
|
|
7
|
+
id: number;
|
|
8
|
+
key: string;
|
|
9
|
+
nameZh: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export declare const COLLECTIBLE_TYPES_BY_ID: Readonly<Record<number, Readonly<CollectibleTypeMeta>>>;
|
|
13
|
+
|
|
14
|
+
export declare const TELEPORT_SUBTYPE_IDS: Readonly<{
|
|
15
|
+
rail: 1;
|
|
16
|
+
road: 2;
|
|
17
|
+
sea: 3;
|
|
18
|
+
air: 4;
|
|
19
|
+
}>;
|
|
20
|
+
|
|
21
|
+
export declare const TELEPORT_SUBTYPES_BY_ID: Readonly<Record<number, Readonly<CollectibleTypeMeta>>>;
|
|
22
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export const COLLECTIBLE_TYPE_IDS = Object.freeze({
|
|
2
|
+
teleport: 1,
|
|
3
|
+
sacredCrystal: 2,
|
|
4
|
+
});
|
|
5
|
+
|
|
6
|
+
export const COLLECTIBLE_TYPES_BY_ID = Object.freeze({
|
|
7
|
+
[COLLECTIBLE_TYPE_IDS.teleport]: Object.freeze({
|
|
8
|
+
id: COLLECTIBLE_TYPE_IDS.teleport,
|
|
9
|
+
key: "teleport",
|
|
10
|
+
nameZh: "傳送點",
|
|
11
|
+
}),
|
|
12
|
+
[COLLECTIBLE_TYPE_IDS.sacredCrystal]: Object.freeze({
|
|
13
|
+
id: COLLECTIBLE_TYPE_IDS.sacredCrystal,
|
|
14
|
+
key: "sacred_crystal",
|
|
15
|
+
nameZh: "聖晶石",
|
|
16
|
+
}),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const TELEPORT_SUBTYPE_IDS = Object.freeze({
|
|
20
|
+
rail: 1,
|
|
21
|
+
road: 2,
|
|
22
|
+
sea: 3,
|
|
23
|
+
air: 4,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export const TELEPORT_SUBTYPES_BY_ID = Object.freeze({
|
|
27
|
+
[TELEPORT_SUBTYPE_IDS.rail]: Object.freeze({
|
|
28
|
+
id: TELEPORT_SUBTYPE_IDS.rail,
|
|
29
|
+
key: "rail",
|
|
30
|
+
nameZh: "鐵路",
|
|
31
|
+
}),
|
|
32
|
+
[TELEPORT_SUBTYPE_IDS.road]: Object.freeze({
|
|
33
|
+
id: TELEPORT_SUBTYPE_IDS.road,
|
|
34
|
+
key: "road",
|
|
35
|
+
nameZh: "陸路",
|
|
36
|
+
}),
|
|
37
|
+
[TELEPORT_SUBTYPE_IDS.sea]: Object.freeze({
|
|
38
|
+
id: TELEPORT_SUBTYPE_IDS.sea,
|
|
39
|
+
key: "sea",
|
|
40
|
+
nameZh: "海路",
|
|
41
|
+
}),
|
|
42
|
+
[TELEPORT_SUBTYPE_IDS.air]: Object.freeze({
|
|
43
|
+
id: TELEPORT_SUBTYPE_IDS.air,
|
|
44
|
+
key: "air",
|
|
45
|
+
nameZh: "空路",
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
|
package/grid_index.d.ts
CHANGED
|
@@ -2,9 +2,7 @@ export declare const INT_COORD_SCALE: number;
|
|
|
2
2
|
export declare const GRID_FINE_RES: number;
|
|
3
3
|
export declare const GRID_LV0_UNIT: number;
|
|
4
4
|
export declare const MAX_LEVEL: number;
|
|
5
|
-
export declare const
|
|
6
|
-
export declare const GRID16_LON_MAG_BITS: number;
|
|
7
|
-
export declare const GRID16_LAT_MAG_BITS: number;
|
|
5
|
+
export declare const CANONICAL_CODE_LEVEL: number;
|
|
8
6
|
export declare class Gridex {
|
|
9
7
|
readonly londex: number;
|
|
10
8
|
readonly latdex: number;
|
|
@@ -53,36 +51,65 @@ export declare class GridUnit {
|
|
|
53
51
|
}
|
|
54
52
|
export declare function gridexAtLonLat(level: number, longitude: number, latitude: number): GridexAtLv;
|
|
55
53
|
export declare function gridexStringAtLonLat(level: number, longitude: number, latitude: number): string;
|
|
56
|
-
export declare function
|
|
57
|
-
export declare function
|
|
58
|
-
export declare function
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
export declare function lonCodeMagnitudeBits(level: number): number;
|
|
55
|
+
export declare function latCodeMagnitudeBits(level: number): number;
|
|
56
|
+
export declare function encodeAxisCode(index: number, magnitudeBits: number): number;
|
|
57
|
+
export declare function decodeAxisCode(code: number, magnitudeBits: number): number;
|
|
58
|
+
export declare function lonCodeFromLondex(level: number, londex: number): number;
|
|
59
|
+
export declare function latCodeFromLatdex(level: number, latdex: number): number;
|
|
60
|
+
export declare function londexFromLonCode(level: number, lonCode: number): number;
|
|
61
|
+
export declare function latdexFromLatCode(level: number, latCode: number): number;
|
|
62
|
+
export declare function londexPrefixCodeFromLonCode(targetLevel: number, lonCode: number, sourceLevel?: number): number;
|
|
63
|
+
export declare function latdexPrefixCodeFromLatCode(targetLevel: number, latCode: number, sourceLevel?: number): number;
|
|
64
|
+
export declare function lonCodeRangeForLondex(
|
|
65
|
+
level: number,
|
|
66
|
+
londex: number,
|
|
67
|
+
sourceLevel?: number
|
|
61
68
|
): {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
londex16: number;
|
|
65
|
-
latdex16: number;
|
|
69
|
+
from: number;
|
|
70
|
+
to: number;
|
|
66
71
|
};
|
|
67
|
-
export declare function
|
|
72
|
+
export declare function latCodeRangeForLatdex(
|
|
68
73
|
level: number,
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
latdex: number,
|
|
75
|
+
sourceLevel?: number
|
|
71
76
|
): {
|
|
72
77
|
from: number;
|
|
73
78
|
to: number;
|
|
74
79
|
};
|
|
75
|
-
export declare function
|
|
80
|
+
export declare function lonLatCodeRangeForGridex(
|
|
76
81
|
level: number,
|
|
77
82
|
londex: number,
|
|
78
|
-
latdex: number
|
|
83
|
+
latdex: number,
|
|
84
|
+
sourceLevel?: number
|
|
79
85
|
): {
|
|
80
|
-
|
|
86
|
+
lonCode: {
|
|
81
87
|
from: number;
|
|
82
88
|
to: number;
|
|
83
89
|
};
|
|
84
|
-
|
|
90
|
+
latCode: {
|
|
85
91
|
from: number;
|
|
86
92
|
to: number;
|
|
87
93
|
};
|
|
88
94
|
};
|
|
95
|
+
export declare function gridCodeFromLonLatCodes(level: number, lonCode: number, latCode: number): number;
|
|
96
|
+
export declare function lonLatCodesFromGridCode(
|
|
97
|
+
level: number,
|
|
98
|
+
gridCode: number
|
|
99
|
+
): {
|
|
100
|
+
lonCode: number;
|
|
101
|
+
latCode: number;
|
|
102
|
+
};
|
|
103
|
+
export declare function gridCodeFromGridex(level: number, gridex: Gridex | number, latdex?: number): number;
|
|
104
|
+
export declare function gridexFromGridCode(level: number, gridCode: number): GridexAtLv;
|
|
105
|
+
export declare function gridCodeAtLonLat(level: number, longitude: number, latitude: number): number;
|
|
106
|
+
export declare function gridCodeComponentsAtLonLat(
|
|
107
|
+
level: number,
|
|
108
|
+
longitude: number,
|
|
109
|
+
latitude: number
|
|
110
|
+
): {
|
|
111
|
+
gridex: GridexAtLv;
|
|
112
|
+
lonCode: number;
|
|
113
|
+
latCode: number;
|
|
114
|
+
gridCode: number;
|
|
115
|
+
};
|
package/grid_index.mjs
CHANGED
|
@@ -11,9 +11,7 @@ const MAX_LONGITUDE_FINE = 180 * INT_COORD_SCALE * GRID_FINE_RES;
|
|
|
11
11
|
const MIN_LONGITUDE_FINE = -MAX_LONGITUDE_FINE;
|
|
12
12
|
const MAX_LATITUDE_FINE = 90 * INT_COORD_SCALE * GRID_FINE_RES;
|
|
13
13
|
const MIN_LATITUDE_FINE = -MAX_LATITUDE_FINE;
|
|
14
|
-
export const
|
|
15
|
-
export const GRID16_LON_MAG_BITS = 19;
|
|
16
|
-
export const GRID16_LAT_MAG_BITS = 18;
|
|
14
|
+
export const CANONICAL_CODE_LEVEL = MAX_LEVEL;
|
|
17
15
|
|
|
18
16
|
export class Gridex {
|
|
19
17
|
constructor(londex, latdex) {
|
|
@@ -266,7 +264,17 @@ export function gridexStringAtLonLat(level, longitude, latitude) {
|
|
|
266
264
|
return `${gridex.londex},${gridex.latdex}`;
|
|
267
265
|
}
|
|
268
266
|
|
|
269
|
-
export function
|
|
267
|
+
export function lonCodeMagnitudeBits(level) {
|
|
268
|
+
const unit = new GridUnit(level);
|
|
269
|
+
return Math.ceil(Math.log2(unit.maxLondex));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function latCodeMagnitudeBits(level) {
|
|
273
|
+
const unit = new GridUnit(level);
|
|
274
|
+
return Math.ceil(Math.log2(unit.maxLatdex));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function encodeAxisCode(index, magnitudeBits) {
|
|
270
278
|
if (!Number.isInteger(index) || index === 0) {
|
|
271
279
|
throw new Error(`Grid index must be a non-zero integer: ${index}`);
|
|
272
280
|
}
|
|
@@ -279,7 +287,7 @@ export function encodeSignedGridIndex(index, magnitudeBits) {
|
|
|
279
287
|
return (signBit << magnitudeBits) | magnitude;
|
|
280
288
|
}
|
|
281
289
|
|
|
282
|
-
export function
|
|
290
|
+
export function decodeAxisCode(code, magnitudeBits) {
|
|
283
291
|
if (!Number.isInteger(code) || code < 0) {
|
|
284
292
|
throw new Error(`Grid code must be a non-negative integer: ${code}`);
|
|
285
293
|
}
|
|
@@ -289,41 +297,142 @@ export function decodeSignedGridIndex(code, magnitudeBits) {
|
|
|
289
297
|
return (magnitude + 1) * (signBit === 1 ? 1 : -1);
|
|
290
298
|
}
|
|
291
299
|
|
|
292
|
-
export function
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
latdex16: gridex16.latdex,
|
|
299
|
-
};
|
|
300
|
+
export function lonCodeFromLondex(level, londex) {
|
|
301
|
+
return encodeAxisCode(londex, lonCodeMagnitudeBits(level));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function latCodeFromLatdex(level, latdex) {
|
|
305
|
+
return encodeAxisCode(latdex, latCodeMagnitudeBits(level));
|
|
300
306
|
}
|
|
301
307
|
|
|
302
|
-
export function
|
|
303
|
-
|
|
304
|
-
|
|
308
|
+
export function londexFromLonCode(level, lonCode) {
|
|
309
|
+
return decodeAxisCode(lonCode, lonCodeMagnitudeBits(level));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function latdexFromLatCode(level, latCode) {
|
|
313
|
+
return decodeAxisCode(latCode, latCodeMagnitudeBits(level));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function axisCodePrefixFromCode(targetLevel, axisCode, sourceLevel, magnitudeBits) {
|
|
317
|
+
if (!Number.isInteger(targetLevel) || targetLevel < 0 || targetLevel > sourceLevel) {
|
|
318
|
+
throw new Error(`targetLevel must be an integer between 0 and ${sourceLevel}: ${targetLevel}`);
|
|
319
|
+
}
|
|
320
|
+
const signBase = 2 ** magnitudeBits;
|
|
321
|
+
const signBit = Math.floor(axisCode / signBase);
|
|
322
|
+
const magnitude = axisCode % signBase;
|
|
323
|
+
const shift = sourceLevel - targetLevel;
|
|
324
|
+
const prefixMagnitude = shift === 0 ? magnitude : Math.floor(magnitude / (2 ** shift)) * (2 ** shift);
|
|
325
|
+
return signBit * signBase + prefixMagnitude;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function londexPrefixCodeFromLonCode(targetLevel, lonCode, sourceLevel = CANONICAL_CODE_LEVEL) {
|
|
329
|
+
return axisCodePrefixFromCode(targetLevel, lonCode, sourceLevel, lonCodeMagnitudeBits(sourceLevel));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function latdexPrefixCodeFromLatCode(targetLevel, latCode, sourceLevel = CANONICAL_CODE_LEVEL) {
|
|
333
|
+
return axisCodePrefixFromCode(targetLevel, latCode, sourceLevel, latCodeMagnitudeBits(sourceLevel));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function axisCodeRangeForIndex(targetLevel, index, sourceLevel, magnitudeBits) {
|
|
337
|
+
if (!Number.isInteger(sourceLevel) || sourceLevel < 0 || sourceLevel > MAX_LEVEL) {
|
|
338
|
+
throw new Error(`sourceLevel must be an integer between 0 and ${MAX_LEVEL}: ${sourceLevel}`);
|
|
339
|
+
}
|
|
340
|
+
if (!Number.isInteger(targetLevel) || targetLevel < 0 || targetLevel > sourceLevel) {
|
|
341
|
+
throw new Error(`targetLevel must be an integer between 0 and ${sourceLevel}: ${targetLevel}`);
|
|
305
342
|
}
|
|
306
343
|
if (!Number.isInteger(index) || index === 0) {
|
|
307
344
|
throw new Error(`Grid index must be a non-zero integer: ${index}`);
|
|
308
345
|
}
|
|
309
346
|
const signBit = index > 0 ? 1 : 0;
|
|
310
|
-
const shift =
|
|
347
|
+
const shift = sourceLevel - targetLevel;
|
|
311
348
|
const magnitude = Math.abs(index) - 1;
|
|
312
349
|
const fromMagnitude = magnitude << shift;
|
|
313
350
|
const toMagnitude = ((magnitude + 1) << shift) - 1;
|
|
314
|
-
const maxMagnitude = (
|
|
351
|
+
const maxMagnitude = (2 ** magnitudeBits) - 1;
|
|
315
352
|
if (toMagnitude > maxMagnitude) {
|
|
316
|
-
throw new Error(`Grid index ${index} at level ${
|
|
353
|
+
throw new Error(`Grid index ${index} at level ${targetLevel} exceeds source level ${sourceLevel} range`);
|
|
354
|
+
}
|
|
355
|
+
const signBase = 2 ** magnitudeBits;
|
|
356
|
+
return {
|
|
357
|
+
from: signBit * signBase + fromMagnitude,
|
|
358
|
+
to: signBit * signBase + toMagnitude,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export function lonCodeRangeForLondex(level, londex, sourceLevel = CANONICAL_CODE_LEVEL) {
|
|
363
|
+
return axisCodeRangeForIndex(level, londex, sourceLevel, lonCodeMagnitudeBits(sourceLevel));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export function latCodeRangeForLatdex(level, latdex, sourceLevel = CANONICAL_CODE_LEVEL) {
|
|
367
|
+
return axisCodeRangeForIndex(level, latdex, sourceLevel, latCodeMagnitudeBits(sourceLevel));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export function lonLatCodeRangeForGridex(level, londex, latdex, sourceLevel = CANONICAL_CODE_LEVEL) {
|
|
371
|
+
return {
|
|
372
|
+
lonCode: lonCodeRangeForLondex(level, londex, sourceLevel),
|
|
373
|
+
latCode: latCodeRangeForLatdex(level, latdex, sourceLevel),
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export function gridCodeFromLonLatCodes(level, lonCode, latCode) {
|
|
378
|
+
const latCodeWidth = latCodeMagnitudeBits(level) + 1;
|
|
379
|
+
const latCodeSpan = 2 ** latCodeWidth;
|
|
380
|
+
if (!Number.isInteger(lonCode) || lonCode < 0) {
|
|
381
|
+
throw new Error(`lonCode must be a non-negative integer: ${lonCode}`);
|
|
382
|
+
}
|
|
383
|
+
if (!Number.isInteger(latCode) || latCode < 0 || latCode >= latCodeSpan) {
|
|
384
|
+
throw new Error(`latCode out of range for level ${level}: ${latCode}`);
|
|
317
385
|
}
|
|
386
|
+
return lonCode * latCodeSpan + latCode;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export function lonLatCodesFromGridCode(level, gridCode) {
|
|
390
|
+
if (!Number.isInteger(gridCode) || gridCode < 0) {
|
|
391
|
+
throw new Error(`gridCode must be a non-negative integer: ${gridCode}`);
|
|
392
|
+
}
|
|
393
|
+
const latCodeWidth = latCodeMagnitudeBits(level) + 1;
|
|
394
|
+
const latCodeSpan = 2 ** latCodeWidth;
|
|
318
395
|
return {
|
|
319
|
-
|
|
320
|
-
|
|
396
|
+
lonCode: Math.floor(gridCode / latCodeSpan),
|
|
397
|
+
latCode: gridCode % latCodeSpan,
|
|
321
398
|
};
|
|
322
399
|
}
|
|
323
400
|
|
|
324
|
-
export function
|
|
401
|
+
export function gridCodeFromGridex(level, gridex, latdex) {
|
|
402
|
+
const londex = typeof gridex === "object" ? gridex.londex : gridex;
|
|
403
|
+
const resolvedLatdex = typeof gridex === "object" ? gridex.latdex : latdex;
|
|
404
|
+
if (!Number.isInteger(londex) || !Number.isInteger(resolvedLatdex)) {
|
|
405
|
+
throw new Error("gridCodeFromGridex expects londex/latdex integers or a Gridex-like object");
|
|
406
|
+
}
|
|
407
|
+
return gridCodeFromLonLatCodes(
|
|
408
|
+
level,
|
|
409
|
+
lonCodeFromLondex(level, londex),
|
|
410
|
+
latCodeFromLatdex(level, resolvedLatdex),
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export function gridexFromGridCode(level, gridCode) {
|
|
415
|
+
const { lonCode, latCode } = lonLatCodesFromGridCode(level, gridCode);
|
|
416
|
+
const unit = new GridUnit(level);
|
|
417
|
+
return unit.makeGridex(
|
|
418
|
+
londexFromLonCode(level, lonCode),
|
|
419
|
+
latdexFromLatCode(level, latCode),
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export function gridCodeAtLonLat(level, longitude, latitude) {
|
|
424
|
+
const gridex = gridexAtLonLat(level, longitude, latitude);
|
|
425
|
+
return gridCodeFromGridex(level, gridex);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export function gridCodeComponentsAtLonLat(level, longitude, latitude) {
|
|
429
|
+
const gridex = gridexAtLonLat(level, longitude, latitude);
|
|
430
|
+
const lonCode = lonCodeFromLondex(level, gridex.londex);
|
|
431
|
+
const latCode = latCodeFromLatdex(level, gridex.latdex);
|
|
325
432
|
return {
|
|
326
|
-
|
|
327
|
-
|
|
433
|
+
gridex,
|
|
434
|
+
lonCode,
|
|
435
|
+
latCode,
|
|
436
|
+
gridCode: gridCodeFromLonLatCodes(level, lonCode, latCode),
|
|
328
437
|
};
|
|
329
438
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truelies/osm-dybuf",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"description": "Gridex OSM DyBuf parser and schema utilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./osm_dybuf.mjs",
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"import": "./grid_index.mjs",
|
|
20
20
|
"types": "./grid_index.d.ts"
|
|
21
21
|
},
|
|
22
|
+
"./collectible_types": {
|
|
23
|
+
"import": "./collectible_types.mjs",
|
|
24
|
+
"types": "./collectible_types.d.ts"
|
|
25
|
+
},
|
|
22
26
|
"./region_numeric_codes.json": "./region_numeric_codes.json"
|
|
23
27
|
},
|
|
24
28
|
"files": [
|
|
@@ -29,7 +33,9 @@
|
|
|
29
33
|
"region_numeric_codes.json",
|
|
30
34
|
"region_numeric_codes.js",
|
|
31
35
|
"grid_index.mjs",
|
|
32
|
-
"grid_index.d.ts"
|
|
36
|
+
"grid_index.d.ts",
|
|
37
|
+
"collectible_types.mjs",
|
|
38
|
+
"collectible_types.d.ts"
|
|
33
39
|
],
|
|
34
40
|
"keywords": [
|
|
35
41
|
"dybuf",
|