@osmix/core 0.0.1

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.
@@ -0,0 +1,221 @@
1
+ import type {
2
+ OsmEntityType,
3
+ OsmRelation,
4
+ OsmRelationMember,
5
+ OsmTags,
6
+ } from "@osmix/json"
7
+ import type { OsmPbfRelation } from "@osmix/pbf"
8
+ import { assertValue } from "@osmix/shared/assert"
9
+ import { Entities, type EntitiesTransferables } from "./entities"
10
+ import { Ids } from "./ids"
11
+ import type StringTable from "./stringtable"
12
+ import { Tags } from "./tags"
13
+ import {
14
+ type BufferType,
15
+ IdArrayType,
16
+ ResizeableTypedArray,
17
+ } from "./typed-arrays"
18
+
19
+ const RELATION_MEMBER_TYPES: OsmEntityType[] = ["node", "way", "relation"]
20
+
21
+ export interface RelationsTransferables extends EntitiesTransferables {
22
+ memberStart: BufferType
23
+ memberCount: BufferType
24
+ memberRefs: BufferType
25
+ memberTypes: BufferType
26
+ memberRoles: BufferType
27
+ }
28
+
29
+ export class Relations extends Entities<OsmRelation> {
30
+ memberStart: ResizeableTypedArray<Uint32Array>
31
+ memberCount: ResizeableTypedArray<Uint16Array> // Maximum 65,535 members per relation
32
+
33
+ // Store the ID of the member because relations have other relations as members.
34
+ memberRefs: ResizeableTypedArray<Float64Array>
35
+ memberTypes: ResizeableTypedArray<Uint8Array>
36
+ memberRoles: ResizeableTypedArray<Uint32Array>
37
+
38
+ static from(stringTable: StringTable, rit: RelationsTransferables) {
39
+ const idIndex = Ids.from(rit)
40
+ const tagIndex = Tags.from(stringTable, rit)
41
+ const ri = new Relations(stringTable, idIndex, tagIndex)
42
+ ri.memberStart = ResizeableTypedArray.from(Uint32Array, rit.memberStart)
43
+ ri.memberCount = ResizeableTypedArray.from(Uint16Array, rit.memberCount)
44
+ ri.memberRefs = ResizeableTypedArray.from(IdArrayType, rit.memberRefs)
45
+ ri.memberTypes = ResizeableTypedArray.from(Uint8Array, rit.memberTypes)
46
+ ri.memberRoles = ResizeableTypedArray.from(Uint32Array, rit.memberRoles)
47
+ return ri
48
+ }
49
+
50
+ constructor(stringTable: StringTable, idIndex?: Ids, tagIndex?: Tags) {
51
+ super("relation", stringTable, idIndex, tagIndex)
52
+ this.memberStart = new ResizeableTypedArray(Uint32Array)
53
+ this.memberCount = new ResizeableTypedArray(Uint16Array)
54
+ this.memberRefs = new ResizeableTypedArray(IdArrayType)
55
+ this.memberTypes = new ResizeableTypedArray(Uint8Array)
56
+ this.memberRoles = new ResizeableTypedArray(Uint32Array)
57
+ }
58
+
59
+ transferables(): RelationsTransferables {
60
+ return {
61
+ memberStart: this.memberStart.array.buffer,
62
+ memberCount: this.memberCount.array.buffer,
63
+ memberRefs: this.memberRefs.array.buffer,
64
+ memberTypes: this.memberTypes.array.buffer,
65
+ memberRoles: this.memberRoles.array.buffer,
66
+ ...this.ids.transferables(),
67
+ ...this.tags.transferables(),
68
+ }
69
+ }
70
+
71
+ addRelation(relation: OsmRelation) {
72
+ this.ids.add(relation.id)
73
+ this.tags.addTags(relation.tags)
74
+ this.memberStart.push(this.memberRefs.length)
75
+ this.memberCount.push(relation.members.length)
76
+ for (const member of relation.members) {
77
+ this.memberRefs.push(member.ref)
78
+ this.memberTypes.push(RELATION_MEMBER_TYPES.indexOf(member.type))
79
+ this.memberRoles.push(this.stringTable.add(member.role ?? ""))
80
+ }
81
+ }
82
+
83
+ addRelations(
84
+ relations: OsmPbfRelation[],
85
+ blockStringIndexMap: Map<number, number>,
86
+ filter?: (relation: OsmRelation) => OsmRelation | null,
87
+ ): number {
88
+ const blockToStringTable = (k: number) => {
89
+ const index = blockStringIndexMap.get(k)
90
+ if (index === undefined) throw Error("Tag key not found")
91
+ return index
92
+ }
93
+
94
+ let added = 0
95
+ for (const relation of relations) {
96
+ const members: OsmRelationMember[] = []
97
+ const memberRefs: number[] = []
98
+ const memberTypes: number[] = []
99
+ const memberRoles: number[] = []
100
+
101
+ let refId = 0
102
+ for (let i = 0; i < relation.memids.length; i++) {
103
+ const memid = relation.memids[i]
104
+ const roleSid = relation.roles_sid[i]
105
+ const typeIndex = relation.types[i]
106
+ assertValue(memid, "Relation member ID is undefined")
107
+ assertValue(roleSid, "Relation member role SID is undefined")
108
+ assertValue(typeIndex, "Relation member type is undefined")
109
+
110
+ refId += memid
111
+ const roleIndex = blockToStringTable(roleSid)
112
+ const type = RELATION_MEMBER_TYPES[typeIndex]
113
+ assertValue(type, "Relation member type not found")
114
+
115
+ if (filter) {
116
+ members.push({
117
+ type,
118
+ ref: refId,
119
+ role: this.stringTable.get(roleIndex),
120
+ })
121
+ }
122
+
123
+ memberRefs.push(refId)
124
+ memberTypes.push(typeIndex)
125
+ memberRoles.push(roleIndex)
126
+ }
127
+
128
+ const tagKeys: number[] = relation.keys.map(blockToStringTable)
129
+ const tagValues: number[] = relation.vals.map(blockToStringTable)
130
+
131
+ const filteredRelation = filter
132
+ ? filter({
133
+ id: relation.id,
134
+ members,
135
+ tags: this.tags.getTagsFromIndices(tagKeys, tagValues),
136
+ })
137
+ : null
138
+ if (filter && filteredRelation === null) continue
139
+ added++
140
+
141
+ this.ids.add(relation.id)
142
+ this.memberStart.push(this.memberRefs.length)
143
+ this.memberCount.push(
144
+ filteredRelation?.members.length ?? memberRefs.length,
145
+ )
146
+ this.memberRefs.pushMany(
147
+ filteredRelation?.members.map((m) => m.ref) ?? memberRefs,
148
+ )
149
+ this.memberTypes.pushMany(
150
+ filteredRelation?.members.map((m) =>
151
+ RELATION_MEMBER_TYPES.indexOf(m.type),
152
+ ) ?? memberTypes,
153
+ )
154
+ this.memberRoles.pushMany(
155
+ filteredRelation?.members.map((m) =>
156
+ this.stringTable.add(m.role ?? ""),
157
+ ) ?? memberRoles,
158
+ )
159
+
160
+ this.tags.addTagKeysAndValues(tagKeys, tagValues)
161
+ }
162
+ return added
163
+ }
164
+
165
+ buildEntityIndex() {
166
+ this.memberStart.compact()
167
+ this.memberCount.compact()
168
+ this.memberRefs.compact()
169
+ this.memberTypes.compact()
170
+ this.memberRoles.compact()
171
+ }
172
+
173
+ getFullEntity(index: number, id: number, tags?: OsmTags): OsmRelation {
174
+ return {
175
+ id,
176
+ members: this.getMembersByIndex(index),
177
+ tags,
178
+ }
179
+ }
180
+
181
+ getMembersByIndex(
182
+ index: number,
183
+ relationMemberTypes = RELATION_MEMBER_TYPES,
184
+ ) {
185
+ const start = this.memberStart.at(index)
186
+ const count = this.memberCount.at(index)
187
+ const members: OsmRelationMember[] = []
188
+ for (let i = start; i < start + count; i++) {
189
+ const ref = this.memberRefs.at(i)
190
+ const type = RELATION_MEMBER_TYPES[this.memberTypes.at(i)]
191
+ if (type === undefined) throw Error(`Member type not found: ${i}`)
192
+ if (!relationMemberTypes.includes(type)) continue
193
+ const role = this.stringTable.get(this.memberRoles.at(i))
194
+ members.push({ ref, type, role })
195
+ }
196
+ return members
197
+ }
198
+
199
+ includesMember(
200
+ index: number,
201
+ memberRef: number,
202
+ memberType: OsmEntityType,
203
+ memberRole?: string,
204
+ ) {
205
+ const start = this.memberStart.at(index)
206
+ const count = this.memberCount.at(index)
207
+ for (let i = start; i < start + count; i++) {
208
+ const type = RELATION_MEMBER_TYPES[this.memberTypes.at(i)]
209
+ if (type !== memberType) continue
210
+ const ref = this.memberRefs.at(i)
211
+ if (ref !== memberRef) continue
212
+ if (
213
+ memberRole !== undefined &&
214
+ this.stringTable.get(this.memberRoles.at(i)) !== memberRole
215
+ )
216
+ continue
217
+ return true
218
+ }
219
+ return false
220
+ }
221
+ }
@@ -0,0 +1,120 @@
1
+ import type { OsmPbfBlock, OsmPbfStringTable } from "@osmix/pbf"
2
+ import { type BufferType, ResizeableTypedArray } from "./typed-arrays"
3
+
4
+ export interface StringTableTransferables {
5
+ bytes: BufferType
6
+ start: BufferType
7
+ count: BufferType
8
+ }
9
+
10
+ /**
11
+ * StringTable that can be used to store and retrieve strings as bytes.
12
+ */
13
+ export default class StringTable {
14
+ private enc = new TextEncoder()
15
+ private dec = new TextDecoder() // UTF-8
16
+
17
+ // Serializable state
18
+ private bytes: ResizeableTypedArray<Uint8Array>
19
+ private start: ResizeableTypedArray<Uint32Array>
20
+ private count: ResizeableTypedArray<Uint16Array>
21
+
22
+ // Builder state
23
+ private stringToIndex = new Map<string, number>()
24
+
25
+ // Retrieval state
26
+ private indexToString = new Map<number, string>()
27
+
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
+ }
35
+
36
+ constructor() {
37
+ this.bytes = new ResizeableTypedArray(Uint8Array)
38
+ this.start = new ResizeableTypedArray(Uint32Array)
39
+ this.count = new ResizeableTypedArray(Uint16Array)
40
+ }
41
+
42
+ transferables(): StringTableTransferables {
43
+ return {
44
+ bytes: this.bytes.array.buffer,
45
+ start: this.start.array.buffer,
46
+ count: this.count.array.buffer,
47
+ }
48
+ }
49
+
50
+ add(str: string): number {
51
+ const existingIndex = this.stringToIndex.get(str)
52
+ if (existingIndex !== undefined) return existingIndex
53
+ const startIndex = this.start.length
54
+ const encoded = this.enc.encode(str)
55
+ this.start.push(this.bytes.length)
56
+ this.bytes.pushMany(encoded)
57
+ this.count.push(encoded.length)
58
+ this.stringToIndex.set(str, startIndex)
59
+ return startIndex
60
+ }
61
+
62
+ /**
63
+ * Decode all the strings in a primitive block and add them to the string table.
64
+ * Return a mapping of block index -> string table index
65
+ */
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]
70
+ const str = this.dec.decode(bytesString)
71
+ const index = this.add(str)
72
+ map.set(i, index)
73
+ }
74
+ return map
75
+ }
76
+
77
+ /**
78
+ * TextDecoder.decode() does not support SharedArrayBuffer, so we need to first copy to a normal ArrayBuffer before decoding.
79
+ */
80
+ get(index: number): string {
81
+ const string = this.indexToString.get(index)
82
+ if (string) return string
83
+ const bytes = this.getBytes(index)
84
+ const tempBuffer = new ArrayBuffer(bytes.byteLength)
85
+ const tempBytes = new Uint8Array(tempBuffer)
86
+ tempBytes.set(bytes)
87
+ const decoded = this.dec.decode(tempBytes)
88
+ this.indexToString.set(index, decoded)
89
+ return decoded
90
+ }
91
+
92
+ getBytes(index: number): Uint8Array {
93
+ if (index < 0 || index >= this.length)
94
+ throw Error(`String index out of range: ${index}`)
95
+ const start = this.start.at(index)
96
+ const count = this.count.at(index)
97
+ // Important: subarray creates a *view*, not a copy; TextDecoder cannot read from it directly.
98
+ return this.bytes.array.subarray(start, start + count)
99
+ }
100
+
101
+ get length() {
102
+ return this.start.length
103
+ }
104
+
105
+ buildIndex() {
106
+ this.bytes.compact()
107
+ this.start.compact()
108
+ this.count.compact()
109
+
110
+ this.stringToIndex.clear()
111
+ }
112
+
113
+ toOsmPbfStringTable(): OsmPbfStringTable {
114
+ const stringTable: OsmPbfStringTable = []
115
+ for (let i = 0; i < this.length; i++) {
116
+ stringTable.push(this.getBytes(i))
117
+ }
118
+ return stringTable
119
+ }
120
+ }
package/src/tags.ts ADDED
@@ -0,0 +1,122 @@
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
10
+ }
11
+
12
+ 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>
18
+
19
+ private indexBuilt = false
20
+
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
+ }
33
+
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
+ }
41
+
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,
48
+ }
49
+ }
50
+
51
+ addTags(tags?: OsmTags) {
52
+ const tagKeys: number[] = []
53
+ const tagValues: number[] = []
54
+
55
+ if (tags) {
56
+ for (const [key, value] of Object.entries(tags)) {
57
+ tagKeys.push(this.stringTable.add(key))
58
+ tagValues.push(this.stringTable.add(String(value)))
59
+ }
60
+ }
61
+
62
+ this.addTagKeysAndValues(tagKeys, tagValues)
63
+ }
64
+
65
+ addTagKeysAndValues(keys: number[], values: number[]) {
66
+ this.tagStart.push(this.tagKeys.length)
67
+ this.tagCount.push(keys.length)
68
+ this.tagKeys.pushMany(keys)
69
+ this.tagVals.pushMany(values)
70
+ }
71
+
72
+ buildIndex() {
73
+ this.tagStart.compact()
74
+ this.tagCount.compact()
75
+ this.tagKeys.compact()
76
+ this.tagVals.compact()
77
+ this.indexBuilt = true
78
+ }
79
+
80
+ get isReady() {
81
+ return this.indexBuilt
82
+ }
83
+
84
+ cardinality(index: number): number {
85
+ return this.tagCount.at(index) ?? 0
86
+ }
87
+
88
+ getTags(index: number): OsmTags | undefined {
89
+ const tagCount = this.tagCount.at(index) ?? 0
90
+ if (tagCount === 0) return
91
+ const tagStart = this.tagStart.at(index) ?? 0
92
+ const tagKeyIndexes = this.tagKeys.array.slice(
93
+ tagStart,
94
+ tagStart + tagCount,
95
+ )
96
+ const tagValIndexes = this.tagVals.array.slice(
97
+ tagStart,
98
+ tagStart + tagCount,
99
+ )
100
+ const tags: OsmTags = {}
101
+ for (let i = 0; i < tagCount; i++) {
102
+ const keyIndex = tagKeyIndexes[i]
103
+ const valIndex = tagValIndexes[i]
104
+ if (keyIndex === undefined || valIndex === undefined)
105
+ throw Error("Tag key or value not found")
106
+ tags[this.stringTable.get(keyIndex)] = this.stringTable.get(valIndex)
107
+ }
108
+ return tags
109
+ }
110
+
111
+ getTagsFromIndices(keys: number[], values: number[]): OsmTags {
112
+ const tags: OsmTags = {}
113
+ for (let i = 0; i < keys.length; i++) {
114
+ const keyIndex = keys[i]
115
+ const valIndex = values[i]
116
+ if (keyIndex === undefined || valIndex === undefined)
117
+ throw Error("Tag key or value not found")
118
+ tags[this.stringTable.get(keyIndex)] = this.stringTable.get(valIndex)
119
+ }
120
+ return tags
121
+ }
122
+ }
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Use SharedArrayBuffer if the runtime supports it, otherwise fall back to ArrayBuffer.
3
+ */
4
+ export const BufferConstructor =
5
+ typeof SharedArrayBuffer !== "undefined"
6
+ ? (SharedArrayBuffer as SharedArrayBufferConstructor)
7
+ : (ArrayBuffer as ArrayBufferConstructor)
8
+ export type BufferType = InstanceType<typeof BufferConstructor>
9
+
10
+ export type TypedArray<B extends BufferType = BufferType> =
11
+ | Int8Array<B>
12
+ | Uint8Array<B>
13
+ | Uint8ClampedArray<B>
14
+ | Int16Array<B>
15
+ | Uint16Array<B>
16
+ | Int32Array<B>
17
+ | Uint32Array<B>
18
+ | Float32Array<B>
19
+ | Float64Array<B>
20
+
21
+ export interface TypedArrayConstructor<
22
+ T extends TypedArray<BufferType> = TypedArray<BufferType>,
23
+ > {
24
+ new (buffer: BufferType): T
25
+ readonly BYTES_PER_ELEMENT: number
26
+ }
27
+
28
+ /**
29
+ * OSM IDs can be stored as 64-bit floating point numbers.
30
+ */
31
+ export const IdArrayType = Float64Array
32
+
33
+ /**
34
+ * When we are storing coordinates, we need to be able to store 64-bit floating point numbers.
35
+ * However, for benchmarking it is handy to test 32-bit floating point numbers.
36
+ */
37
+ export const CoordinateArrayType = Float64Array
38
+
39
+ /**
40
+ * When we are storing indexes into other arrays, we never need an index to exceed 2^32.
41
+ */
42
+ export const IndexArrayType = Uint32Array
43
+
44
+ /**
45
+ * Default buffer size.
46
+ */
47
+ export const DEFAULT_BUFFER_SIZE = 2 ** 20 // 1 MiB
48
+
49
+ export class ResizeableTypedArray<TA extends TypedArray> {
50
+ ArrayType: TypedArrayConstructor<TA>
51
+ array: TA
52
+ items = 0
53
+
54
+ buffer: BufferType
55
+ bufferSize: number
56
+ maxByteLength: number
57
+
58
+ BC: SharedArrayBufferConstructor | ArrayBufferConstructor
59
+
60
+ static from<TA extends TypedArray>(
61
+ ArrayType: TypedArrayConstructor<TA>,
62
+ buffer: BufferType,
63
+ ) {
64
+ const rta = new ResizeableTypedArray<TA>(
65
+ ArrayType,
66
+ buffer instanceof SharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer,
67
+ )
68
+ rta.buffer = buffer
69
+ rta.array = new ArrayType(buffer)
70
+ rta.items = rta.array.length
71
+ return rta
72
+ }
73
+
74
+ constructor(
75
+ ArrayType: TypedArrayConstructor<TA>,
76
+ BC:
77
+ | SharedArrayBufferConstructor
78
+ | ArrayBufferConstructor = BufferConstructor,
79
+ ) {
80
+ this.ArrayType = ArrayType
81
+ this.bufferSize = DEFAULT_BUFFER_SIZE
82
+ this.maxByteLength = DEFAULT_BUFFER_SIZE * 2
83
+ this.BC = BC
84
+ this.buffer = new BC(this.bufferSize, {
85
+ maxByteLength: this.maxByteLength,
86
+ })
87
+ this.array = new this.ArrayType(this.buffer)
88
+ }
89
+
90
+ [Symbol.iterator](): ArrayIterator<number> {
91
+ return this.array[Symbol.iterator]()
92
+ }
93
+
94
+ expandArray() {
95
+ this.bufferSize *= 2
96
+ if (this.bufferSize > this.buffer.maxByteLength) {
97
+ this.maxByteLength *= 2
98
+ const newBuffer = new this.BC(this.bufferSize, {
99
+ maxByteLength: this.maxByteLength,
100
+ })
101
+ const newArray = new this.ArrayType(newBuffer)
102
+ newArray.set(this.array)
103
+ this.buffer = newBuffer
104
+ this.array = newArray
105
+ } else {
106
+ if (this.buffer instanceof SharedArrayBuffer && this.buffer.growable) {
107
+ this.buffer.grow(this.bufferSize)
108
+ } else if (this.buffer instanceof ArrayBuffer && this.buffer.resizable) {
109
+ this.buffer.resize(this.bufferSize)
110
+ } else {
111
+ throw Error("Buffer is not growable or resizable")
112
+ }
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Handles negative numbers
118
+ */
119
+ at(index: number): number {
120
+ if (index < -this.length || index >= this.length)
121
+ throw Error(`Index out of bounds: ${index}. Length: ${this.length}`)
122
+ if (index < 0) return this.at(this.length + index)
123
+ const result = this.array[index]
124
+ if (result === undefined) throw Error(`No value at index: ${index}`)
125
+ return result
126
+ }
127
+
128
+ slice(start: number, end: number) {
129
+ return this.array.slice(start, end)
130
+ }
131
+
132
+ get length() {
133
+ return this.items
134
+ }
135
+
136
+ push(value: number): number {
137
+ if (this.length >= this.array.length) {
138
+ this.expandArray()
139
+ }
140
+ this.array[this.items++] = value
141
+ return this.length - 1
142
+ }
143
+
144
+ pushMany(values: number[] | TypedArray) {
145
+ while (this.length + values.length > this.array.length) this.expandArray()
146
+ this.array.set(values, this.length)
147
+ this.items += values.length
148
+ }
149
+
150
+ compact() {
151
+ if (this.buffer instanceof SharedArrayBuffer) {
152
+ this.buffer = this.buffer.slice(
153
+ 0,
154
+ this.length * this.ArrayType.BYTES_PER_ELEMENT,
155
+ )
156
+ } else {
157
+ this.buffer = this.buffer.transferToFixedLength(
158
+ this.length * this.ArrayType.BYTES_PER_ELEMENT,
159
+ )
160
+ }
161
+ this.array = new this.ArrayType(this.buffer)
162
+ return this.array
163
+ }
164
+ }
@@ -0,0 +1,29 @@
1
+ declare module "kdbush" {
2
+ import type { BufferConstructor, BufferType } from "../typed-arrays"
3
+ export default class KDBush {
4
+ constructor(
5
+ length: number,
6
+ nodeSize: number,
7
+ ArrayType: Float64ArrayConstructor,
8
+ BufferConstructor: BufferConstructor,
9
+ )
10
+
11
+ /** Serialized index data buffer */
12
+ data: BufferType
13
+
14
+ /** Add a point before calling finish */
15
+ add(x: number, y: number): void
16
+
17
+ /** Finalize the index after adding all points */
18
+ finish(): void
19
+
20
+ /** Query points within a bounding box; returns internal point indexes */
21
+ range(minX: number, minY: number, maxX: number, maxY: number): number[]
22
+
23
+ /** Query points within a radius from (x, y); returns internal point indexes */
24
+ within(x: number, y: number, radius: number): number[]
25
+
26
+ /** Reconstruct an index from a serialized buffer */
27
+ static from(data: BufferType): KDBush
28
+ }
29
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,29 @@
1
+ import type { GeoBbox2D, LonLat } from "@osmix/shared/types"
2
+
3
+ export function throttle<T extends unknown[]>(
4
+ func: (...args: T) => void,
5
+ timeFrame: number,
6
+ ) {
7
+ let lastTime = 0
8
+ return (...args: T) => {
9
+ const now = Date.now()
10
+ if (now - lastTime >= timeFrame) {
11
+ func(...args)
12
+ lastTime = now
13
+ }
14
+ }
15
+ }
16
+
17
+ export function bboxFromLonLats(lonLats: LonLat[]): GeoBbox2D {
18
+ let minLon = Number.POSITIVE_INFINITY
19
+ let minLat = Number.POSITIVE_INFINITY
20
+ let maxLon = Number.NEGATIVE_INFINITY
21
+ let maxLat = Number.NEGATIVE_INFINITY
22
+ for (const [lon, lat] of lonLats) {
23
+ if (lon < minLon) minLon = lon
24
+ if (lat < minLat) minLat = lat
25
+ if (lon > maxLon) maxLon = lon
26
+ if (lat > maxLat) maxLat = lat
27
+ }
28
+ return [minLon, minLat, maxLon, maxLat]
29
+ }