@osmix/pbf 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.
Files changed (45) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +144 -0
  3. package/dist/blobs-to-blocks.d.ts +5 -0
  4. package/dist/blobs-to-blocks.js +21 -0
  5. package/dist/blocks-to-pbf.d.ts +16 -0
  6. package/dist/blocks-to-pbf.js +73 -0
  7. package/dist/index.d.ts +8 -0
  8. package/dist/index.js +8 -0
  9. package/dist/pbf-to-blobs.d.ts +6 -0
  10. package/dist/pbf-to-blobs.js +48 -0
  11. package/dist/pbf-to-blocks.d.ts +20 -0
  12. package/dist/pbf-to-blocks.js +53 -0
  13. package/dist/proto/fileformat.d.ts +26 -0
  14. package/dist/proto/fileformat.js +56 -0
  15. package/dist/proto/osmformat.d.ts +91 -0
  16. package/dist/proto/osmformat.js +458 -0
  17. package/dist/spec.d.ts +5 -0
  18. package/dist/spec.js +9 -0
  19. package/dist/utils.d.ts +27 -0
  20. package/dist/utils.js +92 -0
  21. package/package.json +49 -0
  22. package/src/blobs-to-blocks.ts +28 -0
  23. package/src/blocks-to-pbf.ts +98 -0
  24. package/src/index.ts +8 -0
  25. package/src/pbf-to-blobs.ts +56 -0
  26. package/src/pbf-to-blocks.ts +77 -0
  27. package/src/proto/fileformat.proto +68 -0
  28. package/src/proto/fileformat.ts +70 -0
  29. package/src/proto/osmformat.proto +262 -0
  30. package/src/proto/osmformat.ts +488 -0
  31. package/src/spec.ts +10 -0
  32. package/src/utils.ts +90 -0
  33. package/test/blobs-to-blocks.test.ts +73 -0
  34. package/test/helpers.ts +66 -0
  35. package/test/pbf-to-blobs.test.ts +85 -0
  36. package/test/read.bench.ts +42 -0
  37. package/test/read.test.ts +45 -0
  38. package/test/streams.test.ts +92 -0
  39. package/test/utils.bun.test.ts +327 -0
  40. package/test/utils.test.ts +56 -0
  41. package/test/utils.ts +65 -0
  42. package/test/verify-pbf-reading.bun.test.ts +39 -0
  43. package/test/write.test.ts +86 -0
  44. package/tsconfig.json +9 -0
  45. package/vitest.config.ts +7 -0
@@ -0,0 +1,56 @@
1
+ import { assert, describe, expect, it } from "vitest"
2
+ import {
3
+ concatUint8,
4
+ toAsyncGenerator,
5
+ uint32BE,
6
+ webCompress,
7
+ webDecompress,
8
+ } from "../src/utils"
9
+
10
+ describe("utils", () => {
11
+ it("wraps values into an async generator", async () => {
12
+ const generator = toAsyncGenerator(3)
13
+ const first = await generator.next()
14
+ assert.deepEqual(first, { value: 3, done: false })
15
+ const done = await generator.next()
16
+ assert.deepEqual(done, { value: undefined, done: true })
17
+ })
18
+
19
+ it("consumes readable streams", async () => {
20
+ const stream = new ReadableStream<number>({
21
+ start(controller) {
22
+ controller.enqueue(1)
23
+ controller.enqueue(2)
24
+ controller.close()
25
+ },
26
+ })
27
+ const values: number[] = []
28
+ for await (const value of toAsyncGenerator(stream)) values.push(value)
29
+ assert.deepEqual(values, [1, 2])
30
+ })
31
+
32
+ it("throws on nullish inputs", async () => {
33
+ const invalidInput = null as unknown as never
34
+ await expect(toAsyncGenerator(invalidInput).next()).rejects.toThrow(
35
+ "Value is null",
36
+ )
37
+ })
38
+
39
+ it("concatenates Uint8Array segments", () => {
40
+ const a = Uint8Array.of(1, 2)
41
+ const b = Uint8Array.of(3)
42
+ assert.deepEqual(concatUint8(a, b), Uint8Array.of(1, 2, 3))
43
+ })
44
+
45
+ it("encodes big-endian 32-bit integers", () => {
46
+ assert.deepEqual(uint32BE(0x01020304), Uint8Array.of(1, 2, 3, 4))
47
+ })
48
+
49
+ it("compresses and decompresses data", async () => {
50
+ const input = new TextEncoder().encode("osmix") as Uint8Array<ArrayBuffer>
51
+ const compressed = await webCompress(input)
52
+ assert.notDeepEqual(compressed, input)
53
+ const decompressed = await webDecompress(compressed)
54
+ assert.deepEqual(decompressed, input)
55
+ })
56
+ })
package/test/utils.ts ADDED
@@ -0,0 +1,65 @@
1
+ import type { PbfFixture } from "@osmix/shared/test/fixtures"
2
+ import { assert } from "vitest"
3
+ import type {
4
+ OsmPbfBlock,
5
+ OsmPbfGroup,
6
+ OsmPbfHeaderBlock,
7
+ } from "../src/proto/osmformat"
8
+
9
+ export async function testOsmPbfReader(
10
+ osm: {
11
+ header: OsmPbfHeaderBlock
12
+ blocks: AsyncGenerator<OsmPbfBlock>
13
+ },
14
+ pbf: PbfFixture,
15
+ ) {
16
+ assert.deepEqual(osm.header.bbox, pbf.bbox)
17
+
18
+ const { onGroup, count } = createOsmEntityCounter()
19
+ for await (const block of osm.blocks)
20
+ for (const group of block.primitivegroup) onGroup(group)
21
+
22
+ assert.equal(count.nodes, pbf.nodes)
23
+ assert.equal(count.ways, pbf.ways)
24
+ assert.equal(count.relations, pbf.relations)
25
+ assert.equal(count.node0, pbf.node0.id)
26
+ assert.equal(count.way0, pbf.way0)
27
+ assert.equal(count.relation0, pbf.relation0)
28
+
29
+ return count
30
+ }
31
+
32
+ export function createOsmEntityCounter() {
33
+ const count = {
34
+ nodes: 0,
35
+ ways: 0,
36
+ relations: 0,
37
+ node0: -1,
38
+ way0: -1,
39
+ relation0: -1,
40
+ }
41
+
42
+ const onGroup = (group: OsmPbfGroup) => {
43
+ if (count.node0 === -1 && group.dense?.id?.[0] != null) {
44
+ count.node0 = group.dense.id[0]
45
+ }
46
+ if (count.way0 === -1 && group.ways?.[0]?.id != null) {
47
+ count.way0 = group.ways[0].id
48
+ }
49
+ if (count.relation0 === -1 && group.relations?.[0]?.id != null) {
50
+ count.relation0 = group.relations[0].id
51
+ }
52
+
53
+ count.nodes += group.nodes?.length ?? 0
54
+ if (group.dense) {
55
+ count.nodes += group.dense.id.length
56
+ }
57
+ count.ways += group.ways?.length ?? 0
58
+ count.relations += group.relations?.length ?? 0
59
+ }
60
+
61
+ return {
62
+ onGroup,
63
+ count,
64
+ }
65
+ }
@@ -0,0 +1,39 @@
1
+ import { getFixtureFile } from "@osmix/shared/test/fixtures"
2
+ import { describe, expect, test } from "vitest"
3
+ import { readOsmPbf } from "../src/pbf-to-blocks"
4
+ import { isBun } from "../src/utils"
5
+
6
+ describe.runIf(isBun())("PBF Reading with Bun", () => {
7
+ test("can read an actual OSM PBF file", async () => {
8
+ // Load a real PBF fixture
9
+ const pbfData = await getFixtureFile("monaco.pbf")
10
+
11
+ // Try to read it
12
+ const result = await readOsmPbf(pbfData)
13
+
14
+ // Basic sanity checks
15
+ expect(result.header).toBeDefined()
16
+ expect(result.header.required_features).toBeDefined()
17
+ expect(result.blocks).toBeDefined()
18
+
19
+ // Try to read at least one block
20
+ const firstBlock = await result.blocks.next()
21
+ expect(firstBlock.done).toBe(false)
22
+ expect(firstBlock.value).toBeDefined()
23
+
24
+ // If it's a primitive block, it should have primitivegroup
25
+ if ("primitivegroup" in firstBlock.value) {
26
+ expect(Array.isArray(firstBlock.value.primitivegroup)).toBe(true)
27
+ }
28
+ })
29
+
30
+ test("header contains expected OSM data", async () => {
31
+ const pbfData = await getFixtureFile("monaco.pbf")
32
+
33
+ const result = await readOsmPbf(pbfData)
34
+
35
+ // Check that header has the required OSM features
36
+ expect(result.header.required_features).toBeDefined()
37
+ expect(result.header.required_features.length).toBeGreaterThan(0)
38
+ })
39
+ })
@@ -0,0 +1,86 @@
1
+ import { unlink } from "node:fs/promises"
2
+ import {
3
+ getFixtureFile,
4
+ getFixtureFileReadStream,
5
+ getFixtureFileWriteStream,
6
+ getFixturePath,
7
+ PBFs,
8
+ } from "@osmix/shared/test/fixtures"
9
+ import { assert, describe, it } from "vitest"
10
+ import {
11
+ OsmBlocksToPbfBytesTransformStream,
12
+ osmBlockToPbfBlobBytes,
13
+ } from "../src/blocks-to-pbf"
14
+ import {
15
+ OsmPbfBytesToBlocksTransformStream,
16
+ readOsmPbf,
17
+ } from "../src/pbf-to-blocks"
18
+ import { testOsmPbfReader } from "./utils"
19
+
20
+ describe("write", () => {
21
+ describe.each(Object.entries(PBFs))("%s", (name, pbf) => {
22
+ it("to buffer", async () => {
23
+ const fileData = await getFixtureFile(pbf.url)
24
+ const osm = await readOsmPbf(fileData)
25
+
26
+ let node0: number | null = null
27
+ let way0: number | null = null
28
+ let relation0: number | null = null
29
+
30
+ // Write the PBF to an array buffer
31
+ let data = new Uint8Array(0)
32
+ const write = (chunk: Uint8Array) => {
33
+ const newData = new Uint8Array(data.length + chunk.length)
34
+ newData.set(data)
35
+ newData.set(chunk, data.length)
36
+ data = newData
37
+ }
38
+
39
+ write(await osmBlockToPbfBlobBytes(osm.header))
40
+ for await (const block of osm.blocks) {
41
+ for (const group of block.primitivegroup) {
42
+ if (node0 == null && group.dense?.id?.[0] != null) {
43
+ node0 = group.dense.id[0]
44
+ }
45
+ if (way0 == null && group.ways?.[0]?.id != null) {
46
+ way0 = group.ways[0].id
47
+ }
48
+ if (relation0 == null && group.relations?.[0]?.id != null) {
49
+ relation0 = group.relations[0].id
50
+ }
51
+ }
52
+ write(await osmBlockToPbfBlobBytes(block))
53
+ }
54
+
55
+ // Re-parse the new PBF and test
56
+ assert.exists(data.buffer)
57
+ // TODO: assert.equal(stream.buffer.byteLength, fileData.byteLength)
58
+ const osm2 = await readOsmPbf(data.buffer)
59
+
60
+ assert.deepEqual(osm.header, osm2.header)
61
+ const entities = await testOsmPbfReader(osm2, pbf)
62
+ assert.equal(entities.node0, node0)
63
+ assert.equal(entities.way0, way0)
64
+ assert.equal(entities.relation0, relation0)
65
+ })
66
+
67
+ it("to file", async () => {
68
+ const testFileName = `${name}-write-test.pbf`
69
+ const fileStream = getFixtureFileReadStream(pbf.url)
70
+
71
+ const fileWriteStream = getFixtureFileWriteStream(testFileName)
72
+ await fileStream
73
+ .pipeThrough(new OsmPbfBytesToBlocksTransformStream())
74
+ .pipeThrough(new OsmBlocksToPbfBytesTransformStream())
75
+ .pipeTo(fileWriteStream)
76
+
77
+ const testFileData = await getFixtureFile(pbf.url)
78
+ const testOsm = await readOsmPbf(testFileData)
79
+
80
+ assert.deepEqual(testOsm.header.bbox, pbf.bbox)
81
+ await testOsmPbfReader(testOsm, pbf)
82
+
83
+ await unlink(getFixturePath(testFileName))
84
+ })
85
+ })
86
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "@osmix/shared/tsconfig/base.json",
4
+ "exclude": ["node_modules", "dist"],
5
+ "include": ["src", "test"],
6
+ "compilerOptions": {
7
+ "outDir": "./dist"
8
+ }
9
+ }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vitest/config"
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ // exclude: ["**/node_modules/**", "**/*.bun.test.ts"],
6
+ },
7
+ })