@osmix/shared 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.
- package/CHANGELOG.md +7 -0
- package/README.md +30 -0
- package/package.json +52 -0
- package/src/assert.ts +11 -0
- package/src/bytes-to-stream.ts +8 -0
- package/src/concat-bytes.ts +15 -0
- package/src/haversine-distance.ts +21 -0
- package/src/lineclip.d.ts +11 -0
- package/src/lineclip.ts +2 -0
- package/src/spherical-mercator.test.ts +42 -0
- package/src/spherical-mercator.ts +42 -0
- package/src/stream-to-bytes.ts +20 -0
- package/src/test/fixtures.ts +198 -0
- package/src/transform-bytes.ts +12 -0
- package/src/types.ts +27 -0
- package/test/haversine-distance.test.ts +8 -0
- package/tsconfig/app.json +24 -0
- package/tsconfig/base.json +28 -0
- package/tsconfig/test.json +13 -0
- package/tsconfig.json +9 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# @osmix/shared
|
|
2
|
+
|
|
3
|
+
`@osmix/shared` exposes small geometry helpers that packages across the Osmix workspace rely on.
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
|
|
7
|
+
- `haversineDistance` – Compute great-circle distances between lon/lat pairs in meters.
|
|
8
|
+
- `clipPolyline` – Re-export of [`lineclip`](https://github.com/mourner/lineclip) for clipping projected polylines to axis-aligned bounding boxes with types added.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install @osmix/shared
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { haversineDistance, clipPolyline } from "@osmix/shared"
|
|
20
|
+
|
|
21
|
+
const meters = haversineDistance([-122.33, 47.61], [-122.30, 47.63])
|
|
22
|
+
|
|
23
|
+
const segments = clipPolyline(
|
|
24
|
+
[
|
|
25
|
+
[-122.33, 47.61],
|
|
26
|
+
[-122.31, 47.62],
|
|
27
|
+
],
|
|
28
|
+
[-122.40, 47.50, -122.20, 47.70],
|
|
29
|
+
)
|
|
30
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/package",
|
|
3
|
+
"name": "@osmix/shared",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"version": "0.0.1",
|
|
6
|
+
"description": "Shared utilities for Osmix",
|
|
7
|
+
"exports": {
|
|
8
|
+
"./*": "./src/*.ts",
|
|
9
|
+
"./tsconfig/base.json": "./tsconfig/base.json",
|
|
10
|
+
"./tsconfig/test.json": "./tsconfig/test.json",
|
|
11
|
+
"./tsconfig/app.json": "./tsconfig/app.json",
|
|
12
|
+
"./test/*": "./src/test/*.ts"
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public",
|
|
16
|
+
"exports": {
|
|
17
|
+
"./*": {
|
|
18
|
+
"import": "./dist/*.js",
|
|
19
|
+
"types": "./dist/*.d.ts"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/conveyal/osmix.git"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/conveyal/osmix#readme",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/conveyal/osmix/issues"
|
|
36
|
+
},
|
|
37
|
+
"sideEffects": false,
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc",
|
|
40
|
+
"bench": "vitest bench",
|
|
41
|
+
"test": "vitest",
|
|
42
|
+
"typecheck": "tsc --noEmit"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"typescript": "catalog:",
|
|
46
|
+
"vitest": "catalog:"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@mapbox/sphericalmercator": "catalog:",
|
|
50
|
+
"lineclip": "^2.0.0"
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/assert.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Short utility for checking index access.
|
|
3
|
+
*/
|
|
4
|
+
export function assertValue<T>(
|
|
5
|
+
value?: T,
|
|
6
|
+
message?: string,
|
|
7
|
+
): asserts value is NonNullable<T> {
|
|
8
|
+
if (value === undefined || value === null) {
|
|
9
|
+
throw Error(message ?? "Value is undefined or null")
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Concatenates multiple `Uint8Array` segments into a contiguous array.
|
|
3
|
+
*/
|
|
4
|
+
export function concatBytes(
|
|
5
|
+
parts: Uint8Array<ArrayBuffer>[],
|
|
6
|
+
): Uint8Array<ArrayBuffer> {
|
|
7
|
+
const total = parts.reduce((n, p) => n + p.length, 0)
|
|
8
|
+
const out = new Uint8Array<ArrayBuffer>(new ArrayBuffer(total))
|
|
9
|
+
let offset = 0
|
|
10
|
+
for (const p of parts) {
|
|
11
|
+
out.set(p, offset)
|
|
12
|
+
offset += p.length
|
|
13
|
+
}
|
|
14
|
+
return out
|
|
15
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculate the haversine distance between two LonLat points.
|
|
3
|
+
* @param p1 - The first point
|
|
4
|
+
* @param p2 - The second point
|
|
5
|
+
* @returns The haversine distance in meters
|
|
6
|
+
*/
|
|
7
|
+
export function haversineDistance(
|
|
8
|
+
p1: [number, number],
|
|
9
|
+
p2: [number, number],
|
|
10
|
+
): number {
|
|
11
|
+
const R = 6371008.8 // Earth's radius in meters
|
|
12
|
+
const dLat = (p2[1] - p1[1]) * (Math.PI / 180)
|
|
13
|
+
const dLon = (p2[0] - p1[0]) * (Math.PI / 180)
|
|
14
|
+
const lat1 = p1[1] * (Math.PI / 180)
|
|
15
|
+
const lat2 = p2[1] * (Math.PI / 180)
|
|
16
|
+
const a =
|
|
17
|
+
Math.sin(dLat / 2) ** 2 +
|
|
18
|
+
Math.sin(dLon / 2) ** 2 * Math.cos(lat1) * Math.cos(lat2)
|
|
19
|
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
|
|
20
|
+
return R * c
|
|
21
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
declare module "lineclip" {
|
|
2
|
+
export function clipPolyline(
|
|
3
|
+
points: [number, number][],
|
|
4
|
+
bbox: [number, number, number, number],
|
|
5
|
+
): [number, number][][]
|
|
6
|
+
|
|
7
|
+
export function clipPolygon(
|
|
8
|
+
points: [number, number][],
|
|
9
|
+
bbox: [number, number, number, number],
|
|
10
|
+
): [number, number][]
|
|
11
|
+
}
|
package/src/lineclip.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import SphericalMercatorTile from "./spherical-mercator"
|
|
3
|
+
import type { Tile } from "./types"
|
|
4
|
+
|
|
5
|
+
function lonLatForPixel(
|
|
6
|
+
merc: SphericalMercatorTile,
|
|
7
|
+
tileIndex: Tile,
|
|
8
|
+
tileSize: number,
|
|
9
|
+
px: number,
|
|
10
|
+
py: number,
|
|
11
|
+
): [number, number] {
|
|
12
|
+
const [x, y, z] = tileIndex
|
|
13
|
+
return merc.ll([x * tileSize + px, y * tileSize + py], z) as [number, number]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe("SphericalMercatorTile", () => {
|
|
17
|
+
it("projects lon/lat to tile-local pixels", () => {
|
|
18
|
+
const tile: Tile = [300, 300, 10]
|
|
19
|
+
const [tx, ty, tz] = tile
|
|
20
|
+
const tileSize = 256
|
|
21
|
+
const merc = new SphericalMercatorTile({ size: tileSize, tile })
|
|
22
|
+
|
|
23
|
+
const insideLonLat = lonLatForPixel(merc, tile, tileSize, 32, 16)
|
|
24
|
+
expect(merc.llToTilePx(insideLonLat)).toEqual([32, 16])
|
|
25
|
+
|
|
26
|
+
const outsideTopLeft = merc.ll(
|
|
27
|
+
[tx * tileSize - 10, ty * tileSize - 10],
|
|
28
|
+
tz,
|
|
29
|
+
) as [number, number]
|
|
30
|
+
expect(outsideTopLeft).toEqual([-74.54498291015625, 59.54128017205441])
|
|
31
|
+
expect(merc.llToTilePx(outsideTopLeft)).toEqual([-10, -10])
|
|
32
|
+
|
|
33
|
+
const outsideBottomRight = merc.ll(
|
|
34
|
+
[(tx + 1) * tileSize + 10, (ty + 1) * tileSize + 10],
|
|
35
|
+
tz,
|
|
36
|
+
) as [number, number]
|
|
37
|
+
expect(merc.llToTilePx(outsideBottomRight, tile)).toEqual([
|
|
38
|
+
tileSize + 10,
|
|
39
|
+
tileSize + 10,
|
|
40
|
+
])
|
|
41
|
+
})
|
|
42
|
+
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { SphericalMercator } from "@mapbox/sphericalmercator"
|
|
2
|
+
import type { GeoBbox2D, LonLat, Tile, XY } from "./types"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extends the SphericalMercator class to provide tile-local pixel coordinate calculations and clamping.
|
|
6
|
+
*/
|
|
7
|
+
export default class SphericalMercatorTile extends SphericalMercator {
|
|
8
|
+
tileSize: number
|
|
9
|
+
tile?: Tile
|
|
10
|
+
constructor(
|
|
11
|
+
options: ConstructorParameters<typeof SphericalMercator>[0] & {
|
|
12
|
+
tile?: Tile
|
|
13
|
+
},
|
|
14
|
+
) {
|
|
15
|
+
super(options)
|
|
16
|
+
this.tile = options?.tile
|
|
17
|
+
this.tileSize = options?.size ?? 256
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
llToTilePx(ll: LonLat, tile?: Tile): XY {
|
|
21
|
+
if (tile == null && this.tile == null)
|
|
22
|
+
throw Error("Tile must be set on construction or passed as an argument.")
|
|
23
|
+
const [tx, ty, tz] = (tile ?? this.tile)!
|
|
24
|
+
const merc = this.px(ll, tz)
|
|
25
|
+
const x = merc[0] - tx * this.tileSize
|
|
26
|
+
const y = merc[1] - ty * this.tileSize
|
|
27
|
+
return [x, y]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
clampAndRoundPx(px: XY, bbox?: GeoBbox2D): XY {
|
|
31
|
+
const [minX, minY, maxX, maxY] = bbox ?? [
|
|
32
|
+
0,
|
|
33
|
+
0,
|
|
34
|
+
this.tileSize,
|
|
35
|
+
this.tileSize,
|
|
36
|
+
]
|
|
37
|
+
return [
|
|
38
|
+
Math.max(minX, Math.min(maxX, Math.round(px[0]))),
|
|
39
|
+
Math.max(minY, Math.min(maxY, Math.round(px[1]))),
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { concatBytes } from "./concat-bytes"
|
|
2
|
+
|
|
3
|
+
export async function streamToBytes(
|
|
4
|
+
stream: ReadableStream<Uint8Array<ArrayBuffer>>,
|
|
5
|
+
): Promise<Uint8Array<ArrayBuffer>> {
|
|
6
|
+
const reader = stream.getReader()
|
|
7
|
+
const chunks: Uint8Array<ArrayBuffer>[] = []
|
|
8
|
+
|
|
9
|
+
while (true) {
|
|
10
|
+
const { done, value } = await reader.read()
|
|
11
|
+
|
|
12
|
+
if (done) {
|
|
13
|
+
break
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
chunks.push(value)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return concatBytes(chunks)
|
|
20
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { createReadStream, createWriteStream } from "node:fs"
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises"
|
|
3
|
+
import { dirname, join, resolve } from "node:path"
|
|
4
|
+
import { Readable, Writable } from "node:stream"
|
|
5
|
+
import { fileURLToPath } from "node:url"
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
8
|
+
const ROOT_DIR = resolve(__dirname, "../../../../")
|
|
9
|
+
const FIXTURES_DIR = resolve(ROOT_DIR, "fixtures")
|
|
10
|
+
|
|
11
|
+
export function getFixturePath(url: string) {
|
|
12
|
+
if (url.startsWith("http")) {
|
|
13
|
+
const fileName = url.split("/").pop()
|
|
14
|
+
if (!fileName) throw new Error("Invalid URL")
|
|
15
|
+
return join(FIXTURES_DIR, fileName)
|
|
16
|
+
}
|
|
17
|
+
return join(FIXTURES_DIR, url)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get file from the cache folder or download it from the URL
|
|
22
|
+
*/
|
|
23
|
+
export async function getFixtureFile(url: string): Promise<ArrayBuffer> {
|
|
24
|
+
const filePath = getFixturePath(url)
|
|
25
|
+
try {
|
|
26
|
+
const file = await readFile(filePath)
|
|
27
|
+
return file.buffer as ArrayBuffer
|
|
28
|
+
} catch (_error) {
|
|
29
|
+
const response = await fetch(url)
|
|
30
|
+
const buffer = await response.arrayBuffer()
|
|
31
|
+
await writeFile(filePath, new Uint8Array(buffer))
|
|
32
|
+
return buffer
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getFixtureFileReadStream(url: string) {
|
|
37
|
+
return Readable.toWeb(
|
|
38
|
+
createReadStream(getFixturePath(url)),
|
|
39
|
+
) as unknown as ReadableStream<ArrayBuffer>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getFixtureFileWriteStream(url: string) {
|
|
43
|
+
return Writable.toWeb(
|
|
44
|
+
createWriteStream(getFixturePath(url)),
|
|
45
|
+
) as unknown as WritableStream<Uint8Array>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type PbfFixture = {
|
|
49
|
+
url: string
|
|
50
|
+
bbox: {
|
|
51
|
+
bottom: number
|
|
52
|
+
top: number
|
|
53
|
+
left: number
|
|
54
|
+
right: number
|
|
55
|
+
}
|
|
56
|
+
nodesWithTags: number
|
|
57
|
+
nodes: number
|
|
58
|
+
ways: number
|
|
59
|
+
relations: number
|
|
60
|
+
node0: {
|
|
61
|
+
lat: number
|
|
62
|
+
lon: number
|
|
63
|
+
id: number
|
|
64
|
+
}
|
|
65
|
+
way0: number
|
|
66
|
+
relation0: number
|
|
67
|
+
uniqueStrings: number
|
|
68
|
+
primitiveGroups: number
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* List of PBFs and their metadata used for testing. Cached locally in the top level fixtures directory so they can
|
|
73
|
+
* be used across packages and apps.
|
|
74
|
+
*
|
|
75
|
+
* `monaco.pbf` is checked into the repository so it can be used in CI without causing repeated downloads.
|
|
76
|
+
*
|
|
77
|
+
* Below, we export a subset of the PBFs that we want to use for current tests.
|
|
78
|
+
*/
|
|
79
|
+
const monacoPbfFixture: PbfFixture = {
|
|
80
|
+
url: "monaco.pbf",
|
|
81
|
+
bbox: {
|
|
82
|
+
bottom: 43.7232244,
|
|
83
|
+
top: 43.7543687,
|
|
84
|
+
left: 7.4053929,
|
|
85
|
+
right: 7.4447259,
|
|
86
|
+
},
|
|
87
|
+
nodesWithTags: 1_254,
|
|
88
|
+
nodes: 14_286,
|
|
89
|
+
ways: 3_346,
|
|
90
|
+
relations: 46,
|
|
91
|
+
node0: {
|
|
92
|
+
lat: 43.7371175,
|
|
93
|
+
lon: 7.4229093,
|
|
94
|
+
id: 21911883,
|
|
95
|
+
},
|
|
96
|
+
way0: 4097656,
|
|
97
|
+
relation0: 3410831,
|
|
98
|
+
uniqueStrings: 1_060,
|
|
99
|
+
primitiveGroups: 7,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const AllPBFs: Record<string, PbfFixture> = {
|
|
103
|
+
monaco: monacoPbfFixture,
|
|
104
|
+
montenegro: {
|
|
105
|
+
url: "https://download.geofabrik.de/europe/montenegro-250101.osm.pbf",
|
|
106
|
+
bbox: {
|
|
107
|
+
bottom: 41.61621,
|
|
108
|
+
top: 43.562169,
|
|
109
|
+
left: 18.17282,
|
|
110
|
+
right: 20.358827,
|
|
111
|
+
},
|
|
112
|
+
nodesWithTags: 63_321,
|
|
113
|
+
nodes: 3_915_383,
|
|
114
|
+
ways: 321_330,
|
|
115
|
+
relations: 5_501,
|
|
116
|
+
node0: {
|
|
117
|
+
lat: 42.1982436,
|
|
118
|
+
lon: 18.9656482,
|
|
119
|
+
id: 26860768,
|
|
120
|
+
},
|
|
121
|
+
way0: 123,
|
|
122
|
+
relation0: 123,
|
|
123
|
+
uniqueStrings: 55_071,
|
|
124
|
+
primitiveGroups: 532,
|
|
125
|
+
},
|
|
126
|
+
croatia: {
|
|
127
|
+
url: "https://download.geofabrik.de/europe/croatia-250101.osm.pbf",
|
|
128
|
+
bbox: {
|
|
129
|
+
bottom: 42.16483,
|
|
130
|
+
top: 46.557562,
|
|
131
|
+
left: 13.08916,
|
|
132
|
+
right: 19.459968,
|
|
133
|
+
},
|
|
134
|
+
nodesWithTags: 481_613,
|
|
135
|
+
nodes: 23_063_621,
|
|
136
|
+
ways: 2_315_247,
|
|
137
|
+
relations: 39_098,
|
|
138
|
+
primitiveGroups: 3_178,
|
|
139
|
+
node0: {
|
|
140
|
+
lat: 42.9738772,
|
|
141
|
+
lon: 17.021989,
|
|
142
|
+
id: 4_511_653,
|
|
143
|
+
},
|
|
144
|
+
way0: 123,
|
|
145
|
+
relation0: 123,
|
|
146
|
+
uniqueStrings: 269_315,
|
|
147
|
+
},
|
|
148
|
+
italy: {
|
|
149
|
+
url: "https://download.geofabrik.de/europe/italy-250101.osm.pbf",
|
|
150
|
+
bbox: {
|
|
151
|
+
bottom: 35.07638,
|
|
152
|
+
left: 6.602696,
|
|
153
|
+
right: 19.12499,
|
|
154
|
+
top: 47.100045,
|
|
155
|
+
},
|
|
156
|
+
nodesWithTags: 1_513_303,
|
|
157
|
+
nodes: 250_818_620,
|
|
158
|
+
ways: 27_837_987,
|
|
159
|
+
relations: 100_000,
|
|
160
|
+
primitiveGroups: 34_901,
|
|
161
|
+
node0: {
|
|
162
|
+
lat: 41.9033,
|
|
163
|
+
lon: 12.4534,
|
|
164
|
+
id: 1,
|
|
165
|
+
},
|
|
166
|
+
way0: 123,
|
|
167
|
+
relation0: 123,
|
|
168
|
+
uniqueStrings: 3190,
|
|
169
|
+
},
|
|
170
|
+
washington: {
|
|
171
|
+
url: "https://download.geofabrik.de/north-america/us/washington-250101.osm.pbf",
|
|
172
|
+
bbox: {
|
|
173
|
+
bottom: 45.53882,
|
|
174
|
+
top: 49.00708,
|
|
175
|
+
left: -126.7423,
|
|
176
|
+
right: -116.911526,
|
|
177
|
+
},
|
|
178
|
+
nodesWithTags: 1_513_303,
|
|
179
|
+
nodes: 43_032_447,
|
|
180
|
+
ways: 4_541_651,
|
|
181
|
+
relations: 44_373,
|
|
182
|
+
node0: {
|
|
183
|
+
lat: 47.64248,
|
|
184
|
+
lon: -122.3196898,
|
|
185
|
+
id: 29445653,
|
|
186
|
+
},
|
|
187
|
+
way0: 123,
|
|
188
|
+
relation0: 123,
|
|
189
|
+
uniqueStrings: 598_993,
|
|
190
|
+
primitiveGroups: 34_901,
|
|
191
|
+
},
|
|
192
|
+
} as const
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* A subset of the PBFs that we want to use for current tests. Do not check in changes to this list as it will cause CI to
|
|
196
|
+
* attempt to download PBFs that are not checked into the repository.
|
|
197
|
+
*/
|
|
198
|
+
export const PBFs: Record<string, PbfFixture> = { monaco: monacoPbfFixture }
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { bytesToStream } from "./bytes-to-stream"
|
|
2
|
+
import { streamToBytes } from "./stream-to-bytes"
|
|
3
|
+
|
|
4
|
+
export async function transformBytes(
|
|
5
|
+
bytes: Uint8Array<ArrayBuffer>,
|
|
6
|
+
transformStream: TransformStream<
|
|
7
|
+
Uint8Array<ArrayBuffer>,
|
|
8
|
+
Uint8Array<ArrayBuffer>
|
|
9
|
+
>,
|
|
10
|
+
): Promise<Uint8Array<ArrayBuffer>> {
|
|
11
|
+
return streamToBytes(bytesToStream(bytes).pipeThrough(transformStream))
|
|
12
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type LonLat = [lon: number, lat: number]
|
|
2
|
+
export type XY = [x: number, y: number]
|
|
3
|
+
export interface ILonLat {
|
|
4
|
+
lon: number
|
|
5
|
+
lat: number
|
|
6
|
+
}
|
|
7
|
+
export type Tile = [x: number, y: number, z: number]
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Project LonLat to pixels
|
|
11
|
+
*/
|
|
12
|
+
export type LonLatToPixel = (ll: LonLat, zoom: number) => XY
|
|
13
|
+
|
|
14
|
+
export type LonLatToTilePixel = (ll: LonLat, z: number, extent: number) => XY
|
|
15
|
+
|
|
16
|
+
export type Rgba = [number, number, number, number] | Uint8ClampedArray
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A bounding box in the format [minLon, minLat, maxLon, maxLat].
|
|
20
|
+
* GeoJSON.BBox allows for 3D bounding boxes, but we use tools that expect 2D bounding boxes.
|
|
21
|
+
*/
|
|
22
|
+
export type GeoBbox2D = [
|
|
23
|
+
minLon: number,
|
|
24
|
+
minLat: number,
|
|
25
|
+
maxLon: number,
|
|
26
|
+
maxLat: number,
|
|
27
|
+
]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { assert, test } from "vitest"
|
|
2
|
+
import { haversineDistance } from "../src/haversine-distance"
|
|
3
|
+
|
|
4
|
+
test("haversineDistance", () => {
|
|
5
|
+
const p1: [number, number] = [-75.343, 39.984]
|
|
6
|
+
const p2: [number, number] = [-75.534, 39.123]
|
|
7
|
+
assert.closeTo(haversineDistance(p1, p2), 97129.2211, 0.0001)
|
|
8
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"extends": "./base.json",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"noEmit": true,
|
|
6
|
+
"lib": [
|
|
7
|
+
"ESNext",
|
|
8
|
+
"ESNext.AsyncIterable",
|
|
9
|
+
"ESNext.Array",
|
|
10
|
+
"ESNext.Intl",
|
|
11
|
+
"ESNext.Symbol",
|
|
12
|
+
"DOM",
|
|
13
|
+
"DOM.Iterable"
|
|
14
|
+
],
|
|
15
|
+
"target": "es2024",
|
|
16
|
+
"jsx": "react-jsx",
|
|
17
|
+
"allowImportingTsExtensions": true,
|
|
18
|
+
|
|
19
|
+
"noUnusedLocals": false,
|
|
20
|
+
"noUnusedParameters": false,
|
|
21
|
+
"noPropertyAccessFromIndexSignature": false,
|
|
22
|
+
"noUncheckedIndexedAccess": false
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"display": "@osmix/tsconfig/base",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"declarationMap": true,
|
|
7
|
+
"sourceMap": true,
|
|
8
|
+
"noEmitOnError": true,
|
|
9
|
+
|
|
10
|
+
"lib": ["ESNext"],
|
|
11
|
+
"target": "es2024",
|
|
12
|
+
"module": "preserve",
|
|
13
|
+
"moduleDetection": "force",
|
|
14
|
+
|
|
15
|
+
"moduleResolution": "bundler",
|
|
16
|
+
"verbatimModuleSyntax": true,
|
|
17
|
+
"isolatedModules": true,
|
|
18
|
+
"erasableSyntaxOnly": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
|
|
21
|
+
"strict": true,
|
|
22
|
+
"noFallthroughCasesInSwitch": true,
|
|
23
|
+
"noUncheckedIndexedAccess": true,
|
|
24
|
+
"noUnusedLocals": true,
|
|
25
|
+
"noUnusedParameters": true,
|
|
26
|
+
"noPropertyAccessFromIndexSignature": true
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"display": "@osmix/tsconfig/test",
|
|
4
|
+
"extends": "./base.json",
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"noEmit": true,
|
|
7
|
+
|
|
8
|
+
"noUncheckedIndexedAccess": false,
|
|
9
|
+
"noUnusedLocals": false,
|
|
10
|
+
"noUnusedParameters": false,
|
|
11
|
+
"noPropertyAccessFromIndexSignature": false
|
|
12
|
+
}
|
|
13
|
+
}
|