@osmix/core 0.0.1 → 0.0.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.
Files changed (107) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +118 -107
  3. package/dist/core/src/entities.d.ts +114 -0
  4. package/dist/core/src/entities.d.ts.map +1 -0
  5. package/dist/core/src/entities.js +152 -0
  6. package/dist/core/src/entities.js.map +1 -0
  7. package/dist/core/src/ids.d.ts +113 -0
  8. package/dist/core/src/ids.d.ts.map +1 -0
  9. package/dist/core/src/ids.js +236 -0
  10. package/dist/core/src/ids.js.map +1 -0
  11. package/dist/core/src/index.d.ts +32 -0
  12. package/dist/core/src/index.d.ts.map +1 -0
  13. package/dist/core/src/index.js +35 -0
  14. package/dist/core/src/index.js.map +1 -0
  15. package/dist/core/src/nodes.d.ts +101 -0
  16. package/dist/core/src/nodes.d.ts.map +1 -0
  17. package/dist/core/src/nodes.js +309 -0
  18. package/dist/core/src/nodes.js.map +1 -0
  19. package/dist/core/src/osm.d.ts +74 -0
  20. package/dist/core/src/osm.d.ts.map +1 -0
  21. package/dist/core/src/osm.js +120 -0
  22. package/dist/core/src/osm.js.map +1 -0
  23. package/dist/core/src/relations.d.ts +118 -0
  24. package/dist/core/src/relations.d.ts.map +1 -0
  25. package/dist/core/src/relations.js +412 -0
  26. package/dist/core/src/relations.js.map +1 -0
  27. package/dist/core/src/stringtable.d.ts +95 -0
  28. package/dist/core/src/stringtable.d.ts.map +1 -0
  29. package/dist/core/src/stringtable.js +170 -0
  30. package/dist/core/src/stringtable.js.map +1 -0
  31. package/dist/core/src/tags.d.ts +126 -0
  32. package/dist/core/src/tags.d.ts.map +1 -0
  33. package/dist/core/src/tags.js +228 -0
  34. package/dist/core/src/tags.js.map +1 -0
  35. package/dist/core/src/typed-arrays.d.ts +124 -0
  36. package/dist/core/src/typed-arrays.d.ts.map +1 -0
  37. package/dist/core/src/typed-arrays.js +198 -0
  38. package/dist/core/src/typed-arrays.js.map +1 -0
  39. package/dist/core/src/utils.d.ts +11 -0
  40. package/dist/core/src/utils.d.ts.map +1 -0
  41. package/dist/core/src/utils.js +26 -0
  42. package/dist/core/src/utils.js.map +1 -0
  43. package/dist/core/src/ways.d.ts +101 -0
  44. package/dist/core/src/ways.d.ts.map +1 -0
  45. package/dist/core/src/ways.js +336 -0
  46. package/dist/core/src/ways.js.map +1 -0
  47. package/dist/core/test/memory.test.d.ts +7 -0
  48. package/dist/core/test/memory.test.d.ts.map +1 -0
  49. package/dist/core/test/memory.test.js +29 -0
  50. package/dist/core/test/memory.test.js.map +1 -0
  51. package/dist/core/test/mock-osm.d.ts +13 -0
  52. package/dist/core/test/mock-osm.d.ts.map +1 -0
  53. package/dist/core/test/mock-osm.js +122 -0
  54. package/dist/core/test/mock-osm.js.map +1 -0
  55. package/dist/core/test/tags-search.test.d.ts +2 -0
  56. package/dist/core/test/tags-search.test.d.ts.map +1 -0
  57. package/dist/core/test/tags-search.test.js +56 -0
  58. package/dist/core/test/tags-search.test.js.map +1 -0
  59. package/dist/core/test/typed-arrays.test.d.ts +2 -0
  60. package/dist/core/test/typed-arrays.test.d.ts.map +1 -0
  61. package/dist/core/test/typed-arrays.test.js +102 -0
  62. package/dist/core/test/typed-arrays.test.js.map +1 -0
  63. package/dist/osmix/src/extract.d.ts +43 -0
  64. package/dist/osmix/src/extract.d.ts.map +1 -0
  65. package/dist/osmix/src/extract.js +156 -0
  66. package/dist/osmix/src/extract.js.map +1 -0
  67. package/dist/osmix/src/pbf.d.ts +55 -0
  68. package/dist/osmix/src/pbf.d.ts.map +1 -0
  69. package/dist/osmix/src/pbf.js +187 -0
  70. package/dist/osmix/src/pbf.js.map +1 -0
  71. package/dist/osmix/src/utils.d.ts +36 -0
  72. package/dist/osmix/src/utils.d.ts.map +1 -0
  73. package/dist/osmix/src/utils.js +108 -0
  74. package/dist/osmix/src/utils.js.map +1 -0
  75. package/dist/osmix/test/extract.bench.d.ts +2 -0
  76. package/dist/osmix/test/extract.bench.d.ts.map +1 -0
  77. package/dist/osmix/test/extract.bench.js +26 -0
  78. package/dist/osmix/test/extract.bench.js.map +1 -0
  79. package/dist/osmix/test/extract.test.d.ts +2 -0
  80. package/dist/osmix/test/extract.test.d.ts.map +1 -0
  81. package/dist/osmix/test/extract.test.js +392 -0
  82. package/dist/osmix/test/extract.test.js.map +1 -0
  83. package/package.json +11 -11
  84. package/src/entities.ts +138 -22
  85. package/src/ids.ts +117 -45
  86. package/src/index.ts +34 -5
  87. package/src/nodes.ts +237 -58
  88. package/src/osm.ts +174 -0
  89. package/src/relations.ts +383 -54
  90. package/src/stringtable.ts +111 -36
  91. package/src/tags.ts +207 -46
  92. package/src/typed-arrays.ts +97 -10
  93. package/src/types/geoflatbush.d.ts +37 -0
  94. package/src/types/geokdbush.d.ts +37 -0
  95. package/src/types/kdbush.d.ts +24 -6
  96. package/src/utils.ts +9 -14
  97. package/src/ways.ts +248 -84
  98. package/test/memory.test.ts +38 -0
  99. package/test/mock-osm.ts +8 -8
  100. package/test/tags-search.test.ts +60 -0
  101. package/test/typed-arrays.test.ts +5 -6
  102. package/tsconfig.json +7 -1
  103. package/src/osmix.ts +0 -556
  104. package/test/extract.bench.ts +0 -29
  105. package/test/extract.test.ts +0 -156
  106. package/test/read.test.ts +0 -22
  107. package/test/write.test.ts +0 -66
@@ -1,44 +1,76 @@
1
- import type { OsmPbfBlock, OsmPbfStringTable } from "@osmix/pbf"
2
- import { type BufferType, ResizeableTypedArray } from "./typed-arrays"
1
+ /**
2
+ * Deduplicated UTF-8 string storage.
3
+ *
4
+ * Strings are stored in a flat buffer and referenced by index.
5
+ * Supports fast deduplication (string→index) and lazy reconstruction (index→string).
6
+ *
7
+ * @module
8
+ */
3
9
 
4
- export interface StringTableTransferables {
5
- bytes: BufferType
6
- start: BufferType
7
- count: BufferType
10
+ import type { OsmPbfStringTable } from "@osmix/pbf"
11
+ import { type BufferType, ResizeableTypedArray as RTA } from "./typed-arrays"
12
+
13
+ /**
14
+ * Serializable state for worker transfer.
15
+ */
16
+ export interface StringTableTransferables<T extends BufferType = BufferType> {
17
+ /** Concatenated UTF-8 bytes. */
18
+ bytes: T
19
+ /** Maps string index → byte offset. */
20
+ start: T
21
+ /** Maps string index → byte length. */
22
+ count: T
8
23
  }
9
24
 
10
25
  /**
11
- * StringTable that can be used to store and retrieve strings as bytes.
26
+ * Append-only deduplicated string table.
27
+ *
28
+ * Limits: Max string length 65,535 bytes.
29
+ * Rebuilds reverse index lazily after transfer.
12
30
  */
13
31
  export default class StringTable {
32
+ /** UTF-8 encoder for string→bytes conversion */
14
33
  private enc = new TextEncoder()
34
+ /** UTF-8 decoder for bytes→string conversion */
15
35
  private dec = new TextDecoder() // UTF-8
16
36
 
17
- // Serializable state
18
- private bytes: ResizeableTypedArray<Uint8Array>
19
- private start: ResizeableTypedArray<Uint32Array>
20
- private count: ResizeableTypedArray<Uint16Array>
37
+ // ─── Serializable State ────────────────────────────────────────────────────
38
+ /** Concatenated UTF-8 bytes of all strings */
39
+ bytes: RTA<Uint8Array>
40
+ /** Maps string index → byte offset in bytes array */
41
+ start: RTA<Uint32Array>
42
+ /** Maps string index → byte length */
43
+ count: RTA<Uint16Array>
21
44
 
22
- // Builder state
45
+ // ─── Builder State (string → index) ────────────────────────────────────────
46
+ /** Forward lookup: string → index (populated during add) */
23
47
  private stringToIndex = new Map<string, number>()
24
48
 
25
- // Retrieval state
26
- private indexToString = new Map<number, string>()
49
+ /** Whether the reverse index has been built (lazy after transfer) */
50
+ private reverseIndexBuilt = false
27
51
 
28
- static from({ bytes, start, count }: StringTableTransferables): StringTable {
29
- const builder = new StringTable()
30
- builder.bytes = ResizeableTypedArray.from(Uint8Array, bytes)
31
- builder.start = ResizeableTypedArray.from(Uint32Array, start)
32
- builder.count = ResizeableTypedArray.from(Uint16Array, count)
33
- return builder
34
- }
52
+ // ─── Retrieval Cache (index → string) ──────────────────────────────────────
53
+ /** Cache of decoded strings to avoid repeated UTF-8 decoding */
54
+ private indexToString = new Map<number, string>()
35
55
 
36
- constructor() {
37
- this.bytes = new ResizeableTypedArray(Uint8Array)
38
- this.start = new ResizeableTypedArray(Uint32Array)
39
- this.count = new ResizeableTypedArray(Uint16Array)
56
+ /**
57
+ * Create a new StringTable.
58
+ */
59
+ constructor(opts?: StringTableTransferables) {
60
+ this.bytes = opts?.bytes
61
+ ? RTA.from(Uint8Array, opts.bytes)
62
+ : new RTA(Uint8Array)
63
+ this.start = opts?.start
64
+ ? RTA.from(Uint32Array, opts.start)
65
+ : new RTA(Uint32Array)
66
+ this.count = opts?.count
67
+ ? RTA.from(Uint16Array, opts.count)
68
+ : new RTA(Uint16Array)
40
69
  }
41
70
 
71
+ /**
72
+ * Get transferable objects for passing to another thread.
73
+ */
42
74
  transferables(): StringTableTransferables {
43
75
  return {
44
76
  bytes: this.bytes.array.buffer,
@@ -47,6 +79,9 @@ export default class StringTable {
47
79
  }
48
80
  }
49
81
 
82
+ /**
83
+ * Add a string to the table and return its index.
84
+ */
50
85
  add(str: string): number {
51
86
  const existingIndex = this.stringToIndex.get(str)
52
87
  if (existingIndex !== undefined) return existingIndex
@@ -63,19 +98,24 @@ export default class StringTable {
63
98
  * Decode all the strings in a primitive block and add them to the string table.
64
99
  * Return a mapping of block index -> string table index
65
100
  */
66
- createBlockIndexMap(block: OsmPbfBlock) {
67
- const map = new Map<number, number>()
68
- for (let i = 0; i < block.stringtable.length; i++) {
69
- const bytesString = block.stringtable[i]
101
+ createBlockIndexMap(blockStringtable: OsmPbfStringTable) {
102
+ const index = new Uint32Array(blockStringtable.length)
103
+ for (let i = 0; i < blockStringtable.length; i++) {
104
+ const bytesString = blockStringtable[i]
70
105
  const str = this.dec.decode(bytesString)
71
- const index = this.add(str)
72
- map.set(i, index)
106
+ const existingIndex = this.stringToIndex.get(str)
107
+ if (existingIndex !== undefined) {
108
+ index[i] = existingIndex
109
+ continue
110
+ }
111
+ index[i] = this.add(str)
73
112
  }
74
- return map
113
+ return index
75
114
  }
76
115
 
77
116
  /**
78
- * TextDecoder.decode() does not support SharedArrayBuffer, so we need to first copy to a normal ArrayBuffer before decoding.
117
+ * Get a string by its index.
118
+ * Caches results to avoid repeated UTF-8 decoding.
79
119
  */
80
120
  get(index: number): string {
81
121
  const string = this.indexToString.get(index)
@@ -89,27 +129,39 @@ export default class StringTable {
89
129
  return decoded
90
130
  }
91
131
 
132
+ /**
133
+ * Get the raw UTF-8 bytes of a string.
134
+ * Returns a subarray view (not a copy).
135
+ */
92
136
  getBytes(index: number): Uint8Array {
93
137
  if (index < 0 || index >= this.length)
94
138
  throw Error(`String index out of range: ${index}`)
95
139
  const start = this.start.at(index)
96
140
  const count = this.count.at(index)
97
- // Important: subarray creates a *view*, not a copy; TextDecoder cannot read from it directly.
98
141
  return this.bytes.array.subarray(start, start + count)
99
142
  }
100
143
 
144
+ /** Number of strings in the table. */
101
145
  get length() {
102
146
  return this.start.length
103
147
  }
104
148
 
149
+ /**
150
+ * Finalize the string table by compacting internal arrays.
151
+ *
152
+ * This releases unused buffer capacity and marks the reverse index as built
153
+ * (since all strings were added before this call).
154
+ */
105
155
  buildIndex() {
106
156
  this.bytes.compact()
107
157
  this.start.compact()
108
158
  this.count.compact()
109
-
110
- this.stringToIndex.clear()
159
+ this.reverseIndexBuilt = true
111
160
  }
112
161
 
162
+ /**
163
+ * Convert the string table to an OSM PBF string table.
164
+ */
113
165
  toOsmPbfStringTable(): OsmPbfStringTable {
114
166
  const stringTable: OsmPbfStringTable = []
115
167
  for (let i = 0; i < this.length; i++) {
@@ -117,4 +169,27 @@ export default class StringTable {
117
169
  }
118
170
  return stringTable
119
171
  }
172
+
173
+ /**
174
+ * Lazily build the reverse index (string → index).
175
+ * Decodes all strings once to populate the lookup map.
176
+ */
177
+ private ensureReverseIndex() {
178
+ if (this.reverseIndexBuilt) return
179
+ // Decode all strings and populate the forward lookup map
180
+ for (let i = 0; i < this.length; i++) {
181
+ this.stringToIndex.set(this.get(i), i)
182
+ }
183
+ this.reverseIndexBuilt = true
184
+ }
185
+
186
+ /**
187
+ * Find the index of a string.
188
+ */
189
+ find(str: string): number {
190
+ const existing = this.stringToIndex.get(str)
191
+ if (existing !== undefined) return existing
192
+ this.ensureReverseIndex()
193
+ return this.stringToIndex.get(str) ?? -1
194
+ }
120
195
  }
package/src/tags.ts CHANGED
@@ -1,54 +1,108 @@
1
- import type { OsmTags } from "@osmix/json"
2
- import type StringTable from "./stringtable"
3
- import { type BufferType, ResizeableTypedArray } from "./typed-arrays"
4
-
5
- export interface TagsTransferables {
6
- tagStart: BufferType
7
- tagCount: BufferType
8
- tagKeys: BufferType
9
- tagVals: BufferType
1
+ /**
2
+ * Tag storage and lookup.
3
+ *
4
+ * Stores key=value pairs using string table indices. Supports:
5
+ * 1. **Entity → Tags**: Retrieve tags for a given entity.
6
+ * 2. **Key → Entities**: Find entities with a specific tag key.
7
+ *
8
+ * @module
9
+ */
10
+
11
+ import type { OsmTags } from "@osmix/shared/types"
12
+ import StringTable from "./stringtable"
13
+ import { type BufferType, ResizeableTypedArray as RTA } from "./typed-arrays"
14
+
15
+ /**
16
+ * Serializable state for worker transfer.
17
+ */
18
+ export interface TagsTransferables<T extends BufferType = BufferType> {
19
+ /** Maps entity index → start position in tagKeys/tagVals. */
20
+ tagStart: T
21
+ /** Maps entity index → number of tags. */
22
+ tagCount: T
23
+ /** Flattened tag key indices. */
24
+ tagKeys: T
25
+ /** Flattened tag value indices. */
26
+ tagVals: T
27
+
28
+ /** Flattened entity indices for reverse key lookup. */
29
+ keyEntities: T
30
+ /** Maps key index → start position in keyEntities. */
31
+ keyIndexStart: T
32
+ /** Maps key index → count of entities with that key. */
33
+ keyIndexCount: T
10
34
  }
11
35
 
36
+ /**
37
+ * Bidirectional tag storage.
38
+ *
39
+ * Limits: Max 255 tags per entity.
40
+ * Note: String indices reference a shared `StringTable`.
41
+ */
12
42
  export class Tags {
13
- private stringTable: StringTable
14
- private tagStart: ResizeableTypedArray<Uint32Array>
15
- private tagCount: ResizeableTypedArray<Uint8Array>
16
- private tagKeys: ResizeableTypedArray<Uint32Array>
17
- private tagVals: ResizeableTypedArray<Uint32Array>
43
+ /** Reference to the shared string table for key/value storage */
44
+ private stringTable: StringTable = new StringTable()
18
45
 
19
- private indexBuilt = false
46
+ // ─── Entity → Tag Lookup ───────────────────────────────────────────────────
47
+ /** Maps entity index → start position in tagKeys/tagVals */
48
+ private tagStart: RTA<Uint32Array>
49
+ /** Maps entity index → number of tags (max 255) */
50
+ private tagCount: RTA<Uint8Array>
51
+ /** All tag key string indices, concatenated */
52
+ private tagKeys: RTA<Uint32Array>
53
+ /** All tag value string indices, concatenated (parallel to tagKeys) */
54
+ private tagVals: RTA<Uint32Array>
20
55
 
21
- static from(
22
- stringTable: StringTable,
23
- { tagStart, tagCount, tagKeys, tagVals }: TagsTransferables,
24
- ) {
25
- const tagIndex = new Tags(stringTable)
26
- tagIndex.tagStart = ResizeableTypedArray.from(Uint32Array, tagStart)
27
- tagIndex.tagCount = ResizeableTypedArray.from(Uint8Array, tagCount)
28
- tagIndex.tagKeys = ResizeableTypedArray.from(Uint32Array, tagKeys)
29
- tagIndex.tagVals = ResizeableTypedArray.from(Uint32Array, tagVals)
30
- tagIndex.indexBuilt = true
31
- return tagIndex
32
- }
56
+ // ─── Key → Entity Reverse Lookup ───────────────────────────────────────────
57
+ /**
58
+ * Flattened array of entity indices that have each tag key.
59
+ * Indexed via keyIndexStart and keyIndexCount.
60
+ */
61
+ private keyEntities: RTA<Uint32Array>
33
62
 
34
- constructor(stringTable: StringTable) {
35
- this.stringTable = stringTable
36
- this.tagStart = new ResizeableTypedArray(Uint32Array)
37
- this.tagCount = new ResizeableTypedArray(Uint8Array)
38
- this.tagKeys = new ResizeableTypedArray(Uint32Array)
39
- this.tagVals = new ResizeableTypedArray(Uint32Array)
40
- }
63
+ /** Maps key string index → start position in keyEntities */
64
+ private keyIndexStart: RTA<Uint32Array>
65
+ /** Maps key string index → count of entities with that key */
66
+ private keyIndexCount: RTA<Uint32Array>
41
67
 
42
- transferables() {
43
- return {
44
- tagStart: this.tagStart.array.buffer,
45
- tagCount: this.tagCount.array.buffer,
46
- tagKeys: this.tagKeys.array.buffer,
47
- tagVals: this.tagVals.array.buffer,
68
+ /**
69
+ * Temporary map used during ingestion to collect entity indices per key.
70
+ * Converted to flat arrays during buildIndex() and then cleared.
71
+ */
72
+ private keyEntityIndexBuilder = new Map<number, number[]>()
73
+
74
+ /** Whether buildIndex() has been called */
75
+ private indexBuilt = false
76
+
77
+ /**
78
+ * Create a new Tags index.
79
+ */
80
+ constructor(stringTable: StringTable, transferables?: TagsTransferables) {
81
+ this.stringTable = stringTable
82
+ if (transferables) {
83
+ this.tagStart = RTA.from(Uint32Array, transferables.tagStart)
84
+ this.tagCount = RTA.from(Uint8Array, transferables.tagCount)
85
+ this.tagKeys = RTA.from(Uint32Array, transferables.tagKeys)
86
+ this.tagVals = RTA.from(Uint32Array, transferables.tagVals)
87
+ this.keyEntities = RTA.from(Uint32Array, transferables.keyEntities)
88
+ this.keyIndexStart = RTA.from(Uint32Array, transferables.keyIndexStart)
89
+ this.keyIndexCount = RTA.from(Uint32Array, transferables.keyIndexCount)
90
+ this.indexBuilt = true
91
+ } else {
92
+ this.tagStart = new RTA(Uint32Array)
93
+ this.tagCount = new RTA(Uint8Array)
94
+ this.tagKeys = new RTA(Uint32Array)
95
+ this.tagVals = new RTA(Uint32Array)
96
+ this.keyEntities = new RTA(Uint32Array)
97
+ this.keyIndexStart = new RTA(Uint32Array)
98
+ this.keyIndexCount = new RTA(Uint32Array)
48
99
  }
49
100
  }
50
101
 
51
- addTags(tags?: OsmTags) {
102
+ /**
103
+ * Add tags to an entity.
104
+ */
105
+ addTags(index: number, tags?: OsmTags): [number[], number[]] {
52
106
  const tagKeys: number[] = []
53
107
  const tagValues: number[] = []
54
108
 
@@ -59,32 +113,73 @@ export class Tags {
59
113
  }
60
114
  }
61
115
 
62
- this.addTagKeysAndValues(tagKeys, tagValues)
116
+ this.addTagKeysAndValues(index, tagKeys, tagValues)
117
+
118
+ return [tagKeys, tagValues]
63
119
  }
64
120
 
65
- addTagKeysAndValues(keys: number[], values: number[]) {
66
- this.tagStart.push(this.tagKeys.length)
67
- this.tagCount.push(keys.length)
121
+ /**
122
+ * Add tags to an entity using key and value indexes.
123
+ */
124
+ addTagKeysAndValues(index: number, keys: number[], values: number[]) {
125
+ this.tagStart.set(index, this.tagKeys.length)
126
+ this.tagCount.set(index, keys.length)
68
127
  this.tagKeys.pushMany(keys)
69
128
  this.tagVals.pushMany(values)
129
+
130
+ keys.forEach((key) => {
131
+ const keyEntities = this.keyEntityIndexBuilder.get(key)
132
+ if (keyEntities) {
133
+ keyEntities.push(index)
134
+ } else {
135
+ this.keyEntityIndexBuilder.set(key, [index])
136
+ }
137
+ })
70
138
  }
71
139
 
140
+ /**
141
+ * Finalize the tag index.
142
+ *
143
+ * Compacts arrays and builds the reverse key→entity index.
144
+ * Must be called before `hasKey()`.
145
+ */
72
146
  buildIndex() {
73
147
  this.tagStart.compact()
74
148
  this.tagCount.compact()
75
149
  this.tagKeys.compact()
76
150
  this.tagVals.compact()
151
+
152
+ // Convert the builder map to flat arrays for the reverse index
153
+ for (const [keyIndex, entityIndexes] of this.keyEntityIndexBuilder) {
154
+ this.keyIndexStart.set(keyIndex, this.keyEntities.length)
155
+ this.keyIndexCount.set(keyIndex, entityIndexes.length)
156
+ this.keyEntities.pushMany(entityIndexes)
157
+ }
158
+
159
+ this.keyIndexStart.compact()
160
+ this.keyIndexCount.compact()
161
+ this.keyEntityIndexBuilder.clear()
162
+
77
163
  this.indexBuilt = true
78
164
  }
79
165
 
80
- get isReady() {
166
+ /**
167
+ * Check if the index is built and ready for use.
168
+ */
169
+ isReady() {
81
170
  return this.indexBuilt
82
171
  }
83
172
 
173
+ /**
174
+ * Get the number of tags for an entity.
175
+ */
84
176
  cardinality(index: number): number {
85
177
  return this.tagCount.at(index) ?? 0
86
178
  }
87
179
 
180
+ /**
181
+ * Get the tags for an entity.
182
+ */
88
183
  getTags(index: number): OsmTags | undefined {
89
184
  const tagCount = this.tagCount.at(index) ?? 0
90
185
  if (tagCount === 0) return
@@ -108,6 +203,9 @@ export class Tags {
108
203
  return tags
109
204
  }
110
205
 
206
+ /**
207
+ * Get tags from key and value indexes.
208
+ */
111
209
  getTagsFromIndices(keys: number[], values: number[]): OsmTags {
112
210
  const tags: OsmTags = {}
113
211
  for (let i = 0; i < keys.length; i++) {
@@ -119,4 +217,67 @@ export class Tags {
119
217
  }
120
218
  return tags
121
219
  }
220
+
221
+ /**
222
+ * Find the index of a tag key.
223
+ */
224
+ find(key: string): number {
225
+ return this.stringTable.find(key)
226
+ }
227
+
228
+ /**
229
+ * Get all entity indexes that have a specific tag key.
230
+ */
231
+ hasKey(keyIndex: number): number[] {
232
+ if (keyIndex < 0) return []
233
+ const start = this.keyIndexStart.at(keyIndex) ?? 0
234
+ const count = this.keyIndexCount.at(keyIndex) ?? 0
235
+ return Array.from(this.keyEntities.array.subarray(start, start + count))
236
+ }
237
+
238
+ /**
239
+ * Create a unique composite index for a key=value pair.
240
+ * Uses row-major indexing: `key * width + val`.
241
+ */
242
+ kvToIndex(key: number, val: number) {
243
+ const width = this.stringTable.length
244
+ return key * width + val
245
+ }
246
+
247
+ /**
248
+ * Get transferable objects for passing to another thread.
249
+ */
250
+ transferables(): TagsTransferables {
251
+ return {
252
+ tagStart: this.tagStart.array.buffer,
253
+ tagCount: this.tagCount.array.buffer,
254
+ tagKeys: this.tagKeys.array.buffer,
255
+ tagVals: this.tagVals.array.buffer,
256
+ keyEntities: this.keyEntities.array.buffer,
257
+ keyIndexStart: this.keyIndexStart.array.buffer,
258
+ keyIndexCount: this.keyIndexCount.array.buffer,
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Get the approximate memory requirements for a given number of tags in bytes.
264
+ */
265
+ static getBytesRequired(count: number) {
266
+ return (
267
+ count * Uint32Array.BYTES_PER_ELEMENT + // tagStart
268
+ count * Uint8Array.BYTES_PER_ELEMENT // tagCount
269
+ )
270
+ }
271
+
272
+ /**
273
+ * Reconstruct a Tags index from transferable objects.
274
+ */
275
+ static fromTransferables(
276
+ stringTable: StringTable,
277
+ transferables: TagsTransferables,
278
+ ) {
279
+ const tagIndex = new Tags(stringTable, transferables)
280
+ tagIndex.indexBuilt = true
281
+ return tagIndex
282
+ }
122
283
  }