From de335d22da059f89f830294f51e1241d99571afa Mon Sep 17 00:00:00 2001 From: Mr Martian Date: Wed, 2 Oct 2024 22:27:26 -0600 Subject: [PATCH] try to build docs in actions; cleanup project to only be a specification --- .github/workflows/coveralls.yml | 82 -- .github/workflows/pages.yml | 44 ++ .github/workflows/test.yml | 8 +- .gitignore | 1 + README.md | 7 +- eslint.config.mjs | 25 + src/bbox.ts | 95 --- src/clip.ts | 513 ------------ src/convert.ts | 116 --- src/geometry.spec.ts | 68 +- src/id.ts | 692 ---------------- src/index.ts | 84 +- src/s2/convert.ts | 55 -- src/s2/index.ts | 3 - src/s2/s2Coords.ts | 383 --------- src/s2/s2Point.ts | 335 -------- src/simplify.ts | 214 ----- src/tile.ts | 257 ------ src/util.ts | 40 - src/wm/convert.ts | 478 ----------- src/wm/index.ts | 2 - src/wm/mercCoords.ts | 333 -------- test/bbox.test.ts | 39 - test/clip.test.ts | 1308 ------------------------------- test/convert.test.ts | 448 ----------- test/id.test.ts | 392 --------- test/s2/convert.test.ts | 271 ------- test/s2/s2Coords.test.ts | 264 ------- test/s2/s2Point.test.ts | 321 -------- test/schema.test.ts | 410 ---------- test/simplify.test.ts | 262 ------- test/tile.test.ts | 322 -------- test/util.test.ts | 80 -- test/values.test.ts | 15 - test/wm/convert.test.ts | 1186 ---------------------------- test/wm/mercCoords.test.ts | 371 --------- 36 files changed, 169 insertions(+), 9355 deletions(-) delete mode 100644 .github/workflows/coveralls.yml create mode 100644 .github/workflows/pages.yml delete mode 100644 src/bbox.ts delete mode 100644 src/clip.ts delete mode 100644 src/convert.ts delete mode 100644 src/id.ts delete mode 100644 src/s2/convert.ts delete mode 100644 src/s2/index.ts delete mode 100644 src/s2/s2Coords.ts delete mode 100644 src/s2/s2Point.ts delete mode 100644 src/simplify.ts delete mode 100644 src/tile.ts delete mode 100644 src/util.ts delete mode 100644 src/wm/convert.ts delete mode 100644 src/wm/index.ts delete mode 100644 src/wm/mercCoords.ts delete mode 100644 test/bbox.test.ts delete mode 100644 test/clip.test.ts delete mode 100644 test/convert.test.ts delete mode 100644 test/id.test.ts delete mode 100644 test/s2/convert.test.ts delete mode 100644 test/s2/s2Coords.test.ts delete mode 100644 test/s2/s2Point.test.ts delete mode 100644 test/schema.test.ts delete mode 100644 test/simplify.test.ts delete mode 100644 test/tile.test.ts delete mode 100644 test/util.test.ts delete mode 100644 test/values.test.ts delete mode 100644 test/wm/convert.test.ts delete mode 100644 test/wm/mercCoords.test.ts diff --git a/.github/workflows/coveralls.yml b/.github/workflows/coveralls.yml deleted file mode 100644 index 4626147..0000000 --- a/.github/workflows/coveralls.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: coveralls - -on: push - -jobs: - test: - name: Coveralls Upload - runs-on: ubuntu-latest - - # setup - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Set up Bun - uses: oven-sh/setup-bun@v1 - with: - bun-version: 1.1.26 - - - name: Install dependencies - run: bun install - - # Bun tests - - - name: Run JavaScript/TypeScript tests - run: bun run test - - - name: Set up Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - - # setup rust - - - name: Install Clippy - run: rustup component add clippy - - - name: Install llvm-tools-preview - run: rustup component add llvm-tools-preview - - - name: Install grcov - run: | - sudo apt-get install lcov - cargo install grcov - - - name: Build Rust project - run: cargo build - shell: bash - - # Rust tests - - - name: Run Rust tests with coverage - run: | - export CARGO_INCREMENTAL=0 - export RUSTFLAGS='-Cinstrument-coverage' - export LLVM_PROFILE_FILE='cargo-test-%p-%m.profraw' - cargo test --lib - grcov . --binary-path ./target/debug/ -s . -t lcov --branch --ignore-not-existing --ignore "/*/.cargo/*" --ignore "/*/target/*" -o ./coverage/rust-lcov.info - shell: bash - - # Merge LCOV reports - - - name: Merge Bun and Rust LCOV reports - run: | - mkdir -p coverage - if [ -f ./coverage/lcov.info ] && [ -f ./coverage/rust-lcov.info ]; then - cat ./coverage/rust-lcov.info >> ./coverage/lcov.info - elif [ -f ./coverage/rust-lcov.info ]; then - mv ./coverage/rust-lcov.info ./coverage/lcov.info - fi - - # upload to Coveralls - - - name: Upload coverage to Coveralls - run: | - if [ -f ./coverage/lcov.info ]; then - bun run coveralls < ./coverage/lcov.info - fi - env: - COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..0383101 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,44 @@ +name: pages-workflow + +on: + push: + branches: + - master + + # This event is required to deploy to GitHub Pages with actions/deploy-pages@v4 + workflow_run: + workflows: ["Generate and Deploy Docs"] + types: + - completed + +jobs: + generate-docs: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: 1.1.29 + + - name: Build the docs + run: bun run docs + + - name: Upload artifacts + uses: actions/upload-pages-artifact@v1 + with: + path: ./docs # Path to the docs folder + + deploy-docs: + runs-on: ubuntu-latest + needs: generate-docs + permissions: + pages: write # Needed to deploy to GitHub Pages + id-token: write # Needed for authentication + + steps: + - name: Deploy to GitHub Pages + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4ec0cb2..c43a34b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,19 +6,19 @@ jobs: test: name: Test runs-on: ${{ matrix.os }} - + strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Bun uses: oven-sh/setup-bun@v1 with: - bun-version: 1.1.26 + bun-version: 1.1.29 - name: Install dependencies run: bun install @@ -31,7 +31,7 @@ jobs: with: toolchain: stable override: true - + - name: Install Rustfmt run: rustup component add rustfmt diff --git a/.gitignore b/.gitignore index 6d01fda..41823a6 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ server/out/** coverage/** *.profraw cobertura.xml +docs/ diff --git a/README.md b/README.md index 6729e98..f824929 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,6 @@ docs-rust - - code-coverage - Discord @@ -88,6 +85,10 @@ Notable features of S2JSON are: [s2json-spec](/s2json-spec/1.0.0/README.md) +## Implementations + +* [s2-tools](https://github.com/Open-S2/s2-tools) + ## Install ```bash diff --git a/eslint.config.mjs b/eslint.config.mjs index ae3ba65..1014859 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -24,6 +24,31 @@ export default tseslint.config( jsdoc.configs['flat/recommended-typescript'], { rules: { + // ensure explicit comparisons + eqeqeq: ['error', 'always'], + 'no-implicit-coercion': ['error', { boolean: false, number: true, string: true }], + '@typescript-eslint/strict-boolean-expressions': [ + 'error', + { + allowString: false, + allowNumber: false, + allowNullableObject: false, + allowNullableBoolean: false, + allowNullableString: false, + allowNullableNumber: false, + allowAny: false, + }, + ], + 'no-extra-boolean-cast': 'error', + 'no-constant-condition': ['error', { checkLoops: false }], + 'no-unused-expressions': ['error', { allowTernary: true, allowShortCircuit: true }], + // manage promises correctly + '@typescript-eslint/no-misused-promises': 'error', + 'no-async-promise-executor': 'error', + '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/prefer-promise-reject-errors': 'error', + '@typescript-eslint/promise-function-async': 'error', + 'require-await': 'error', // console logs 'no-console': ['error', { allow: ['info', 'warn', 'error'] }], // https://github.com/gajus/eslint-plugin-jsdoc diff --git a/src/bbox.ts b/src/bbox.ts deleted file mode 100644 index d3f1793..0000000 --- a/src/bbox.ts +++ /dev/null @@ -1,95 +0,0 @@ -import type { BBOX, BBox, BBox3D, VectorPoint } from './'; - -/** - * @param point - input vector point - * @returns - BBox of the point - */ -export function fromPoint(point: VectorPoint): BBOX { - const { x, y, z } = point; - if (z !== undefined) return [x, y, x, y, z, z] as BBox3D; - return [x, y, x, y] as BBox; -} - -/** - * Checks if a point is within a bounding box - * @param bbox - the bounding box to test - * @param point - point to test if it exists within the bbox - * @returns - true if the point is within the bbox, false otherwise - */ -export function pointOverlap(bbox: BBox, point: VectorPoint): boolean { - const [left, bottom, right, top] = bbox; - return point.x >= left && point.x <= right && point.y >= bottom && point.y <= top; -} - -/** - * Checks if two bounding boxes overlap. If they don't overlap, returns undefined. - * If they do, return the overlap - * @param b1 - first bounding box - * @param b2 - second bounding box - * @returns - undefined if no overlap, or a bbox of the overlap - */ -export function bboxOverlap(b1: BBox, b2: BBox): undefined | BBox { - // check if the bboxes overlap at all - if (b2[2] < b1[0] || b1[2] < b2[0] || b2[3] < b1[1] || b1[3] < b2[1]) return; - // find the middle two X values - const left = b1[0] < b2[0] ? b2[0] : b1[0]; - const right = b1[2] < b2[2] ? b1[2] : b2[2]; - // find the middle two Y values - const bottom = b1[1] < b2[1] ? b2[1] : b1[1]; - const top = b1[3] < b2[3] ? b1[3] : b2[3]; - - return [left, bottom, right, top]; -} - -/** - * @param bbox - the bounding box to extend, if it doesn't exist it will be created otherwise just modified - * @param point - the point to add to the bbox - * @returns - the extended bbox - */ -export function extendBBox(bbox: BBOX | undefined, point: VectorPoint): BBOX { - bbox = bbox ?? fromPoint(point); - bbox = mergeBBoxes(bbox, fromPoint(point)); - return bbox; -} - -/** - * Merges two bounding boxes into the first and returns the result - * @param b1 - the first bounding box - * @param b2 - the second bounding box - * @returns - the merged bounding box - */ -export function mergeBBoxes(b1: BBOX, b2: BBOX): BBOX { - const { min, max } = Math; - b1[0] = min(b1[0] ?? b2[0], b2[0]); - b1[1] = min(b1[1] ?? b2[1], b2[1]); - b1[2] = max(b1[2] ?? b2[2], b2[2]); - b1[3] = max(b1[3] ?? b2[3], b2[3]); - if (b1.length > 4 || b2.length > 4) { - b1[4] = min(b1[4] ?? 0, b2[4] ?? 0); - b1[5] = max(b1[5] ?? 0, b2[5] ?? 0); - } - - return b1; -} - -/** - * Create a new bounding box clipped by the axis and min-max - * @param bb - the original bounding box - * @param axis - 0 for x, 1 for y - * @param k1 - the lower bound - * @param k2 - the upper bound - * @returns the new bounding box clipped by the axis and min-max - */ -export function clipBBox(bb: BBOX | undefined, axis: 0 | 1, k1: number, k2: number): BBOX { - const { min, max } = Math; - const newBox: BBOX = bb !== undefined ? [...bb] : [0, 0, 0, 0]; - if (axis === 0) { - newBox[0] = max(newBox[0], k1); - newBox[2] = min(newBox[2], k2); - } else { - newBox[1] = max(newBox[1], k1); - newBox[3] = min(newBox[3], k2); - } - - return newBox; -} diff --git a/src/clip.ts b/src/clip.ts deleted file mode 100644 index e21af1b..0000000 --- a/src/clip.ts +++ /dev/null @@ -1,513 +0,0 @@ -import { Tile } from '.'; -import { childrenIJ, toFaceIJ } from './id'; -import { clipBBox, extendBBox } from './bbox'; - -import type { - BBOX, - BBox, - MValue, - VectorFeatures, - VectorGeometry, - VectorLineString, - VectorLineStringGeometry, - VectorMultiLineOffset, - VectorMultiLineStringGeometry, - VectorMultiPointGeometry, - VectorMultiPolygonGeometry, - VectorMultiPolygonOffset, - VectorPoint, - VectorPointGeometry, - VectorPolygon, - VectorPolygonGeometry, -} from './'; - -/** Split features into the 4 children of a tile */ -export type TileChildren = [ - Tile, // bottom left - Tile, // bottom right - Tile, // top left - Tile, // top right -]; - -/** - * @param tile - the tile to split - * @param buffer - the buffer around the tile for lines and polygons - * @returns - the tile's children split into 4 sub-tiles - */ -export function splitTile(tile: Tile, buffer: number = 0.0625): TileChildren { - const { id } = tile; - const [face, zoom, i, j] = toFaceIJ(id); - const [blID, brID, tlID, trID] = childrenIJ(face, zoom, i, j); - const children: TileChildren = [new Tile(blID), new Tile(brID), new Tile(tlID), new Tile(trID)]; - const scale = 1 << zoom; - const k1 = 0; - const k2 = 0.5; - const k3 = 0.5; - const k4 = 1; - - let tl: null | VectorFeatures[] = null; - let bl: null | VectorFeatures[] = null; - let tr: null | VectorFeatures[] = null; - let br: null | VectorFeatures[] = null; - - for (const [name, { features }] of Object.entries(tile.layers)) { - const left = _clip(features, scale, i - k1, i + k3, 0, buffer); - const right = _clip(features, scale, i + k2, i + k4, 0, buffer); - - if (left !== null) { - bl = _clip(left, scale, j - k1, j + k3, 1, buffer); - tl = _clip(left, scale, j + k2, j + k4, 1, buffer); - if (bl !== null) for (const d of bl) children[0].addFeature(d, name); - if (tl !== null) for (const d of tl) children[2].addFeature(d, name); - } - - if (right !== null) { - br = _clip(right, scale, j - k1, j + k3, 1, buffer); - tr = _clip(right, scale, j + k2, j + k4, 1, buffer); - if (br !== null) for (const d of br) children[1].addFeature(d, name); - if (tr !== null) for (const d of tr) children[3].addFeature(d, name); - } - } - - return children; -} - -/** - * @param features - input features to clip - * @param scale - the tile scale - * @param k1 - minimum accepted value of the axis - * @param k2 - maximum accepted value of the axis - * @param axis - the axis 0 for x, 1 for y - * @param baseBuffer - the top level buffer value - * @returns - the clipped features - */ -function _clip( - features: VectorFeatures[], - scale: number, - k1: number, - k2: number, - axis: 0 | 1, - baseBuffer: number, -): null | VectorFeatures[] { - // scale - k1 /= scale; - k2 /= scale; - // prep buffer and result container - const buffer = baseBuffer / scale; - const k1b = k1 - buffer; - const k2b = k2 + buffer; - const clipped: VectorFeatures[] = []; - - for (const feature of features) { - const { geometry } = feature; - const { type } = geometry; - // build the new clipped geometry - let newGeometry: VectorGeometry | undefined = undefined; - if (type === 'Point') newGeometry = clipPoint(geometry, axis, k1, k2); - else if (type === 'MultiPoint') newGeometry = clipMultiPoint(geometry, axis, k1, k2); - else if (type === 'LineString') newGeometry = clipLineString(geometry, axis, k1b, k2b); - else if (type === 'MultiLineString') - newGeometry = clipMultiLineString(geometry, axis, k1b, k2b); - else if (type === 'Polygon') newGeometry = clipPolygon(geometry, axis, k1b, k2b); - else if (type === 'MultiPolygon') newGeometry = clipMultiPolygon(geometry, axis, k1b, k2b); - // store if the geometry was inside the range - if (newGeometry !== undefined) { - newGeometry.vecBBox = clipBBox(newGeometry.vecBBox, axis, k1b, k2b); - clipped.push({ ...feature, geometry: newGeometry }); - } - } - - return clipped.length > 0 ? clipped : null; -} - -/** - * @param geometry - input vector geometry - * @param axis - 0 for x, 1 for y - * @param k1 - minimum accepted value of the axis - * @param k2 - maximum accepted value of the axis - * @returns - the clipped geometry or undefined if the geometry was not inside the range - */ -export function clipPoint( - geometry: VectorPointGeometry, - axis: 0 | 1, - k1: number, - k2: number, -): VectorPointGeometry | undefined { - const { type, is3D, coordinates, bbox, vecBBox } = geometry; - const value = axis === 0 ? coordinates.x : coordinates.y; - if (value >= k1 && value < k2) - return { type, is3D, coordinates: { ...coordinates }, bbox, vecBBox }; -} - -/** - * @param geometry - input vector geometry - * @param axis - 0 for x, 1 for y - * @param k1 - minimum accepted value of the axis - * @param k2 - maximum accepted value of the axis - * @returns - the clipped geometry or undefined if the geometry was not inside the range - */ -function clipMultiPoint( - geometry: VectorMultiPointGeometry, - axis: 0 | 1, - k1: number, - k2: number, -): VectorMultiPointGeometry | undefined { - const { type, is3D, coordinates, bbox } = geometry; - let vecBBox: BBOX | undefined = undefined; - const points = coordinates - .filter((point) => { - const value = axis === 0 ? point.x : point.y; - return value >= k1 && value < k2; - }) - .map((p) => ({ ...p })); - points.forEach((p) => (vecBBox = extendBBox(vecBBox, p))); - - if (points.length > 0) return { type, is3D, coordinates: points, bbox, vecBBox }; -} - -/** - * @param geometry - input vector geometry - * @param axis - 0 for x, 1 for y - * @param k1 - minimum accepted value of the axis - * @param k2 - maximum accepted value of the axis - * @returns - the clipped geometry or undefined if the geometry was not inside the range - */ -function clipLineString( - geometry: VectorLineStringGeometry, - axis: 0 | 1, - k1: number, - k2: number, -): VectorMultiLineStringGeometry | undefined { - const { is3D, coordinates: line, bbox, vecBBox } = geometry; - const initO = geometry.offset ?? 0; - const newOffsets: VectorMultiLineOffset = []; - const newLines: VectorLineString[] = []; - for (const clip of _clipLine({ line, offset: initO }, k1, k2, axis, false)) { - newOffsets.push(clip.offset); - newLines.push(clip.line); - } - if (newLines.length === 0) return undefined; - return { - type: 'MultiLineString', - is3D, - coordinates: newLines, - bbox, - offset: newOffsets, - vecBBox, - }; -} - -/** - * @param geometry - input vector geometry - * @param axis - 0 for x, 1 for y - * @param k1 - minimum accepted value of the axis - * @param k2 - maximum accepted value of the axis - * @param isPolygon - true if the geometry is a polygon - * @returns - the clipped geometry or undefined if the geometry was not inside the range - */ -function clipMultiLineString( - geometry: VectorMultiLineStringGeometry | VectorPolygonGeometry, - axis: 0 | 1, - k1: number, - k2: number, - isPolygon = false, -): VectorMultiLineStringGeometry | VectorPolygonGeometry | undefined { - const { is3D, coordinates, bbox, vecBBox } = geometry; - const initO = geometry.offset ?? coordinates.map((_) => 0); - const newOffsets: VectorMultiLineOffset = []; - const newLines: VectorLineString[] = []; - coordinates.forEach((line, i) => { - for (const clip of _clipLine({ line, offset: initO[i] }, k1, k2, axis, isPolygon)) { - newOffsets.push(clip.offset); - newLines.push(clip.line); - } - }); - if (newLines.length === 0 || (isPolygon && newLines[0].length === 0)) return undefined; - return { - type: isPolygon ? 'Polygon' : 'MultiLineString', - is3D, - coordinates: newLines, - bbox, - offset: newOffsets, - vecBBox, - }; -} - -/** - * @param geometry - input vector geometry - * @param axis - 0 for x, 1 for y - * @param k1 - minimum accepted value of the axis - * @param k2 - maximum accepted value of the axis - * @returns - the clipped geometry or undefined if the geometry was not inside the range - */ -function clipPolygon( - geometry: VectorPolygonGeometry, - axis: 0 | 1, - k1: number, - k2: number, -): VectorPolygonGeometry | undefined { - return clipMultiLineString(geometry, axis, k1, k2, true) as VectorPolygonGeometry | undefined; -} - -/** - * @param geometry - input vector geometry - * @param axis - 0 for x, 1 for y - * @param k1 - minimum accepted value of the axis - * @param k2 - maximum accepted value of the axis - * @returns - the clipped geometry or undefined if the geometry was not inside the range - */ -function clipMultiPolygon( - geometry: VectorMultiPolygonGeometry, - axis: 0 | 1, - k1: number, - k2: number, -): VectorMultiPolygonGeometry | undefined { - const { is3D, coordinates, bbox, vecBBox } = geometry; - const initO = geometry.offset ?? coordinates.map((l) => l.map(() => 0)); - const newCoordinates: VectorPolygon[] = []; - const newOffsets: VectorMultiPolygonOffset = []; - coordinates.forEach((polygon, p) => { - const newPolygon = clipPolygon( - { type: 'Polygon', is3D, coordinates: polygon, bbox, offset: initO[p] }, - axis, - k1, - k2, - ); - if (newPolygon !== undefined) { - newCoordinates.push(newPolygon.coordinates); - if (newPolygon.offset !== undefined) newOffsets.push(newPolygon.offset); - } - }); - if (newCoordinates.length === 0) return undefined; - return { - type: 'MultiPolygon', - is3D, - coordinates: newCoordinates, - bbox, - vecBBox, - offset: newOffsets, - }; -} - -/** - * After clipping a line, return the altered line, - * the offset the new line starts at, - * and if the line is ccw - */ -export interface ClipLineResult { - line: VectorLineString; - offset: number; - vecBBox?: BBOX; -} -/** Ensuring `vecBBox` exists */ -export interface ClipLineResultWithBBox { - line: VectorLineString; - offset: number; - vecBBox: BBOX; -} - -/** - * Data should always be in a 0->1 coordinate system to use this clip function - * @param geom - the original geometry line - * @param bbox - the bounding box to clip the line to - * @param isPolygon - true if the line comes from a polygon - * @param offset - the starting offset the line starts at - * @param buffer - the buffer to apply to the line (spacing outside the bounding box) - * @returns - the clipped geometry - */ -export function clipLine( - geom: VectorLineString, - bbox: BBox, - isPolygon: boolean, - offset: number = 0, - buffer: number = 0.0625, // default for a full size tile. Assuming 1024 extent and a 64 point buffer -): ClipLineResultWithBBox[] { - const res: ClipLineResult[] = []; - const [left, bottom, right, top] = bbox; - // clip horizontally - const horizontalClips = _clipLine( - { line: geom, offset, vecBBox: [0, 0, 0, 0] }, - left - buffer, - right + buffer, - 0, - isPolygon, - ); - for (const clip of horizontalClips) { - // clip vertically - res.push(..._clipLine(clip, bottom - buffer, top + buffer, 1, isPolygon)); - } - return res.map((clip) => { - let vecBBox: BBOX | undefined; - for (const p of clip.line) vecBBox = extendBBox(vecBBox, p); - clip.vecBBox = vecBBox; - return clip; - }) as ClipLineResultWithBBox[]; -} - -/** - * @param input - the original geometry line - * @param k1 - the lower bound - * @param k2 - the upper bound - * @param axis - 0 for x, 1 for y - * @param isPolygon - true if the line comes from a polygon - * @returns - the clipped geometry - */ -function _clipLine( - input: ClipLineResult, - k1: number, - k2: number, - axis: 0 | 1, - isPolygon: boolean, -): ClipLineResult[] { - const { line: geom, offset: startOffset } = input; - const newGeom: ClipLineResult[] = []; - let slice: VectorLineString = []; - let last = geom.length - 1; - const intersect = axis === 0 ? intersectX : intersectY; - - let curOffset = startOffset; - let accOffset = startOffset; - let prevP = geom[0]; - let firstEnter = false; - - for (let i = 0; i < last; i++) { - const { x: ax, y: ay, z: az, m: am } = geom[i]; - const { x: bx, y: by, z: bz, m: bm } = geom[i + 1]; - const a = axis === 0 ? ax : ay; - const b = axis === 0 ? bx : by; - const z = az && bz ? (az + bz) / 2 : az ? az : bz ? bz : undefined; - let entered = false; - let exited = false; - let intP: VectorPoint | undefined; - - // ENTER OR CONTINUE CASES - if (a < k1) { - // ---|--> | (line enters the clip region from the left) - if (b > k1) { - intP = intersect(ax, ay, bx, by, k1, z, bm); - slice.push(intP); - entered = true; - } - } else if (a > k2) { - // | <--|--- (line enters the clip region from the right) - if (b < k2) { - intP = intersect(ax, ay, bx, by, k2, z, bm); - slice.push(intP); - entered = true; - } - } else { - intP = { x: ax, y: ay, z: az, m: am }; - slice.push(intP); - } - - // Update the intersection point and offset if the intP exists - if (intP) { - // our first enter will change the offset for the line - if (entered && !firstEnter) { - curOffset = accOffset + distance(prevP, intP); - firstEnter = true; - } - } - - // EXIT CASES - if (b < k1 && a >= k1) { - // <--|--- | or <--|-----|--- (line exits the clip region on the left) - intP = intersect(ax, ay, bx, by, k1, z, bm ?? am); - slice.push(intP); - exited = true; - } - if (b > k2 && a <= k2) { - // | ---|--> or ---|-----|--> (line exits the clip region on the right) - intP = intersect(ax, ay, bx, by, k2, z, bm ?? am); - slice.push(intP); - exited = true; - } - - // update the offset - accOffset += distance(prevP, geom[i + 1]); - prevP = geom[i + 1]; - - // If not a polygon, we can cut it into parts, otherwise we just keep tracking the edges - if (!isPolygon && exited) { - newGeom.push({ line: slice, offset: curOffset }); - slice = []; - firstEnter = false; - } - } - - // add the last point if inside the clip - const lastPoint = geom[last]; - const a = axis === 0 ? lastPoint.x : lastPoint.y; - if (a >= k1 && a <= k2) slice.push({ ...lastPoint }); - - // close the polygon if its endpoints are not the same after clipping - if (slice.length > 0 && isPolygon) { - last = slice.length - 1; - const firstP = slice[0]; - if (last >= 1 && (slice[last].x !== firstP.x || slice[last].y !== firstP.y)) { - slice.push({ ...firstP }); - } - } - - // add the final slice - if (slice.length > 0) newGeom.push({ line: slice, offset: curOffset }); - - return newGeom; -} - -/** - * @param ax - the first x - * @param ay - the first y - * @param bx - the second x - * @param by - the second y - * @param x - the x to intersect - * @param z - the elevation if it exists - * @param m - the MValue - * @returns - the intersecting point - */ -function intersectX( - ax: number, - ay: number, - bx: number, - by: number, - x: number, - z?: number, - m?: MValue, -): VectorPoint { - const t = (x - ax) / (bx - ax); - return { x, y: ay + (by - ay) * t, z, m, t: 1 }; -} - -/** - * @param ax - the first x - * @param ay - the first y - * @param bx - the second x - * @param by - the second y - * @param y - the y to intersect - * @param z - the elevation if it exists - * @param m - the MValue - * @returns - the intersecting point - */ -function intersectY( - ax: number, - ay: number, - bx: number, - by: number, - y: number, - z?: number, - m?: MValue, -): VectorPoint { - const t = (y - ay) / (by - ay); - return { x: ax + (bx - ax) * t, y, z, m, t: 1 }; -} - -/** - * Calculate the Euclidean distance between two points. - * @param p1 - The first point. - * @param p2 - The second point. - * @returns - The distance between the points. - */ -function distance(p1: VectorPoint, p2: VectorPoint): number { - const { sqrt, pow } = Math; - return sqrt(pow(p2.x - p1.x, 2) + pow(p2.y - p1.y, 2)); -} diff --git a/src/convert.ts b/src/convert.ts deleted file mode 100644 index 214ce76..0000000 --- a/src/convert.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { toWM } from './s2'; -import { toS2, toUnitScale, toVector } from './wm'; - -import type { - Feature, - JSONCollection, - Projection, - S2Feature, - VectorFeature, - VectorFeatures, -} from '.'; - -/** - * @param projection - output either S2 or WM - * @param data - the data to convert - * @param tolerance - optionally specify a tolerance to prepare for future simplification - * @param maxzoom - optionally specify a maxzoom to prepare for future simplification - * @param buildBBox - optional - build a bbox for the feature if desired - * @returns - the converted data - */ -export function convert( - projection: Projection, - data: JSONCollection, - tolerance?: number, - maxzoom?: number, - buildBBox?: boolean, -): VectorFeatures[] { - const res: VectorFeatures[] = []; - - if (data.type === 'Feature') { - res.push(...convertFeature(projection, data, tolerance, maxzoom, buildBBox)); - } else if (data.type === 'VectorFeature') { - res.push(...convertVectorFeature(projection, data, tolerance, maxzoom)); - } else if (data.type === 'FeatureCollection') { - for (const feature of data.features) { - if (feature.type === 'Feature') - res.push(...convertFeature(projection, feature, tolerance, maxzoom, buildBBox)); - else res.push(...convertVectorFeature(projection, feature, tolerance, maxzoom)); - } - } else if (data.type === 'S2Feature') { - res.push(convertS2Feature(projection, data, tolerance, maxzoom)); - } else if (data.type === 'S2FeatureCollection') { - for (const feature of data.features) { - res.push(convertS2Feature(projection, feature, tolerance, maxzoom)); - } - } - - return res; -} - -/** - * @param projection - either S2 or WM is the end goal feature - * @param data - input feature data - * @param tolerance - optionally specify a tolerance to prepare for future simplification - * @param maxzoom - optionally specify a maxzoom to prepare for future simplification - * @param buildBBox - optional - build a bbox for the feature if desired - * @returns - converted feature - */ -function convertFeature( - projection: Projection, - data: Feature, - tolerance?: number, - maxzoom?: number, - buildBBox?: boolean, -): VectorFeatures[] { - if (projection === 'WM') { - const vf = toVector(data, buildBBox); - toUnitScale(vf, tolerance, maxzoom); - return [vf]; - } else { - return toS2(data, tolerance, maxzoom, buildBBox); - } -} - -/** - * @param projection - either S2 or WM is the end goal feature - * @param data - input feature data - * @param tolerance - optionally specify a tolerance to prepare for future simplification - * @param maxzoom - optionally specify a maxzoom to prepare for future simplification - * @returns - converted feature(s) - */ -function convertVectorFeature( - projection: Projection, - data: VectorFeature, - tolerance?: number, - maxzoom?: number, -): VectorFeatures[] { - if (projection === 'WM') { - toUnitScale(data, tolerance, maxzoom); - return [data]; - } else { - return toS2(data, tolerance, maxzoom); - } -} - -/** - * @param projection - either S2 or WM is the end goal feature - * @param data - input feature data - * @param tolerance - optionally specify a tolerance to prepare for future simplification - * @param maxzoom - optionally specify a maxzoom to prepare for future simplification - * @returns - converted feature - */ -function convertS2Feature( - projection: Projection, - data: S2Feature, - tolerance?: number, - maxzoom?: number, -): VectorFeatures { - if (projection === 'WM') { - const vf = toWM(data); - toUnitScale(vf, tolerance, maxzoom); - return vf; - } else { - return data; - } -} diff --git a/src/geometry.spec.ts b/src/geometry.spec.ts index e7ef760..4bb7a3a 100644 --- a/src/geometry.spec.ts +++ b/src/geometry.spec.ts @@ -52,7 +52,7 @@ export type LineString = Point[]; /** Definition of a MultiLineString */ export type MultiLineString = LineString[]; /** Definition of a Polygon */ -export type Polygon = Point[][]; +export type Polygon = LineString[]; /** Definition of a MultiPolygon */ export type MultiPolygon = Polygon[]; /** Definition of a 3D Point. May represent WebMercator Lon-Lat or S2Geometry S-T with a z-value */ @@ -204,33 +204,33 @@ export type MultiPolygon3DGeometry = BaseGeometry< /// Vector Types /** Definition of a Vector Point */ -export interface VectorPoint { +export interface VectorPoint { x: number; y: number; z?: number; - m?: MValue; + m?: M; // t for tolerance. A tmp value used for simplification t?: number; } /** Definition of a Vector MultiPoint */ -export type VectorMultiPoint = VectorPoint[]; +export type VectorMultiPoint = VectorPoint[]; /** Definition of a Vector LineString */ -export type VectorLineString = VectorPoint[]; +export type VectorLineString = VectorPoint[]; /** Definition of a Vector MultiLineString */ -export type VectorMultiLineString = VectorLineString[]; +export type VectorMultiLineString = VectorLineString[]; /** Definition of a Vector Polygon */ -export type VectorPolygon = VectorLineString[]; +export type VectorPolygon = VectorLineString[]; /** Definition of a Vector MultiPolygon */ -export type VectorMultiPolygon = VectorPolygon[]; +export type VectorMultiPolygon = VectorPolygon[]; /** All possible geometry coordinates */ -export type VectorCoordinates = - | VectorPoint - | VectorMultiPoint - | VectorLineString - | VectorMultiLineString - | VectorPolygon - | VectorMultiPolygon; +export type VectorCoordinates = + | VectorPoint + | VectorMultiPoint + | VectorLineString + | VectorMultiLineString + | VectorPolygon + | VectorMultiPolygon; /** All possible geometry types */ export type VectorGeometryType = @@ -241,9 +241,9 @@ export type VectorGeometryType = | 'Polygon' | 'MultiPolygon'; /** All possible geometry shapes */ -export type VectorGeometry = - | VectorPointGeometry - | VectorMultiPointGeometry +export type VectorGeometry = + | VectorPointGeometry + | VectorMultiPointGeometry | VectorLineStringGeometry | VectorMultiLineStringGeometry | VectorPolygonGeometry @@ -284,37 +284,47 @@ export type VectorPolygonOffset = VectorLineOffset[]; export type VectorMultiPolygonOffset = VectorPolygonOffset[]; /** PointGeometry is a point */ -export type VectorPointGeometry = VectorBaseGeometry<'Point', VectorPoint, undefined, BBOX>; +export type VectorPointGeometry = VectorBaseGeometry< + 'Point', + VectorPoint, + undefined, + BBOX +>; /** MultiPointGeometry contains multiple points */ -export type VectorMultiPointGeometry = VectorBaseGeometry< +export type VectorMultiPointGeometry = VectorBaseGeometry< 'MultiPoint', - VectorMultiPoint, + VectorMultiPoint, undefined, BBOX >; /** LineStringGeometry is a line */ -export type VectorLineStringGeometry = VectorBaseGeometry< +export type VectorLineStringGeometry = VectorBaseGeometry< 'LineString', - VectorLineString, + VectorLineString, VectorLineOffset, BBOX >; /** MultiLineStringGeometry contians multiple lines */ -export type VectorMultiLineStringGeometry = VectorBaseGeometry< +export type VectorMultiLineStringGeometry = VectorBaseGeometry< 'MultiLineString', - VectorMultiLineString, + VectorMultiLineString, VectorMultiLineOffset, BBOX >; /** PolygonGeometry is a polygon with potential holes */ -export interface VectorPolygonGeometry - extends VectorBaseGeometry<'Polygon', VectorPolygon, VectorPolygonOffset, BBOX> { +export interface VectorPolygonGeometry + extends VectorBaseGeometry<'Polygon', VectorPolygon, VectorPolygonOffset, BBOX> { indices?: number[]; tesselation?: number[]; } /** MultiPolygonGeometry is a polygon with multiple polygons with their own potential holes */ -export interface VectorMultiPolygonGeometry - extends VectorBaseGeometry<'MultiPolygon', VectorMultiPolygon, VectorMultiPolygonOffset, BBOX> { +export interface VectorMultiPolygonGeometry + extends VectorBaseGeometry< + 'MultiPolygon', + VectorMultiPolygon, + VectorMultiPolygonOffset, + BBOX + > { indices?: number[]; tesselation?: number[]; } diff --git a/src/id.ts b/src/id.ts deleted file mode 100644 index 1f31068..0000000 --- a/src/id.ts +++ /dev/null @@ -1,692 +0,0 @@ -/** COMPONENTS */ -import { - IJtoST, - STtoIJ, - quadraticSTtoUV as STtoUV, - SiTiToST, - quadraticUVtoST as UVtoST, - XYZtoFaceUV, - faceUVtoXYZ, - lonLatToXYZ, - xyzToLonLat, -} from './s2/s2Coords'; -import { toIJ as S2PointToIJ, fromS2CellID } from './s2/s2Point'; - -import type { BBox, Face, Point3D } from './'; - -/** CONSTANTS */ -const LOOKUP_POS: bigint[] = []; -const LOOKUP_IJ: bigint[] = []; -const FACE_BITS = 3n; -const NUM_FACES = 6n; -const MAX_LEVEL = 30n; -const POS_BITS = 61n; -const K_WRAP_OFFSET = 13835058055282163712n; -const K_MAX_SIZE = 1073741824; - -/** INITIALIZATION */ -for (let i = 0; i < 4; i++) initLookupCell(0, 0, 0, i, 0, i); -/** - * @param level - zoom level of the cell - * @param i - x coord - * @param j - y coord - * @param origOrientation - original orientation - * @param pos - position - * @param orientation - orientation - */ -function initLookupCell( - level: number, - i: number, - j: number, - origOrientation: number, - pos: number, - orientation: number, -): void { - const kPosToOriengation = [1, 0, 0, 3]; - const kPosToIJ = [ - [0, 1, 3, 2], - [0, 2, 3, 1], - [3, 2, 0, 1], - [3, 1, 0, 2], - ]; - if (level === 4) { - const ij = (i << 4) + j; - LOOKUP_POS[(ij << 2) + origOrientation] = BigInt((pos << 2) + orientation); - LOOKUP_IJ[(pos << 2) + origOrientation] = BigInt((ij << 2) + orientation); - } else { - level++; - i <<= 1; - j <<= 1; - pos <<= 2; - const r = kPosToIJ[orientation]; - initLookupCell( - level, - i + (r[0] >> 1), - j + (r[0] & 1), - origOrientation, - pos, - orientation ^ kPosToOriengation[0], - ); - initLookupCell( - level, - i + (r[1] >> 1), - j + (r[1] & 1), - origOrientation, - pos + 1, - orientation ^ kPosToOriengation[1], - ); - initLookupCell( - level, - i + (r[2] >> 1), - j + (r[2] & 1), - origOrientation, - pos + 2, - orientation ^ kPosToOriengation[2], - ); - initLookupCell( - level, - i + (r[3] >> 1), - j + (r[3] & 1), - origOrientation, - pos + 3, - orientation ^ kPosToOriengation[3], - ); - } -} - -/** - * Create a default S2CellID given a face on the sphere [0-6) - * @param face - the face - * @returns the S2CellID - */ -export function fromFace(face: Face): bigint { - return (BigInt(face) << POS_BITS) + (1n << 60n); -} - -/** - * Create an S2CellID from a lon-lat coordinate - * @param lon - longitude - * @param lat - latitude - * @returns the S2CellID - */ -export function fromLonLat(lon: number, lat: number): bigint { - const xyz = lonLatToXYZ(lon, lat); - return fromS2Point(xyz); -} - -/** - * Create an S2CellID from an XYZ Point - * @param xyz - 3D input vector - * @returns the S2CellID - */ -export function fromS2Point(xyz: Point3D): bigint { - // convert to face-i-j - const [face, i, j] = S2PointToIJ(xyz); - // now convert from ij - return fromIJ(face, i, j); -} - -/** - * Create an S2CellID from an Face-U-V coordinate - * @param face - the face - * @param u - u coordinate - * @param v - v coordinate - * @returns the S2CellID - */ -export function fromUV(face: Face, u: number, v: number): bigint { - // now convert from st - return fromST(face, UVtoST(u), UVtoST(v)); -} - -/** - * Create an S2CellID from an Face-S-T coordinate - * @param face - the face - * @param s - s coordinate - * @param t - t coordinate - * @returns the S2CellID - */ -export function fromST(face: Face, s: number, t: number): bigint { - // now convert from ij - return fromIJ(face, STtoIJ(s), STtoIJ(t)); -} - -/** - * Create an S2CellID given a distance and level (zoom). Default level is 30n - * @param distance - distance - * @param level - level - * @returns the S2CellID - */ -export function fromDistance(distance: bigint, level = MAX_LEVEL): bigint { - level = 2n * (MAX_LEVEL - level); - return (distance << (level + 1n)) + (1n << level); -} - -/** - * @param id - the S2CellID - * @returns [face, zoom, i, j] - */ -export function toFaceIJ(id: bigint): [face: Face, zoom: number, i: number, j: number] { - const zoom = level(id); - const [face, i, j] = toIJ(id, zoom); - return [face, zoom, i, j]; -} - -/** - * Create an S2CellID from an Face-I-J coordinate and map it to a zoom if desired. - * @param face - the face - * @param i - i coordinate - * @param j - j coordinate - * @param level - zoom level - * @returns the S2CellID - */ -export function fromIJ(face: Face, i: number, j: number, level?: number): bigint { - const bigFace = BigInt(face); - let bigI = BigInt(i); - let bigJ = BigInt(j); - if (level !== undefined) { - const levelB = BigInt(level); - bigI = bigI << (MAX_LEVEL - levelB); - bigJ = bigJ << (MAX_LEVEL - levelB); - } - let n = bigFace << 60n; - // Alternating faces have opposite Hilbert curve orientations; this - // is necessary in order for all faces to have a right-handed - // coordinate system. - let bits = bigFace & 1n; - // Each iteration maps 4 bits of "i" and "j" into 8 bits of the Hilbert - // curve position. The lookup table transforms a 10-bit key of the form - // "iiiijjjjoo" to a 10-bit value of the form "ppppppppoo", where the - // letters [ijpo] denote bits of "i", "j", Hilbert curve position, and - // Hilbert curve orientation respectively. - for (let k = 7n; k >= 0n; k--) { - const kk = k * 4n; - bits += ((bigI >> kk) & 15n) << NUM_FACES; - bits += ((bigJ >> kk) & 15n) << 2n; - bits = LOOKUP_POS[Number(bits)]; - n |= (bits >> 2n) << (k * 8n); - bits &= FACE_BITS; - } - - const id = n * 2n + 1n; - - if (level !== undefined) return parent(id, level); - return id; -} - -/** - * Convert an S2CellID to a Face-I-J coordinate and provide its orientation. - * If a level is provided, the I-J coordinates will be shifted to that level. - * @param id - the S2CellID - * @param level - zoom level - * @returns face-i-j with orientation - */ -export function toIJ( - id: bigint, - level?: number | bigint, -): [face: Face, i: number, j: number, orientation: number] { - let i = 0n; - let j = 0n; - const face = Number(id >> POS_BITS); - let bits = BigInt(face) & 1n; - - // Each iteration maps 8 bits of the Hilbert curve position into - // 4 bits of "i" and "j". The lookup table transforms a key of the - // form "ppppppppoo" to a value of the form "iiiijjjjoo", where the - // letters [ijpo] represents bits of "i", "j", the Hilbert curve - // position, and the Hilbert curve orientation respectively. - // - // On the first iteration we need to be careful to clear out the bits - // representing the cube face. - for (let k = 7n; k >= 0n; k--) { - const nbits = k === 7n ? 2n : 4n; - bits += ((id >> (k * 8n + 1n)) & ((1n << (2n * nbits)) - 1n)) << 2n; - bits = LOOKUP_IJ[Number(bits)]; - i += (bits >> NUM_FACES) << (k * 4n); - j += ((bits >> 2n) & 15n) << (k * 4n); - bits &= FACE_BITS; - } - - // adjust bits to the orientation - const lsb = id & (~id + 1n); - if ((lsb & 1229782938247303424n) !== 0n) bits ^= 1n; - - if (level !== undefined) { - level = BigInt(level); - i = i >> (MAX_LEVEL - level); - j = j >> (MAX_LEVEL - level); - } - return [face as Face, Number(i), Number(j), Number(bits)]; -} - -/** - * Convert an S2CellID to an Face-S-T coordinate - * @param id - the S2CellID - * @returns face-s-t coordinate associated with the S2CellID - */ -export function toST(id: bigint): [face: Face, s: number, t: number] { - const [face, i, j] = toIJ(id); - const s = IJtoST(i); - const t = IJtoST(j); - - return [face, s, t]; -} - -/** - * Convert an S2CellID to an Face-U-V coordinate - * @param id - the S2CellID - * @returns face-u-v coordinate associated with the S2CellID - */ -export function toUV(id: bigint): [face: Face, u: number, v: number] { - const [face, s, t] = toST(id); - const u = STtoUV(s); - const v = STtoUV(t); - - return [face, u, v]; -} - -/** - * Convert an S2CellID to an lon-lat coordinate - * @param id - the S2CellID - * @returns lon-lat coordinates - */ -export function toLonLat(id: bigint): [lon: number, lat: number] { - const xyz = toS2Point(id); - - return xyzToLonLat(xyz); -} - -/** - * Convert an S2CellID to an XYZ Point - * @param id - the S2CellID - * @returns a 3D vector - */ -export function toS2Point(id: bigint): Point3D { - return fromS2CellID(id); -} - -/** - * Given an S2CellID, get the face it's located in - * @param id - the S2CellID - * @returns face of the cell - */ -export function face(id: bigint): Face { - const face = Number(id >> POS_BITS); - return face as Face; -} - -/** - * Given an S2CellID, check if it is a Face Cell. - * @param id - the S2CellID - * @returns true if the cell is a face (lowest zoom level) - */ -export function isFace(id: bigint): boolean { - return (id & ((1n << 60n) - 1n)) === 0n; -} - -/** - * Given an S2CellID, find the quad tree position [0-4) it's located in - * @param id - the S2CellID - * @returns quad tree position - */ -export function pos(id: bigint): bigint { - return id & 2305843009213693951n; -} - -/** - * Given an S2CellID, find the level (zoom) its located in - * @param id - the S2CellID - * @returns zoom level - */ -export function level(id: bigint): number { - let count = 0; - - let i = 0n; - while ((id & (1n << i)) === 0n && i < 60n) { - i += 2n; - count++; - } - - return 30 - count; -} - -/** - * Given an S2CellID, get the distance it spans (or length it covers) - * @param id - the S2CellID - * @param lev - optional zoom level - * @returns distance - */ -export function distance(id: bigint, lev?: number): bigint { - if (lev === undefined) lev = level(id); - return id >> BigInt(2 * (30 - lev) + 1); -} - -/** - * Given an S2CellID, get the quad child tile of your choice [0, 4) - * @param id - the S2CellID - * @param pos - quad position 0, 1, 2, or 3 - * @returns the child tile at that position - */ -export function child(id: bigint, pos: 0n | 1n | 2n | 3n): bigint { - const newLSB = (id & (~id + 1n)) >> 2n; - return id + (2n * pos - FACE_BITS) * newLSB; -} - -/** - * Given an S2CellID, get all the quad children tiles - * @param id - the S2CellID - * @param orientation - orientation of the child (0 or 1) - * @returns the child tile at that position - */ -export function children(id: bigint, orientation = 0): [bigint, bigint, bigint, bigint] { - const childs: [bigint, bigint, bigint, bigint] = [ - child(id, 0n), - child(id, 3n), - child(id, 2n), - child(id, 1n), - ]; - if (orientation === 0) { - const tmp = childs[1]; - childs[1] = childs[3]; - childs[3] = tmp; - } - - return childs; -} - -/** - * Given a Face-level-i-j coordinate, get all its quad children tiles - * @param face - the Face - * @param level - zoom level - * @param i - i coordinate - * @param j - j coordinate - * @returns the child tile at that position - */ -export function childrenIJ( - face: Face, - level: number, - i: number, - j: number, -): [blID: bigint, brID: bigint, tlID: bigint, trID: bigint] { - i = i << 1; - j = j << 1; - - return [ - fromIJ(face, i, j, level + 1), - fromIJ(face, i + 1, j, level + 1), - fromIJ(face, i, j + 1, level + 1), - fromIJ(face, i + 1, j + 1, level + 1), - ]; -} - -/** - * Given an S2CellID, get the quad position relative to its parent - * @param id - the S2CellID - * @param level - zoom level - * @returns the child tile at that position - */ -export function childPosition(id: bigint, level: number): number { - return Number((id >> (2n * (MAX_LEVEL - BigInt(level)) + 1n)) & FACE_BITS); -} - -/** - * Given an S2CellID, get the parent quad tile - * @param id - the S2CellID - * @param level - zoom level - * @returns the parent of the input S2CellID - */ -export function parent(id: bigint, level?: number): bigint { - const newLSB = - level !== undefined ? 1n << (2n * (MAX_LEVEL - BigInt(level))) : (id & (~id + 1n)) << 2n; - return (id & (~newLSB + 1n)) | newLSB; -} - -/** - * Given an S2CellID, get the hilbert range it spans - * @param id - the S2CellID - * @returns [min, max] - */ -export function range(id: bigint): [min: bigint, max: bigint] { - const lsb = id & (~id + 1n); - - return [id - (lsb - 1n), id + (lsb - 1n)]; -} - -/** - * Check if the first S2CellID contains the second. - * @param a - the first S2CellID - * @param b - the second S2CellID - * @returns true if a contains b - */ -export function contains(a: bigint, b: bigint): boolean { - const [min, max] = range(a); - return b >= min && b <= max; -} - -/** - * Check if an S2CellID intersects another. This includes edges touching. - * @param a - the first S2CellID - * @param b - the second S2CellID - * @returns true if a intersects b - */ -export function intersects(a: bigint, b: bigint): boolean { - const [aMin, aMax] = range(a); - const [bMin, bMax] = range(b); - return bMin <= aMax && bMax >= aMin; -} - -/** - * Get the next S2CellID in the hilbert space - * @param id - input S2CellID - * @returns the next S2CellID in the hilbert space - */ -export function next(id: bigint): bigint { - const n = id + ((id & (~id + 1n)) << 1n); - if (n < K_WRAP_OFFSET) return n; - return n - K_WRAP_OFFSET; -} - -/** - * Get the previous S2CellID in the hilbert space - * @param id - input S2CellID - * @returns the previous S2CellID in the hilbert space - */ -export function prev(id: bigint): bigint { - const p = id - ((id & (~id + 1n)) << 1n); - if (p < K_WRAP_OFFSET) return p; - return p + K_WRAP_OFFSET; -} - -/** - * Check if the S2CellID is a leaf value. This means it's the smallest possible cell - * @param id - input S2CellID - * @returns true if the S2CellID is a leaf - */ -export function isLeaf(id: bigint): boolean { - return (id & 1n) === 1n; -} - -/** - * Given an S2CellID and level (zoom), get the center point of that cell in S-T space - * @param id - the S2CellID - * @returns [face, s, t] - */ -export function centerST(id: bigint): [face: Face, s: number, t: number] { - const [face, i, j] = toIJ(id); - const delta = (id & 1n) !== 0n ? 1 : ((BigInt(i) ^ (id >> 2n)) & 1n) !== 0n ? 2 : 0; - // Note that (2 * {i,j} + delta) will never overflow a 32-bit integer. - const si = 2 * i + delta; - const ti = 2 * j + delta; - - return [face, SiTiToST(Number(si)), SiTiToST(Number(ti))]; -} - -/** - * Given an S2CellID and level (zoom), get the S-T bounding range of that cell - * @param id - the S2CellID - * @param lev - zoom level - * @returns [sMin, tMin, sMax, tMax] - */ -export function boundsST(id: bigint, lev: number): BBox { - if (lev === undefined) lev = level(id); - - const [, s, t] = centerST(id); - const halfSize = sizeST(lev) * 0.5; - - return [s - halfSize, t - halfSize, s + halfSize, t + halfSize]; -} - -/** - * Return the range maximum of a level (zoom) in S-T space - * @param level - zoom level - * @returns sMax or tMax - */ -export function sizeST(level: number): number { - return IJtoST(sizeIJ(level)); -} - -/** - * Return the range maximum of a level (zoom) in I-J space - * @param level - zoom level - * @returns iMax or jMax - */ -export function sizeIJ(level: number): number { - return 1 << (30 - level); -} - -/** - * Given an S2CellID, find the neighboring S2CellIDs - * @param id - the S2CellID - * @returns [up, right, down, left] - */ -export function neighbors(id: bigint): [bigint, bigint, bigint, bigint] { - const lev = level(id); - const size = sizeIJ(lev); - const [face, i, j] = toIJ(id); - - return [ - parent(fromIJSame(face, i, j - size, j - size >= 0), lev), - parent(fromIJSame(face, i + size, j, i + size < K_MAX_SIZE), lev), - parent(fromIJSame(face, i, j + size, j + size < K_MAX_SIZE), lev), - parent(fromIJSame(face, i - size, j, i - size >= 0), lev), - ]; -} - -/** - * Given a Face-I-J and a desired level (zoom), find the neighboring S2CellIDs - * @param face - the Face - * @param i - the I coordinate - * @param j - the J coordinate - * @param level - the zoom level (desired) - * @returns neighbors: [down, right, up, left] - */ -export function neighborsIJ( - face: Face, - i: number, - j: number, - level: number, -): [bigint, bigint, bigint, bigint] { - const size = sizeIJ(level); - - return [ - parent(fromIJSame(face, i, j - size, j - size >= 0), level), - parent(fromIJSame(face, i + size, j, i + size < K_MAX_SIZE), level), - parent(fromIJSame(face, i, j + size, j + size < K_MAX_SIZE), level), - parent(fromIJSame(face, i - size, j, i - size >= 0), level), - ]; -} - -/** - * Build an S2CellID given a Face-I-J, but ensure the face is the same if desired - * @param face - the Face - * @param i - the I coordinate - * @param j - the J coordinate - * @param sameFace - if the face should be the same - * @returns the S2CellID - */ -export function fromIJSame(face: Face, i: number, j: number, sameFace: boolean): bigint { - if (sameFace) return fromIJ(face, i, j); - else return fromIJWrap(face, i, j); -} - -/** - * Build an S2CellID given a Face-I-J, but ensure it's a legal value, otherwise wrap before creation - * @param face - the Face - * @param i - the I coordinate - * @param j - the J coordinate - * @returns the S2CellID - */ -export function fromIJWrap(face: Face, i: number, j: number): bigint { - const { max, min } = Math; - - // Convert i and j to the coordinates of a leaf cell just beyond the - // boundary of this face. This prevents 32-bit overflow in the case - // of finding the neighbors of a face cell. - i = max(-1, min(K_MAX_SIZE, i)); - j = max(-1, min(K_MAX_SIZE, j)); - - // We want to wrap these coordinates onto the appropriate adjacent face. - // The easiest way to do this is to convert the (i,j) coordinates to (x,y,z) - // (which yields a point outside the normal face boundary), and then call - // S2::XYZtoFaceUV() to project back onto the correct face. - // - // The code below converts (i,j) to (si,ti), and then (si,ti) to (u,v) using - // the linear projection (u=2*s-1 and v=2*t-1). (The code further below - // converts back using the inverse projection, s=0.5*(u+1) and t=0.5*(v+1). - // Any projection would work here, so we use the simplest.) We also clamp - // the (u,v) coordinates so that the point is barely outside the - // [-1,1]x[-1,1] face rectangle, since otherwise the reprojection step - // (which divides by the new z coordinate) might change the other - // coordinates enough so that we end up in the wrong leaf cell. - const kScale = 1 / K_MAX_SIZE; - const kLimit = 1 + 2.2204460492503131e-16; - const u = max(-kLimit, min(kLimit, kScale * (2 * (i - K_MAX_SIZE / 2) + 1))); - const v = max(-kLimit, min(kLimit, kScale * (2 * (j - K_MAX_SIZE / 2) + 1))); - - // Find the leaf cell coordinates on the adjacent face, and convert - // them to a cell id at the appropriate level. - const [nFace, nU, nV] = XYZtoFaceUV(faceUVtoXYZ(face, u, v)); - return fromIJ(nFace, STtoIJ(0.5 * (nU + 1)), STtoIJ(0.5 * (nV + 1))); -} - -/** - * Given an S2CellID, find it's nearest neighbors associated with it - * @param id - the S2CellID - * @param lev - the zoom level (if not provided, defaults to current level of id) - * @returns neighbors - */ -export function vertexNeighbors(id: bigint, lev?: number): bigint[] { - if (lev === undefined) lev = level(id); - const res: bigint[] = []; - - const [face, i, j] = toIJ(id); - - // Determine the i- and j-offsets to the closest neighboring cell in each - // direction. This involves looking at the next bit of "i" and "j" to - // determine which quadrant of this->parent(level) this cell lies in. - const halfsize = sizeIJ(lev + 1); - const size = halfsize << 1; - let isame: boolean, jsame: boolean, ioffset: number, joffset: number; - - if ((i & halfsize) !== 0) { - ioffset = size; - isame = i + size < K_MAX_SIZE; - } else { - ioffset = -size; - isame = i - size >= 0; - } - if ((j & halfsize) !== 0) { - joffset = size; - jsame = j + size < K_MAX_SIZE; - } else { - joffset = -size; - jsame = j - size >= 0; - } - - res.push(parent(id, lev)); - res.push(parent(fromIJSame(face, i + ioffset, j, isame), lev)); - res.push(parent(fromIJSame(face, i, j + joffset, jsame), lev)); - if (isame || jsame) - res.push(parent(fromIJSame(face, i + ioffset, j + joffset, isame && jsame), lev)); - - return res; -} diff --git a/src/index.ts b/src/index.ts index ab5dbc1..8677cd8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,20 +1,9 @@ -import type { BBox, Geometry, VectorGeometry } from './geometry.spec'; +import type { BBOX, Geometry, VectorGeometry } from './geometry.spec'; import type { MValue, Properties } from './values.spec'; -export * from './s2'; -export * from './wm'; -export * from './bbox'; -export * from './id'; -export * from './simplify'; -export * from './tile'; -export * from './util'; - export * from './geometry.spec'; export * from './values.spec'; -// import * as schema from './s2json.schema.json'; -// export { schema }; - // NOTE: S2 -> S2Geometry // NOTE: WG -> WGS84 @@ -35,13 +24,20 @@ export interface BaseFeatureCollection type: T; features: F[]; attributions?: Attributions; - bbox?: BBox; + bbox?: BBOX; } /** WG FeatureCollection */ -export type FeatureCollection = BaseFeatureCollection<'FeatureCollection', Feature | VectorFeature>; +export type FeatureCollection< + M = Record, + D extends MValue = MValue, + P extends Properties = Properties, +> = BaseFeatureCollection<'FeatureCollection', Feature | VectorFeature>; /** S2 FeatureCollection */ -export interface S2FeatureCollection - extends BaseFeatureCollection<'S2FeatureCollection', S2Feature> { +export interface S2FeatureCollection< + M = Record, + D extends MValue = MValue, + P extends Properties = Properties, +> extends BaseFeatureCollection<'S2FeatureCollection', S2Feature> { faces: Face[]; } @@ -52,31 +48,39 @@ export type FeatureType = 'Feature' | 'VectorFeature' | 'S2Feature'; /** Base component to build either an S2 or WG Feature */ export interface BaseFeature< T = FeatureType, + M = Record, + D extends MValue = MValue, P extends Properties = Properties, - G = Geometry | VectorGeometry, + G = Geometry | VectorGeometry, > { type: T; id?: number; face?: Face; properties: P; geometry: G; - metadata?: Record; + metadata?: M; } /** WG Feature */ export type Feature< + M = Record, + D extends MValue = MValue, P extends Properties = Properties, - M extends MValue = MValue, - G = Geometry, -> = BaseFeature<'Feature', P, G>; + G = Geometry, +> = BaseFeature<'Feature', M, D, P, G>; /** WG Vector Feature */ -export type VectorFeature

= BaseFeature< - 'VectorFeature', - P, - G ->; +export type VectorFeature< + M = Record, + D extends MValue = MValue, + P extends Properties = Properties, + G = VectorGeometry, +> = BaseFeature<'VectorFeature', M, D, P, G>; /** S2 Feature */ -export interface S2Feature

- extends BaseFeature<'S2Feature', P, G> { +export interface S2Feature< + M = Record, + D extends MValue = MValue, + P extends Properties = Properties, + G = VectorGeometry, +> extends BaseFeature<'S2Feature', M, D, P, G> { face: Face; } @@ -90,13 +94,29 @@ export interface S2Feature

; /** Either an S2 or WG FeatureCollection */ -export type FeatureCollections = FeatureCollection | S2FeatureCollection; +export type FeatureCollections< + M = Record, + D extends MValue = MValue, + P extends Properties = Properties, +> = FeatureCollection | S2FeatureCollection; /** Either an S2 or WG Feature */ -export type Features = Feature | VectorFeature | S2Feature; +export type Features< + M = Record, + D extends MValue = MValue, + P extends Properties = Properties, +> = Feature | VectorFeature | S2Feature; /** Any Vector Geometry type */ -export type VectorFeatures = VectorFeature | S2Feature; +export type VectorFeatures< + M = Record, + D extends MValue = MValue, + P extends Properties = Properties, +> = VectorFeature | S2Feature; /** All major S2JSON types */ -export type JSONCollection = FeatureCollection | S2FeatureCollection | Features; +export type JSONCollection< + M = Record, + D extends MValue = MValue, + P extends Properties = Properties, +> = FeatureCollection | S2FeatureCollection | Features; diff --git a/src/s2/convert.ts b/src/s2/convert.ts deleted file mode 100644 index d123a51..0000000 --- a/src/s2/convert.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { fromST, toLonLat } from './s2Point'; - -import type { Face, S2Feature, VectorFeature, VectorGeometry, VectorPoint } from '../'; - -/** - * Convert an S2Feature to a GeoJSON Feature - * @param data - S2Feature - * @returns - GeoJSON Feature - */ -export function toWM(data: S2Feature): VectorFeature { - const { id, face, properties, metadata, geometry } = data; - convertGeometry(face, geometry); - return { - id, - type: 'VectorFeature', - properties, - metadata, - geometry, - }; -} - -/** - * Underlying conversion mechanic to move S2Geometry to GeoJSON Geometry - * @param face - Face - * @param geometry - S2 Geometry - */ -function convertGeometry(face: Face, geometry: VectorGeometry): void { - const { type, coordinates } = geometry; - if (type === 'Point') convertGeometryPoint(face, coordinates); - else if (type === 'MultiPoint') coordinates.forEach((point) => convertGeometryPoint(face, point)); - else if (type === 'LineString') coordinates.forEach((point) => convertGeometryPoint(face, point)); - else if (type === 'MultiLineString') - coordinates.forEach((line) => line.forEach((point) => convertGeometryPoint(face, point))); - else if (type === 'Polygon') - coordinates.forEach((line) => line.forEach((point) => convertGeometryPoint(face, point))); - else if (type === 'MultiPolygon') - coordinates.forEach((polygon) => - polygon.forEach((line) => line.forEach((point) => convertGeometryPoint(face, point))), - ); - else { - throw new Error('Invalid S2Geometry type'); - } -} - -/** - * Mutate an S2 Point to a GeoJSON Point - * @param face - Face - * @param point - S2 Point - */ -function convertGeometryPoint(face: Face, point: VectorPoint): void { - const { x: s, y: t } = point; - const [lon, lat] = toLonLat(fromST(face, s, t)); - point.x = lon; - point.y = lat; -} diff --git a/src/s2/index.ts b/src/s2/index.ts deleted file mode 100644 index 481cf67..0000000 --- a/src/s2/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './convert'; -export * as S2Coords from './s2Coords'; -export * as S2Point from './s2Point'; diff --git a/src/s2/s2Coords.ts b/src/s2/s2Coords.ts deleted file mode 100644 index 58d11f5..0000000 --- a/src/s2/s2Coords.ts +++ /dev/null @@ -1,383 +0,0 @@ -import { degToRad, radToDeg } from '../util'; - -import type { BBox, Face, Point, Point3D } from '../'; - -export const K_LIMIT_IJ = 1 << 30; - -/** - * Convert a [0, 1] to a [-1, 1] in a linear fashion - * @param s - input S or T coordinate - * @returns output U or V coordinate - */ -export function linearSTtoUV(s: number): number { - return 2 * s - 1; -} - -/** - * Convert a [-1, 1] to a [0, 1] in a linear fashion - * @param u - input U or V coordinate - * @returns output S or T coordinate - */ -export function linearUVtoST(u: number): number { - return 0.5 * (u + 1); -} - -/** - * Convert a [0, 1] to a [-1, 1] in a tangential fashion - * @param s - input S or T coordinate - * @returns output U or V coordinate - */ -export function tanSTtoUV(s: number): number { - const { PI, tan } = Math; - return tan((PI / 2) * s - PI / 4); -} - -/** - * Convert a [-1, 1] to a [0, 1] in a tangential fashion - * @param u - input U or V coordinate - * @returns output S or T coordinate - */ -export function tanUVtoST(u: number): number { - const { PI, atan } = Math; - return 2 * (1 / PI) * (atan(u) + PI / 4); -} - -/** - * Convert a [0, 1] to a [-1, 1] in a quadratic fashion - * @param s - input S or T coordinate - * @returns output U or V coordinate - */ -export function quadraticSTtoUV(s: number): number { - if (s >= 0.5) return (1 / 3) * (4 * s * s - 1); - return (1 / 3) * (1 - 4 * (1 - s) * (1 - s)); -} - -/** - * Convert a [-1, 1] to a [0, 1] in a quadratic fashion - * @param u - input U or V coordinate - * @returns output S or T coordinate - */ -export function quadraticUVtoST(u: number): number { - const { sqrt } = Math; - if (u >= 0) return 0.5 * sqrt(1 + 3 * u); - return 1 - 0.5 * sqrt(1 - 3 * u); -} - -/** - * Convert from st space to ij space (ij are whole numbers ranging an entire u30) - * @param s - input S or T - * @returns output I or J - */ -export function STtoIJ(s: number): number { - const { max, min, floor } = Math; - return max(0, min(K_LIMIT_IJ - 1, floor(K_LIMIT_IJ * s))); -} - -/** - * Convert from ij space to st space (ij are whole numbers ranging an entire u30) - * @param i - input I or J - * @returns output S or T - */ -export function IJtoST(i: number): number { - return i / K_LIMIT_IJ; -} - -/** - * Convert SiTi to ST. - * @param si - input Si or Ti - * @returns output S or T - */ -export function SiTiToST(si: number): number { - return (1 / 2_147_483_648) * si; -} - -/** - * Convert a face-u-v coords to left-hand-rule XYZ Point coords - * @param face - input face - * @param u - input u - * @param v - input v - * @returns output - */ -export function faceUVtoXYZ(face: Face, u: number, v: number): Point3D { - switch (face) { - case 0: - return [1, u, v]; - case 1: - return [-u, 1, v]; - case 2: - return [-u, -v, 1]; - case 3: - return [-1, -v, -u]; - case 4: - return [v, -1, -u]; - default: - return [v, u, -1]; - } -} - -/** - * Convert a face-u-v coords to right-hand-rule XYZ Point coords - * @param face - input face - * @param u - input u - * @param v - input v - * @returns output - */ -export function faceUVtoXYZGL(face: Face, u: number, v: number): Point3D { - switch (face) { - case 0: - return [u, v, 1]; - case 1: - return [1, v, -u]; - case 2: - return [-v, 1, -u]; - case 3: - return [-v, -u, -1]; - case 4: - return [-1, -u, v]; - default: - return [u, -1, v]; - } -} - -/** - * Convert from a face and left-hand-rule XYZ Point to u-v coords - * @param face - input face - * @param xyz - input - * @returns output - */ -export function faceXYZtoUV(face: Face, xyz: Point3D): [u: number, v: number] { - const [x, y, z] = xyz; - - switch (face) { - case 0: - return [y / x, z / x]; - case 1: - return [-x / y, z / y]; - case 2: - return [-x / z, -y / z]; - case 3: - return [z / x, y / x]; - case 4: - return [z / y, -x / y]; - default: - return [-y / z, -x / z]; - } -} - -/** - * Find the face the point is located at - * @param xyz - point3D input - * @returns - outputs the associated face - */ -export function XYZtoFace(xyz: Point3D): Face { - const temp = xyz.map((n: number): number => Math.abs(n)); - - let face: number = temp[0] > temp[1] ? (temp[0] > temp[2] ? 0 : 2) : temp[1] > temp[2] ? 1 : 2; - if (xyz[face] < 0) face += 3; - - return face as Face; -} - -/** - * Convert from an left-hand-rule XYZ Point to a Face-U-V coordinate - * @param xyz - point3D input - * @returns output's a face, u, and v - */ -export function XYZtoFaceUV(xyz: Point3D): [face: Face, u: number, v: number] { - const face = XYZtoFace(xyz); - return [face, ...faceXYZtoUV(face, xyz)]; -} - -/** - * Convert from a face and right-hand-rule XYZ Point to u-v coords - * @param face - input face - * @param xyz - input Point3D - * @returns output WebGL oriented UV coords - */ -export function faceXYZGLtoUV(face: number, xyz: Point3D): [u: number, v: number] { - const [x, y, z] = xyz; - - switch (face) { - case 0: - return [x / z, y / z]; - case 1: - return [-z / x, y / x]; - case 2: - return [-z / y, -x / y]; - case 3: - return [y / z, x / z]; - case 4: - return [y / x, -z / x]; - default: - return [-x / y, -z / y]; - } -} - -/** - * Convert from an left-hand-rule XYZ Point to a lon-lat coord - * @param xyz - point3D input - * @returns - lon-lat coordinates - */ -export function xyzToLonLat(xyz: Point3D): [lon: number, lat: number] { - const { atan2, sqrt } = Math; - const [x, y, z] = xyz; - - return [radToDeg(atan2(y, x)), radToDeg(atan2(z, sqrt(x * x + y * y)))]; -} - -/** - * Convert from a lon-lat coord to an left-hand-rule XYZ Point - * @param lon - longitude - * @param lat - latitude - * @returns - Point3D - */ -export function lonLatToXYZ(lon: number, lat: number): Point3D { - const { sin, cos } = Math; - lon = degToRad(lon); - lat = degToRad(lat); - return [ - cos(lat) * cos(lon), // x - cos(lat) * sin(lon), // y - sin(lat), // z - ]; -} - -/** - * Convert from a lon-lat coord to an right-hand-rule XYZ Point - * @param lon - longitude - * @param lat - latitude - * @returns - WebGL oriented Point3D - */ -export function lonLatToXYZGL(lon: number, lat: number): Point3D { - const { sin, cos } = Math; - lon = degToRad(lon); - lat = degToRad(lat); - return [ - cos(lat) * sin(lon), // y - sin(lat), // z - cos(lat) * cos(lon), // x - ]; -} - -/** - * Convert an u-v-zoom coordinate to a tile coordinate - * @param u - u coordinate - * @param v - v coordinate - * @param zoom - zoom level - * @returns - tile X-Y coordinate - */ -export function tileXYFromUVZoom(u: number, v: number, zoom: number): Point { - return tileXYFromSTZoom(quadraticUVtoST(u), quadraticUVtoST(v), zoom); -} - -/** - * Convert an x-y-zoom coordinate to a tile coordinate - * @param x - x coordinate - * @param y - y coordinate - * @param zoom - zoom level - * @returns - tile X-Y coordinate - */ -export function tileXYFromSTZoom(x: number, y: number, zoom: number): Point { - const { floor } = Math; - const divisionFactor = (2 / (1 << zoom)) * 0.5; - - return [floor(x / divisionFactor), floor(y / divisionFactor)]; -} - -/** - * Given a quad-based tile schema of "zoom-x-y", get the local UV bounds of said tile. - * @param u - u coordinate - * @param v - v coordinate - * @param zoom - zoom level - * @returns - local UV bounds for the tile - */ -export function bboxUV(u: number, v: number, zoom: number): BBox { - const divisionFactor = 2 / (1 << zoom); - - return [ - divisionFactor * u - 1, - divisionFactor * v - 1, - divisionFactor * (u + 1) - 1, - divisionFactor * (v + 1) - 1, - ]; -} - -/** - * Given a quad-based tile schema of "zoom-x-y", get the local ST bounds of said tile. - * @param s - s coordinate - * @param t - t coordinate - * @param zoom - zoom level - * @returns - local ST bounds for the tile - */ -export function bboxST(s: number, t: number, zoom: number): BBox { - const divisionFactor = (2 / (1 << zoom)) * 0.5; - - return [ - divisionFactor * s, - divisionFactor * t, - divisionFactor * (s + 1), - divisionFactor * (t + 1), - ]; -} - -/** - * Find the face-i-j coordinates of neighbors for a specific face-i-j coordinate. - * Define an adjusted level (zoom) for the i-j coordinates. The level is 30 by default. - * @param face - face of the cell - * @param i - i coordinate - * @param j - j coordinate - * @param level - zoom level - * @returns - Face-i-j coordinates - */ -export function neighborsIJ( - face: Face, - i: number, - j: number, - level = 30, -): [face: Face, i: number, j: number][] { - const size = 1 << (30 - level); - - if (level !== 30) { - i = i << (30 - level); - j = j << (30 - level); - } - - return [ - fromIJWrap(face, i, j - size, level, j - size >= 0), - fromIJWrap(face, i + size, j, level, i + size < size), - fromIJWrap(face, i, j + size, level, j + size < size), - fromIJWrap(face, i - size, j, level, i - size >= 0), - ]; -} - -/** - * Adjust a manipulated face-i-j coordinate to a legal one if necessary. - * @param face - face of the cell - * @param i - i coordinate - * @param j - j coordinate - * @param level - zoom level - * @param sameFace - if the face should be the same - * @returns - Face-i-j coordinates - */ -function fromIJWrap( - face: Face, - i: number, - j: number, - level: number, - sameFace = false, -): [face: Face, i: number, j: number] { - if (sameFace) return [face, i >> (30 - level), j >> (30 - level)]; - const { max, min } = Math; - const kMaxSize = 1073741824; - - i = max(-1, min(kMaxSize, i)); - j = max(-1, min(kMaxSize, j)); - - const kScale = 1 / kMaxSize; - const kLimit = 1 + 2.2204460492503131e-16; - const u = max(-kLimit, min(kLimit, kScale * (2 * (i - kMaxSize / 2) + 1))); - const v = max(-kLimit, min(kLimit, kScale * (2 * (j - kMaxSize / 2) + 1))); - - const [nFace, nU, nV] = XYZtoFaceUV(faceUVtoXYZ(face, u, v)); - return [nFace, STtoIJ(0.5 * (nU + 1)) >> (30 - level), STtoIJ(0.5 * (nV + 1)) >> (30 - level)]; -} diff --git a/src/s2/s2Point.ts b/src/s2/s2Point.ts deleted file mode 100644 index e0b19cc..0000000 --- a/src/s2/s2Point.ts +++ /dev/null @@ -1,335 +0,0 @@ -import * as S2CellID from '../id'; -import { - IJtoST, - STtoIJ, - quadraticSTtoUV as STtoUV, - quadraticUVtoST as UVtoST, - XYZtoFace, - XYZtoFaceUV, - faceUVtoXYZ, - faceUVtoXYZGL, - lonLatToXYZ, - lonLatToXYZGL, - xyzToLonLat, -} from './s2Coords'; - -import { EARTH_RADIUS_EQUATORIAL, EARTH_RADIUS_POLAR, type Face, type Point3D } from '../'; - -/** - * Convert a lon-lat coord to an XYZ Point using the left-hand-rule - * @param lon The longitude in degrees - * @param lat The latitude in degrees - * @returns The XYZ Point - */ -export function fromLonLat(lon: number, lat: number): Point3D { - return lonLatToXYZ(lon, lat); -} - -/** - * Convert a lon-lat coord to an XYZ Point using the right-hand-rule. - * This function takes longitude and latitude as input and returns the corresponding XYZ coordinates. - * @param lon The longitude in degrees. - * @param lat The latitude in degrees. - * @returns The XYZ Point representing the provided longitude and latitude. - */ -export function fromLonLatGL(lon: number, lat: number): Point3D { - // Convert longitude and latitude to XYZ coordinates using the right-hand rule. - return lonLatToXYZGL(lon, lat); -} - -/** - * Convert a u-v coordinate to an XYZ Point. - * @param face - The face of the S2 cell. - * @param u - The u-coordinate on the face. - * @param v - The v-coordinate on the face. - * @returns The XYZ Point representing the given u-v coordinates. - */ -export function fromUV(face: Face, u: number, v: number): Point3D { - // Convert the given u-v coordinates to an XYZ Point using the left-hand rule. - return faceUVtoXYZ(face, u, v); -} - -/** - * Convert an s-t coordinate to an XYZ Point. - * @param face - The face of the S2 cell. - * @param s - The s-coordinate on the face. - * @param t - The t-coordinate on the face. - * @returns The XYZ Point representing the given s-t coordinates. - */ -export function fromST(face: Face, s: number, t: number): Point3D { - // Convert the given s-t coordinates to u-v coordinates. - const u = STtoUV(s); - const v = STtoUV(t); - - // Convert the u-v coordinates to an XYZ Point. - return fromUV(face, u, v); -} - -/** - * Convert an i-j coordinate to an XYZ Point. - * @param face - The face of the S2 cell. - * @param i - The i-coordinate on the face. - * @param j - The j-coordinate on the face. - * @returns The XYZ Point representing the given i-j coordinates. - */ -export function fromIJ(face: Face, i: number, j: number): Point3D { - // Convert the given i-j coordinates to s-t coordinates. - const s = IJtoST(i); - const t = IJtoST(j); - - // Convert the s-t coordinates to an XYZ Point. - return fromST(face, s, t); -} - -/** - * Convert an S2CellID to an XYZ Point. - * @param id - The S2CellID to convert. - * @returns The XYZ Point representing the given S2CellID. - */ -export function fromS2CellID(id: bigint): Point3D { - // Decompose the S2CellID into its constituent parts: face, u, and v. - const [face, u, v] = S2CellID.toUV(id); - - // Use the decomposed parts to construct an XYZ Point. - return fromUV(face, u, v); -} - -/** - * Convert an Face-U-V coord to an XYZ Point using the right-hand-rule - * @param face - The face of the S2 cell. - * @param u - The u-coordinate on the face. - * @param v - The v-coordinate on the face. - * @returns The XYZ Point representing the given Face-U-V coordinates. - */ -export function fromUVGL(face: Face, u: number, v: number): Point3D { - // Convert the given Face-U-V coordinates to an XYZ Point using the right-hand rule. - return faceUVtoXYZGL(face, u, v); -} - -/** - * Convert an Face-S-T coord to an XYZ Point using the right-hand-rule - * @param face - The face of the S2 cell. - * @param s - The s-coordinate on the face. - * @param t - The t-coordinate on the face. - * @returns The XYZ Point representing the given Face-S-T coordinates. - */ -export function fromSTGL(face: Face, s: number, t: number): Point3D { - // Convert the given Face-S-T coordinates to an XYZ Point using the right-hand rule. - // First, convert the s-t coordinates to u-v coordinates. - const [u, v] = [STtoUV(s), STtoUV(t)]; - - // Then, convert the u-v coordinates to an XYZ Point. - return fromUVGL(face, u, v); -} - -/** - * Convert an XYZ Point to a Face-U-V coord - * @param xyz - The XYZ Point to convert. - * @returns - The Face-U-V coordinates representing the given XYZ Point. - */ -export function toUV(xyz: Point3D): [face: Face, u: number, v: number] { - // Convert the given XYZ Point to Face-U-V coordinates using the right-hand rule. - return XYZtoFaceUV(xyz); -} - -/** - * Convert an XYZ Point to a Face-S-T coord - * @param xyz - The XYZ Point to convert. - * @returns - The Face-S-T coordinates representing the given XYZ Point. - */ -export function toST(xyz: Point3D): [face: Face, s: number, t: number] { - // Convert the given XYZ Point to Face-U-V coordinates. - const [face, u, v] = toUV(xyz); - - // Convert the U-V coordinates to S-T coordinates using the inverse of the - // UVtoST function. - return [face, UVtoST(u), UVtoST(v)]; -} - -/** - * Convert an XYZ Point to a Face-I-J coord - * @param xyz - The XYZ Point to convert. - * @param level - The zoom level of the result. If not provided, the result will have 30 bits of precision. - * @returns The Face-I-J coordinates representing the given XYZ Point. - */ -export function toIJ(xyz: Point3D, level?: number): [face: Face, i: number, j: number] { - // Convert the given XYZ Point to Face-S-T coordinates. - const [face, s, t] = toST(xyz); - - // Convert the S-T coordinates to I-J coordinates using the STtoIJ function. - let i = STtoIJ(s); - let j = STtoIJ(t); - - // If a level is provided, shift the I-J coordinates to the right by (30 - level) bits. - if (level !== undefined) { - i = i >> (30 - level); - j = j >> (30 - level); - } - - // Return the Face-I-J coordinates. - return [face, i, j]; -} - -/** - * Convert an XYZ Point to a lon-lat coord - * @param xyz - The XYZ Point to convert. - * @returns The lon-lat coordinates representing the given XYZ Point. - */ -export function toLonLat(xyz: Point3D): [lon: number, lat: number] { - return xyzToLonLat(xyz); -} - -/** - * Convert an XYZ Point to an S2CellID - * @param xyz - The XYZ Point to convert. - * @returns The S2CellID representing the given XYZ Point. - */ -export function toS2CellID(xyz: Point3D): bigint { - return S2CellID.fromS2Point(xyz); -} - -/** - * Take an XYZ Point and add an n to each component - * @param xyz - The XYZ Point to add to. - * @param n - The amount to add. - * @returns - The XYZ Point with the added amount. - */ -export function add(xyz: Point3D, n: number): Point3D { - xyz[0] += n; - xyz[1] += n; - xyz[2] += n; - - return xyz; -} - -/** - * Take an XYZ Point and add another XYZ Point to it - * @param xyz - The XYZ Point to add to. - * @param point - The XYZ Point to add. - * @returns - The XYZ Point with the added XYZ Point. - */ -export function addScalar(xyz: Point3D, point: Point3D): Point3D { - xyz[0] += point[0]; - xyz[1] += point[1]; - xyz[2] += point[2]; - - return xyz; -} - -/** - * Take an XYZ Point and subtract an n from each component - * @param xyz - The XYZ Point to subtract from. - * @param n - The amount to subtract. - * @returns - The XYZ Point with the subtracted amount. - */ -export function sub(xyz: Point3D, n: number): Point3D { - xyz[0] -= n; - xyz[1] -= n; - xyz[2] -= n; - - return xyz; -} - -/** - * Take an XYZ Point and subtract another XYZ Point from it - * @param xyz - The XYZ Point to subtract from. - * @param point - The XYZ Point to subtract. - * @returns - The XYZ Point with the subtracted XYZ Point. - */ -export function subScalar(xyz: Point3D, point: Point3D): Point3D { - xyz[0] -= point[0]; - xyz[1] -= point[1]; - xyz[2] -= point[2]; - - return xyz; -} - -/** - * Take an XYZ Point and multiply each component by n - * @param xyz - The XYZ Point to multiply. - * @param n - The amount to multiply. - * @returns - The XYZ Point with the multiplied amount. - */ -export function mul(xyz: Point3D, n: number): Point3D { - xyz[0] *= n; - xyz[1] *= n; - xyz[2] *= n; - - return xyz; -} - -/** - * Take an XYZ Point and multiply it by another XYZ Point - * @param xyz - The XYZ Point to multiply. - * @param point - The XYZ Point to multiply. - * @returns - The XYZ Point with the multiplied XYZ Point. - */ -export function mulScalar(xyz: Point3D, point: Point3D): Point3D { - xyz[0] *= point[0]; - xyz[1] *= point[1]; - xyz[2] *= point[2]; - - return xyz; -} - -/** - * Take an XYZ Point and divide each component by its length - * @param xyz - The XYZ Point to divide. - * @returns - The XYZ Point with the divided amount. - */ -export function normalize(xyz: Point3D): Point3D { - const len = length(xyz); - xyz[0] /= len; - xyz[1] /= len; - xyz[2] /= len; - - return xyz; -} - -/** - * Get the length of the XYZ Point - * @param xyz - The XYZ Point - * @returns - The length of the XYZ Point - */ -export function length(xyz: Point3D): number { - const [x, y, z] = xyz; - return Math.sqrt(x * x + y * y + z * z); -} - -/** - * Get the distance between two XYZ Points using Earth's size - * @param a - The first XYZ Point - * @param b - The second XYZ Point - * @returns - The distance between the two XYZ Points - */ -export function distanceEarth(a: Point3D, b: Point3D): number { - a[0] *= EARTH_RADIUS_EQUATORIAL; - b[0] *= EARTH_RADIUS_EQUATORIAL; - a[1] *= EARTH_RADIUS_EQUATORIAL; - b[1] *= EARTH_RADIUS_EQUATORIAL; - a[2] *= EARTH_RADIUS_POLAR; - b[2] *= EARTH_RADIUS_POLAR; - - return distance(a, b); -} - -/** - * Get the distance between two XYZ Points - * @param a - The first XYZ Point - * @param b - The second XYZ Point - * @returns - The raw distance between the two XYZ Points. Highly inaccurate for large distances - */ -export function distance(a: Point3D, b: Point3D): number { - const { sqrt, pow, abs } = Math; - - return sqrt(pow(abs(b[0] - a[0]), 2) + pow(abs(b[1] - a[1]), 2) + pow(abs(b[2] - a[2]), 2)); -} - -/** - * Find the S2 Hilbert Face of the XYZ Point [0, 6) - * @param xyz - The XYZ Point - * @returns - The S2 Hilbert Face - */ -export function getFace(xyz: Point3D): number { - return XYZtoFace(xyz); -} diff --git a/src/simplify.ts b/src/simplify.ts deleted file mode 100644 index d54cfe6..0000000 --- a/src/simplify.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { - VectorGeometry, - VectorLineString, - VectorMultiLineString, - VectorMultiPolygon, - VectorPolygon, -} from '.'; - -/** - * Builds squared distances for the vector geometry using the Douglas-Peucker algorithm. - * @param geometry - input vector geometry - * @param tolerance - simplification tolerance - * @param maxzoom - max zoom level to simplify - */ -export function buildSqDists(geometry: VectorGeometry, tolerance: number, maxzoom = 16): void { - const tol = Math.pow(tolerance / ((1 << maxzoom) * 4_096), 2); - const { type, coordinates: coords } = geometry; - if (type === 'LineString') - buildSqDist(coords as VectorLineString, 0, (coords as VectorLineString).length - 1, tol); - else if (type === 'MultiLineString') - (coords as VectorMultiLineString).forEach((line) => buildSqDist(line, 0, line.length - 1, tol)); - else if (type === 'Polygon') - (coords as VectorPolygon).forEach((line) => buildSqDist(line, 0, line.length - 1, tol)); - else if (type === 'MultiPolygon') - (coords as VectorMultiPolygon).forEach((polygon) => - polygon.forEach((line) => buildSqDist(line, 0, line.length - 1, tol)), - ); -} - -/** - * calculate simplification of line vector data using - * optimized Douglas-Peucker algorithm - * @param coords - input coordinates - * @param first - first point index - * @param last - last points index - * @param sqTolerance - simplification tolerance (higher means simpler) - */ -export function buildSqDist( - coords: VectorLineString, - first: number, - last: number, - sqTolerance: number, -): void { - coords[first].t = 1; - _buildSqDist(coords, first, last, sqTolerance); - coords[last].t = 1; -} - -/** - * calculate simplification of line vector data using - * optimized Douglas-Peucker algorithm - * @param coords - input coordinates - * @param first - first point index - * @param last - last points index - * @param sqTolerance - simplification tolerance (higher means simpler) - */ -function _buildSqDist( - coords: VectorLineString, - first: number, - last: number, - sqTolerance: number, -): void { - let maxSqDist = sqTolerance; - const mid = (last - first) >> 1; - let minPosToMid = last - first; - let index: undefined | number; - - const { x: as, y: at } = coords[first]; - const { x: bs, y: bt } = coords[last]; - - for (let i = first; i < last; i++) { - const { x, y } = coords[i]; - const d = getSqSegDist(x, y, as, at, bs, bt); - - if (d > maxSqDist) { - index = i; - maxSqDist = d; - } else if (d === maxSqDist) { - // a workaround to ensure we choose a pivot close to the middle of the list, - // reducing recursion depth, for certain degenerate inputs - const posToMid = Math.abs(i - mid); - if (posToMid < minPosToMid) { - index = i; - minPosToMid = posToMid; - } - } - } - - if (index !== undefined && maxSqDist > sqTolerance) { - if (index - first > 1) _buildSqDist(coords, first, index, sqTolerance); - coords[index].t = maxSqDist; - if (last - index > 1) _buildSqDist(coords, index, last, sqTolerance); - } -} - -/** - * square distance from a point to a segment - * @param ps - the reference point x - * @param pt - the reference point y - * @param s - the first point x in the segment - * @param t - the first point y in the segment - * @param bs - the last point x in the segment - * @param bt - the last point y in the segment - * @returns - the square distance - */ -function getSqSegDist( - ps: number, - pt: number, - s: number, - t: number, - bs: number, - bt: number, -): number { - let ds = bs - s; - let dt = bt - t; - - if (ds !== 0 || dt !== 0) { - const m = ((ps - s) * ds + (pt - t) * dt) / (ds * ds + dt * dt); - - if (m > 1) { - s = bs; - t = bt; - } else if (m > 0) { - s += ds * m; - t += dt * m; - } - } - - ds = ps - s; - dt = pt - t; - - return ds * ds + dt * dt; -} - -/** - * Simplifies the vector geometry based on zoom level and tolerance. - * @param geometry - input vector geometry - * @param tolerance - simplification tolerance - * @param zoom - curent zoom - * @param maxzoom - max zoom level - */ -export function simplify(geometry: VectorGeometry, tolerance: number, zoom: number, maxzoom = 16) { - const zoomTol = zoom >= maxzoom ? 0 : tolerance / ((1 << zoom) * 4_096); - const { type, coordinates: coords } = geometry; - if (type === 'LineString') - geometry.coordinates = simplifyLine(coords as VectorLineString, zoomTol, false, false); - else if (type === 'MultiLineString') - geometry.coordinates = (coords as VectorMultiLineString).map((line) => - simplifyLine(line, zoomTol, false, false), - ); - else if (type === 'Polygon') - geometry.coordinates = (coords as VectorPolygon).map((line, i) => - simplifyLine(line, zoomTol, true, i === 0), - ); - else if (type === 'MultiPolygon') - geometry.coordinates = (coords as VectorMultiPolygon).map((polygon) => - polygon.map((line, i) => simplifyLine(line, zoomTol, true, i === 0)), - ); -} - -/** - * @param line - input vector line - * @param tolerance - simplification tolerance - * @param isPolygon - whether the line is a polygon - * @param isOuter - whether the line is an outer ring or inner ring (for polygons) - * @returns - simplified line - */ -function simplifyLine( - line: VectorLineString, - tolerance: number, - isPolygon: boolean, - isOuter: boolean, -): VectorLineString { - const sqTolerance = tolerance * tolerance; - const size = line.length; - if (tolerance > 0 && size < (isPolygon ? sqTolerance : tolerance)) return line; - - const ring: VectorLineString = []; - for (const point of line) { - if (tolerance === 0 || (point.t ?? 0) > sqTolerance) ring.push({ ...point }); - } - if (isPolygon) rewind(ring, isOuter); - - return ring; -} - -/** - * In place adjust the ring if necessary - * @param ring - the ring to rewind - * @param clockwise - whether the ring needs to be clockwise - */ -export function rewind(ring: VectorLineString, clockwise: boolean): void { - let area = 0; - for (let i = 0, len = ring.length, j = len - 2; i < len; j = i, i += 2) { - area += (ring[i].x - ring[j].x) * (ring[i].y + ring[j].y); - } - if (area > 0 === clockwise) { - for (let i = 0, len = ring.length; i < len / 2; i += 2) { - swapPoints(ring, i, len - i - 1); - } - } -} - -/** - * Only swap the x, y, and z coordinates - * @param ring - the ring - * @param i - i position in the ring - * @param j - j position in the ring - */ -function swapPoints(ring: VectorLineString, i: number, j: number): void { - const tmp = ring[i]; - ring[i] = ring[j]; - ring[j] = tmp; -} diff --git a/src/tile.ts b/src/tile.ts deleted file mode 100644 index 7da8934..0000000 --- a/src/tile.ts +++ /dev/null @@ -1,257 +0,0 @@ -import { convert } from './convert'; -import { simplify } from '.'; -import { splitTile } from './clip'; -import { - contains, - fromFace, - face as getFace, - isFace, - level, - parent as parentID, - toFaceIJ, -} from './id'; - -import type { - Face, - JSONCollection, - Projection, - VectorFeatures, - VectorGeometry, - VectorPoint, -} from '.'; - -/** Tile Class to contain the tile information for splitting or simplifying */ -export class Tile { - /** - * @param id - the tile id - * @param layers - the tile's layers - * @param transformed - whether the tile feature geometry has been transformed to tile coordinates - */ - constructor( - public id: bigint, - public layers: Record = {}, - public transformed = false, - ) {} - - /** @returns true if the tile is empty of features */ - isEmpty(): boolean { - for (const layer of Object.values(this.layers)) { - if (layer.features.length > 0) return false; - } - return true; - } - - /** - * Add a vector feature to the tile, optionally to a specific layer to store it in. Defaults to "default". - * @param feature - Vector Feature - * @param layer - layer to store the feature to - */ - addFeature(feature: VectorFeatures, layer?: string): void { - const { metadata = {} } = feature; - - const layerName = (metadata.layer as string) ?? layer ?? 'default'; - if (!this.layers[layerName]) { - this.layers[layerName] = new Layer(layerName, []); - } - this.layers[layerName].features.push(feature); - } - - /** - * Simplify the geometry to have a tolerance which will be relative to the tile's zoom level. - * NOTE: This should be called after the tile has been split into children if that functionality - * is needed. - * @param tolerance - tolerance - * @param maxzoom - max zoom at which to simplify - */ - transform(tolerance: number, maxzoom?: number) { - const { transformed, id, layers } = this; - if (transformed) return; - const [, zoom, i, j] = toFaceIJ(id); - - for (const layer of Object.values(layers)) { - for (const feature of layer.features) { - simplify(feature.geometry, tolerance, zoom, maxzoom); - _transform(feature.geometry, zoom, i, j); - } - } - - this.transformed = true; - } -} - -/** - * @param geometry - input vector geometry to be mutated in place - * @param zoom - tile zoom - * @param ti - tile i - * @param tj - tile j - */ -function _transform(geometry: VectorGeometry, zoom: number, ti: number, tj: number) { - const { type, coordinates } = geometry; - zoom = 1 << zoom; - - if (type === 'Point') transformPoint(coordinates, zoom, ti, tj); - else if (type === 'MultiPoint' || type === 'LineString') - coordinates.forEach((p) => transformPoint(p, zoom, ti, tj)); - else if (type === 'MultiLineString' || type === 'Polygon') - coordinates.forEach((l) => l.forEach((p) => transformPoint(p, zoom, ti, tj))); - else if (type === 'MultiPolygon') - coordinates.forEach((p) => p.forEach((l) => l.forEach((p) => transformPoint(p, zoom, ti, tj)))); -} - -/** - * Mutates the point in place to a tile coordinate - * @param vp - input vector point that we are mutating in place - * @param zoom - current zoom - * @param ti - x translation - * @param tj - y translation - */ -export function transformPoint(vp: VectorPoint, zoom: number, ti: number, tj: number) { - vp.x = vp.x * zoom - ti; - vp.y = vp.y * zoom - tj; -} - -/** Layer Class to contain the layer information for splitting or simplifying */ -export class Layer { - /** - * @param name - the layer name - * @param features - the layer's features - */ - constructor( - public name: string, - public features: VectorFeatures[] = [], - ) {} -} - -/** Options for creating a TileStore */ -export interface TileStoreOptions { - /** manually set the projection, otherwise it defaults to whatever the data type is */ - projection?: Projection; - /** min zoom to generate data on */ - minzoom?: number; - /** max zoom level to cluster the points on */ - maxzoom?: number; - /** tile buffer on each side in pixels */ - indexMaxzoom?: number; - /** simplification tolerance (higher means simpler) */ - tolerance?: number; - /** tile buffer on each side so lines and polygons don't get clipped */ - buffer?: number; -} - -/** TileStore Class is a tile-lookup system that splits and simplifies as needed for each tile request */ -export class TileStore { - minzoom = 0; // min zoom to preserve detail on - maxzoom = 18; // max zoom to preserve detail on - faces = new Set(); // store which faces are active. 0 face could be entire WM projection - indexMaxzoom = 4; // max zoom in the tile index - tolerance = 3; // simplification tolerance (higher means simpler) - buffer = 0.0625; // tile buffer for lines and polygons - tiles: Map = new Map(); // stores both WM and S2 tiles - projection: Projection; // projection to build tiles for - /** - * @param data - input data may be WM or S2 as a Feature or a Collection of Features - * @param options - options to define how to store the data - */ - constructor(data: JSONCollection, options?: TileStoreOptions) { - // set options should they exist - this.minzoom = options?.minzoom ?? 0; - this.maxzoom = options?.maxzoom ?? 20; - this.indexMaxzoom = options?.indexMaxzoom ?? 4; - this.tolerance = options?.tolerance ?? 3; - this.buffer = options?.buffer ?? 64; - // update projection - if (options?.projection !== undefined) this.projection = options.projection; - else if (data.type === 'Feature' || data.type === 'FeatureCollection') this.projection = 'WM'; - else this.projection = 'S2'; - // sanity check - if (this.maxzoom < 0 || this.maxzoom > 20) - throw new Error('maxzoom should be in the 0-20 range'); - // convert features - const features: VectorFeatures[] = convert(this.projection, data); - for (const feature of features) this.addFeature(feature); - for (let face = 0; face < 6; face++) { - const id = fromFace(face as Face); - this.splitTile(id); - } - } - - /** - * Stores a feature to a tile, creating the tile if it doesn't exist and tracking the faces we use - * @param feature - the feature to store to a face tile. Creates the tile if it doesn't exist - */ - addFeature(feature: VectorFeatures): void { - const { faces, tiles } = this; - const face = feature.face ?? 0; - const id = fromFace(face); - let tile = tiles.get(id); - if (tile === undefined) { - faces.add(face); - tile = new Tile(id); - tiles.set(id, tile); - } - tile?.addFeature(feature); - } - - /** - * Splits a tile into smaller tiles given a start and end range, stopping at maxzoom - * @param startID - where to start tiling - * @param endID - where to stop tiling - * @param endZoom - stop tiling at this zoom - */ - splitTile(startID: bigint, endID?: bigint, endZoom: number = this.maxzoom): void { - const { buffer, tiles, tolerance, maxzoom, indexMaxzoom } = this; - const stack: bigint[] = [startID]; - // avoid recursion by using a processing queue - while (stack.length > 0) { - // find our next tile to split - const stackID = stack.pop(); - if (stackID === undefined) break; - const tile = tiles.get(stackID); - // if the tile we need does not exist, is empty, or already transformed, skip it - if (tile === undefined || tile.isEmpty() || tile.transformed) continue; - const tileZoom = level(tile.id); - // 1: stop tiling if we reached a defined end - // 2: stop tiling if it's the first-pass tiling, and we reached max zoom for indexing - // 3: stop at currently needed maxzoom OR current tile does not include child - if ( - tileZoom >= maxzoom || // 1 - (endID === undefined && tileZoom >= indexMaxzoom) || // 2 - (endID !== undefined && (tileZoom > endZoom || !contains(tile.id, endID))) // 3 - ) - continue; - - // split the tile and store the children - const [blID, brID, tlID, trID] = splitTile(tile, buffer); - tiles.set(blID.id, blID); - tiles.set(brID.id, brID); - tiles.set(tlID.id, tlID); - tiles.set(trID.id, trID); - // now that the tile has been split, we can transform it - tile.transform(tolerance, maxzoom); - // push the new features to the stack - stack.push(blID.id, brID.id, tlID.id, trID.id); - } - } - - /** - * @param id - the tile id to acquire - * @returns - the tile if it exists - */ - getTile(id: bigint): undefined | Tile { - const { tiles, faces } = this; - const zoom = level(id); - const face = getFace(id); - // If the zoom is out of bounds, return nothing - if (zoom < 0 || zoom > 20 || !faces.has(face)) return; - - // we want to find the closest tile to the data. - let pID = id; - while (!tiles.has(pID) && !isFace(pID)) { - pID = parentID(pID); - } - // split as necessary, the algorithm will know if the tile is already split - this.splitTile(pID, id, zoom); - - return tiles.get(id); - } -} diff --git a/src/util.ts b/src/util.ts deleted file mode 100644 index 489eb7c..0000000 --- a/src/util.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** Earth's radius in meters */ -export const EARTH_RADIUS = 6_371_008.8; -/** Earth's equitorial radius in meters */ -export const EARTH_RADIUS_EQUATORIAL = 6_378_137; -/** Earth's polar radius in meters */ -export const EARTH_RADIUS_POLAR = 6_356_752.3; -/** The average circumference of the world in meters. */ -export const EARTH_CIRCUMFERENCE = 2 * Math.PI * EARTH_RADIUS; // meters - -/** Mars' radius in meters */ -export const MARS_RADIUS = 3_389_500; -/** Mars' equitorial radius in meters */ -export const MARS_RADIUS_EQUATORIAL = 3_396_200; -/** Mars' polar radius in meters */ -export const MARS_RADIUS_POLAR = 3_376_200; - -/** 900913 (Web Mercator) constant */ -export const A = 6_378_137.0; -/** 900913 (Web Mercator) max extent */ -export const MAXEXTENT = 20_037_508.342789244; -/** 900913 (Web Mercator) maximum latitude */ -export const MAXLAT = 85.0511287798; - -/** - * convert radians to degrees - * @param radians - radian value - * @returns degrees - */ -export function radToDeg(radians: number): number { - return (radians * 180) / Math.PI; -} - -/** - * convert degrees to radians - * @param deg - degree value - * @returns radians - */ -export function degToRad(deg: number): number { - return (deg * Math.PI) / 180; -} diff --git a/src/wm/convert.ts b/src/wm/convert.ts deleted file mode 100644 index 20ee8ef..0000000 --- a/src/wm/convert.ts +++ /dev/null @@ -1,478 +0,0 @@ -import { clipLine } from '../clip'; -import { buildSqDists, radToDeg } from '../'; -import { extendBBox, fromPoint, mergeBBoxes } from '../bbox'; -import { fromLonLat, toST } from '../s2/s2Point'; - -import type { - BBOX, - Face, - Feature, - Geometry, - MValue, - Point, - Point3D, - S2Feature, - STPoint, - VectorCoordinates, - VectorFeature, - VectorGeometry, - VectorLineString, - VectorLineStringGeometry, - VectorMultiLineStringGeometry, - VectorMultiPointGeometry, - VectorMultiPolygonGeometry, - VectorPoint, - VectorPointGeometry, - VectorPolygon, - VectorPolygonGeometry, -} from '../'; - -/** - * Convet a GeoJSON Feature to an S2Feature - * @param data - GeoJSON Feature - * @param tolerance - optional tolerance - * @param maxzoom - optional maxzoom - * @param buildBBox - optional - build a bbox for the feature if desired - * @returns - S2Feature - */ -export function toS2( - data: Feature | VectorFeature, - tolerance?: number, - maxzoom?: number, - buildBBox?: boolean, -): S2Feature[] { - const { id, properties, metadata } = data; - const res: S2Feature[] = []; - const vectorGeo = - data.type === 'VectorFeature' ? data.geometry : convertGeometry(data.geometry, buildBBox); - for (const { geometry, face } of convertVectorGeometry(vectorGeo, tolerance, maxzoom)) { - res.push({ - id, - type: 'S2Feature', - face, - properties, - metadata, - geometry, - }); - } - - return res; -} - -/** - * Convert a GeoJSON Feature to a GeoJSON Vector Feature - * @param data - GeoJSON Feature - * @param buildBBox - optional - build a bbox for the feature if desired - * @returns - GeoJson Vector Feature - */ -export function toVector(data: Feature, buildBBox?: boolean): VectorFeature { - const { id, properties, metadata } = data; - const vectorGeo = convertGeometry(data.geometry, buildBBox); - return { - id, - type: 'VectorFeature', - properties, - metadata, - geometry: vectorGeo, - }; -} - -/** - * Mutate a GeoJSON Point to a GeoJson Vector Point - * @param point - GeoJSON flat Point - * @param m - optional m-value - * @param bbox - if bbox is provided, we will extend the bbox - * @returns - GeoJson Vector Point - */ -function convertPoint(point: Point | Point3D, m?: MValue, bbox?: BBOX): VectorPoint { - const newPoint: VectorPoint = { x: point[0], y: point[1], z: point[2], m }; - if (bbox !== undefined) { - const newBBox = extendBBox(bbox, newPoint); - for (let i = 0; i < newBBox.length; i++) bbox[i] = newBBox[i]; - } - return newPoint; -} - -/** - * Convert a GeoJSON Geometry to an Vector Geometry - * @param geometry - GeoJSON Geometry - * @param buildBBox - optional - build a bbox for the feature if desired - * @returns - GeoJson Vector Geometry - */ -function convertGeometry(geometry: Geometry, buildBBox?: boolean): VectorGeometry { - const { type, coordinates: coords, mValues, bbox } = geometry; - const newBBox: BBOX | undefined = - buildBBox && bbox === undefined ? ([] as unknown as BBOX) : undefined; - - let coordinates: VectorCoordinates; - if (type === 'Point' || type === 'Point3D') coordinates = convertPoint(coords, mValues, newBBox); - else if (type === 'MultiPoint' || type === 'MultiPoint3D') - coordinates = coords.map((point, i) => convertPoint(point, mValues?.[i], newBBox)); - else if (type === 'LineString' || type === 'LineString3D') - coordinates = coords.map((point, i) => convertPoint(point, mValues?.[i], newBBox)); - else if (type === 'MultiLineString' || type === 'MultiLineString3D') - coordinates = coords.map((line, i) => - line.map((point, j) => convertPoint(point, mValues?.[i]?.[j], newBBox)), - ); - else if (type === 'Polygon' || type === 'Polygon3D') - coordinates = coords.map((line, i) => - line.map((point, j) => convertPoint(point, mValues?.[i]?.[j], newBBox)), - ); - else if (type === 'MultiPolygon' || type === 'MultiPolygon3D') - coordinates = coords.map((polygon, i) => - polygon.map((line, j) => - line.map((point, k) => convertPoint(point, mValues?.[i]?.[j]?.[k], newBBox)), - ), - ); - else { - throw new Error('Invalid GeoJSON type'); - } - const is3D = type.slice(-2) === '3D'; - // @ts-expect-error - coordinates complains, but the way this is all written is simpler - return { type: type.replace('3D', ''), is3D, coordinates, bbox: newBBox ?? bbox }; -} - -/** The resultant geometry after conversion */ -export interface ConvertedGeometry { - /** The vector geometry that was converted */ - geometry: VectorGeometry; - /** The face of the vector geometry that was converted */ - face: Face; -} -/** A list of converted geometries */ -export type ConvertedGeometryList = ConvertedGeometry[]; - -/** - * Underlying conversion mechanic to move GeoJSON Geometry to S2Geometry - * @param geometry - GeoJSON Geometry - * @param tolerance - if provided, geometry will be prepared for simplification by this tolerance - * @param maxzoom - if provided, geometry will be prepared for simplification up to this zoom - * @returns - S2Geometry - */ -function convertVectorGeometry( - geometry: VectorGeometry, - tolerance?: number, - maxzoom?: number, -): ConvertedGeometryList { - const { type } = geometry; - let cGeo: ConvertedGeometryList; - if (type === 'Point') cGeo = convertGeometryPoint(geometry); - else if (type === 'MultiPoint') cGeo = convertGeometryMultiPoint(geometry); - else if (type === 'LineString') cGeo = convertGeometryLineString(geometry); - else if (type === 'MultiLineString') cGeo = convertGeometryMultiLineString(geometry); - else if (type === 'Polygon') cGeo = convertGeometryPolygon(geometry); - else if (type === 'MultiPolygon') cGeo = convertGeometryMultiPolygon(geometry); - else { - throw new Error('Either the conversion is not yet supported or Invalid S2Geometry type.'); - } - if (tolerance !== undefined) - for (const { geometry } of cGeo) buildSqDists(geometry, tolerance, maxzoom); - return cGeo; -} - -/** - * @param geometry - GeoJSON PointGeometry - * @returns - S2 PointGeometry - */ -function convertGeometryPoint(geometry: VectorPointGeometry): ConvertedGeometryList { - const { type, is3D, coordinates, bbox } = geometry; - const { x: lon, y: lat, z, m } = coordinates; - const [face, s, t] = toST(fromLonLat(lon, lat)); - const vecBBox = fromPoint({ x: s, y: t, z }); - return [{ face, geometry: { type, is3D, coordinates: { x: s, y: t, z, m }, bbox, vecBBox } }]; -} - -/** - * @param geometry - GeoJSON PointGeometry - * @returns - S2 PointGeometry - */ -function convertGeometryMultiPoint(geometry: VectorMultiPointGeometry): ConvertedGeometryList { - const { is3D, coordinates, bbox } = geometry; - return coordinates.flatMap((coordinates) => - convertGeometryPoint({ type: 'Point', is3D, coordinates, bbox }), - ); -} - -/** - * @param geometry - GeoJSON LineStringGeometry - * @returns - S2 LineStringGeometry - */ -function convertGeometryLineString(geometry: VectorLineStringGeometry): ConvertedGeometryList { - const { type, is3D, coordinates, bbox } = geometry; - - return convertLineString(coordinates, false).map(({ face, line, offset, vecBBox }) => { - return { face, geometry: { type, is3D, coordinates: line, bbox, offset, vecBBox } }; - }); -} - -/** - * @param geometry - GeoJSON MultiLineStringGeometry - * @returns - S2 MultiLineStringGeometry - */ -function convertGeometryMultiLineString( - geometry: VectorMultiLineStringGeometry, -): ConvertedGeometryList { - const { coordinates, is3D, bbox } = geometry; - return coordinates - .flatMap((line) => convertLineString(line, false)) - .map(({ face, line, offset, vecBBox }) => ({ - face, - geometry: { type: 'LineString', is3D, coordinates: line, bbox, offset, vecBBox }, - })); -} - -/** - * @param geometry - GeoJSON PolygonGeometry - * @returns - S2 PolygonGeometry - */ -function convertGeometryPolygon(geometry: VectorPolygonGeometry): ConvertedGeometryList { - const { type, is3D, coordinates, bbox } = geometry; - const res: ConvertedGeometryList = []; - - // conver all lines - const outerRing = convertLineString(coordinates[0], true); - const innerRings = coordinates.slice(1).flatMap((line) => convertLineString(line, true)); - - // for each face, build a new polygon - for (const { face, line, offset, vecBBox: polyBBox } of outerRing) { - const polygon: VectorPolygon = [line]; - const polygonOffsets = [offset]; - for (const { face: innerFace, line: innerLine, offset: innerOffset, vecBBox } of innerRings) { - if (innerFace === face) { - polygon.push(innerLine); - polygonOffsets.push(innerOffset); - mergeBBoxes(polyBBox, vecBBox); - } - } - - res.push({ - face, - geometry: { - type, - coordinates: polygon, - is3D, - bbox, - offset: polygonOffsets, - vecBBox: polyBBox, - }, - }); - } - - return res; -} - -/** - * @param geometry - GeoJSON MultiPolygonGeometry - * @returns - S2 MultiPolygonGeometry - */ -function convertGeometryMultiPolygon(geometry: VectorMultiPolygonGeometry): ConvertedGeometryList { - const { is3D, coordinates, bbox, offset } = geometry; - return coordinates.flatMap((polygon, i) => - convertGeometryPolygon({ - type: 'Polygon', - is3D, - coordinates: polygon, - bbox, - offset: offset?.[i], - }), - ); -} - -/** LineString converted from WM to S2 */ -interface ConvertedLineString { - face: Face; - line: VectorLineString; - offset: number; - vecBBox: BBOX; -} - -/** - * @param line - GeoJSON LineString - * @param isPolygon - true if the line originates from a polygon - * @returns - S2 LineStrings clipped to it's 0->1 coordinate system - */ -function convertLineString(line: VectorLineString, isPolygon: boolean): ConvertedLineString[] { - const res: ConvertedLineString[] = []; - // first re-project all the coordinates to S2 - const newGeometry: STPoint[] = []; - for (const { x: lon, y: lat, z, m } of line) { - const [face, s, t] = toST(fromLonLat(lon, lat)); - newGeometry.push({ face, s, t, z, m }); - } - // find all the faces that exist in the line - const faces = new Set(); - newGeometry.forEach(({ face }) => faces.add(face)); - // for each face, build a line - for (const face of faces) { - const line: VectorLineString = []; - for (const stPoint of newGeometry) line.push(stPointToFace(face, stPoint)); - const clippedLines = clipLine(line, [0, 0, 1, 1], isPolygon); - for (const { line, offset, vecBBox } of clippedLines) res.push({ face, line, offset, vecBBox }); - } - - return res; -} - -/** - * Reproject GeoJSON geometry coordinates from lon-lat to a 0->1 coordinate system in place - * @param feature - input GeoJSON - * @param tolerance - if provided, geometry will be prepared for simplification by this tolerance - * @param maxzoom - if provided, - */ -export function toUnitScale(feature: VectorFeature, tolerance?: number, maxzoom?: number): void { - const { geometry } = feature; - const { type, coordinates } = geometry; - if (type === 'Point') projectPoint(coordinates, geometry); - else if (type === 'MultiPoint') coordinates.map((p) => projectPoint(p, geometry)); - else if (type === 'LineString') coordinates.map((p) => projectPoint(p, geometry)); - else if (type === 'MultiLineString') - coordinates.map((l) => l.map((p) => projectPoint(p, geometry))); - else if (type === 'Polygon') coordinates.map((l) => l.map((p) => projectPoint(p, geometry))); - else if (type === 'MultiPolygon') - coordinates.map((p) => p.map((l) => l.map((p) => projectPoint(p, geometry)))); - else { - throw new Error('Either the conversion is not yet supported or Invalid S2Geometry type.'); - } - if (tolerance !== undefined) buildSqDists(geometry, tolerance, maxzoom); -} - -/** - * Reproject GeoJSON geometry coordinates from 0->1 coordinate system to lon-lat in place - * @param feature - input GeoJSON - */ -export function toLL(feature: VectorFeature): void { - const { type, coordinates } = feature.geometry; - if (type === 'Point') unprojectPoint(coordinates); - else if (type === 'MultiPoint') coordinates.map((p) => unprojectPoint(p)); - else if (type === 'LineString') coordinates.map((p) => unprojectPoint(p)); - else if (type === 'MultiLineString') coordinates.map((l) => l.map((p) => unprojectPoint(p))); - else if (type === 'Polygon') coordinates.map((l) => l.map((p) => unprojectPoint(p))); - else if (type === 'MultiPolygon') - coordinates.map((p) => p.map((l) => l.map((p) => unprojectPoint(p)))); - else { - throw new Error('Either the conversion is not yet supported or Invalid S2Geometry type.'); - } -} - -/** - * Project a point from lon-lat to a 0->1 coordinate system in place - * @param input - input point - * @param geo - input geometry (used to update the bbox) - */ -function projectPoint(input: VectorPoint, geo: VectorGeometry): void { - const { x, y } = input; - const sin = Math.sin((y * Math.PI) / 180); - const y2 = 0.5 - (0.25 * Math.log((1 + sin) / (1 - sin))) / Math.PI; - input.x = x / 360 + 0.5; - input.y = y2 < 0 ? 0 : y2 > 1 ? 1 : y2; - // update bbox - geo.vecBBox = extendBBox(geo.vecBBox, input); -} - -/** - * Project a point from 0->1 coordinate space to lon-lat in place - * @param input - input vector to mutate - */ -function unprojectPoint(input: VectorPoint): void { - const { x, y } = input; - - // Revert the x coordinate - const lon = (x - 0.5) * 360; - // Revert the y coordinate - const y2 = 0.5 - y; - const lat = radToDeg(Math.atan(Math.sinh(Math.PI * (y2 * 2)))); - - input.x = lon; - input.y = lat; -} - -/** - * @param targetFace - face you want to project to - * @param stPoint - the point you want to project - * @returns - the projected point - */ -function stPointToFace(targetFace: Face, stPoint: STPoint): VectorPoint { - const { face: curFace, s, t, z, m } = stPoint; - if (targetFace === curFace) return { x: s, y: t, z, m }; - - const [rot, x, y] = FACE_RULE_SET[targetFace][curFace]; - const [newS, newT] = rotate(rot as 0 | 90 | -90, s, t); - - return { x: newS + x, y: newT + y, z, m }; -} - -/** - * @param rot - rotation - * @param s - input s - * @param t - input t - * @returns - new [s, t] after rotating - */ -function rotate(rot: 0 | 90 | -90, s: number, t: number): [s: number, t: number] { - if (rot === 90) return [t, 1 - s]; - else if (rot === -90) return [1 - t, s]; - else return [s, t]; // Handles the 0° case and any other unspecified rotations -} - -/** - * Ruleset for converting an S2Point from a face to another. - * While this this set includes opposite side faces, without axis mirroring, - * it is not technically accurate and shouldn't be used. Instead, data should let two points travel - * further than a full face width. - * FACE_RULE_SET[targetFace][currentFace] = [rot, x, y] - */ -const FACE_RULE_SET: [rotation: number, moveX: number, MoveY: number][][] = [ - // Target Face 0 - [ - [0, 0, 0], // Current Face 0 - [0, 1, 0], // Current Face 1 - [90, 0, 1], // Current Face 2 - [-90, 2, 0], // Current Face 3 - [-90, -1, 0], /// Current Face 4 - [0, 0, -1], /// Current Face 5 - ], - // Target Face 1 - [ - [0, -1, 0], // Current Face 0 - [0, 0, 0], // Current Face 1 - [0, 0, 1], // Current Face 2 - [-90, 1, 0], // Current Face 3 - [-90, 2, 0], // Current Face 4 - [90, 0, -1], // Current Face 5 - ], - // Target Face 2 - [ - [-90, -1, 0], // Current Face 0 - [0, 0, -1], // Current Face 1 - [0, 0, 0], // Current Face 2 - [0, 1, 0], // Current Face 3 - [90, 0, 1], // Current Face 4 - [-90, 2, 0], // Current Face 5 - ], - // Target Face 3 - [ - [-90, 2, 0], // Current Face 0 - [90, 0, -1], // Current Face 1 - [0, -1, 0], // Current Face 2 - [0, 0, 0], // Current Face 3 - [0, 0, 1], // Current Face 4 - [-90, 1, 0], // Current Face 5 - ], - // Target Face 4 - [ - [90, 0, 1], // Current Face 0 - [-90, 2, 0], // Current Face 1 - [-90, -1, 0], // Current Face 2 - [0, 0, -1], // Current Face 3 - [0, 0, 0], // Current Face 4 - [0, 1, 0], // Current Face 5 - ], - // Target Face 5 - [ - [0, 0, 1], // Current Face 0 - [-90, 1, 0], // Current Face 1 - [-90, 2, 0], // Current Face 2 - [90, 0, -1], // Current Face 3 - [0, -1, 0], // Current Face 4 - [0, 0, 0], // Current Face 5 - ], -]; diff --git a/src/wm/index.ts b/src/wm/index.ts deleted file mode 100644 index 6ae425a..0000000 --- a/src/wm/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './convert'; -export * as MercPoint from './mercCoords'; diff --git a/src/wm/mercCoords.ts b/src/wm/mercCoords.ts deleted file mode 100644 index 1e1db2a..0000000 --- a/src/wm/mercCoords.ts +++ /dev/null @@ -1,333 +0,0 @@ -import { A, EARTH_CIRCUMFERENCE, MAXEXTENT, degToRad, radToDeg } from '../util'; - -import type { BBox, Point, Point3D } from '../'; - -/** The source of the coordinate inputs */ -export type Sources = 'WGS84' | '900913'; - -/** - * Given a zoom and tilesize, build mercator positional attributes - * @param zoom - the zoom level - * @param tileSize - in pixels - * @returns - a bounding box sharing zoom size bounds - */ -function getZoomSize(zoom: number, tileSize: number): BBox { - const size = tileSize * Math.pow(2, zoom); - return [size / 360, size / (2 * Math.PI), size / 2, size]; -} - -/** - * Convert Longitude and Latitude to a mercator pixel coordinate - * @param ll - the longitude and latitude - * @param zoom - the zoom level - * @param antiMeridian - true if you want to use the antimeridian - * @param tileSize - in pixels - * @returns - the mercator pixel - */ -export function llToPX(ll: Point, zoom: number, antiMeridian = false, tileSize = 512): Point { - const { min, max, sin, log } = Math; - const [Bc, Cc, Zc, Ac] = getZoomSize(zoom, tileSize); - const expansion = antiMeridian ? 2 : 1; - const d = Zc; - const f = min(max(sin(degToRad(ll[1])), -0.999999999999), 0.999999999999); - let x = d + ll[0] * Bc; - let y = d + 0.5 * log((1 + f) / (1 - f)) * -Cc; - if (x > Ac * expansion) x = Ac * expansion; - if (y > Ac) y = Ac; - - return [x, y]; -} - -/** - * Convert mercator pixel coordinates to Longitude and Latitude - * @param px - the mercator pixel - * @param zoom - the zoom level - * @param tileSize - in pixels - * @returns - the longitude and latitude - */ -export function pxToLL(px: Point, zoom: number, tileSize = 512): Point { - const { atan, exp, PI } = Math; - const [Bc, Cc, Zc] = getZoomSize(zoom, tileSize); - const g = (px[1] - Zc) / -Cc; - const lon = (px[0] - Zc) / Bc; - const lat = radToDeg(2 * atan(exp(g)) - 0.5 * PI); - return [lon, lat]; -} - -/** - * Convert Longitude and Latitude to a mercator x-y coordinates - * @param ll - the longitude and latitude - * @returns - the mercator pixel - */ -export function llToMerc(ll: Point): Point { - const { tan, log, PI } = Math; - let x = degToRad(A * ll[0]); - let y = A * log(tan(PI * 0.25 + degToRad(0.5 * ll[1]))); - // if xy value is beyond maxextent (e.g. poles), return maxextent. - if (x > MAXEXTENT) x = MAXEXTENT; - if (x < -MAXEXTENT) x = -MAXEXTENT; - if (y > MAXEXTENT) y = MAXEXTENT; - if (y < -MAXEXTENT) y = -MAXEXTENT; - - return [x, y]; -} - -/** - * Convert mercator x-y coordinates to Longitude and Latitude - * @param merc - the mercator pixel - * @returns - the longitude and latitude - */ -export function mercToLL(merc: Point): Point { - const { atan, exp, PI } = Math; - const x = radToDeg(merc[0] / A); - const y = radToDeg(0.5 * PI - 2 * atan(exp(-merc[1] / A))); - return [x, y]; -} - -/** - * Convert a pixel coordinate to a tile x-y coordinate - * @param px - the pixel - * @param tileSize - in pixels - * @returns - the tile x-y - */ -export function pxToTile(px: Point, tileSize = 512): Point { - const { floor } = Math; - const x = floor(px[0] / tileSize); - const y = floor(px[1] / tileSize); - return [x, y]; -} - -/** - * Convert a tile x-y-z to a bbox of the form `[w, s, e, n]` - * @param tile - the tile - * @param tileSize - in pixels - * @returns - the bbox - */ -export function tilePxBounds(tile: Point3D, tileSize = 512): BBox { - const [, x, y] = tile; - const minX = x * tileSize; - const minY = y * tileSize; - const maxX = minX + tileSize; - const maxY = minY + tileSize; - return [minX, minY, maxX, maxY]; -} - -/** - * Convert a lat-lon and zoom to the tile's x-y coordinates - * @param ll - the lat-lon - * @param zoom - the zoom - * @param tileSize - in pixels - * @returns - the tile x-y - */ -export function llToTile(ll: Point, zoom: number, tileSize = 512): Point { - const px = llToPX(ll, zoom, false, tileSize); - return pxToTile(px, tileSize); -} - -/** - * given a lon-lat and tile, find the offset in pixels - * @param ll - the lon-lat - * @param tile - the tile - * @param tileSize - in pixels - * @returns - the tile x-y - */ -export function llToTilePx(ll: Point, tile: Point3D, tileSize = 512): Point { - const [zoom, x, y] = tile; - const px = llToPX(ll, zoom, false, tileSize); - const tileXStart = x * tileSize; - const tileYStart = y * tileSize; - - return [(px[0] - tileXStart) / tileSize, (px[1] - tileYStart) / tileSize]; -} - -/** - * Convert a bbox of the form `[w, s, e, n]` to a bbox of the form `[w, s, e, n]` - * The result can be in lon-lat (WGS84) or WebMercator (900913) - * If the input is in WebMercator (900913), the outSource should be set to 'WGS84' - * @param bbox - the bounding box to convert - * @param outSource - the output source - * @returns - the converted bbox - */ -export function convert(bbox: BBox, outSource: Sources): BBox { - if (outSource === 'WGS84') { - const low = mercToLL([bbox[0], bbox[1]]); - const high = mercToLL([bbox[2], bbox[3]]); - return [low[0], low[1], high[0], high[1]]; - } else { - const low = llToMerc([bbox[0], bbox[1]]); - const high = llToMerc([bbox[2], bbox[3]]); - return [low[0], low[1], high[0], high[1]]; - } -} - -/** - * Convert a tile x-y-z to a bbox of the form `[w, s, e, n]` - * The result can be in lon-lat (WGS84) or WebMercator (900913) - * The default result is in WebMercator (900913) - * @param x - the x tile position - * @param y - the y tile position - * @param zoom - the zoom level - * @param tmsStyle - if true, the y is inverted - * @param source - the source - * @param tileSize - in pixels - * @returns - the bounding box in WGS84 - */ -export function xyzToBBOX( - x: number, - y: number, - zoom: number, - tmsStyle = true, - source: Sources = '900913', - tileSize = 512, -): BBox { - // Convert xyz into bbox with srs WGS84 - // if tmsStyle, the y is inverted - if (tmsStyle) y = Math.pow(2, zoom) - 1 - y; - // Use +y to make sure it's a number to avoid inadvertent concatenation. - const bl: Point = [x * tileSize, (y + 1) * tileSize]; // bottom left - // Use +x to make sure it's a number to avoid inadvertent concatenation. - const tr: Point = [(x + 1) * tileSize, y * tileSize]; // top right - // to pixel-coordinates - const pxBL = pxToLL(bl, zoom, tileSize); - const pxTR = pxToLL(tr, zoom, tileSize); - - // If web mercator requested reproject to 900913. - if (source === '900913') { - const llBL = llToMerc(pxBL); - const llTR = llToMerc(pxTR); - return [llBL[0], llBL[1], llTR[0], llTR[1]]; - } - return [pxBL[0], pxBL[1], pxTR[0], pxTR[1]]; -} - -/** - * Convert a bbox of the form `[w, s, e, n]` to a tile's bounding box - * in the form of { minX, maxX, minY, maxY } - * The bbox can be in lon-lat (WGS84) or WebMercator (900913) - * The default expectation is in WebMercator (900913) - * @param bbox - the bounding box - * @param zoom - the zoom level - * @param tmsStyle - if true, the y is inverted - * @param source - the source - * @param tileSize - in pixels - * @returns - the tile's bounding box [minX, minY, maxX, maxY] - */ -export function bboxToXYZBounds( - bbox: BBox, - zoom: number, - tmsStyle = true, - source: Sources = '900913', - tileSize = 512, -): BBox { - const { min, max, pow, floor } = Math; - let bl: Point = [bbox[0], bbox[1]]; // bottom left - let tr: Point = [bbox[2], bbox[3]]; // top right - - if (source === '900913') { - bl = llToMerc(bl); - tr = llToMerc(tr); - } - - const pxBL = llToPX(bl, zoom, false, tileSize); - const pxTR = llToPX(tr, zoom, false, tileSize); - // Y = 0 for XYZ is the top hence minY uses pxTR[1]. - const x = [floor(pxBL[0] / tileSize), floor((pxTR[0] - 1) / tileSize)]; - const y = [floor(pxTR[1] / tileSize), floor((pxBL[1] - 1) / tileSize)]; - - const bounds: BBox = [ - min(...x) < 0 ? 0 : min(...x), - min(...y) < 0 ? 0 : min(...y), - max(...x), - max(...y), - ]; - - if (tmsStyle) { - const tmsMinY = pow(2, zoom) - 1 - bounds[3]; - const tmsMaxY = pow(2, zoom) - 1 - bounds[1]; - bounds[1] = tmsMinY; - bounds[3] = tmsMaxY; - } - - return bounds; -} - -/** - * The circumference at a line of latitude in meters. - * @param latitude - in degrees - * @returns - the circumference - */ -function circumferenceAtLatitude(latitude: number): number { - return EARTH_CIRCUMFERENCE * Math.cos((latitude * Math.PI) / 180); -} - -/** - * Convert longitude to mercator projection X-Value - * @param lng - in degrees - * @returns the X-Value - */ -export function mercatorXfromLng(lng: number): number { - return (180 + lng) / 360; -} - -/** - * Convert latitude to mercator projection Y-Value - * @param lat - in degrees - * @returns the Y-Value - */ -export function mercatorYfromLat(lat: number): number { - const { PI, log, tan } = Math; - return (180 - (180 / PI) * log(tan(PI / 4 + (lat * PI) / 360))) / 360; -} - -/** - * Convert altitude to mercator projection Z-Value - * @param altitude - in meters - * @param lat - in degrees - * @returns the Z-Value - */ -export function mercatorZfromAltitude(altitude: number, lat: number): number { - return altitude / circumferenceAtLatitude(lat); -} - -/** - * Convert mercator projection's X-Value to longitude - * @param x - in radians - * @returns the longitude - */ -export function lngFromMercatorX(x: number): number { - return x * 360 - 180; -} - -/** - * Convert mercator projection's Y-Value to latitude - * @param y - in radians - * @returns the latitude - */ -export function latFromMercatorY(y: number): number { - const { PI, atan, exp } = Math; - const y2 = 180 - y * 360; - return (360 / PI) * atan(exp((y2 * PI) / 180)) - 90; -} - -/** - * Convert mercator projection's Z-Value to altitude - * @param z - in meters - * @param y - in radians - * @returns the altitude - */ -export function altitudeFromMercatorZ(z: number, y: number): number { - return z * circumferenceAtLatitude(latFromMercatorY(y)); -} - -/** - * Determine the Mercator scale factor for a given latitude, see - * https://en.wikipedia.org/wiki/Mercator_projection#Scale_factor - * - * At the equator the scale factor will be 1, which increases at higher latitudes. - * @param lat - in degrees - * @returns the scale factor - */ -export function mercatorLatScale(lat: number): number { - const { cos, PI } = Math; - return 1 / cos((lat * PI) / 180); -} diff --git a/test/bbox.test.ts b/test/bbox.test.ts deleted file mode 100644 index 0f0dd0b..0000000 --- a/test/bbox.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { bboxOverlap, clipBBox, mergeBBoxes, pointOverlap } from '../src/bbox'; -import { describe, expect, it, test } from 'bun:test'; - -import type { BBox, BBox3D } from '../src'; - -describe('pointOverlap', () => { - it('check if point is within bbox', () => { - expect(pointOverlap([0, 0, 1, 1], { x: 0.5, y: 0.5 })).toBeTrue(); - }); - it('check if point is not within bbox', () => { - expect(pointOverlap([0, 0, 1, 1], { x: 2, y: 2 })).toBeFalse(); - }); -}); - -describe('bboxOverlap', () => { - it('no overlap returns undefined', () => { - expect(bboxOverlap([0, 0, 1, 1], [2, 2, 3, 3])).toBeUndefined(); - }); - it('overlap returns bbox', () => { - expect(bboxOverlap([0, 0, 1, 1], [0.5, 0.5, 1.5, 1.5])).toEqual([0.5, 0.5, 1, 1]); - }); -}); - -describe('mergeBBoxes', () => { - it('first is 2D, second is 3D', () => { - const bb1: BBox = [0, 0, 1, 1]; - const bb2: BBox3D = [0.4, 0.4, 1.2, 1.2, 0, 1]; - - expect(mergeBBoxes(bb1, bb2)).toEqual([0, 0, 1.2, 1.2, 0, 1]); - }); -}); - -test('clipBBox', () => { - const bbox: BBox = [0, 0, 10, 10]; - const res = clipBBox(bbox, 0, 2, 8); - expect(res).toEqual([2, 0, 8, 10]); - const res2 = clipBBox(res, 1, 2, 8); - expect(res2).toEqual([2, 2, 8, 8]); -}); diff --git a/test/clip.test.ts b/test/clip.test.ts deleted file mode 100644 index 9b473a6..0000000 --- a/test/clip.test.ts +++ /dev/null @@ -1,1308 +0,0 @@ -import { Tile } from '../src'; -import { fromFace } from '../src/id'; -import { clipLine, clipPoint, splitTile } from '../src/clip'; -import { describe, expect, it, test } from 'bun:test'; - -import type { BBox, VectorFeature, VectorLineString, VectorPointGeometry } from '../src'; - -test('clipPoint', () => { - const point: VectorPointGeometry = { - type: 'Point', - is3D: false, - coordinates: { x: 0.5, y: 0.5 }, - }; - const res = clipPoint(point, 0, 0, 1); - expect(res).toEqual(point); - - const res2 = clipPoint(point, 0, 0, 0.1); - expect(res2).toBeUndefined(); -}); - -test('clipLine - simple', () => { - const bbox: BBox = [0, 0, 10.5, 10.5]; - const line: VectorLineString = [ - { x: 0, y: 0 }, - { x: 5, y: 5, z: 4, m: { a: 1 } }, - { x: 10, y: 10, z: -2, m: { a: 2 } }, - { x: 15, y: 15, z: 3, m: { a: 3 } }, - ]; - - const res = clipLine(line, bbox, false, 0, 0); - expect(res).toEqual([ - { - line: [ - { m: undefined, x: 0, y: 0, z: undefined }, - { m: { a: 1 }, x: 5, y: 5, z: 4 }, - { m: { a: 2 }, x: 10, y: 10, z: -2 }, - { x: 10.5, y: 10.5, z: 0.5, m: { a: 3 }, t: 1 }, - ], - offset: 0, - vecBBox: [0, 0, 10.5, +10.5, +-2, +4], - }, - ]); - - // polygon case: - const res2 = clipLine(line, bbox, true, 0, 0); - expect(res2).toEqual([ - { - line: [ - { m: undefined, x: 0, y: 0, z: undefined }, - { m: { a: 1 }, x: 5, y: 5, z: 4 }, - { m: { a: 2 }, x: 10, y: 10, z: -2 }, - { x: 10.5, y: 10.5, z: 0.5, m: { a: 3 } }, - { m: undefined, x: 0, y: 0, z: undefined }, - ], - offset: 0, - vecBBox: [0, 0, 10.5, 10.5, -2, 4], - }, - ]); -}); - -test('clipLine - starts outside left', () => { - const bbox: BBox = [2.5, 2.5, 10.5, 10.5]; - const line: VectorLineString = [ - { x: 0, y: 0 }, - { x: 5, y: 5, z: 4, m: { a: 1 } }, - { x: 10, y: 10, z: -2, m: { a: 2 } }, - { x: 15, y: 15, z: 3, m: { a: 3 } }, - ]; - - const res = clipLine(line, bbox, false, 0, 0.5); - expect(res).toEqual([ - { - line: [ - { m: { a: 1 }, x: 2, y: 2, z: 4 }, - { m: { a: 1 }, x: 5, y: 5, z: 4 }, - { m: { a: 2 }, x: 10, y: 10, z: -2 }, - { x: 11, y: 11, z: 0.5, m: { a: 3 }, t: 1 }, - ], - offset: 2.8284271247461903, - vecBBox: [2, 2, 11, 11, -2, 4], - }, - ]); - - // polygon case: - const res2 = clipLine(line, bbox, true, 0, 0.5); - expect(res2).toEqual([ - { - line: [ - { m: { a: 1 }, x: 2, y: 2, z: 4 }, - { m: { a: 1 }, x: 5, y: 5, z: 4 }, - { m: { a: 2 }, x: 10, y: 10, z: -2 }, - { x: 11, y: 11, z: 0.5, m: { a: 3 } }, - { m: { a: 1 }, x: 2, y: 2, z: 4, t: 1 }, - ], - offset: 2.8284271247461903, - vecBBox: [2, 2, 11, 11, -2, 4], - }, - ]); -}); - -test('clipLine - starts outside right', () => { - const bbox: BBox = [2.5, 2.5, 10.5, 10.5]; - const line: VectorLineString = [ - { x: 15, y: 15, z: 3, m: { a: 3 } }, - { x: 10, y: 10, z: -2, m: { a: 2 } }, - { x: 5, y: 5, z: 4, m: { a: 1 } }, - { x: 0, y: 0 }, - ]; - - const res = clipLine(line, bbox, false, 0, 0); - expect(res).toEqual([ - { - line: [ - { x: 10.5, y: 10.5, z: 0.5, m: { a: 2 } }, - { m: { a: 2 }, x: 10, y: 10, z: -2 }, - { m: { a: 1 }, x: 5, y: 5, z: 4 }, - { m: { a: 1 }, x: 2.5, y: 2.5, z: 4, t: 1 }, - ], - offset: 6.363961030678928, - vecBBox: [2.5, 2.5, 10.5, 10.5, -2, 4], - }, - ]); - - // polygon case: - const res2 = clipLine(line, bbox, true, 0, 0); - expect(res2).toEqual([ - { - line: [ - { x: 10.5, y: 10.5, z: 0.5, m: { a: 2 } }, - { m: { a: 2 }, x: 10, y: 10, z: -2 }, - { m: { a: 1 }, x: 5, y: 5, z: 4 }, - { m: { a: 1 }, x: 2.5, y: 2.5, z: 4 }, - { x: 10.5, y: 10.5, z: 0.5, m: { a: 2 }, t: 1 }, - ], - offset: 6.363961030678928, - vecBBox: [2.5, 2.5, 10.5, 10.5, -2, 4], - }, - ]); -}); - -test('clipLine - starts outside right and moves to outside left', () => { - const bbox: BBox = [2.5, 2.5, 10.5, 10.5]; - const line: VectorLineString = [ - { x: 15, y: 15, z: 3, m: { a: 3 } }, - { x: 0, y: 0 }, - ]; - - const res = clipLine(line, bbox, false, 0, 0); - expect(res).toEqual([ - { - line: [ - { x: 10.5, y: 10.5, z: 3, m: undefined }, - { m: { a: 3 }, x: 2.5, y: 2.5, z: 3, t: 1 }, - ], - offset: 6.363961030678928, - vecBBox: [2.5, 2.5, 10.5, 10.5, 3, 3], - }, - ]); -}); - -test('clipLine - only vertically', () => { - const bbox: BBox = [2.5, 2.5, 10.5, 10.5]; - const line: VectorLineString = [ - { x: 4, y: 0 }, - { x: 5, y: 5, z: 4, m: { a: 1 } }, - { x: 7, y: 10, z: -2, m: { a: 2 } }, - { x: 9, y: 15, z: 3, m: { a: 3 } }, - ]; - - const res = clipLine(line, bbox, false, 0, 0); - expect(res).toEqual([ - { - line: [ - { m: { a: 1 }, x: 4.5, y: 2.5, z: 4, t: 1 }, - { m: { a: 1 }, x: 5, y: 5, z: 4 }, - { m: { a: 2 }, x: 7, y: 10, z: -2 }, - { m: { a: 3 }, x: 7.2, y: 10.5, z: 0.5, t: 1 }, - ], - offset: 2.5495097567963922, - vecBBox: [4.5, 2.5, 7.2, 10.5, -2, 4], - }, - ]); - - const res2 = clipLine(line, bbox, true, 0, 0); - expect(res2).toEqual([ - { - line: [ - { m: { a: 1 }, x: 4.5, y: 2.5, z: 4, t: 1 }, - { m: { a: 1 }, x: 5, y: 5, z: 4 }, - { m: { a: 2 }, x: 7, y: 10, z: -2 }, - { m: { a: 3 }, x: 7.2, y: 10.5, z: 0.5, t: 1 }, - { m: undefined, x: 7.5, y: 10.5, z: 3, t: 1 }, - { m: { a: 3 }, x: 4.833333333333333, y: 2.5, z: 3, t: 1 }, - { m: { a: 1 }, x: 4.5, y: 2.5, z: 4, t: 1 }, - ], - offset: 2.5495097567963922, - vecBBox: [4.5, 2.5, 7.5, 10.5, -2, 4], - }, - ]); -}); - -test('clipLine - passes through the x axis from left to right, then again right to left', () => { - const bbox: BBox = [0, 0, 10, 10]; - const line: VectorLineString = [ - { x: -2, y: 4 }, - { x: 2, y: 4 }, - { x: 8, y: 4 }, - { x: 12, y: 4 }, - { x: 12, y: 8 }, - { x: 8, y: 8 }, - { x: 2, y: 8 }, - { x: -2, y: 8 }, - ]; - - const res = clipLine(line, bbox, false, 0, 0); - expect(res).toEqual([ - { - line: [ - { x: 0, y: 4 }, - { x: 2, y: 4 }, - { x: 8, y: 4 }, - { x: 10, y: 4, t: 1 }, - ], - offset: 2, - vecBBox: [0, 4, 10, 4], - }, - { - line: [ - { x: 10, y: 8 }, - { x: 8, y: 8 }, - { x: 2, y: 8 }, - { x: 0, y: 8, t: 1 }, - ], - offset: 20, - vecBBox: [0, 8, 10, 8], - }, - ]); -}); - -describe('splitTile', () => { - it('Point', () => { - const faceID = fromFace(0); - const features: VectorFeature[] = [ - { - type: 'VectorFeature', - properties: { a: 2 }, - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: 0.25, y: 0.25 }, - vecBBox: [0.25, 0.25, 0.25, 0.25], - }, - }, - { - type: 'VectorFeature', - properties: { a: 2 }, - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: 0.75, y: 0.75 }, - vecBBox: [0.75, 0.75, 0.75, 0.75], - }, - }, - { - type: 'VectorFeature', - properties: { a: 2 }, - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: 0.75, y: 0.25 }, - vecBBox: [0.75, 0.25, 0.75, 0.25], - }, - }, - { - type: 'VectorFeature', - properties: { a: 2 }, - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: 0.25, y: 0.75 }, - vecBBox: [0.25, 0.75, 0.25, 0.75], - }, - }, - ]; - - const tile = new Tile(faceID); - for (const feature of features) tile.addFeature(feature); - - const res = splitTile(tile); - expect(res).toEqual([ - { - id: 288230376151711744n, - layers: { - default: { - features: [ - { - geometry: { - coordinates: { - x: 0.25, - y: 0.25, - }, - is3D: false, - type: 'Point', - vecBBox: [0.25, 0.25, 0.25, 0.25], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - { - id: 2017612633061982208n, - layers: { - default: { - features: [ - { - geometry: { - coordinates: { - x: 0.75, - y: 0.25, - }, - is3D: false, - type: 'Point', - vecBBox: [0.75, 0.25, 0.75, 0.25], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - { - id: 864691128455135232n, - layers: { - default: { - features: [ - { - geometry: { - coordinates: { - x: 0.25, - y: 0.75, - }, - is3D: false, - type: 'Point', - vecBBox: [0.25, 0.75, 0.25, 0.75], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - { - id: 1441151880758558720n, - layers: { - default: { - features: [ - { - geometry: { - coordinates: { - x: 0.75, - y: 0.75, - }, - is3D: false, - type: 'Point', - vecBBox: [0.75, 0.75, 0.75, 0.75], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - ]); - }); - it('MultiPoint', () => { - const faceID = fromFace(0); - const features: VectorFeature[] = [ - { - type: 'VectorFeature', - properties: { a: 2 }, - geometry: { - type: 'MultiPoint', - is3D: false, - coordinates: [ - { x: 0.25, y: 0.25 }, - { x: 0.75, y: 0.75 }, - { x: 0.75, y: 0.25 }, - { x: 0.25, y: 0.75 }, - ], - vecBBox: [0.25, 0.25, 0.75, 0.75], - }, - }, - ]; - - const tile = new Tile(faceID); - for (const feature of features) tile.addFeature(feature); - - const res = splitTile(tile); - expect(res).toEqual([ - { - id: 288230376151711744n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [{ x: 0.25, y: 0.25 }], - type: 'MultiPoint', - is3D: false, - vecBBox: [0.25, 0.25, 0.25, 0.25], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - { - id: 2017612633061982208n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [{ x: 0.75, y: 0.25 }], - type: 'MultiPoint', - is3D: false, - vecBBox: [0.75, 0.25, 0.75, 0.25], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - { - id: 864691128455135232n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [{ x: 0.25, y: 0.75 }], - type: 'MultiPoint', - is3D: false, - vecBBox: [0.25, 0.75, 0.25, 0.75], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - { - id: 1441151880758558720n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [{ x: 0.75, y: 0.75 }], - type: 'MultiPoint', - is3D: false, - vecBBox: [0.75, 0.75, 0.75, 0.75], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - ]); - - const splitAgain = splitTile(res[3]); - - expect(splitAgain).toEqual([ - { - id: 1224979098644774912n, - layers: {}, - transformed: false, - } as unknown as Tile, - { - id: 1657324662872342528n, - layers: {}, - transformed: false, - } as unknown as Tile, - { - id: 1369094286720630784n, - layers: {}, - transformed: false, - } as unknown as Tile, - { - id: 1513209474796486656n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [{ x: 0.75, y: 0.75 }], - type: 'MultiPoint', - is3D: false, - vecBBox: [0.75, 0.75, 0.75, 0.75], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - ]); - }); - - it('LineString', () => { - const faceID = fromFace(0); - const features: VectorFeature[] = [ - { - type: 'VectorFeature', - properties: { a: 2 }, - geometry: { - type: 'LineString', - is3D: false, - coordinates: [ - { x: 0.25, y: 0.25 }, - { x: 0.75, y: 0.75 }, - { x: 0.75, y: 0.25 }, - { x: 0.25, y: 0.75 }, - ], - vecBBox: [0.25, 0.25, 0.75, 0.75], - }, - }, - ]; - - const tile = new Tile(faceID); - for (const feature of features) tile.addFeature(feature); - - const res = splitTile(tile); - expect(res).toEqual([ - { - id: 288230376151711744n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [ - [ - { x: 0.25, y: 0.25 }, - { x: 0.5625, y: 0.5625, t: 1 }, - ], - [ - { x: 0.5625, y: 0.4375 }, - { x: 0.4375, y: 0.5625, t: 1 }, - ], - ], - offset: [0, 1.4722718241315027], - type: 'MultiLineString', - is3D: false, - vecBBox: [0.25, 0.25, 0.5625, 0.5625], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - { - id: 2017612633061982208n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [ - [ - { x: 0.4375, y: 0.4375 }, - { x: 0.5625, y: 0.5625, t: 1 }, - ], - [ - { x: 0.75, y: 0.5625, t: 1 }, - { x: 0.75, y: 0.25 }, - { x: 0.4375, y: 0.5625, t: 1 }, - ], - ], - offset: [0.2651650429449553, 0.8946067811865475], - type: 'MultiLineString', - is3D: false, - vecBBox: [0.4375, 0.25, 0.75, 0.5625], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - { - id: 864691128455135232n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [ - [ - { x: 0.4375, y: 0.4375, t: 1 }, - { x: 0.5625, y: 0.5625, t: 1 }, - ], - [ - { x: 0.5625, y: 0.4375 }, - { x: 0.25, y: 0.75 }, - ], - ], - offset: [0.2651650429449553, 1.4722718241315027], - type: 'MultiLineString', - is3D: false, - vecBBox: [0.25, 0.4375, 0.5625, 0.75], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - { - id: 1441151880758558720n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [ - [ - { x: 0.4375, y: 0.4375 }, - { x: 0.75, y: 0.75 }, - { x: 0.75, y: 0.4375, t: 1 }, - ], - [ - { x: 0.5625, y: 0.4375, t: 1 }, - { x: 0.4375, y: 0.5625, t: 1 }, - ], - ], - offset: [0.2651650429449553, 1.4722718241315027], - type: 'MultiLineString', - is3D: false, - vecBBox: [0.4375, 0.4375, 0.75, 0.75], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - ]); - }); - - it('MultiLineString', () => { - const faceID = fromFace(0); - const features: VectorFeature[] = [ - { - type: 'VectorFeature', - properties: { a: 2 }, - geometry: { - type: 'MultiLineString', - is3D: false, - coordinates: [ - [ - { x: 0.25, y: 0.25 }, - { x: 0.75, y: 0.25 }, - { x: 0.75, y: 0.75 }, - { x: 0.25, y: 0.75 }, - ], - [ - { x: 0.4, y: 0.4 }, - { x: 0.6, y: 0.4 }, - { x: 0.6, y: 0.6 }, - { x: 0.4, y: 0.6 }, - ], - ], - vecBBox: [0.25, 0.25, 0.75, 0.75], - }, - }, - ]; - - const tile = new Tile(faceID); - for (const feature of features) tile.addFeature(feature); - - const res = splitTile(tile); - expect(res).toEqual([ - { - id: 288230376151711744n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [ - [ - { x: 0.25, y: 0.25 }, - { x: 0.5625, y: 0.25, t: 1 }, - ], - [ - { x: 0.4, y: 0.4 }, - { x: 0.5625, y: 0.4, t: 1 }, - ], - ], - offset: [0, 0], - type: 'MultiLineString', - is3D: false, - vecBBox: [0.25, 0.25, 0.5625, 0.5625], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - { - id: 2017612633061982208n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [ - [ - { x: 0.4375, y: 0.25 }, - { x: 0.75, y: 0.25 }, - { x: 0.75, y: 0.5625, t: 1 }, - ], - [ - { x: 0.4375, y: 0.4 }, - { x: 0.6, y: 0.4 }, - { x: 0.6, y: 0.5625, t: 1 }, - ], - ], - offset: [0.1875, 0.03749999999999998], - type: 'MultiLineString', - is3D: false, - vecBBox: [0.4375, 0.25, 0.75, 0.5625], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - { - id: 864691128455135232n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [ - [ - { x: 0.5625, y: 0.75 }, - { x: 0.25, y: 0.75 }, - ], - [ - { x: 0.5625, y: 0.6 }, - { x: 0.4, y: 0.6 }, - ], - ], - offset: [1.1875, 0.4374999999999999], - type: 'MultiLineString', - is3D: false, - vecBBox: [0.25, 0.4375, 0.5625, 0.75], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - { - id: 1441151880758558720n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [ - [ - { x: 0.75, y: 0.4375, t: 1 }, - { x: 0.75, y: 0.75 }, - { x: 0.4375, y: 0.75, t: 1 }, - ], - [ - { x: 0.6, y: 0.4375, t: 1 }, - { x: 0.6, y: 0.6 }, - { x: 0.4375, y: 0.6, t: 1 }, - ], - ], - offset: [0.6875, 0.23749999999999993], - type: 'MultiLineString', - is3D: false, - vecBBox: [0.4375, 0.4375, 0.75, 0.75], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - ]); - }); - - it('Polygon', () => { - const faceID = fromFace(0); - const features: VectorFeature[] = [ - { - type: 'VectorFeature', - properties: { a: 2 }, - geometry: { - type: 'Polygon', - is3D: false, - coordinates: [ - [ - { x: 0.25, y: 0.25 }, - { x: 0.75, y: 0.25 }, - { x: 0.75, y: 0.75 }, - { x: 0.25, y: 0.75 }, - ], - [ - { x: 0.4, y: 0.6 }, - { x: 0.6, y: 0.6 }, - { x: 0.6, y: 0.4 }, - { x: 0.4, y: 0.4 }, - ], - ], - vecBBox: [0.25, 0.25, 0.75, 0.75], - }, - }, - ]; - - const tile = new Tile(faceID); - for (const feature of features) tile.addFeature(feature); - - const res = splitTile(tile); - expect(res).toEqual([ - { - id: 288230376151711744n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [ - [ - { x: 0.25, y: 0.25 }, - { x: 0.5625, y: 0.25 }, - { x: 0.5625, y: 0.5625, t: 1 }, - { x: 0.25, y: 0.5625, t: 1 }, - { x: 0.25, y: 0.25 }, - ], - [ - { x: 0.5625, y: 0.5625, t: 1 }, - { x: 0.5625, y: 0.4 }, - { x: 0.4, y: 0.4 }, - { x: 0.4, y: 0.5625, t: 1 }, - { x: 0.5625, y: 0.5625, t: 1 }, - ], - ], - offset: [2.5, 0.6374999999999998], - type: 'Polygon', - is3D: false, - vecBBox: [0.25, 0.25, 0.5625, 0.5625], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - { - id: 2017612633061982208n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [ - [ - { x: 0.4375, y: 0.25 }, - { x: 0.75, y: 0.25 }, - { x: 0.75, y: 0.5625, t: 1 }, - { x: 0.4375, y: 0.5625, t: 1 }, - { x: 0.4375, y: 0.25, t: 1 }, - ], - [ - { x: 0.6, y: 0.5625, t: 1 }, - { x: 0.6, y: 0.4 }, - { x: 0.4375, y: 0.4 }, - { x: 0.4375, y: 0.5625, t: 1 }, - { x: 0.6, y: 0.5625, t: 1 }, - ], - ], - offset: [1.5, 0.23749999999999993], - type: 'Polygon', - is3D: false, - vecBBox: [0.4375, 0.25, 0.75, 0.5625], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - { - id: 864691128455135232n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [ - [ - { x: 0.5625, y: 0.4375, t: 1 }, - { x: 0.5625, y: 0.75 }, - { x: 0.25, y: 0.75 }, - { x: 0.25, y: 0.4375, t: 1 }, - { x: 0.5625, y: 0.4375, t: 1 }, - ], - [ - { x: 0.4, y: 0.6 }, - { x: 0.5625, y: 0.6 }, - { x: 0.5625, y: 0.4375, t: 1 }, - { x: 0.4, y: 0.4375, t: 1 }, - { x: 0.4, y: 0.6 }, - ], - ], - offset: [1.6875, 0.9999999999999998], - type: 'Polygon', - is3D: false, - vecBBox: [0.25, 0.4375, 0.5625, 0.75], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - { - id: 1441151880758558720n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [ - [ - { x: 0.75, y: 0.4375, t: 1 }, - { x: 0.75, y: 0.75 }, - { x: 0.4375, y: 0.75 }, - { x: 0.4375, y: 0.4375, t: 1 }, - { x: 0.75, y: 0.4375, t: 1 }, - ], - [ - { x: 0.4375, y: 0.6 }, - { x: 0.6, y: 0.6 }, - { x: 0.6, y: 0.4375, t: 1 }, - { x: 0.4375, y: 0.4375, t: 1 }, - { x: 0.4375, y: 0.6, t: 1 }, - ], - ], - offset: [0.6875, 0.5999999999999999], - type: 'Polygon', - is3D: false, - vecBBox: [0.4375, 0.4375, 0.75, 0.75], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - ]); - }); - - it('MultiPolygon', () => { - const faceID = fromFace(0); - const features: VectorFeature[] = [ - { - type: 'VectorFeature', - properties: { a: 2 }, - geometry: { - type: 'MultiPolygon', - is3D: false, - coordinates: [ - [ - [ - { x: 0.25, y: 0.25 }, - { x: 0.75, y: 0.25 }, - { x: 0.75, y: 0.75 }, - { x: 0.25, y: 0.75 }, - ], - [ - { x: 0.4, y: 0.6 }, - { x: 0.6, y: 0.6 }, - { x: 0.6, y: 0.4 }, - { x: 0.4, y: 0.4 }, - ], - ], - ], - vecBBox: [0.25, 0.25, 0.75, 0.75], - }, - }, - ]; - - const tile = new Tile(faceID); - for (const feature of features) tile.addFeature(feature); - - const res = splitTile(tile); - expect(res).toEqual([ - { - id: 288230376151711744n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [ - [ - [ - { x: 0.25, y: 0.25 }, - { x: 0.5625, y: 0.25 }, - { x: 0.5625, y: 0.5625, t: 1 }, - { x: 0.25, y: 0.5625, t: 1 }, - { x: 0.25, y: 0.25 }, - ], - [ - { x: 0.5625, y: 0.5625, t: 1 }, - { x: 0.5625, y: 0.4 }, - { x: 0.4, y: 0.4 }, - { x: 0.4, y: 0.5625, t: 1 }, - { x: 0.5625, y: 0.5625, t: 1 }, - ], - ], - ], - offset: [[2.5, 0.6374999999999998]], - type: 'MultiPolygon', - is3D: false, - vecBBox: [0.25, 0.25, 0.5625, 0.5625], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - { - id: 2017612633061982208n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [ - [ - [ - { x: 0.4375, y: 0.25 }, - { x: 0.75, y: 0.25 }, - { x: 0.75, y: 0.5625, t: 1 }, - { x: 0.4375, y: 0.5625, t: 1 }, - { x: 0.4375, y: 0.25, t: 1 }, - ], - [ - { x: 0.6, y: 0.5625, t: 1 }, - { x: 0.6, y: 0.4 }, - { x: 0.4375, y: 0.4 }, - { x: 0.4375, y: 0.5625, t: 1 }, - { x: 0.6, y: 0.5625, t: 1 }, - ], - ], - ], - offset: [[1.5, 0.23749999999999993]], - type: 'MultiPolygon', - is3D: false, - vecBBox: [0.4375, 0.25, 0.75, 0.5625], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - { - id: 864691128455135232n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [ - [ - [ - { x: 0.5625, y: 0.4375, t: 1 }, - { x: 0.5625, y: 0.75 }, - { x: 0.25, y: 0.75 }, - { x: 0.25, y: 0.4375, t: 1 }, - { x: 0.5625, y: 0.4375, t: 1 }, - ], - [ - { x: 0.4, y: 0.6 }, - { x: 0.5625, y: 0.6 }, - { x: 0.5625, y: 0.4375, t: 1 }, - { x: 0.4, y: 0.4375, t: 1 }, - { x: 0.4, y: 0.6 }, - ], - ], - ], - offset: [[1.6875, 0.9999999999999998]], - type: 'MultiPolygon', - is3D: false, - vecBBox: [0.25, 0.4375, 0.5625, 0.75], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - { - id: 1441151880758558720n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [ - [ - [ - { x: 0.75, y: 0.4375, t: 1 }, - { x: 0.75, y: 0.75 }, - { x: 0.4375, y: 0.75 }, - { x: 0.4375, y: 0.4375, t: 1 }, - { x: 0.75, y: 0.4375, t: 1 }, - ], - [ - { x: 0.4375, y: 0.6 }, - { x: 0.6, y: 0.6 }, - { x: 0.6, y: 0.4375, t: 1 }, - { x: 0.4375, y: 0.4375, t: 1 }, - { x: 0.4375, y: 0.6, t: 1 }, - ], - ], - ], - offset: [[0.6875, 0.5999999999999999]], - type: 'MultiPolygon', - is3D: false, - vecBBox: [0.4375, 0.4375, 0.75, 0.75], - }, - properties: { - a: 2, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: false, - } as unknown as Tile, - ]); - }); -}); diff --git a/test/convert.test.ts b/test/convert.test.ts deleted file mode 100644 index 3237a16..0000000 --- a/test/convert.test.ts +++ /dev/null @@ -1,448 +0,0 @@ -import { convert } from '../src/convert'; -import { describe, expect, it } from 'bun:test'; - -import type { - Feature, - FeatureCollection, - S2Feature, - S2FeatureCollection, - VectorFeature, -} from '../src'; - -// FeatureCollection | S2FeatureCollection | Feature | VectorFeature | S2Feature - -describe('convert point', () => { - it('WM', () => { - const feature: Feature = { - type: 'Feature', - properties: { a: 1 }, - geometry: { - type: 'Point', - coordinates: [0, 0], - }, - }; - const vectorFeature: VectorFeature = { - type: 'VectorFeature', - properties: { b: 2 }, - geometry: { - type: 'Point', - is3D: true, - coordinates: { x: 45, y: 45, z: 10, m: { c: 3 } }, - bbox: [0.5, 0.5, 0.75, 0.75], - }, - }; - const s2Feature: S2Feature = { - type: 'S2Feature', - properties: { c: 3 }, - geometry: { - type: 'Point', - is3D: true, - coordinates: { x: 45, y: 45, z: 10, m: { d: 4 } }, - bbox: [0, 0, 1, 1], - }, - face: 0, - }; - const featureCollection: FeatureCollection = { - type: 'FeatureCollection', - features: [ - { - type: 'Feature', - properties: { a: 1 }, - geometry: { - type: 'Point', - coordinates: [0, 0], - bbox: [0.1, 0.1, 0.2, 0.2], - }, - }, - { - type: 'VectorFeature', - properties: { b: 2 }, - geometry: { - type: 'Point', - is3D: true, - coordinates: { x: 45, y: 45, z: 10, m: { c: 3 } }, - bbox: [0.5, 0.5, 0.75, 0.75], - }, - }, - ], - }; - const s2FeatureCollection: S2FeatureCollection = { - type: 'S2FeatureCollection', - features: [ - { - type: 'S2Feature', - properties: { c: 3 }, - geometry: { - is3D: true, - type: 'Point', - coordinates: { x: 45, y: 45, z: 10, m: { d: 4 } }, - bbox: [0, 0, 1, 1], - }, - face: 0, - }, - ], - faces: [0], - }; - - const res1 = convert('WM', feature, 3, 14, true); - const res2 = convert('WM', vectorFeature, 3, 14); - const res3 = convert('WM', s2Feature, 3, 14); - const res4 = convert('WM', featureCollection, 3, 14); - const res5 = convert('WM', s2FeatureCollection, 3, 14); - - expect(res1).toEqual([ - { - geometry: { - bbox: [0, 0, 0, 0], - is3D: false, - coordinates: { - m: undefined, - x: 0.5, - y: 0.5, - z: undefined, - }, - type: 'Point', - vecBBox: [0.5, 0.5, 0.5, 0.5], - }, - id: undefined, - metadata: undefined, - properties: { - a: 1, - }, - type: 'VectorFeature', - }, - ]); - expect(res2).toEqual([ - { - geometry: { - bbox: [0.5, 0.5, 0.75, 0.75], - is3D: true, - coordinates: { - m: { c: 3 }, - x: 0.625, - y: 0.35972503691520497, - z: 10, - }, - type: 'Point', - vecBBox: [0.625, 0.35972503691520497, 0.625, 0.35972503691520497, 10, 10], - }, - properties: { b: 2 }, - type: 'VectorFeature', - }, - ]); - expect(res3).toEqual([ - { - geometry: { - bbox: [0, 0, 1, 1], - is3D: true, - coordinates: { - m: { d: 4 }, - x: 0.7499410464492606, - y: 0.35972504463587185, - z: 10, - }, - type: 'Point', - vecBBox: [ - 0.7499410464492606, 0.35972504463587185, 0.7499410464492606, 0.35972504463587185, 10, - 10, - ], - }, - id: undefined, - metadata: undefined, - properties: { - c: 3, - }, - type: 'VectorFeature', - }, - ]); - expect(res4).toEqual([ - { - geometry: { - bbox: [0.1, 0.1, 0.2, 0.2], - is3D: false, - coordinates: { - m: undefined, - x: 0.5, - y: 0.5, - z: undefined, - }, - type: 'Point', - vecBBox: [0.5, 0.5, 0.5, 0.5], - }, - id: undefined, - metadata: undefined, - properties: { - a: 1, - }, - type: 'VectorFeature', - }, - { - geometry: { - bbox: [0.5, 0.5, 0.75, 0.75], - is3D: true, - coordinates: { - m: { - c: 3, - }, - x: 0.625, - y: 0.35972503691520497, - z: 10, - }, - type: 'Point', - vecBBox: [0.625, 0.35972503691520497, 0.625, 0.35972503691520497, 10, 10], - }, - properties: { - b: 2, - }, - type: 'VectorFeature', - }, - ]); - expect(res5).toEqual([ - { - geometry: { - bbox: [0, 0, 1, 1], - is3D: true, - coordinates: { - m: { - d: 4, - }, - x: 0.7499410464492606, - y: 0.35972504463587185, - z: 10, - }, - type: 'Point', - vecBBox: [ - 0.7499410464492606, 0.35972504463587185, 0.7499410464492606, 0.35972504463587185, 10, - 10, - ], - }, - id: undefined, - metadata: undefined, - properties: { - c: 3, - }, - type: 'VectorFeature', - }, - ]); - }); - - it('S2', () => { - const feature: Feature = { - type: 'Feature', - properties: { a: 1 }, - geometry: { - type: 'Point', - coordinates: [0, 0], - bbox: [0.1, 0.1, 0.2, 0.2], - }, - }; - const vectorFeature: VectorFeature = { - type: 'VectorFeature', - properties: { b: 2 }, - geometry: { - type: 'Point', - is3D: true, - coordinates: { x: 45, y: 45, z: 10, m: { c: 3 } }, - bbox: [0.5, 0.5, 0.75, 0.75], - }, - }; - const s2Feature: S2Feature = { - type: 'S2Feature', - properties: { c: 3 }, - geometry: { - type: 'Point', - is3D: true, - coordinates: { x: 45, y: 45, z: 10, m: { d: 4 } }, - bbox: [0, 0, 1, 1], - }, - face: 0, - }; - const featureCollection: FeatureCollection = { - type: 'FeatureCollection', - features: [ - { - type: 'Feature', - properties: { a: 1 }, - geometry: { - type: 'Point', - coordinates: [45, 22], - }, - }, - { - type: 'VectorFeature', - properties: { b: 2 }, - geometry: { - type: 'Point', - is3D: true, - coordinates: { x: 45, y: 45, z: 10, m: { c: 3 } }, - bbox: [0.5, 0.5, 0.75, 0.75], - }, - }, - ], - }; - const s2FeatureCollection: S2FeatureCollection = { - type: 'S2FeatureCollection', - features: [ - { - type: 'S2Feature', - properties: { c: 3 }, - geometry: { - type: 'Point', - is3D: true, - coordinates: { x: 45, y: 45, z: 10, m: { d: 4 } }, - bbox: [0, 0, 1, 1], - }, - face: 0, - }, - ], - faces: [0], - }; - - const res1 = convert('S2', feature, 3, 14); - const res2 = convert('S2', vectorFeature, 3, 14); - const res3 = convert('S2', s2Feature, 3, 14); - const res4 = convert('S2', featureCollection, 3, 14, true); - const res5 = convert('S2', s2FeatureCollection, 3, 14); - - expect(res1).toEqual([ - { - face: 0, - geometry: { - bbox: [0.1, 0.1, 0.2, 0.2], - is3D: false, - coordinates: { - m: undefined, - x: 0.5, - y: 0.5, - z: undefined, - }, - type: 'Point', - vecBBox: [0.5, 0.5, 0.5, 0.5], - }, - id: undefined, - metadata: undefined, - properties: { - a: 1, - }, - type: 'S2Feature', - }, - ]); - - expect(res2).toEqual([ - { - face: 2, - geometry: { - bbox: [0.5, 0.5, 0.75, 0.75], - is3D: true, - coordinates: { - m: { c: 3 }, - x: 0.11663705879751174, - y: 0.11663705879751174, - z: 10, - }, - type: 'Point', - vecBBox: [ - 0.11663705879751174, 0.11663705879751174, 0.11663705879751174, 0.11663705879751174, 10, - 10, - ], - }, - id: undefined, - metadata: undefined, - properties: { b: 2 }, - type: 'S2Feature', - }, - ]); - - expect(res3).toEqual([ - { - face: 0, - geometry: { - bbox: [0, 0, 1, 1], - is3D: true, - coordinates: { - m: { - d: 4, - }, - x: 45, - y: 45, - z: 10, - }, - type: 'Point', - }, - properties: { - c: 3, - }, - type: 'S2Feature', - }, - ]); - - expect(res4).toEqual([ - { - face: 0, - geometry: { - bbox: [45, 22, 45, 22], - is3D: false, - coordinates: { - m: undefined, - x: 0.9999999999999999, - y: 0.8237320717914717, - z: undefined, - }, - type: 'Point', - vecBBox: [0.9999999999999999, 0.8237320717914717, 0.9999999999999999, 0.8237320717914717], - }, - id: undefined, - metadata: undefined, - properties: { - a: 1, - }, - type: 'S2Feature', - }, - { - face: 2, - geometry: { - bbox: [0.5, 0.5, 0.75, 0.75], - is3D: true, - coordinates: { - m: { - c: 3, - }, - x: 0.11663705879751174, - y: 0.11663705879751174, - z: 10, - }, - type: 'Point', - vecBBox: [ - 0.11663705879751174, 0.11663705879751174, 0.11663705879751174, 0.11663705879751174, 10, - 10, - ], - }, - id: undefined, - metadata: undefined, - properties: { - b: 2, - }, - type: 'S2Feature', - }, - ]); - - expect(res5).toEqual([ - { - face: 0, - geometry: { - bbox: [0, 0, 1, 1], - is3D: true, - coordinates: { - m: { d: 4 }, - x: 45, - y: 45, - z: 10, - }, - type: 'Point', - }, - properties: { c: 3 }, - type: 'S2Feature', - }, - ]); - }); -}); diff --git a/test/id.test.ts b/test/id.test.ts deleted file mode 100644 index 90c1c22..0000000 --- a/test/id.test.ts +++ /dev/null @@ -1,392 +0,0 @@ -import { - boundsST, - centerST, - child, - childPosition, - children, - childrenIJ, - contains, - distance, - face, - fromDistance, - fromFace, - fromIJ, - fromIJSame, - fromIJWrap, - fromLonLat, - fromS2Point, - fromST, - fromUV, - intersects, - isFace, - isLeaf, - level, - neighbors, - neighborsIJ, - next, - parent, - pos, - prev, - range, - sizeST, - toFaceIJ, - toIJ, - toLonLat, - toS2Point, - toST, - toUV, - vertexNeighbors, -} from '../src/id'; -import { describe, expect, it } from 'bun:test'; - -describe('boundsST', () => { - it('should return the bounds for a given id and level', () => { - expect(boundsST(fromFace(0), 0)).toEqual([0, 0, 1, 1]); - expect(boundsST(fromFace(0), 1)).toEqual([0.25, 0.25, 0.75, 0.75]); - expect(boundsST(fromFace(0), 2)).toEqual([0.375, 0.375, 0.625, 0.625]); - expect(boundsST(fromFace(1), 0)).toEqual([0, 0, 1, 1]); - }); -}); - -describe('centerST', () => { - it('should return the center for a given id and level', () => { - expect(centerST(fromFace(0))).toEqual([0, 0.5, 0.5]); - expect(centerST(fromFace(1))).toEqual([1, 0.5, 0.5]); - expect(centerST(fromFace(2))).toEqual([2, 0.5, 0.5]); - expect(centerST(fromFace(3))).toEqual([3, 0.5, 0.5]); - }); -}); - -describe('child', () => { - it('should return the child cell', () => { - expect(child(fromFace(0), 0n)).toBe(288230376151711744n); - expect(child(fromFace(0), 1n)).toBe(864691128455135232n); - expect(child(fromFace(0), 2n)).toBe(1441151880758558720n); - expect(child(fromFace(0), 3n)).toBe(2017612633061982208n); - }); -}); - -describe('childPosition', () => { - it('should return the child position', () => { - expect(childPosition(fromFace(0), 0)).toBe(0); - expect(childPosition(fromFace(0), 1)).toBe(2); - expect(childPosition(fromFace(0), 2)).toBe(0); - expect(childPosition(fromFace(1), 0)).toBe(1); - expect(childPosition(child(fromFace(0), 1n), 1)).toBe(1); - }); -}); - -describe('children', () => { - it('should return the children cells', () => { - expect(children(fromFace(0))).toEqual([ - 288230376151711744n, - 864691128455135232n, - 1441151880758558720n, - 2017612633061982208n, - ]); - }); -}); - -describe('childrenIJ', () => { - it('should return the children cells', () => { - expect(childrenIJ(0, 0, 0, 0)).toEqual([ - 288230376151711744n, - 2017612633061982208n, - 864691128455135232n, - 1441151880758558720n, - ]); - }); -}); - -describe('contains', () => { - it('should return if the cell contains the given cell', () => { - expect(contains(fromFace(0), fromFace(0))).toBe(true); - expect(contains(fromFace(0), fromFace(1))).toBe(false); - expect(contains(fromFace(0), child(fromFace(0), 1n))).toBe(true); - }); -}); - -describe('distance', () => { - it('should return the distance between two cells', () => { - expect(distance(fromFace(0), 0)).toBe(0n); - expect(distance(fromFace(0), 1)).toBe(2n); - expect(distance(fromFace(0), 2)).toBe(8n); - expect(distance(fromFace(0), 3)).toBe(32n); - }); -}); - -describe('face', () => { - it('should return the face for a given cell', () => { - expect(face(fromFace(0))).toBe(0); - expect(face(fromFace(1))).toBe(1); - expect(face(fromFace(2))).toBe(2); - expect(face(fromFace(3))).toBe(3); - expect(face(fromFace(4))).toBe(4); - expect(face(fromFace(5))).toBe(5); - }); -}); - -describe('fromDistance', () => { - it('should return the cell id for a given distance', () => { - expect(fromDistance(0n)).toBe(1n); - expect(fromDistance(1n)).toBe(3n); - expect(fromDistance(2n)).toBe(5n); - expect(fromDistance(3n)).toBe(7n); - expect(fromDistance(4n)).toBe(9n); - expect(fromDistance(5n)).toBe(11n); - }); -}); - -describe('fromFace', () => { - it('should return the cell id for a given face', () => { - expect(fromFace(0)).toBe(1152921504606846976n); - expect(fromFace(1)).toBe(3458764513820540928n); - expect(fromFace(2)).toBe(5764607523034234880n); - expect(fromFace(3)).toBe(8070450532247928832n); - expect(fromFace(4)).toBe(10376293541461622784n); - expect(fromFace(5)).toBe(12682136550675316736n); - }); -}); - -describe('fromIJ', () => { - it('should return the cell id for a given i-j', () => { - expect(fromIJ(0, 0, 0)).toBe(1n); - expect(fromIJ(0, 1, 0)).toBe(3n); - expect(fromIJ(0, 1, 1)).toBe(5n); - expect(fromIJ(0, 0, 1)).toBe(7n); - }); -}); - -describe('fromIJSame', () => { - it('should return the cell id for a given i-j', () => { - expect(fromIJSame(0, 0, 0, true)).toBe(1n); - expect(fromIJSame(0, 1, 0, true)).toBe(3n); - expect(fromIJSame(0, 1, 1, true)).toBe(5n); - expect(fromIJSame(0, 0, 1, true)).toBe(7n); - }); -}); - -describe('fromIJWrap', () => { - it('should return the cell id for a given i-j', () => { - expect(fromIJWrap(0, 0, 0)).toBe(1n); - expect(fromIJWrap(0, 1, 0)).toBe(3n); - expect(fromIJWrap(0, 1, 1)).toBe(5n); - expect(fromIJWrap(0, 0, 1)).toBe(7n); - }); -}); - -describe('fromLonLat', () => { - it('should return the cell id for a given lon-lat', () => { - expect(fromLonLat(0, 0)).toBe(1152921504606846977n); - expect(fromLonLat(90, 0)).toBe(3458764513820540929n); - expect(fromLonLat(0, 90)).toBe(5764607523034234881n); - expect(fromLonLat(-90, 0)).toBe(10376293541461622785n); - expect(fromLonLat(0, -90)).toBe(12682136550675316737n); - }); -}); - -describe('fromS2Point', () => { - it('should return the cell id for a given s2 point', () => { - expect(fromS2Point([1, 0, 0])).toBe(1152921504606846977n); - expect(fromS2Point([0, 1, 0])).toBe(3458764513820540929n); - expect(fromS2Point([0, 0, 1])).toBe(5764607523034234881n); - expect(fromS2Point([-1, 0, 0])).toBe(8070450532247928833n); - expect(fromS2Point([0, -1, 0])).toBe(10376293541461622785n); - expect(fromS2Point([0, 0, -1])).toBe(12682136550675316737n); - }); -}); - -describe('fromST', () => { - it('should return the cell id for a given a Face-S-T', () => { - expect(fromST(0, 0, 0)).toBe(1n); - expect(fromST(0, 1, 0)).toBe(2305843009213693951n); - expect(fromST(0, 0, 1)).toBe(768614336404564651n); - expect(fromST(0, 0.5, 0.5)).toBe(1152921504606846977n); - expect(fromST(0, 1, 1)).toBe(1537228672809129301n); - }); -}); - -describe('fromUV', () => { - it('should return the cell id for a given a Face-U-V', () => { - expect(fromUV(0, 0, 0)).toBe(1152921504606846977n); - expect(fromUV(0, 1, 0)).toBe(1729382256910270463n); - expect(fromUV(0, 0, 1)).toBe(1345075088707988139n); - expect(fromUV(0, -1, 0)).toBe(576460752303423489n); - expect(fromUV(0, 0, -1)).toBe(2113689425112552789n); - }); -}); - -describe('intersects', () => { - it('should return if the cell intersects the given cell', () => { - expect(intersects(fromFace(0), fromFace(0))).toBe(true); - expect(intersects(fromFace(0), fromFace(1))).toBe(false); - expect(intersects(fromFace(0), child(fromFace(0), 1n))).toBe(true); - }); -}); - -describe('isFace', () => { - it('should return if the cell is a face', () => { - expect(isFace(1152921504606846976n)).toBe(true); - expect(isFace(1152921504606846977n)).toBe(false); - }); -}); - -describe('isLeaf', () => { - it('should return if the cell is a leaf', () => { - expect(isLeaf(1152921504606846976n)).toBe(false); - expect(isLeaf(1152921504606846977n)).toBe(true); - }); -}); - -describe('level', () => { - it('should return the level for a given cell', () => { - expect(level(1152921504606846976n)).toBe(0); - expect(level(1152921504606846977n)).toBe(30); - }); -}); - -describe('neighbors', () => { - it('should return the neighbors cells', () => { - expect(neighbors(fromFace(0))).toEqual([ - 12682136550675316736n, - 3458764513820540928n, - 5764607523034234880n, - 10376293541461622784n, - ]); - }); -}); - -describe('neighborsIJ', () => { - it('should return the neighbors cells', () => { - expect(neighborsIJ(0, 0, 0, 0)).toEqual([ - 12682136550675316736n, - 3458764513820540928n, - 5764607523034234880n, - 10376293541461622784n, - ]); - }); -}); - -describe('next', () => { - it('should return the next cell', () => { - expect(next(fromFace(0))).toBe(3458764513820540928n); - expect(next(fromFace(1))).toBe(5764607523034234880n); - expect(next(fromFace(2))).toBe(8070450532247928832n); - expect(next(fromFace(3))).toBe(10376293541461622784n); - }); -}); - -describe('parent', () => { - it('should return the parent cell', () => { - expect(parent(child(fromFace(0), 0n))).toBe(fromFace(0)); - }); -}); - -describe('pos', () => { - it('should return the position for a given cell', () => { - expect(pos(fromFace(0))).toBe(1152921504606846976n); - expect(pos(fromFace(1))).toBe(1152921504606846976n); - expect(pos(fromFace(2))).toBe(1152921504606846976n); - expect(pos(fromFace(3))).toBe(1152921504606846976n); - }); -}); - -describe('prev', () => { - it('should return the prev cell', () => { - expect(prev(fromFace(1))).toBe(1152921504606846976n); - expect(prev(fromFace(2))).toBe(3458764513820540928n); - expect(prev(fromFace(3))).toBe(5764607523034234880n); - expect(prev(fromFace(4))).toBe(8070450532247928832n); - expect(prev(fromFace(5))).toBe(10376293541461622784n); - }); -}); - -describe('range', () => { - it('should return the range for a given level', () => { - expect(range(0n)).toEqual([1n, -1n]); - expect(range(1n)).toEqual([1n, 1n]); - expect(range(fromFace(0))).toEqual([1n, 2305843009213693951n]); - }); -}); - -describe('sizeST', () => { - it('should return the size for a given level', () => { - expect(sizeST(0)).toBe(1); - expect(sizeST(1)).toBe(0.5); - expect(sizeST(2)).toBe(0.25); - }); -}); - -describe('toIJ', () => { - it('should return the i-j for a given cell', () => { - expect(toIJ(fromFace(0))).toEqual([0, 536870912, 536870912, 0]); - expect(toIJ(fromFace(1))).toEqual([1, 536870912, 536870912, 1]); - expect(toIJ(fromFace(2))).toEqual([2, 536870912, 536870912, 0]); - expect(toIJ(fromFace(3))).toEqual([3, 536870912, 536870912, 1]); - }); - it('also given a level, should return the i-j for a given cell', () => { - expect(toIJ(fromFace(0), 0)).toEqual([0, 0, 0, 0]); - expect(toIJ(fromFace(0), 1)).toEqual([0, 1, 1, 0]); - }); -}); - -describe('toLonLat', () => { - it('should return the lon-lat for a given cell', () => { - expect(toLonLat(fromFace(0))).toEqual([0, 0]); - expect(toLonLat(fromFace(1))).toEqual([90, 0]); - expect(toLonLat(fromFace(2))).toEqual([-180, 90]); - expect(toLonLat(fromFace(3))).toEqual([-180, -0]); - }); -}); - -describe('toS2Point', () => { - it('should return the s2 point for a given cell', () => { - expect(toS2Point(fromFace(0))).toEqual([1, 0, 0]); - expect(toS2Point(fromFace(1))).toEqual([-0, 1, 0]); - expect(toS2Point(fromFace(2))).toEqual([-0, -0, 1]); - expect(toS2Point(fromFace(3))).toEqual([-1, -0, -0]); - }); -}); - -describe('toST', () => { - it('should return the s-t for a given cell', () => { - expect(toST(fromFace(0))).toEqual([0, 0.5, 0.5]); - expect(toST(fromFace(1))).toEqual([1, 0.5, 0.5]); - expect(toST(fromFace(2))).toEqual([2, 0.5, 0.5]); - expect(toST(fromFace(3))).toEqual([3, 0.5, 0.5]); - expect(toST(child(fromFace(0), 0n))).toEqual([0, 0.25, 0.25]); - expect(toST(child(fromFace(0), 1n))).toEqual([0, 0.25, 0.75]); - }); -}); - -describe('toUV', () => { - it('should return the u-v for a given cell', () => { - expect(toUV(fromFace(0))).toEqual([0, 0, 0]); - expect(toUV(fromFace(1))).toEqual([1, 0, 0]); - expect(toUV(fromFace(2))).toEqual([2, 0, 0]); - expect(toUV(fromFace(3))).toEqual([3, 0, 0]); - expect(toUV(child(fromFace(0), 0n))).toEqual([0, -0.41666666666666663, -0.41666666666666663]); - expect(toUV(child(fromFace(0), 1n))).toEqual([0, -0.41666666666666663, 0.41666666666666663]); - }); -}); - -describe('vertexNeighbors', () => { - it('should return the vertex neighbors for a given cell', () => { - expect(vertexNeighbors(fromFace(0))).toEqual([ - 1152921504606846976n, - 3458764513820540928n, - 5764607523034234880n, - ]); - expect(vertexNeighbors(123974589433424n)).toEqual([ - 123974589433424n, - 123974589433584n, - 123974589433776n, - 123974589433616n, - ]); - }); -}); - -describe('toFaceIJ', () => { - const id = toFaceIJ(0n); - expect(id).toEqual([0, 0, 0, 0]); -}); diff --git a/test/s2/convert.test.ts b/test/s2/convert.test.ts deleted file mode 100644 index f3aacda..0000000 --- a/test/s2/convert.test.ts +++ /dev/null @@ -1,271 +0,0 @@ -import { toWM } from '../../src/s2/convert'; -import { describe, expect, it } from 'bun:test'; - -import type { S2Feature } from '../../src'; - -describe('toWM', () => { - it('should convert an S2Feature Point to a GeoJSON Feature', () => { - const s2Feature: S2Feature = { - type: 'S2Feature', - id: 2, - face: 0, - properties: { name: 'test' }, - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: 0.5, y: 0.5, m: { a: 0.5 } }, - bbox: [0, 0, 0.5, 1], - }, - metadata: { name2: 'test2' }, - }; - expect(toWM(s2Feature)).toEqual({ - type: 'VectorFeature', - id: 2, - properties: { name: 'test' }, - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: 0, y: 0, m: { a: 0.5 } }, - bbox: [0, 0, 0.5, 1], - }, - metadata: { name2: 'test2' }, - }); - }); - - it('should throw an error if the conversion is not yet supported or Invalid S2Geometry type', () => { - const s2Feature: S2Feature = { - type: 'S2Feature', - face: 0, - properties: { name: 'test' }, - geometry: { - // @ts-expect-error - invalid type on purpose - type: 'MultiPoints', - coordinates: [{ x: 0.5, y: 0.5 }], - }, - }; - expect(() => toWM(s2Feature)).toThrow('Invalid S2Geometry type'); - }); - - it("should throw an error if it's Invalid S2Geometry type", () => { - const feature = { type: 'S2Feature', geometry: { type: 'badInput' } }; - expect(() => toWM(feature as never)).toThrow('Invalid S2Geometry type'); - }); - - it('should convert an S2Feature MultiPoint to a GeoJSON Feature', () => { - const s2Feature: S2Feature = { - type: 'S2Feature', - id: 2, - face: 0, - properties: { name: 'test' }, - geometry: { - type: 'MultiPoint', - is3D: false, - coordinates: [ - { x: 0.5, y: 0.5, m: { a: 0.5 } }, - { x: 1.0, y: 1.0, m: { a: 2.5 } }, - ], - bbox: [0, 0, 0.5, 1], - }, - metadata: { name2: 'test2' }, - }; - expect(toWM(s2Feature)).toEqual({ - type: 'VectorFeature', - id: 2, - properties: { name: 'test' }, - geometry: { - type: 'MultiPoint', - is3D: false, - coordinates: [ - { x: 0, y: 0, m: { a: 0.5 } }, - { x: 45, y: 35.264389682754654, m: { a: 2.5 } }, - ], - bbox: [0, 0, 0.5, 1], - }, - metadata: { name2: 'test2' }, - }); - }); - - it('should convert an S2Feature LineString to a GeoJSON Feature', () => { - const s2Feature: S2Feature = { - type: 'S2Feature', - id: 2, - face: 0, - properties: { name: 'test' }, - geometry: { - type: 'LineString', - is3D: false, - coordinates: [ - { x: 0.5, y: 0.5, m: { a: 0.5 } }, - { x: 1.0, y: 1.0, m: { a: 2.5 } }, - ], - bbox: [0, 0, 0.5, 1], - offset: 1, - }, - metadata: { name2: 'test2' }, - }; - expect(toWM(s2Feature)).toEqual({ - type: 'VectorFeature', - id: 2, - properties: { name: 'test' }, - geometry: { - type: 'LineString', - is3D: false, - coordinates: [ - { x: 0, y: 0, m: { a: 0.5 } }, - { x: 45, y: 35.264389682754654, m: { a: 2.5 } }, - ], - bbox: [0, 0, 0.5, 1], - offset: 1, - }, - metadata: { name2: 'test2' }, - }); - }); - - it('should convert an S2Feature MultiLineString to a GeoJSON Feature', () => { - const s2Feature: S2Feature = { - type: 'S2Feature', - id: 2, - face: 0, - properties: { name: 'test' }, - geometry: { - type: 'MultiLineString', - is3D: false, - coordinates: [ - [ - { x: 0.5, y: 0.5, m: { a: 0.5 } }, - { x: 1.0, y: 1.0, m: { a: 2.5 } }, - ], - [ - { x: -0.5, y: -0.5, m: { a: -0.5 } }, - { x: 2.0, y: 2.0, m: { a: -2.5 } }, - ], - ], - bbox: [0, 0, 0.5, 1], - offset: [0, 1], - }, - metadata: { name2: 'test2' }, - }; - expect(toWM(s2Feature)).toEqual({ - type: 'VectorFeature', - id: 2, - properties: { name: 'test' }, - geometry: { - type: 'MultiLineString', - is3D: false, - coordinates: [ - [ - { x: 0, y: 0, m: { a: 0.5 } }, - { x: 45, y: 35.264389682754654, m: { a: 2.5 } }, - ], - [ - { x: -69.44395478041653, y: -43.1166655526282, m: { a: -0.5 } }, - { x: 78.69006752597979, y: 44.43824067114979, m: { a: -2.5 } }, - ], - ], - bbox: [0, 0, 0.5, 1], - offset: [0, 1], - }, - metadata: { name2: 'test2' }, - }); - }); - - it('should convert an S2Feature Polygon to a GeoJSON Feature', () => { - const s2Feature: S2Feature = { - type: 'S2Feature', - id: 2, - face: 0, - properties: { name: 'test' }, - geometry: { - type: 'Polygon', - is3D: false, - coordinates: [ - [ - { x: 0.5, y: 0.5, m: { a: 0.5 } }, - { x: 1.0, y: 1.0, m: { a: 2.5 } }, - ], - [ - { x: -0.5, y: -0.5, m: { a: -0.5 } }, - { x: 2.0, y: 2.0, m: { a: -2.5 } }, - ], - ], - bbox: [0, 0, 0.5, 1], - offset: [0.123, 0.456], - }, - metadata: { name2: 'test2' }, - }; - expect(toWM(s2Feature)).toEqual({ - type: 'VectorFeature', - id: 2, - properties: { name: 'test' }, - geometry: { - type: 'Polygon', - is3D: false, - coordinates: [ - [ - { x: 0, y: 0, m: { a: 0.5 } }, - { x: 45, y: 35.264389682754654, m: { a: 2.5 } }, - ], - [ - { x: -69.44395478041653, y: -43.1166655526282, m: { a: -0.5 } }, - { x: 78.69006752597979, y: 44.43824067114979, m: { a: -2.5 } }, - ], - ], - bbox: [0, 0, 0.5, 1], - offset: [0.123, 0.456], - }, - metadata: { name2: 'test2' }, - }); - }); - - it('should convert an S2Feature MultiPolygon to a GeoJSON Feature', () => { - const s2Feature: S2Feature = { - type: 'S2Feature', - id: 2, - face: 0, - properties: { name: 'test' }, - geometry: { - type: 'MultiPolygon', - is3D: false, - coordinates: [ - [ - [ - { x: 0.5, y: 0.5, m: { a: 0.5 } }, - { x: 1.0, y: 1.0, m: { a: 2.5 } }, - ], - [ - { x: -0.5, y: -0.5, m: { a: -0.5 } }, - { x: 2.0, y: 2.0, m: { a: -2.5 } }, - ], - ], - ], - bbox: [0, 0, 0.5, 1], - offset: [[0.123, 0.456]], - }, - metadata: { name2: 'test2' }, - }; - expect(toWM(s2Feature)).toEqual({ - type: 'VectorFeature', - id: 2, - properties: { name: 'test' }, - geometry: { - type: 'MultiPolygon', - is3D: false, - coordinates: [ - [ - [ - { x: 0, y: 0, m: { a: 0.5 } }, - { x: 45, y: 35.264389682754654, m: { a: 2.5 } }, - ], - [ - { x: -69.44395478041653, y: -43.1166655526282, m: { a: -0.5 } }, - { x: 78.69006752597979, y: 44.43824067114979, m: { a: -2.5 } }, - ], - ], - ], - bbox: [0, 0, 0.5, 1], - offset: [[0.123, 0.456]], - }, - metadata: { name2: 'test2' }, - }); - }); -}); diff --git a/test/s2/s2Coords.test.ts b/test/s2/s2Coords.test.ts deleted file mode 100644 index 567d760..0000000 --- a/test/s2/s2Coords.test.ts +++ /dev/null @@ -1,264 +0,0 @@ -import { - IJtoST, - K_LIMIT_IJ, - STtoIJ, - SiTiToST, - XYZtoFace, - XYZtoFaceUV, - bboxST, - bboxUV, - faceUVtoXYZ, - faceUVtoXYZGL, - faceXYZGLtoUV, - faceXYZtoUV, - linearSTtoUV, - linearUVtoST, - lonLatToXYZ, - lonLatToXYZGL, - neighborsIJ, - quadraticSTtoUV, - quadraticUVtoST, - tanSTtoUV, - tanUVtoST, - tileXYFromSTZoom, - tileXYFromUVZoom, - xyzToLonLat, -} from '../../src/s2/s2Coords'; -import { describe, expect, it } from 'bun:test'; - -describe('IJtoST', () => { - it('should convert any whole number to between [0, 1]', () => { - expect(IJtoST(0)).toBe(0); - expect(IJtoST(1)).toBe(1 / K_LIMIT_IJ); - expect(IJtoST(K_LIMIT_IJ - 1)).toBe(1 - 1 / K_LIMIT_IJ); - }); -}); - -describe('K_LIMIT_IJ', () => { - it('should be a number', () => { - expect(K_LIMIT_IJ).toBe(1 << 30); - }); -}); - -describe('STtoIJ', () => { - it('should convert any number between [0, 1] to a whole number', () => { - expect(STtoIJ(0)).toBe(0); - expect(STtoIJ(1 / K_LIMIT_IJ)).toBe(1); - expect(STtoIJ(1 - 1 / K_LIMIT_IJ)).toBe(K_LIMIT_IJ - 1); - }); -}); - -describe('SiTiToST', () => { - it('should convert any number to between [0, 1]', () => { - expect(SiTiToST(0)).toBe(0); - expect(SiTiToST(1)).toBe(1 / 2_147_483_648); - expect(SiTiToST(2_147_483_647)).toBe(0.9999999995343387); - }); -}); - -describe('XYZtoFace', () => { - it('should convert a XYZ to a face', () => { - expect(XYZtoFace([1, 0, 0])).toBe(0); - expect(XYZtoFace([0, 1, 0])).toBe(1); - expect(XYZtoFace([0, 0, 1])).toBe(2); - expect(XYZtoFace([-1, 0, 0])).toBe(3); - expect(XYZtoFace([0, -1, 0])).toBe(4); - expect(XYZtoFace([0, 0, -1])).toBe(5); - }); -}); - -describe('XYZtoFaceUV', () => { - it('should convert a XYZ to a face and UV', () => { - expect(XYZtoFaceUV([1, 0, 0])).toEqual([0, 0, 0]); - expect(XYZtoFaceUV([0, 1, 0])).toEqual([1, -0, 0]); - expect(XYZtoFaceUV([0, 0, 1])).toEqual([2, -0, -0]); - expect(XYZtoFaceUV([-1, 0, 0])).toEqual([3, -0, -0]); - expect(XYZtoFaceUV([0, -1, 0])).toEqual([4, -0, 0]); - expect(XYZtoFaceUV([0, 0, -1])).toEqual([5, 0, 0]); - }); -}); - -describe('bboxST', () => { - it('should convert a bbox to ST at 0-0-0', () => { - expect(bboxST(0, 0, 0)).toEqual([0, 0, 1, 1]); - }); - it('should convert a bbox to ST at 1-0-1', () => { - expect(bboxST(1, 0, 1)).toEqual([0.5, 0, 1, 0.5]); - }); - it('should convert a bbox to ST at 2-0-2', () => { - expect(bboxST(2, 0, 2)).toEqual([0.5, 0, 0.75, 0.25]); - }); -}); - -describe('bboxUV', () => { - it('should convert a bbox to UV at 0-0-0', () => { - expect(bboxUV(0, 0, 0)).toEqual([-1, -1, 1, 1]); - }); - it('should convert a bbox to UV at 1-0-1', () => { - expect(bboxUV(1, 0, 1)).toEqual([0, -1, 1, 0]); - }); - it('should convert a bbox to UV at 2-0-2', () => { - expect(bboxUV(2, 0, 2)).toEqual([0, -1, 0.5, -0.5]); - }); -}); - -describe('faceUVtoXYZ', () => { - it('should convert a face and UV to XYZ', () => { - expect(faceUVtoXYZ(0, 0, 0)).toEqual([1, 0, 0]); - expect(faceUVtoXYZ(1, 0, 0)).toEqual([-0, 1, 0]); - expect(faceUVtoXYZ(2, 0, 0)).toEqual([-0, -0, 1]); - expect(faceUVtoXYZ(3, 0, 0)).toEqual([-1, -0, -0]); - expect(faceUVtoXYZ(4, 0, 0)).toEqual([0, -1, -0]); - expect(faceUVtoXYZ(5, 0, 0)).toEqual([0, 0, -1]); - }); -}); - -describe('faceUVtoXYZGL', () => { - it('should convert a face and UV to XYZ', () => { - expect(faceUVtoXYZGL(0, 0, 0)).toEqual([0, 0, 1]); - expect(faceUVtoXYZGL(1, 0, 0)).toEqual([1, 0, -0]); - expect(faceUVtoXYZGL(2, 0, 0)).toEqual([-0, 1, -0]); - expect(faceUVtoXYZGL(3, 0, 0)).toEqual([-0, -0, -1]); - expect(faceUVtoXYZGL(4, 0, 0)).toEqual([-1, -0, 0]); - expect(faceUVtoXYZGL(5, 0, 0)).toEqual([0, -1, 0]); - }); -}); - -describe('faceXYZGLtoUV', () => { - it('should convert a face and XYZ to UV', () => { - expect(faceXYZGLtoUV(0, [0, 0, 1])).toEqual([0, 0]); - expect(faceXYZGLtoUV(1, [1, 0, 0])).toEqual([-0, 0]); - expect(faceXYZGLtoUV(2, [0, 1, 0])).toEqual([-0, -0]); - expect(faceXYZGLtoUV(3, [0, 0, -1])).toEqual([-0, -0]); - expect(faceXYZGLtoUV(4, [-1, 0, 0])).toEqual([-0, 0]); - expect(faceXYZGLtoUV(5, [0, -1, 0])).toEqual([0, 0]); - }); -}); - -describe('faceXYZtoUV', () => { - it('should convert a face and XYZ to UV', () => { - expect(faceXYZtoUV(0, [1, 0, 0])).toEqual([0, 0]); - expect(faceXYZtoUV(1, [0, 1, 0])).toEqual([-0, 0]); - expect(faceXYZtoUV(2, [0, 0, 1])).toEqual([-0, -0]); - expect(faceXYZtoUV(3, [-1, 0, 0])).toEqual([-0, -0]); - expect(faceXYZtoUV(4, [0, -1, 0])).toEqual([-0, 0]); - expect(faceXYZtoUV(5, [0, 0, -1])).toEqual([0, 0]); - }); -}); - -describe('linearSTtoUV', () => { - it('should convert a [0, 1] to a [-1, 1] in a linear fashion', () => { - expect(linearSTtoUV(0)).toBe(-1); - expect(linearSTtoUV(0.5)).toBe(0); - expect(linearSTtoUV(1)).toBe(1); - }); -}); - -describe('linearUVtoST', () => { - it('should convert a [-1, 1] to a [0, 1] in a linear fashion', () => { - expect(linearUVtoST(-1)).toBe(0); - expect(linearUVtoST(0)).toBe(0.5); - expect(linearUVtoST(1)).toBe(1); - }); -}); - -describe('lonLatToXYZ', () => { - it('should convert a lon-lat to XYZ', () => { - expect(lonLatToXYZ(0, 0)).toEqual([1, 0, 0]); - expect(lonLatToXYZ(90, 0)).toEqual([0.00000000000000006123233995736766, 1, 0]); - expect(lonLatToXYZ(0, 90)).toEqual([0.00000000000000006123233995736766, 0, 1]); - expect(lonLatToXYZ(-90, 0)).toEqual([0.00000000000000006123233995736766, -1, 0]); - expect(lonLatToXYZ(0, -90)).toEqual([0.00000000000000006123233995736766, 0, -1]); - expect(lonLatToXYZ(0, 0)).toEqual([1, 0, 0]); - }); -}); - -describe('lonLatToXYZGL', () => { - it('should convert a lon-lat to XYZ', () => { - expect(lonLatToXYZGL(0, 0)).toEqual([0, 0, 1]); - expect(lonLatToXYZGL(90, 0)).toEqual([1, 0, 0.00000000000000006123233995736766]); - expect(lonLatToXYZGL(0, 90)).toEqual([0, 1, 0.00000000000000006123233995736766]); - expect(lonLatToXYZGL(-90, 0)).toEqual([-1, 0, 0.00000000000000006123233995736766]); - expect(lonLatToXYZGL(0, -90)).toEqual([0, -1, 0.00000000000000006123233995736766]); - expect(lonLatToXYZGL(0, 0)).toEqual([0, 0, 1]); - }); -}); - -describe('neighborsIJ', () => { - it('should take a Face-I-J and find its neighbors', () => { - expect(neighborsIJ(0, 0, 0)).toEqual([ - [5, 0, 1073741823], - [0, 1, 0], - [0, 0, 1], - [4, 1073741823, 1073741823], - ]); - }); - - it('should take a Face-I-J and find its neighbors given a level', () => { - expect(neighborsIJ(0, 1, 1, 3)).toEqual([ - [0, 1, 0], - [0, 2, 1], - [0, 1, 2], - [0, 0, 1], - ]); - }); -}); - -describe('quadraticSTtoUV', () => { - it('should convert a [0, 1] to a [-1, 1] in a quadratic fashion', () => { - expect(quadraticSTtoUV(0)).toBe(-1); - expect(quadraticSTtoUV(0.5)).toBe(0); - expect(quadraticSTtoUV(1)).toBe(1); - }); -}); - -describe('quadraticUVtoST', () => { - it('should convert a [-1, 1] to a [0, 1] in a quadratic fashion', () => { - expect(quadraticUVtoST(-1)).toBe(0); - expect(quadraticUVtoST(0)).toBe(0.5); - expect(quadraticUVtoST(1)).toBe(1); - }); -}); - -describe('tanSTtoUV', () => { - it('should convert a [0, 1] to a [-1, 1] in a tangential fashion', () => { - expect(tanSTtoUV(0)).toBe(-0.9999999999999999); - expect(tanSTtoUV(0.5)).toBe(0); - expect(tanSTtoUV(1)).toBe(0.9999999999999999); - }); -}); - -describe('tanUVtoST', () => { - it('should convert a [-1, 1] to a [0, 1] in a tangential fashion', () => { - expect(tanUVtoST(-1)).toBe(0); - expect(tanUVtoST(0)).toBe(0.5); - expect(tanUVtoST(1)).toBe(1); - }); -}); - -describe('tileXYFromSTZoom', () => { - it('should convert an ST to a tile coordinate', () => { - expect(tileXYFromSTZoom(0, 0, 0)).toEqual([0, 0]); - expect(tileXYFromSTZoom(0.5, 0, 1)).toEqual([1, 0]); - expect(tileXYFromSTZoom(0.5, 0.5, 2)).toEqual([2, 2]); - }); -}); - -describe('tileXYFromUVZoom', () => { - it('should convert a UV to a tile coordinate', () => { - expect(tileXYFromUVZoom(-1, -1, 0)).toEqual([0, 0]); - expect(tileXYFromUVZoom(0, -1, 1)).toEqual([1, 0]); - expect(tileXYFromUVZoom(0, 0, 2)).toEqual([2, 2]); - }); -}); - -describe('xyzToLonLat', () => { - it('should convert a XYZ to lon-lat', () => { - expect(xyzToLonLat([1, 0, 0])).toEqual([0, 0]); - expect(xyzToLonLat([0, 1, 0])).toEqual([90, 0]); - expect(xyzToLonLat([0, 0, 1])).toEqual([0, 90]); - expect(xyzToLonLat([-1, 0, 0])).toEqual([180, 0]); - expect(xyzToLonLat([0, -1, 0])).toEqual([-90, 0]); - expect(xyzToLonLat([0, 0, -1])).toEqual([0, -90]); - }); -}); diff --git a/test/s2/s2Point.test.ts b/test/s2/s2Point.test.ts deleted file mode 100644 index 5b78259..0000000 --- a/test/s2/s2Point.test.ts +++ /dev/null @@ -1,321 +0,0 @@ -import { - add, - addScalar, - distance, - distanceEarth, - fromIJ, - fromLonLat, - fromLonLatGL, - fromS2CellID, - fromST, - fromSTGL, - fromUV, - fromUVGL, - getFace, - length, - mul, - mulScalar, - normalize, - sub, - subScalar, - toIJ, - toLonLat, - toS2CellID, - toST, - toUV, -} from '../../src/s2/s2Point'; -import { describe, expect, it } from 'bun:test'; - -describe('add', (): void => { - it('should add 1 to each component of an XYZ point', (): void => { - expect(add([1, 2, 3], 1)).toEqual([2, 3, 4]); - }); -}); - -describe('addScalar', (): void => { - it('should add an XYZ point to another XYZ point', (): void => { - expect(addScalar([1, 2, 3], [1, 2, 3])).toEqual([2, 4, 6]); - }); -}); - -describe('distance', (): void => { - it('should calculate the distance between two XYZ points', (): void => { - expect(distance([1, 2, 3], [4, 5, 6])).toBeCloseTo(5.196152422706632); - }); -}); - -describe('distanceEarth', (): void => { - it('should calculate the distance between two of the same lon/lat points', (): void => { - expect(distanceEarth([0, 0, 0], [0, 0, 0])).toBeCloseTo(0); - }); - it('should calculate the distance between two different lon/lat points', (): void => { - expect(distanceEarth([0, 0, 0], [90, 0, 0])).toBeCloseTo(574032330); - }); -}); - -describe('fromIJ', (): void => { - it('should convert an Face-I-J of 0-0-0 to an XYZ point', (): void => { - expect(fromIJ(0, 0, 0)).toEqual([1, -1, -1]); - }); - it('should convert an Face-I-J of 1-0-0 to an XYZ point', (): void => { - expect(fromIJ(1, 0, 0)).toEqual([1, 1, -1]); - }); - it('should convert an Face-I-J of 2-20-100 to an XYZ point', (): void => { - expect(fromIJ(2, 20, 100)).toEqual([0.9999999503294631, 0.9999997516473249, 1]); - }); -}); - -describe('fromLonLat', (): void => { - it('should convert a lon/lat of 0-0 to an XYZ point', (): void => { - expect(fromLonLat(0, 0)).toEqual([1, 0, 0]); - }); - it('should convert a lon/lat of 90-0 to an XYZ point', (): void => { - expect(fromLonLat(90, 0)).toEqual([0.00000000000000006123233995736766, 1, 0]); - }); - it('should convert a lon/lat of 0-90 to an XYZ point', (): void => { - expect(fromLonLat(0, 90)).toEqual([0.00000000000000006123233995736766, 0, 1]); - }); -}); - -describe('fromLonLatGL', (): void => { - it('should convert a lon/lat of 0-0 to an XYZ point', (): void => { - expect(fromLonLatGL(0, 0)).toEqual([0, 0, 1]); - }); - it('should convert a lon/lat of 90-0 to an XYZ point', (): void => { - expect(fromLonLatGL(90, 0)).toEqual([1, 0, 0.00000000000000006123233995736766]); - }); - it('should convert a lon/lat of 0-90 to an XYZ point', (): void => { - expect(fromLonLatGL(0, 90)).toEqual([0, 1, 0.00000000000000006123233995736766]); - }); -}); - -// fromS2CellID -describe('fromS2CellID', (): void => { - it('should convert a S2CellID of 0n to an XYZ point', (): void => { - expect(fromS2CellID(0n)).toEqual([1, -1, -1]); - }); - it('should convert a S2CellID of 1n to an XYZ point', (): void => { - expect(fromS2CellID(1n)).toEqual([1, -1, -1]); - }); - it('should convert a S2CellID of 2n to an XYZ point', (): void => { - expect(fromS2CellID(2n)).toEqual([1, -0.9999999975164731, -1]); - }); - it('should convert a S2CellID of 12345678n to an XYZ point', (): void => { - expect(fromS2CellID(12345678n)).toEqual([1, -0.9999983757739246, -0.9999918614948804]); - }); -}); - -describe('fromST', (): void => { - it('should convert a Face-S-T of 0-0-0 to an XYZ point', (): void => { - expect(fromST(0, 0, 0)).toEqual([1, -1, -1]); - }); - it('should convert a Face-S-T of 1-0-0 to an XYZ point', (): void => { - expect(fromST(1, 0, 0)).toEqual([1, 1, -1]); - }); - it('should convert a Face-S-T of 2-0-0 to an XYZ point', (): void => { - expect(fromST(2, 0, 0)).toEqual([1, 1, 1]); - }); - it('should convert a Face-S-T of 3-0-0 to an XYZ point', (): void => { - expect(fromST(3, 0, 0)).toEqual([-1, 1, 1]); - }); - it('should convert a Face-S-T of 4-0-0 to an XYZ point', (): void => { - expect(fromST(4, 0, 0)).toEqual([-1, -1, 1]); - }); - it('should convert a Face-S-T of 5-0-0 to an XYZ point', (): void => { - expect(fromST(5, 0, 0)).toEqual([-1, -1, -1]); - }); -}); - -describe('fromSTGL', (): void => { - it('should convert a Face-S-T of 0-0-0 to an XYZ point', (): void => { - expect(fromSTGL(0, 0, 0)).toEqual([-1, -1, 1]); - }); - it('should convert a Face-S-T of 1-0-0 to an XYZ point', (): void => { - expect(fromSTGL(1, 0, 0)).toEqual([1, -1, 1]); - }); - it('should convert a Face-S-T of 2-0-0 to an XYZ point', (): void => { - expect(fromSTGL(2, 0, 0)).toEqual([1, 1, 1]); - }); - it('should convert a Face-S-T of 3-0-0 to an XYZ point', (): void => { - expect(fromSTGL(3, 0, 0)).toEqual([1, 1, -1]); - }); - it('should convert a Face-S-T of 4-0-0 to an XYZ point', (): void => { - expect(fromSTGL(4, 0, 0)).toEqual([-1, 1, -1]); - }); - it('should convert a Face-S-T of 5-0-0 to an XYZ point', (): void => { - expect(fromSTGL(5, 0, 0)).toEqual([-1, -1, -1]); - }); -}); - -describe('fromUV', (): void => { - it('should convert a Face-U-V of 0-0-0 to an XYZ point', (): void => { - expect(fromUV(0, 0, 0)).toEqual([1, 0, 0]); - }); - it('should convert a Face-U-V of 1-0-0 to an XYZ point', (): void => { - expect(fromUV(1, 0, 0)).toEqual([-0, 1, 0]); - }); - it('should convert a Face-U-V of 2-0-0 to an XYZ point', (): void => { - expect(fromUV(2, 0, 0)).toEqual([-0, -0, 1]); - }); - it('should convert a Face-U-V of 3-0-0 to an XYZ point', (): void => { - expect(fromUV(3, 0, 0)).toEqual([-1, -0, -0]); - }); - it('should convert a Face-U-V of 4-0-0 to an XYZ point', (): void => { - expect(fromUV(4, 0, 0)).toEqual([0, -1, -0]); - }); - it('should convert a Face-U-V of 5-0-0 to an XYZ point', (): void => { - expect(fromUV(5, 0, 0)).toEqual([0, 0, -1]); - }); -}); - -describe('fromUVGL', (): void => { - it('should convert a Face-U-V of 0-0-0 to an XYZ point', (): void => { - expect(fromUVGL(0, 0, 0)).toEqual([0, 0, 1]); - }); - it('should convert a Face-U-V of 1-0-0 to an XYZ point', (): void => { - expect(fromUVGL(1, 0, 0)).toEqual([1, 0, -0]); - }); - it('should convert a Face-U-V of 2-0-0 to an XYZ point', (): void => { - expect(fromUVGL(2, 0, 0)).toEqual([-0, 1, -0]); - }); - it('should convert a Face-U-V of 3-0-0 to an XYZ point', (): void => { - expect(fromUVGL(3, 0, 0)).toEqual([-0, -0, -1]); - }); - it('should convert a Face-U-V of 4-0-0 to an XYZ point', (): void => { - expect(fromUVGL(4, 0, 0)).toEqual([-1, -0, 0]); - }); - it('should convert a Face-U-V of 5-0-0 to an XYZ point', (): void => { - expect(fromUVGL(5, 0, 0)).toEqual([0, -1, 0]); - }); -}); - -describe('getFace', (): void => { - it('should return the face of an XYZ point', (): void => { - expect(getFace([1, 0, 0])).toBe(0); - expect(getFace([0, 1, 0])).toBe(1); - expect(getFace([0, 0, 1])).toBe(2); - expect(getFace([-1, 0, 0])).toBe(3); - expect(getFace([0, -1, 0])).toBe(4); - expect(getFace([0, 0, -1])).toBe(5); - expect(getFace([0.5, 0.5, 0.5])).toBe(2); - }); -}); - -describe('length', (): void => { - it('should calculate the length of an XYZ point', (): void => { - expect(length([1, 2, 3])).toBeCloseTo(3.7416573867739413); - }); -}); - -describe('mul', (): void => { - it('should multiply each component of an XYZ point by 2', (): void => { - expect(mul([1, 2, 3], 2)).toEqual([2, 4, 6]); - }); -}); - -describe('mulScalar', (): void => { - it('should multiply each component of an XYZ point by another XYZ point', (): void => { - expect(mulScalar([1, 2, 3], [2, 3, 4])).toEqual([2, 6, 12]); - }); -}); - -describe('normalize', (): void => { - it('should normalize an XYZ point', (): void => { - expect(normalize([1, 2, 3])).toEqual([ - 0.2672612419124244, 0.5345224838248488, 0.8017837257372732, - ]); - }); -}); - -describe('sub', (): void => { - it('should subtract 1 from each component of an XYZ point', (): void => { - expect(sub([1, 2, 3], 1)).toEqual([0, 1, 2]); - }); -}); - -describe('subScalar', (): void => { - it('should subtract an XYZ point from another XYZ point', (): void => { - expect(subScalar([1, 2, 3], [1, 2, 3])).toEqual([0, 0, 0]); - }); -}); - -describe('toIJ', (): void => { - it('should convert an XYZ point to a Face-I-J', (): void => { - expect(toIJ([1, -1, -1])).toEqual([5, 0, 1073741823]); - }); - it('should convert an XYZ point to a Face-I-J', (): void => { - expect(toIJ([1, 1, -1])).toEqual([5, 1073741823, 1073741823]); - }); - it('should convert an XYZ point to a Face-I-J', (): void => { - expect(toIJ([0.9999999503294631, 0.9999997516473249, 1])).toEqual([2, 20, 100]); - }); - it('should convert an XYZ point to a Face-I-J including a level of 10', (): void => { - expect(toIJ([1, -1, -1], 10)).toEqual([5, 0, 1023]); - }); -}); - -describe('toLonLat', (): void => { - it('should convert an XYZ point to a lon/lat', (): void => { - expect(toLonLat([1, 0, 0])).toEqual([0, 0]); - }); - it('should convert an XYZ point to a lon/lat', (): void => { - expect(toLonLat([0.00000000000000006123233995736766, 1, 0])).toEqual([90, 0]); - }); - it('should convert an XYZ point to a lon/lat', (): void => { - expect(toLonLat([0.00000000000000006123233995736766, 0, 1])).toEqual([0, 90]); - }); -}); - -describe('toS2CellID', (): void => { - it('should convert an XYZ point to a S2CellID', (): void => { - expect(toS2CellID([1, -1, -1])).toBe(13835058055282163711n); - }); - it('should convert an XYZ point to a S2CellID', (): void => { - expect(toS2CellID([1, 1, -1])).toBe(13066443718877599061n); - }); - it('should convert an XYZ point to a S2CellID', (): void => { - expect(toS2CellID([0.9999999503294631, 0.9999997516473249, 1])).toBe(4611686018427419201n); - }); -}); - -describe('toST', (): void => { - it('should convert an XYZ point to a Face-S-T', (): void => { - expect(toST([1, -1, -1])).toEqual([5, 0, 1]); - }); - it('should convert an XYZ point to a Face-S-T', (): void => { - expect(toST([1, 1, -1])).toEqual([5, 1, 1]); - }); - it('should convert an XYZ point to a Face-S-T', (): void => { - expect(toST([1, 1, 1])).toEqual([2, 0, 0]); - }); - it('should convert an XYZ point to a Face-S-T', (): void => { - expect(toST([-1, 1, 1])).toEqual([2, 1, 0]); - }); - it('should convert an XYZ point to a Face-S-T', (): void => { - expect(toST([-1, -1, 1])).toEqual([2, 1, 1]); - }); - it('should convert an XYZ point to a Face-S-T', (): void => { - expect(toST([-1, -1, -1])).toEqual([5, 0, 0]); - }); -}); - -describe('toUV', (): void => { - it('should convert an XYZ point to a Face-U-V', (): void => { - expect(toUV([1, 0, 0])).toEqual([0, 0, 0]); - }); - it('should convert an XYZ point to a Face-U-V', (): void => { - expect(toUV([0, 1, 0])).toEqual([1, -0, 0]); - }); - it('should convert an XYZ point to a Face-U-V', (): void => { - expect(toUV([0, 0, 1])).toEqual([2, -0, -0]); - }); - it('should convert an XYZ point to a Face-U-V', (): void => { - expect(toUV([-1, 0, 0])).toEqual([3, -0, -0]); - }); - it('should convert an XYZ point to a Face-U-V', (): void => { - expect(toUV([0, -1, 0])).toEqual([4, -0, 0]); - }); - it('should convert an XYZ point to a Face-U-V', (): void => { - expect(toUV([0, 0, -1])).toEqual([5, 0, 0]); - }); -}); diff --git a/test/schema.test.ts b/test/schema.test.ts deleted file mode 100644 index 1133ca2..0000000 --- a/test/schema.test.ts +++ /dev/null @@ -1,410 +0,0 @@ -import Ajv from 'ajv'; -import { describe, expect, it } from 'bun:test'; - -import schema from '../src/s2json.schema.json'; - -import type { Feature, S2Feature, VectorFeature } from '../src'; - -const ajv = new Ajv(); -const validate = ajv.compile(schema); - -describe('feature', () => { - it('feature point', () => { - const validFeature: Feature = { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [-75.165222, 39.952583], - }, - properties: {}, - }; - expect(validate(validFeature)).toBeTrue(); - - const validFeatureWithOptionals: Feature = { - id: 0, - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [-75.165222, 39.952583], - bbox: [-75.165222, 39.952583, -75.165222, 39.952583], - mValues: { a: 1 }, - }, - properties: { - name: 'Home', - }, - }; - expect(validate(validFeatureWithOptionals)).toBeTrue(); - - const badID: Feature = { - id: -2, - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [-75.165222, 39.952583], - }, - properties: {}, - }; - expect(validate(badID)).toBeFalse(); - - // @ts-expect-error - no properties - const noProperties: Feature = { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [-75.165222, 39.952583], - }, - }; - expect(validate(noProperties)).toBeFalse(); - - // @ts-expect-error - no geometry - const noGeometry: Feature = { - type: 'Feature', - properties: {}, - }; - expect(validate(noGeometry)).toBeFalse(); - - // @ts-expect-error - no type - const noType: Feature = { - geometry: { - type: 'Point', - coordinates: [-75.165222, 39.952583], - }, - properties: {}, - }; - expect(validate(noType)).toBeFalse(); - - const badType: Feature = { - // @ts-expect-error - bad type - type: 'Point', - geometry: { - type: 'Point', - coordinates: [-75.165222, 39.952583], - }, - properties: {}, - }; - expect(validate(badType)).toBeFalse(); - - const badGeometryType: Feature = { - type: 'Feature', - geometry: { - // @ts-expect-error - bad geometry - type: 'Feature', - coordinates: [-75.165222, 39.952583], - }, - properties: {}, - }; - expect(validate(badGeometryType)).toBeFalse(); - - const badGeometryCoordinates: Feature = { - type: 'Feature', - geometry: { - type: 'Point', - // @ts-expect-error - bad geometry - coordinates: [[-75.165222, 39.952583]], - }, - properties: {}, - }; - expect(validate(badGeometryCoordinates)).toBeFalse(); - - const badGeometryBBox: Feature = { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [-75.165222, 39.952583], - // @ts-expect-error - bad geometry - bbox: [-75.165222, 39.952583, -75.165222, 39.952583, -75.165222], - }, - properties: {}, - }; - expect(validate(badGeometryBBox)).toBeFalse(); - - const badGeometryMValues: Feature = { - type: 'Feature', - // @ts-expect-error - bad geometry - geometry: { - type: 'Point', - coordinates: [-75.165222, 39.952583], - mValues: [{ a: 1 }], - }, - properties: {}, - }; - expect(validate(badGeometryMValues)).toBeFalse(); - }); -}); - -describe('vector feature', () => { - it('feature point', () => { - const validFeature: VectorFeature = { - type: 'VectorFeature', - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: -75.165222, y: 39.952583 }, - }, - properties: {}, - }; - expect(validate(validFeature)).toBeTrue(); - - const validFeatureWithOptionals: VectorFeature = { - id: 0, - type: 'VectorFeature', - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: -75.165222, y: 39.952583, m: { a: 1 } }, - bbox: [-75.165222, 39.952583, -75.165222, 39.952583], - }, - properties: { - name: 'Home', - }, - }; - expect(validate(validFeatureWithOptionals)).toBeTrue(); - - const badID: VectorFeature = { - id: -2, - type: 'VectorFeature', - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: -75.165222, y: 39.952583 }, - }, - properties: {}, - }; - expect(validate(badID)).toBeFalse(); - - // @ts-expect-error - no properties - const noProperties: VectorFeature = { - type: 'VectorFeature', - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: -75.165222, y: 39.952583 }, - }, - }; - expect(validate(noProperties)).toBeFalse(); - - // @ts-expect-error - no geometry - const noGeometry: VectorFeature = { - type: 'VectorFeature', - properties: {}, - }; - expect(validate(noGeometry)).toBeFalse(); - - // @ts-expect-error - no type - const noType: VectorFeature = { - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: -75.165222, y: 39.952583 }, - }, - properties: {}, - }; - expect(validate(noType)).toBeFalse(); - - const badType: VectorFeature = { - // @ts-expect-error - bad type - type: 'Point', - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: -75.165222, y: 39.952583 }, - }, - properties: {}, - }; - expect(validate(badType)).toBeFalse(); - - const badGeometryType: VectorFeature = { - type: 'VectorFeature', - geometry: { - // @ts-expect-error - bad geometry - type: 'Feature', - coordinates: { x: -75.165222, y: 39.952583 }, - }, - properties: {}, - }; - expect(validate(badGeometryType)).toBeFalse(); - - const badGeometryCoordinates: VectorFeature = { - type: 'VectorFeature', - // @ts-expect-error - bad geometry - geometry: { - type: 'Point', - coordinates: [{ x: -75.165222, y: 39.952583 }], - }, - properties: {}, - }; - expect(validate(badGeometryCoordinates)).toBeFalse(); - - const badGeometryBBox: VectorFeature = { - type: 'VectorFeature', - geometry: { - type: 'Point', - coordinates: { x: -75.165222, y: 39.952583 }, - // @ts-expect-error - bad bounding box - bbox: [-75.165222, 39.952583, -75.165222, 39.952583, -75.165222], - }, - properties: {}, - }; - expect(validate(badGeometryBBox)).toBeFalse(); - - const badGeometryMValues: VectorFeature = { - type: 'VectorFeature', - geometry: { - type: 'Point', - // @ts-expect-error - bad m-value - coordinates: { x: -75.165222, y: 39.952583, m: [{ a: 1 }] }, - }, - properties: {}, - }; - expect(validate(badGeometryMValues)).toBeFalse(); - }); -}); - -describe('s2 feature', () => { - it('feature point', () => { - const validFeature: S2Feature = { - type: 'S2Feature', - face: 0, - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: -75.165222, y: 39.952583 }, - }, - properties: {}, - }; - expect(validate(validFeature)).toBeTrue(); - - const validFeatureWithOptionals: S2Feature = { - id: 0, - face: 0, - type: 'S2Feature', - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: -75.165222, y: 39.952583, m: { a: 1 } }, - bbox: [-75.165222, 39.952583, -75.165222, 39.952583], - }, - properties: { - name: 'Home', - }, - }; - expect(validate(validFeatureWithOptionals)).toBeTrue(); - - const badID: S2Feature = { - // bad id - id: -2, - face: 0, - type: 'S2Feature', - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: -75.165222, y: 39.952583 }, - }, - properties: {}, - }; - expect(validate(badID)).toBeFalse(); - - // @ts-expect-error - no properties - const noProperties: S2Feature = { - type: 'S2Feature', - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: -75.165222, y: 39.952583 }, - }, - }; - expect(validate(noProperties)).toBeFalse(); - - // @ts-expect-error - no geometry - const noGeometry: S2Feature = { - type: 'S2Feature', - properties: {}, - }; - expect(validate(noGeometry)).toBeFalse(); - - // @ts-expect-error - no type - const noType: S2Feature = { - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: -75.165222, y: 39.952583 }, - }, - properties: {}, - }; - expect(validate(noType)).toBeFalse(); - - const badType: S2Feature = { - // @ts-expect-error - bad type - type: 'Point', - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: -75.165222, y: 39.952583 }, - }, - properties: {}, - }; - expect(validate(badType)).toBeFalse(); - - const badGeometryType: S2Feature = { - type: 'S2Feature', - geometry: { - // @ts-expect-error - bad geometry - type: 'Feature', - coordinates: { x: -75.165222, y: 39.952583 }, - }, - properties: {}, - }; - expect(validate(badGeometryType)).toBeFalse(); - - const badGeometryCoordinates: S2Feature = { - type: 'S2Feature', - // @ts-expect-error - bad geometry - geometry: { - type: 'Point', - coordinates: [{ x: -75.165222, y: 39.952583 }], - }, - properties: {}, - }; - expect(validate(badGeometryCoordinates)).toBeFalse(); - - const badGeometryBBox: S2Feature = { - type: 'S2Feature', - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: -75.165222, y: 39.952583 }, - // @ts-expect-error - bad bounding box - bbox: [-75.165222, 39.952583, -75.165222, 39.952583, -75.165222], - }, - properties: {}, - }; - expect(validate(badGeometryBBox)).toBeFalse(); - - const badGeometryMValues: S2Feature = { - type: 'S2Feature', - face: 0, - geometry: { - type: 'Point', - is3D: false, - // @ts-expect-error - bad m-value - coordinates: { x: -75.165222, y: 39.952583, m: [{ a: 1 }] }, - }, - properties: {}, - }; - expect(validate(badGeometryMValues)).toBeFalse(); - - const badFace: S2Feature = { - id: 0, - // @ts-expect-error - bad face - face: 10, - type: 'S2Feature', - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: -75.165222, y: 39.952583 }, - }, - properties: {}, - }; - expect(validate(badFace)).toBeFalse(); - }); -}); diff --git a/test/simplify.test.ts b/test/simplify.test.ts deleted file mode 100644 index 1c86532..0000000 --- a/test/simplify.test.ts +++ /dev/null @@ -1,262 +0,0 @@ -import { buildSqDists, simplify } from '../src/simplify'; -import { expect, test } from 'bun:test'; - -import type { - VectorLineStringGeometry, - VectorMultiLineStringGeometry, - VectorMultiPolygonGeometry, - VectorPolygonGeometry, -} from '../src'; - -const SIMPLIFY_MAXZOOM = 16; - -test('LineString', () => { - const lineString: VectorLineStringGeometry = { - type: 'LineString', - is3D: false, - coordinates: [ - { x: 0.25, y: 0.25 }, - { x: 0.75, y: 0.25 }, - { x: 0.75, y: 0.75 }, - { x: 0.25, y: 0.75 }, - ], - vecBBox: [0.25, 0.25, 0.75, 0.75], - }; - - buildSqDists(lineString, 3, SIMPLIFY_MAXZOOM); - - expect(lineString).toEqual({ - type: 'LineString', - is3D: false, - coordinates: [ - { x: 0.25, y: 0.25, t: 1 }, - { x: 0.75, y: 0.25, t: 0.125 }, - { x: 0.75, y: 0.75, t: 0.25 }, - { x: 0.25, y: 0.75, t: 1 }, - ], - vecBBox: [0.25, 0.25, 0.75, 0.75], - }); - - simplify(lineString, 3, 0, SIMPLIFY_MAXZOOM); - expect(lineString).toEqual({ - type: 'LineString', - is3D: false, - coordinates: [ - { x: 0.25, y: 0.25, t: 1 }, - { x: 0.75, y: 0.25, t: 0.125 }, - { x: 0.75, y: 0.75, t: 0.25 }, - { x: 0.25, y: 0.75, t: 1 }, - ], - vecBBox: [0.25, 0.25, 0.75, 0.75], - }); -}); - -test('MultiLineString', () => { - const multiLineString: VectorMultiLineStringGeometry = { - type: 'MultiLineString', - is3D: false, - coordinates: [ - [ - { x: 0.25, y: 0.25 }, - { x: 0.75, y: 0.25 }, - { x: 0.75, y: 0.75 }, - { x: 0.25, y: 0.75 }, - ], - [ - { x: 0.5, y: 0.5 }, - { x: 0.5, y: 0.25 }, - { x: 0.75, y: 0.25 }, - { x: 0.75, y: 0.5 }, - { x: 0.5, y: 0.5 }, - ], - ], - vecBBox: [0.25, 0.25, 0.75, 0.75], - }; - - buildSqDists(multiLineString, 3, SIMPLIFY_MAXZOOM); - - expect(multiLineString).toEqual({ - type: 'MultiLineString', - is3D: false, - coordinates: [ - [ - { x: 0.25, y: 0.25, t: 1 }, - { x: 0.75, y: 0.25, t: 0.125 }, - { x: 0.75, y: 0.75, t: 0.25 }, - { x: 0.25, y: 0.75, t: 1 }, - ], - [ - { t: 1, x: 0.5, y: 0.5 }, - { t: 0.03125, x: 0.5, y: 0.25 }, - { t: 0.125, x: 0.75, y: 0.25 }, - { t: 0.03125, x: 0.75, y: 0.5 }, - { t: 1, x: 0.5, y: 0.5 }, - ], - ], - vecBBox: [0.25, 0.25, 0.75, 0.75], - }); - - simplify(multiLineString, 3, 0, SIMPLIFY_MAXZOOM); - expect(multiLineString).toEqual({ - type: 'MultiLineString', - is3D: false, - coordinates: [ - [ - { x: 0.25, y: 0.25, t: 1 }, - { x: 0.75, y: 0.25, t: 0.125 }, - { x: 0.75, y: 0.75, t: 0.25 }, - { x: 0.25, y: 0.75, t: 1 }, - ], - [ - { t: 1, x: 0.5, y: 0.5 }, - { t: 0.03125, x: 0.5, y: 0.25 }, - { t: 0.125, x: 0.75, y: 0.25 }, - { t: 0.03125, x: 0.75, y: 0.5 }, - { t: 1, x: 0.5, y: 0.5 }, - ], - ], - vecBBox: [0.25, 0.25, 0.75, 0.75], - }); -}); - -test('Polygon', () => { - const polygon: VectorPolygonGeometry = { - type: 'Polygon', - is3D: false, - coordinates: [ - [ - { x: 0.25, y: 0.25 }, - { x: 0.75, y: 0.25 }, - { x: 0.75, y: 0.75 }, - { x: 0.25, y: 0.75 }, - ], - [ - { x: 0.5, y: 0.5 }, - { x: 0.5, y: 0.25 }, - { x: 0.75, y: 0.25 }, - { x: 0.75, y: 0.5 }, - { x: 0.5, y: 0.5 }, - ], - ], - vecBBox: [0.25, 0.25, 0.75, 0.75], - }; - - buildSqDists(polygon, 3, SIMPLIFY_MAXZOOM); - - expect(polygon).toEqual({ - type: 'Polygon', - is3D: false, - coordinates: [ - [ - { x: 0.25, y: 0.25, t: 1 }, - { x: 0.75, y: 0.25, t: 0.125 }, - { x: 0.75, y: 0.75, t: 0.25 }, - { x: 0.25, y: 0.75, t: 1 }, - ], - [ - { t: 1, x: 0.5, y: 0.5 }, - { t: 0.03125, x: 0.5, y: 0.25 }, - { t: 0.125, x: 0.75, y: 0.25 }, - { t: 0.03125, x: 0.75, y: 0.5 }, - { t: 1, x: 0.5, y: 0.5 }, - ], - ], - vecBBox: [0.25, 0.25, 0.75, 0.75], - }); - - simplify(polygon, 3, 0, SIMPLIFY_MAXZOOM); - expect(polygon).toEqual({ - type: 'Polygon', - is3D: false, - coordinates: [ - [ - { x: 0.25, y: 0.25, t: 1 }, - { x: 0.75, y: 0.25, t: 0.125 }, - { x: 0.75, y: 0.75, t: 0.25 }, - { x: 0.25, y: 0.75, t: 1 }, - ], - [ - { t: 1, x: 0.5, y: 0.5 }, - { t: 0.03125, x: 0.5, y: 0.25 }, - { t: 0.125, x: 0.75, y: 0.25 }, - { t: 0.03125, x: 0.75, y: 0.5 }, - { t: 1, x: 0.5, y: 0.5 }, - ], - ], - vecBBox: [0.25, 0.25, 0.75, 0.75], - }); -}); - -test('MultiPolygon', () => { - const multiPolygon: VectorMultiPolygonGeometry = { - type: 'MultiPolygon', - is3D: false, - coordinates: [ - [ - [ - { x: 0.25, y: 0.25 }, - { x: 0.75, y: 0.25 }, - { x: 0.75, y: 0.75 }, - { x: 0.25, y: 0.75 }, - ], - [ - { x: 0.5, y: 0.5 }, - { x: 0.5, y: 0.25 }, - { x: 0.75, y: 0.25 }, - { x: 0.75, y: 0.5 }, - { x: 0.5, y: 0.5 }, - ], - ], - ], - vecBBox: [0.25, 0.25, 0.75, 0.75], - }; - - buildSqDists(multiPolygon, 3, SIMPLIFY_MAXZOOM); - - expect(multiPolygon).toEqual({ - type: 'MultiPolygon', - is3D: false, - coordinates: [ - [ - [ - { x: 0.25, y: 0.25, t: 1 }, - { x: 0.75, y: 0.25, t: 0.125 }, - { x: 0.75, y: 0.75, t: 0.25 }, - { x: 0.25, y: 0.75, t: 1 }, - ], - [ - { t: 1, x: 0.5, y: 0.5 }, - { t: 0.03125, x: 0.5, y: 0.25 }, - { t: 0.125, x: 0.75, y: 0.25 }, - { t: 0.03125, x: 0.75, y: 0.5 }, - { t: 1, x: 0.5, y: 0.5 }, - ], - ], - ], - vecBBox: [0.25, 0.25, 0.75, 0.75], - }); - - simplify(multiPolygon, 3, 0, SIMPLIFY_MAXZOOM); - expect(multiPolygon).toEqual({ - type: 'MultiPolygon', - is3D: false, - coordinates: [ - [ - [ - { x: 0.25, y: 0.25, t: 1 }, - { x: 0.75, y: 0.25, t: 0.125 }, - { x: 0.75, y: 0.75, t: 0.25 }, - { x: 0.25, y: 0.75, t: 1 }, - ], - [ - { t: 1, x: 0.5, y: 0.5 }, - { t: 0.03125, x: 0.5, y: 0.25 }, - { t: 0.125, x: 0.75, y: 0.25 }, - { t: 0.03125, x: 0.75, y: 0.5 }, - { t: 1, x: 0.5, y: 0.5 }, - ], - ], - ], - vecBBox: [0.25, 0.25, 0.75, 0.75], - }); -}); diff --git a/test/tile.test.ts b/test/tile.test.ts deleted file mode 100644 index 9712728..0000000 --- a/test/tile.test.ts +++ /dev/null @@ -1,322 +0,0 @@ -import { Tile, TileStore } from '../src/tile'; -import { expect, test } from 'bun:test'; - -import { type FeatureCollection, childrenIJ, fromFace } from '../src'; - -const SIMPLIFY_MAXZOOM = 16; - -test('Tile', () => { - const tile = new Tile(0n); - expect(tile).toEqual({ - id: 0n, - layers: {}, - transformed: false, - } as Tile); - - expect(tile.isEmpty()).toBe(true); - - tile.addFeature({ - type: 'VectorFeature', - properties: {}, - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: 0, y: 0 }, - }, - }); - - expect(tile.isEmpty()).toBe(false); - - tile.transform(3, SIMPLIFY_MAXZOOM); - - expect(tile).toEqual({ - id: 0n, - transformed: true, - layers: { - default: { - name: 'default', - features: [ - { - type: 'VectorFeature', - properties: {}, - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: 0, y: 0 }, - }, - }, - ], - }, - }, - } as unknown as Tile); -}); - -test('TileStore - points', () => { - const featureCollection: FeatureCollection = { - type: 'FeatureCollection', - features: [ - { - type: 'Feature', - properties: { a: 1 }, - geometry: { - type: 'Point', - coordinates: [0, 0], - }, - }, - { - type: 'Feature', - properties: { b: 2 }, - geometry: { - type: 'Point3D', - coordinates: [45, 45, 1], - }, - }, - { - type: 'Feature', - properties: { c: 3 }, - geometry: { - type: 'MultiPoint', - coordinates: [ - [-45, -45], - [-45, 45], - ], - }, - }, - { - type: 'Feature', - properties: { d: 4 }, - geometry: { - type: 'MultiPoint3D', - coordinates: [ - [45, -45, 1], - [-180, 20, 2], - ], - }, - }, - ], - }; - - const store = new TileStore(featureCollection, { projection: 'WM' }); - - const faceID = fromFace(0); - const faceTile = store.getTile(faceID); - - expect(faceTile).toEqual({ - id: 1152921504606846976n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: { - m: undefined, - x: 0.5, - y: 0.5, - z: undefined, - }, - type: 'Point', - is3D: false, - vecBBox: [0.5, 0.5, 0.5, 0.5], - }, - id: undefined, - metadata: undefined, - properties: { - a: 1, - }, - type: 'VectorFeature', - }, - { - geometry: { - bbox: undefined, - coordinates: { - m: undefined, - x: 0.625, - y: 0.35972503691520497, - z: 1, - }, - type: 'Point', - is3D: true, - vecBBox: [0.625, 0.35972503691520497, 0.625, 0.35972503691520497, 1, 1], - }, - id: undefined, - metadata: undefined, - properties: { - b: 2, - }, - type: 'VectorFeature', - }, - { - geometry: { - bbox: undefined, - coordinates: [ - { - m: undefined, - x: 0.375, - y: 0.640274963084795, - z: undefined, - }, - { - m: undefined, - x: 0.375, - y: 0.35972503691520497, - z: undefined, - }, - ], - type: 'MultiPoint', - is3D: false, - vecBBox: [0.375, 0.35972503691520497, 0.375, 0.640274963084795], - }, - id: undefined, - metadata: undefined, - properties: { - c: 3, - }, - type: 'VectorFeature', - }, - { - geometry: { - bbox: undefined, - coordinates: [ - { - m: undefined, - x: 0.625, - y: 0.640274963084795, - z: 1, - }, - { - m: undefined, - x: 0, - y: 0.4432805993614054, - z: 2, - }, - ], - type: 'MultiPoint', - is3D: true, - vecBBox: [0, 0.4432805993614054, 0.625, 0.640274963084795, 1, 2], - }, - id: undefined, - metadata: undefined, - properties: { - d: 4, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: true, - } as unknown as Tile); - - const [childID] = childrenIJ(0, 0, 0, 0); - const childTile = store.getTile(childID); - expect(childTile).toEqual({ - id: 288230376151711744n, - layers: { - default: { - features: [ - { - geometry: { - bbox: undefined, - coordinates: [ - { - m: undefined, - x: 0.75, - y: 0.7194500738304099, - z: undefined, - }, - ], - type: 'MultiPoint', - is3D: false, - vecBBox: [0.375, 0.35972503691520497, 0.375, 0.35972503691520497], - }, - id: undefined, - metadata: undefined, - properties: { - c: 3, - }, - type: 'VectorFeature', - }, - { - geometry: { - bbox: undefined, - coordinates: [ - { - m: undefined, - x: 0, - y: 0.8865611987228108, - z: 2, - }, - ], - type: 'MultiPoint', - is3D: true, - vecBBox: [0, 0.4432805993614054, 0, 0.4432805993614054, 2, 2], - }, - id: undefined, - metadata: undefined, - properties: { - d: 4, - }, - type: 'VectorFeature', - }, - ], - name: 'default', - }, - }, - transformed: true, - } as unknown as Tile); -}); - -// test('TileStore - lines', () => { -// const featureCollection: FeatureCollection = { -// type: 'FeatureCollection', -// features: [ -// { -// type: 'Feature', -// properties: { a: 1 }, -// geometry: { -// type: 'Point', -// coordinates: [0, 0], -// }, -// }, -// { -// type: 'Feature', -// properties: { b: 2 }, -// geometry: { -// type: 'Point3D', -// coordinates: [45, 45, 1], -// }, -// }, -// { -// type: 'Feature', -// properties: { c: 3 }, -// geometry: { -// type: 'MultiPoint', -// coordinates: [ -// [-45, -45], -// [-45, 45], -// ], -// }, -// }, -// { -// type: 'Feature', -// properties: { d: 4 }, -// geometry: { -// type: 'MultiPoint3D', -// coordinates: [ -// [45, -45, 1], -// [-180, 20, 2], -// ], -// }, -// }, -// ], -// }; - -// const store = new TileStore(featureCollection, { projection: 'WM' }); - -// const faceID = fromFace('WM', 0); -// const faceTile = store.getTile(faceID); - -// expect(faceTile).toEqual({} as unknown as Tile); -// }); diff --git a/test/util.test.ts b/test/util.test.ts deleted file mode 100644 index 90d7e19..0000000 --- a/test/util.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { - A, - EARTH_RADIUS, - EARTH_RADIUS_EQUATORIAL, - EARTH_RADIUS_POLAR, - MARS_RADIUS, - MARS_RADIUS_EQUATORIAL, - MARS_RADIUS_POLAR, - MAXEXTENT, - MAXLAT, - degToRad, - radToDeg, -} from '../src/util'; -import { describe, expect, it } from 'bun:test'; - -describe('radToDeg', () => { - it('converts radians to degrees', () => { - expect(radToDeg(Math.PI)).toEqual(180); - }); -}); - -describe('degToRad', () => { - it('converts degrees to radians', () => { - expect(degToRad(180)).toEqual(Math.PI); - }); -}); - -describe('EARTH_RADIUS', () => { - it('is 6371008.8 meters', () => { - expect(EARTH_RADIUS).toEqual(6371008.8); - }); -}); - -describe('EARTH_RADIUS_EQUATORIAL', () => { - it('is 6378137 meters', () => { - expect(EARTH_RADIUS_EQUATORIAL).toEqual(6378137); - }); -}); - -describe('EARTH_RADIUS_POLAR', () => { - it('is 6356752.3 meters', () => { - expect(EARTH_RADIUS_POLAR).toEqual(6356752.3); - }); -}); - -describe('MARS_RADIUS', () => { - it('is 3389500 meters', () => { - expect(MARS_RADIUS).toEqual(3389500); - }); -}); - -describe('MARS_RADIUS_EQUATORIAL', () => { - it('is 3396200 meters', () => { - expect(MARS_RADIUS_EQUATORIAL).toEqual(3396200); - }); -}); - -describe('MARS_RADIUS_POLAR', () => { - it('is 3376200 meters', () => { - expect(MARS_RADIUS_POLAR).toEqual(3376200); - }); -}); - -describe('A', () => { - it('is 6378137.0 meters', () => { - expect(A).toEqual(6378137.0); - }); -}); - -describe('MAXEXTENT', () => { - it('is 20037508.342789244', () => { - expect(MAXEXTENT).toEqual(20037508.342789244); - }); -}); - -describe('MAXLAT', () => { - it('is 85.0511287798', () => { - expect(MAXLAT).toEqual(85.0511287798); - }); -}); diff --git a/test/values.test.ts b/test/values.test.ts deleted file mode 100644 index c0b4aa1..0000000 --- a/test/values.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { describe, expect, it } from 'bun:test'; - -import type { Primitive } from '../src'; - -describe('primitive', () => { - it('can be `string | number | boolean | null`', () => { - expect('string').toBe('string'); - expect(1).toBe(1); - expect(true).toBe(true); - expect(null).toBe(null); - // @ts-expect-error - undefined is not a primitive - const und: Primitive = undefined; - expect(und).toBeUndefined(); - }); -}); diff --git a/test/wm/convert.test.ts b/test/wm/convert.test.ts deleted file mode 100644 index 1c4fef6..0000000 --- a/test/wm/convert.test.ts +++ /dev/null @@ -1,1186 +0,0 @@ -import { toLL, toS2, toUnitScale, toVector } from '../../src/wm/convert'; - -import { expect, test } from 'bun:test'; - -import type { Feature, VectorFeature } from '../../src'; - -// toUnitScale - -test('toUnitScale - toLL - Point', () => { - const point: VectorFeature = { - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'Point', - is3D: true, - coordinates: { x: 0, y: 0, z: 0, m: { b: 2 } }, - }, - }; - - toUnitScale(point); - expect(point).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'Point', - is3D: true, - vecBBox: [0.5, 0.5, 0.5, 0.5, 0, 0], - coordinates: { x: 0.5, y: 0.5, z: 0, m: { b: 2 } }, - }, - }); - - toLL(point); - expect(point).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'Point', - is3D: true, - vecBBox: [0.5, 0.5, 0.5, 0.5, 0, 0], - coordinates: { x: 0, y: 0, z: 0, m: { b: 2 } }, - }, - }); -}); - -test('toUnitScale - toLL - MultiPoint', () => { - const point: VectorFeature = { - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'MultiPoint', - is3D: true, - coordinates: [ - { x: 0, y: 0, z: 0, m: { b: 2 } }, - { x: -180, y: -90, z: 0, m: { b: 3 } }, - { x: 180, y: 90, z: 0, m: { b: 4 } }, - ], - }, - }; - - toUnitScale(point); - expect(point).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'MultiPoint', - is3D: true, - vecBBox: [0, 0, 1, 1, 0, 0], - coordinates: [ - { x: 0.5, y: 0.5, z: 0, m: { b: 2 } }, - { x: 0, y: 1, z: 0, m: { b: 3 } }, - { x: 1, y: 0, z: 0, m: { b: 4 } }, - ], - }, - }); - - toLL(point); - expect(point).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'MultiPoint', - is3D: true, - vecBBox: [0, 0, 1, 1, 0, 0], - coordinates: [ - { x: 0, y: 0, z: 0, m: { b: 2 } }, - { x: -180, y: -85.05112877980659, z: 0, m: { b: 3 } }, - { x: 180, y: 85.05112877980659, z: 0, m: { b: 4 } }, - ], - }, - }); -}); - -test('toUnitScale - toLL - LineString', () => { - const linestring: VectorFeature = { - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'LineString', - is3D: true, - coordinates: [ - { x: 0, y: 0, z: 0, m: { b: 2 } }, - { x: -180, y: -90, z: 0, m: { b: 3 } }, - { x: 180, y: 90, z: 0, m: { b: 4 } }, - ], - }, - }; - - toUnitScale(linestring); - expect(linestring).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'LineString', - is3D: true, - vecBBox: [0, 0, 1, 1, 0, 0], - coordinates: [ - { x: 0.5, y: 0.5, z: 0, m: { b: 2 } }, - { x: 0, y: 1, z: 0, m: { b: 3 } }, - { x: 1, y: 0, z: 0, m: { b: 4 } }, - ], - }, - }); - - toLL(linestring); - expect(linestring).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'LineString', - is3D: true, - vecBBox: [0, 0, 1, 1, 0, 0], - coordinates: [ - { x: 0, y: 0, z: 0, m: { b: 2 } }, - { x: -180, y: -85.05112877980659, z: 0, m: { b: 3 } }, - { x: 180, y: 85.05112877980659, z: 0, m: { b: 4 } }, - ], - }, - }); -}); - -test('toUnitScale - toLL - MultiLineString', () => { - const multilinestring: VectorFeature = { - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'MultiLineString', - is3D: true, - coordinates: [ - [ - { x: 0, y: 0, z: 0, m: { b: 2 } }, - { x: -180, y: -90, z: 0, m: { b: 3 } }, - { x: 180, y: 90, z: 0, m: { b: 4 } }, - ], - [ - { x: 0, y: 0, z: 0, m: { b: 2 } }, - { x: -90, y: -45, z: 0, m: { b: 3 } }, - { x: 90, y: 45, z: 0, m: { b: 4 } }, - ], - ], - }, - }; - - toUnitScale(multilinestring); - expect(multilinestring).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'MultiLineString', - is3D: true, - vecBBox: [0, 0, 1, 1, 0, 0], - coordinates: [ - [ - { x: 0.5, y: 0.5, z: 0, m: { b: 2 } }, - { x: 0, y: 1, z: 0, m: { b: 3 } }, - { x: 1, y: 0, z: 0, m: { b: 4 } }, - ], - [ - { x: 0.5, y: 0.5, z: 0, m: { b: 2 } }, - { x: 0.25, y: 0.640274963084795, z: 0, m: { b: 3 } }, - { x: 0.75, y: 0.35972503691520497, z: 0, m: { b: 4 } }, - ], - ], - }, - }); - - toLL(multilinestring); - expect(multilinestring).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'MultiLineString', - is3D: true, - vecBBox: [0, 0, 1, 1, 0, 0], - coordinates: [ - [ - { x: 0, y: 0, z: 0, m: { b: 2 } }, - { x: -180, y: -85.05112877980659, z: 0, m: { b: 3 } }, - { x: 180, y: 85.05112877980659, z: 0, m: { b: 4 } }, - ], - [ - { x: 0, y: 0, z: 0, m: { b: 2 } }, - { x: -90, y: -45, z: 0, m: { b: 3 } }, - { x: 90, y: 45, z: 0, m: { b: 4 } }, - ], - ], - }, - }); -}); - -test('toUnitScale - toLL - Polygon', () => { - const polygon: VectorFeature = { - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'Polygon', - is3D: true, - coordinates: [ - [ - { x: 0, y: 0, z: 0, m: { b: 2 } }, - { x: -180, y: -90, z: 0, m: { b: 3 } }, - { x: 180, y: 90, z: 0, m: { b: 4 } }, - ], - [ - { x: 0, y: 0, z: 0, m: { b: 2 } }, - { x: -90, y: -45, z: 0, m: { b: 3 } }, - { x: 90, y: 45, z: 0, m: { b: 4 } }, - ], - ], - }, - }; - - toUnitScale(polygon); - expect(polygon).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'Polygon', - is3D: true, - vecBBox: [0, 0, 1, 1, 0, 0], - coordinates: [ - [ - { x: 0.5, y: 0.5, z: 0, m: { b: 2 } }, - { x: 0, y: 1, z: 0, m: { b: 3 } }, - { x: 1, y: 0, z: 0, m: { b: 4 } }, - ], - [ - { x: 0.5, y: 0.5, z: 0, m: { b: 2 } }, - { x: 0.25, y: 0.640274963084795, z: 0, m: { b: 3 } }, - { x: 0.75, y: 0.35972503691520497, z: 0, m: { b: 4 } }, - ], - ], - }, - }); - - toLL(polygon); - expect(polygon).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'Polygon', - is3D: true, - vecBBox: [0, 0, 1, 1, 0, 0], - coordinates: [ - [ - { x: 0, y: 0, z: 0, m: { b: 2 } }, - { x: -180, y: -85.05112877980659, z: 0, m: { b: 3 } }, - { x: 180, y: 85.05112877980659, z: 0, m: { b: 4 } }, - ], - [ - { x: 0, y: 0, z: 0, m: { b: 2 } }, - { x: -90, y: -45, z: 0, m: { b: 3 } }, - { x: 90, y: 45, z: 0, m: { b: 4 } }, - ], - ], - }, - }); -}); - -test('toUnitScale - toLL - MultiPolygon', () => { - const multiPolygon: VectorFeature = { - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'MultiPolygon', - is3D: true, - coordinates: [ - [ - [ - { x: 0, y: 0, z: 0, m: { b: 2 } }, - { x: -180, y: -90, z: 0, m: { b: 3 } }, - { x: 180, y: 90, z: 0, m: { b: 4 } }, - ], - [ - { x: 0, y: 0, z: 0, m: { b: 2 } }, - { x: -90, y: -45, z: 0, m: { b: 3 } }, - { x: 90, y: 45, z: 0, m: { b: 4 } }, - ], - ], - ], - }, - }; - - toUnitScale(multiPolygon); - expect(multiPolygon).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'MultiPolygon', - is3D: true, - vecBBox: [0, 0, 1, 1, 0, 0], - coordinates: [ - [ - [ - { x: 0.5, y: 0.5, z: 0, m: { b: 2 } }, - { x: 0, y: 1, z: 0, m: { b: 3 } }, - { x: 1, y: 0, z: 0, m: { b: 4 } }, - ], - [ - { x: 0.5, y: 0.5, z: 0, m: { b: 2 } }, - { x: 0.25, y: 0.640274963084795, z: 0, m: { b: 3 } }, - { x: 0.75, y: 0.35972503691520497, z: 0, m: { b: 4 } }, - ], - ], - ], - }, - }); - - toLL(multiPolygon); - expect(multiPolygon).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'MultiPolygon', - is3D: true, - vecBBox: [0, 0, 1, 1, 0, 0], - coordinates: [ - [ - [ - { x: 0, y: 0, z: 0, m: { b: 2 } }, - { x: -180, y: -85.05112877980659, z: 0, m: { b: 3 } }, - { x: 180, y: 85.05112877980659, z: 0, m: { b: 4 } }, - ], - [ - { x: 0, y: 0, z: 0, m: { b: 2 } }, - { x: -90, y: -45, z: 0, m: { b: 3 } }, - { x: 90, y: 45, z: 0, m: { b: 4 } }, - ], - ], - ], - }, - }); -}); - -// toVector - -test('toVector - Point', () => { - const point: Feature = { - type: 'Feature', - properties: { a: 1 }, - geometry: { type: 'Point', coordinates: [0, 0] }, - }; - expect(toVector(point)).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { type: 'Point', is3D: false, coordinates: { x: 0, y: 0 } }, - }); -}); - -test('toVector - Point3D', () => { - const point: Feature = { - id: 1, - type: 'Feature', - properties: { a: 1 }, - geometry: { type: 'Point3D', coordinates: [1, 1, 1] }, - }; - expect(toVector(point)).toEqual({ - id: 1, - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { type: 'Point', is3D: true, coordinates: { x: 1, y: 1, z: 1 } }, - }); -}); - -test('toVector - MultiPoint', () => { - const point: Feature = { - type: 'Feature', - properties: { a: 1 }, - geometry: { - type: 'MultiPoint', - coordinates: [ - [0, 0], - [1, 1], - ], - }, - }; - expect(toVector(point)).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'MultiPoint', - is3D: false, - coordinates: [ - { x: 0, y: 0 }, - { x: 1, y: 1 }, - ], - }, - }); -}); - -test('toVector - MultiPoint3D', () => { - const point: Feature = { - type: 'Feature', - properties: { a: 1 }, - geometry: { - type: 'MultiPoint3D', - coordinates: [ - [0, 0, 0], - [1, 1, 1], - ], - }, - }; - expect(toVector(point)).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'MultiPoint', - is3D: true, - coordinates: [ - { x: 0, y: 0, z: 0 }, - { x: 1, y: 1, z: 1 }, - ], - }, - }); -}); - -test('toVector - LineString', () => { - const point: Feature = { - type: 'Feature', - properties: { a: 1 }, - geometry: { - type: 'LineString', - coordinates: [ - [0, 0], - [1, 1], - ], - }, - }; - expect(toVector(point)).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'LineString', - is3D: false, - coordinates: [ - { x: 0, y: 0 }, - { x: 1, y: 1 }, - ], - }, - }); -}); - -test('toVector - LineString3D', () => { - const point: Feature = { - type: 'Feature', - properties: { a: 1 }, - geometry: { - type: 'LineString3D', - coordinates: [ - [0, 0, 0], - [1, 1, 1], - ], - }, - }; - expect(toVector(point)).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'LineString', - is3D: true, - coordinates: [ - { x: 0, y: 0, z: 0 }, - { x: 1, y: 1, z: 1 }, - ], - }, - }); -}); - -test('toVector - MultiLineString', () => { - const point: Feature = { - type: 'Feature', - properties: { a: 1 }, - geometry: { - type: 'MultiLineString', - coordinates: [ - [ - [0, 0], - [1, 1], - ], - [ - [2, 2], - [3, 3], - ], - ], - }, - }; - expect(toVector(point)).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'MultiLineString', - is3D: false, - coordinates: [ - [ - { x: 0, y: 0 }, - { x: 1, y: 1 }, - ], - [ - { x: 2, y: 2 }, - { x: 3, y: 3 }, - ], - ], - }, - }); -}); - -test('toVector - MultiLineString3D', () => { - const point: Feature = { - type: 'Feature', - properties: { a: 1 }, - geometry: { - type: 'MultiLineString3D', - coordinates: [ - [ - [0, 0, 0], - [1, 1, 1], - ], - [ - [2, 2, 2], - [3, 3, 3], - ], - ], - }, - }; - expect(toVector(point)).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'MultiLineString', - is3D: true, - coordinates: [ - [ - { x: 0, y: 0, z: 0 }, - { x: 1, y: 1, z: 1 }, - ], - [ - { x: 2, y: 2, z: 2 }, - { x: 3, y: 3, z: 3 }, - ], - ], - }, - }); -}); - -test('toVector - Polygon', () => { - const point: Feature = { - type: 'Feature', - properties: { a: 1 }, - geometry: { - type: 'Polygon', - coordinates: [ - [ - [0, 0], - [1, 1], - ], - [ - [2, 2], - [3, 3], - ], - ], - }, - }; - expect(toVector(point)).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'Polygon', - is3D: false, - coordinates: [ - [ - { x: 0, y: 0 }, - { x: 1, y: 1 }, - ], - [ - { x: 2, y: 2 }, - { x: 3, y: 3 }, - ], - ], - }, - }); -}); - -test('toVector - Polygon3D', () => { - const point: Feature = { - type: 'Feature', - properties: { a: 1 }, - geometry: { - type: 'Polygon3D', - coordinates: [ - [ - [0, 0, 0], - [1, 1, 1], - ], - [ - [2, 2, 2], - [3, 3, 3], - ], - ], - }, - }; - expect(toVector(point)).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'Polygon', - is3D: true, - coordinates: [ - [ - { x: 0, y: 0, z: 0 }, - { x: 1, y: 1, z: 1 }, - ], - [ - { x: 2, y: 2, z: 2 }, - { x: 3, y: 3, z: 3 }, - ], - ], - }, - }); -}); - -test('toVector - MultiPolygon', () => { - const point: Feature = { - type: 'Feature', - properties: { a: 1 }, - geometry: { - type: 'MultiPolygon', - coordinates: [ - [ - [ - [0, 0], - [1, 1], - ], - [ - [2, 2], - [3, 3], - ], - ], - ], - }, - }; - expect(toVector(point)).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'MultiPolygon', - is3D: false, - coordinates: [ - [ - [ - { x: 0, y: 0 }, - { x: 1, y: 1 }, - ], - [ - { x: 2, y: 2 }, - { x: 3, y: 3 }, - ], - ], - ], - }, - }); -}); - -test('toVector - MultiPolygon3D', () => { - const point: Feature = { - type: 'Feature', - properties: { a: 1 }, - geometry: { - type: 'MultiPolygon3D', - coordinates: [ - [ - [ - [0, 0, 0], - [1, 1, 1], - ], - [ - [2, 2, 2], - [3, 3, 3], - ], - ], - ], - }, - }; - expect(toVector(point)).toEqual({ - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'MultiPolygon', - is3D: true, - coordinates: [ - [ - [ - { x: 0, y: 0, z: 0 }, - { x: 1, y: 1, z: 1 }, - ], - [ - { x: 2, y: 2, z: 2 }, - { x: 3, y: 3, z: 3 }, - ], - ], - ], - }, - }); - - expect(() => - toVector({ - type: 'Feature', - properties: { a: 1 }, - geometry: { - // @ts-expect-error - Invalid GeoJSON type - type: 'mistake', - coordinates: [], - vecBBox: [0, 0, 0, 0], - }, - }), - ).toThrowError('Invalid GeoJSON type'); -}); - -// toS2 - -test('toS2 - Point', () => { - const point: VectorFeature = { - id: 1337, - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: 0, y: 0 }, - }, - }; - expect(toS2(point)).toEqual([ - { - id: 1337, - type: 'S2Feature', - face: 0, - properties: { a: 1 }, - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: 0.5, y: 0.5 }, - vecBBox: [0.5, 0.5, 0.5, 0.5], - }, - }, - ]); -}); - -test('toS2 - MultiPoint', () => { - const multiPoint: VectorFeature = { - id: 1337, - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'MultiPoint', - is3D: false, - coordinates: [ - { x: 0, y: 0 }, - { x: -180, y: -90 }, - { x: 180, y: 90 }, - ], - }, - }; - expect(toS2(multiPoint)).toEqual([ - { - id: 1337, - type: 'S2Feature', - face: 0, - properties: { a: 1 }, - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: 0.5, y: 0.5 }, - vecBBox: [0.5, 0.5, 0.5, 0.5], - }, - }, - { - id: 1337, - type: 'S2Feature', - face: 5, - properties: { a: 1 }, - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: 0.5, y: 0.5 }, - vecBBox: [0.5, 0.5, 0.5, 0.5], - }, - }, - { - id: 1337, - type: 'S2Feature', - face: 2, - properties: { a: 1 }, - geometry: { - type: 'Point', - is3D: false, - coordinates: { x: 0.5, y: 0.5 }, - vecBBox: [0.5, 0.5, 0.5, 0.5], - }, - }, - ]); -}); - -test('toS2 - LineString', () => { - const linestring: VectorFeature = { - id: 1337, - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - type: 'LineString', - is3D: false, - coordinates: [ - { x: 0, y: 0 }, - { x: 20, y: 20 }, - { x: 30, y: 30 }, - { x: 40, y: 40 }, - ], - }, - }; - - expect(toS2(linestring)).toEqual([ - { - face: 0, - geometry: { - is3D: false, - coordinates: [ - { x: 0.5, y: 0.5 }, - { x: 0.7231719544476624, y: 0.7351848576118168 }, - { x: 0.8264458251405347, y: 0.8660254037844386 }, - { x: 0.6953495465482081, y: 1.0625, t: 1 }, - ], - offset: 0, - vecBBox: [0.5, 0.5, 0.8264458251405347, 1.0625], - type: 'LineString', - }, - id: 1337, - properties: { a: 1 }, - type: 'S2Feature', - }, - { - face: 2, - geometry: { - is3D: false, - coordinates: [ - { x: -0.0625, y: 0.17012925937810885 }, - { x: 0.033200039883945376, y: 0.091961822201713 }, - ], - offset: 1.5284052199258356, - vecBBox: [-0.0625, 0.091961822201713, 0.033200039883945376, 0.17012925937810885], - type: 'LineString', - }, - id: 1337, - properties: { a: 1 }, - type: 'S2Feature', - }, - ]); -}); - -test('toS2 - MultiLineString', () => { - const multiLinestring: VectorFeature = { - id: 1337, - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - is3D: false, - type: 'MultiLineString', - coordinates: [ - [ - { x: 0, y: 0 }, - { x: 20, y: 20 }, - { x: 30, y: 30 }, - { x: 40, y: 40 }, - ], - [ - { x: -120, y: -30 }, - { x: -130, y: -40 }, - { x: -140, y: -50 }, - { x: -150, y: -60 }, - ], - ], - }, - }; - - expect(toS2(multiLinestring)).toEqual([ - { - face: 0, - geometry: { - is3D: false, - coordinates: [ - { x: 0.5, y: 0.5 }, - { x: 0.7231719544476624, y: 0.7351848576118168 }, - { x: 0.8264458251405347, y: 0.8660254037844386 }, - { x: 0.6953495465482081, y: 1.0625, t: 1 }, - ], - offset: 0, - vecBBox: [0.5, 0.5, 0.8264458251405347, 1.0625], - type: 'LineString', - }, - id: 1337, - properties: { a: 1 }, - type: 'S2Feature', - }, - { - face: 2, - geometry: { - is3D: false, - coordinates: [ - { x: -0.0625, y: 0.17012925937810885 }, - { x: 0.033200039883945376, y: 0.091961822201713 }, - ], - offset: 1.5284052199258356, - vecBBox: [-0.0625, 0.091961822201713, 0.033200039883945376, 0.17012925937810885], - type: 'LineString', - }, - id: 1337, - properties: { a: 1 }, - type: 'S2Feature', - }, - { - face: 4, - geometry: { - is3D: false, - coordinates: [ - { x: 0.8660254037844386, y: 0.17355417485946534 }, - { x: 1.0332000398839454, y: 0.0919618222017129 }, - { x: 1.0625, y: 0.1016957300340185, t: 1 }, - ], - offset: 0, - vecBBox: [0.8660254037844386, 0.0919618222017129, 1.0625, 0.17355417485946534], - type: 'LineString', - }, - id: 1337, - properties: { a: 1 }, - type: 'S2Feature', - }, - { - face: 5, - geometry: { - is3D: false, - coordinates: [ - { x: -0.0625, y: 0.13866981323286479 }, - { x: 0.033200039883945376, y: 0.0919618222017129 }, - { x: 0.1909745772474294, y: 0.14437700634864636 }, - { x: 0.3169872981077806, y: 0.209430584957905 }, - ], - offset: 0.07953324204553078, - vecBBox: [-0.0625, 0.0919618222017129, 0.3169872981077806, 0.209430584957905], - type: 'LineString', - }, - id: 1337, - properties: { a: 1 }, - type: 'S2Feature', - }, - ]); -}); - -test('toS2 - Polygon', () => { - const polygon: VectorFeature = { - id: 1337, - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - is3D: false, - type: 'Polygon', - coordinates: [ - [ - { x: 0, y: 0 }, - { x: 20, y: 0 }, - { x: 40, y: 0 }, - { x: 40, y: 20 }, - { x: 40, y: 40 }, - { x: 20, y: 40 }, - { x: 0, y: 40 }, - { x: 0, y: 20 }, - { x: 0, y: 0 }, - ], - [ - { x: 10, y: 10 }, - { x: 20, y: 10 }, - { x: 30, y: 10 }, - { x: 30, y: 20 }, - { x: 30, y: 30 }, - { x: 20, y: 30 }, - { x: 10, y: 30 }, - { x: 10, y: 20 }, - { x: 10, y: 10 }, - ], - ], - }, - }; - - expect(toS2(polygon)).toEqual([ - { - face: 0, - geometry: { - is3D: false, - coordinates: [ - [ - { x: 0.5, y: 0.5 }, - { x: 0.7231719544476624, y: 0.5 }, - { x: 0.9377231592442196, y: 0.5 }, - { x: 0.9377231592442196, y: 0.7786828928924201 }, - { x: 0.7356879031193608, y: 1.0625, t: 1 }, - { x: 0.6583568237637192, y: 1.0625, t: 1 }, - { x: 0.7231719544476622, y: 0.9590168832161913 }, - { x: 0.5, y: 0.9377231592442196 }, - { x: 0.5, y: 0.7231719544476624 }, - { x: 0.5, y: 0.5 }, - ], - [ - { x: 0.6182598446699807, y: 0.6199075184683839 }, - { x: 0.7231719544476624, y: 0.6250859462252395 }, - { x: 0.8264458251405347, y: 0.6345893512076446 }, - { x: 0.8264458251405347, y: 0.7518028126416558 }, - { x: 0.8264458251405347, y: 0.8660254037844386 }, - { x: 0.7231719544476624, y: 0.8430910345588061 }, - { x: 0.6182598446699807, y: 0.8304773451370653 }, - { x: 0.6182598446699807, y: 0.7260776792851733 }, - { x: 0.6182598446699807, y: 0.6199075184683839 }, - ], - ], - offset: [3.241841444519629, 0], - vecBBox: [0.5, 0.5, 0.9377231592442196, 1.0625], - type: 'Polygon', - }, - id: 1337, - properties: { a: 1 }, - type: 'S2Feature', - }, - { - face: 2, - geometry: { - is3D: false, - coordinates: [ - [ - { x: -0.0625, y: 0.19165525141383033 }, - { x: 0.033200039883945376, y: 0.091961822201713 }, - { x: -0.0625, y: 0.15284249599867805 }, - { x: -0.0625, y: 0.19165525141383033, t: 1 }, - ], - ], - offset: [1.7505894300567113], - vecBBox: [-0.0625, 0.091961822201713, 0.033200039883945376, 0.19165525141383033], - type: 'Polygon', - }, - id: 1337, - properties: { a: 1 }, - type: 'S2Feature', - }, - ]); -}); - -test('toS2 - MultiPolygon', () => { - const polygon: VectorFeature = { - id: 1337, - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - is3D: false, - type: 'MultiPolygon', - coordinates: [ - [ - [ - { x: 0, y: 0 }, - { x: 20, y: 0 }, - { x: 40, y: 0 }, - { x: 40, y: 20 }, - { x: 40, y: 40 }, - { x: 20, y: 40 }, - { x: 0, y: 40 }, - { x: 0, y: 20 }, - { x: 0, y: 0 }, - ], - [ - { x: 10, y: 10 }, - { x: 20, y: 10 }, - { x: 30, y: 10 }, - { x: 30, y: 20 }, - { x: 30, y: 30 }, - { x: 20, y: 30 }, - { x: 10, y: 30 }, - { x: 10, y: 20 }, - { x: 10, y: 10 }, - ], - ], - ], - }, - }; - - expect(toS2(polygon)).toEqual([ - { - face: 0, - geometry: { - is3D: false, - coordinates: [ - [ - { x: 0.5, y: 0.5 }, - { x: 0.7231719544476624, y: 0.5 }, - { x: 0.9377231592442196, y: 0.5 }, - { x: 0.9377231592442196, y: 0.7786828928924201 }, - { x: 0.7356879031193608, y: 1.0625, t: 1 }, - { x: 0.6583568237637192, y: 1.0625, t: 1 }, - { x: 0.7231719544476622, y: 0.9590168832161913 }, - { x: 0.5, y: 0.9377231592442196 }, - { x: 0.5, y: 0.7231719544476624 }, - { x: 0.5, y: 0.5 }, - ], - [ - { x: 0.6182598446699807, y: 0.6199075184683839 }, - { x: 0.7231719544476624, y: 0.6250859462252395 }, - { x: 0.8264458251405347, y: 0.6345893512076446 }, - { x: 0.8264458251405347, y: 0.7518028126416558 }, - { x: 0.8264458251405347, y: 0.8660254037844386 }, - { x: 0.7231719544476624, y: 0.8430910345588061 }, - { x: 0.6182598446699807, y: 0.8304773451370653 }, - { x: 0.6182598446699807, y: 0.7260776792851733 }, - { x: 0.6182598446699807, y: 0.6199075184683839 }, - ], - ], - offset: [3.241841444519629, 0], - vecBBox: [0.5, 0.5, 0.9377231592442196, 1.0625], - type: 'Polygon', - }, - id: 1337, - properties: { a: 1 }, - type: 'S2Feature', - }, - { - face: 2, - geometry: { - coordinates: [ - [ - { x: -0.0625, y: 0.19165525141383033 }, - { x: 0.033200039883945376, y: 0.091961822201713 }, - { x: -0.0625, y: 0.15284249599867805 }, - { x: -0.0625, y: 0.19165525141383033, t: 1 }, - ], - ], - offset: [1.7505894300567113], - vecBBox: [-0.0625, 0.091961822201713, 0.033200039883945376, 0.19165525141383033], - type: 'Polygon', - is3D: false, - }, - id: 1337, - properties: { a: 1 }, - type: 'S2Feature', - }, - ]); -}); - -test('toS2 - Error', () => { - const err: VectorFeature = { - id: 1337, - type: 'VectorFeature', - properties: { a: 1 }, - geometry: { - // @ts-expect-error Either the conversion is not yet supported or Invalid S2Geometry type. - type: 'error', - coordinates: { x: 0, y: 0 }, - }, - }; - expect(() => toS2(err)).toThrowError( - 'Either the conversion is not yet supported or Invalid S2Geometry type.', - ); - - expect(() => toLL(err)).toThrowError( - 'Either the conversion is not yet supported or Invalid S2Geometry type.', - ); -}); diff --git a/test/wm/mercCoords.test.ts b/test/wm/mercCoords.test.ts deleted file mode 100644 index 427104e..0000000 --- a/test/wm/mercCoords.test.ts +++ /dev/null @@ -1,371 +0,0 @@ -import { - altitudeFromMercatorZ, - bboxToXYZBounds, - convert, - latFromMercatorY, - llToMerc, - llToPX, - llToTile, - llToTilePx, - lngFromMercatorX, - mercToLL, - mercatorLatScale, - mercatorXfromLng, - mercatorYfromLat, - mercatorZfromAltitude, - pxToLL, - pxToTile, - tilePxBounds, - xyzToBBOX, -} from '../../src/wm/mercCoords'; -import { describe, expect, it, test } from 'bun:test'; - -import type { BBox } from '../../src'; - -describe('llToPX', () => { - it('PX with int zoom value converts when antiMeridian=true', () => { - expect(llToPX([-179, 85], 9, true, 256)).toEqual([364.0888888888876, 214.68476683766494]); - }); - - it('PX with int zoom value converts when antiMeridian=false', () => { - expect(llToPX([-179, 85], 9, false, 256)).toEqual([364.0888888888876, 214.68476683766494]); - }); - - it('PX with float zoom value converts when antiMeridian=false', () => { - expect(llToPX([-179, 85], 8.6574, false, 256)).toEqual([ - 287.12734093961626, 169.30444219392666, - ]); - }); - - it('PX with float zoom value converts when antiMeridian=true', () => { - expect(llToPX([-179, 85], 8.6574, true, 256)).toEqual([287.12734093961626, 169.30444219392666]); - }); - - it('Clamps PX by default when lon >180 when antiMeridian=false', () => { - expect(llToPX([250, 3], 4, false, 256)).toEqual([4096, 2013.8510595566413]); - }); - - it('PX with lon > 180 converts when antimeridian=true', () => { - expect(llToPX([250, 3], 4, true, 256)).toEqual([4892.444444444444, 2013.8510595566413]); - }); - - it('Clamps PX when lon >360 and antimeridian=true', () => { - expect(llToPX([400, 3], 4, true, 256)).toEqual([6599.111111111111, 2013.8510595566413]); - }); - - it('Clamps PX when lon >360 and antimeridian=false', () => { - expect(llToPX([400, 3], 4, false, 256)).toEqual([4096, 2013.8510595566413]); - }); -}); - -describe('pxToLL', () => { - it('LL with int zoom value converts', () => { - expect(pxToLL([200, 200], 9, 256)).toEqual([-179.45068359375, 85.00351401304401]); - }); - - it('LL with float zoom value converts', () => { - expect(pxToLL([200, 200], 8.6574, 256)).toEqual([-179.3034449476476, 84.99067388699072]); - }); -}); - -describe('xyzToBBOX', () => { - it('[0,0,0] converted to proper bbox.', () => { - expect(xyzToBBOX(0, 0, 0, true, 'WGS84', 256)).toEqual([ - -180, -85.05112877980659, 180, 85.05112877980659, - ]); - }); - - it('[0,0,0] converted to proper bbox. source=900913', () => { - expect(xyzToBBOX(0, 0, 0, true, '900913', 256)).toEqual([ - -20037508.34278924, -20037508.342789236, 20037508.34278924, 20037508.342789244, - ]); - }); - - it('[0,0,1] converted to proper bbox.', () => { - expect(xyzToBBOX(0, 0, 1, true, 'WGS84', 256)).toEqual([-180, -85.05112877980659, 0, 0]); - }); - - it('[0,0,1] converted to proper bbox. source=900913', () => { - expect(xyzToBBOX(0, 0, 1, true, '900913', 256)).toEqual([ - -20037508.34278924, -20037508.342789236, 0, -0.0000000007081154551613622, - ]); - }); -}); - -describe('bboxToXYZBounds', () => { - it('World extents converted to proper tile ranges.', () => { - expect( - bboxToXYZBounds([-180, -85.05112877980659, 180, 85.0511287798066], 0, true, 'WGS84', 256), - ).toEqual([0, 0, 0, 0]); - }); - - it('World extents converted to proper tile ranges. source=900913', () => { - expect( - bboxToXYZBounds([-180, -85.05112877980659, 180, 85.0511287798066], 0, true, '900913', 256), - ).toEqual([0, -1, 0, 0]); - }); - - it('SW converted to proper tile ranges.', () => { - expect(bboxToXYZBounds([-180, -85.05112877980659, 0, 0], 1, true, 'WGS84', 256)).toEqual([ - 0, 0, 0, 0, - ]); - }); - - it('SW converted to proper tile ranges. source=900913', () => { - expect(bboxToXYZBounds([-180, -85.05112877980659, 0, 0], 1, true, '900913', 256)).toEqual([ - 0, 0, 0, 1, - ]); - }); - - it('broken case', () => { - const extent: BBox = [-0.087891, 40.95703, 0.087891, 41.044916]; - const xyz = bboxToXYZBounds(extent, 3, true, 'WGS84', 256); - expect(xyz[0] <= xyz[2]).toBe(true); - expect(xyz[1] <= xyz[3]).toBe(true); - }); - - it('negative case', () => { - const extent: BBox = [-112.5, 85.0511, -112.5, 85.0511]; - const xyz = bboxToXYZBounds(extent, 0, true, 'WGS84', 256); - expect(xyz[1]).toBe(0); - }); - - it('fuzz', () => { - const { max, min } = Math; - for (let i = 0; i < 1000; i++) { - const x = [-180 + 360 * Math.random(), -180 + 360 * Math.random()]; - const y = [-85 + 170 * Math.random(), -85 + 170 * Math.random()]; - const z = Math.floor(22 * Math.random()); - const extent: BBox = [min(...x), min(...y), max(...x), max(...y)]; - const xyz = bboxToXYZBounds(extent, z, true, 'WGS84', 256); - expect(xyz[0] <= xyz[2]).toBe(true); - expect(xyz[1] <= xyz[3]).toBe(true); - } - }); -}); - -describe('convert', () => { - it('extremes', () => { - expect( - convert( - [-20037508.342789244, -20037508.342789244, 20037508.342789244, 20037508.342789244], - 'WGS84', - ), - ).toEqual([-180.00000000000003, -85.05112877980659, 180.00000000000003, 85.05112877980659]); - expect(convert([-180, -85.05112877980659, 180, 85.05112877980659], '900913')).toEqual([ - -20037508.34278924, -20037508.342789236, 20037508.34278924, 20037508.342789244, - ]); - expect( - convert( - [-20037508.34278924, -20037508.34278924, 20037508.34278924, 20037508.342789244], - 'WGS84', - ), - ).toEqual([-179.99999999999997, -85.05112877980659, 179.99999999999997, 85.05112877980659]); - }); - - it('extents', () => { - expect(convert([-240, -90, 240, 90], '900913')).toEqual([ - -20037508.342789244, -20037508.342789244, 20037508.342789244, 20037508.342789244, - ]); - - expect(bboxToXYZBounds([-240, -90, 240, 90], 4, true, 'WGS84', 256)).toEqual([0, 0, 15, 15]); - }); -}); - -test('high precision float 256', () => { - let withInt = pxToLL([200, 200], 4, 256); - let withFloat = pxToLL([200, 200], 4.0000000001, 256); - - /** - * @param val - input - * @returns rounded - */ - function round(val: number): number { - return +parseFloat(String(val)).toFixed(6); - } - - expect(round(withInt[0])).toEqual(round(withFloat[0])); - expect(round(withInt[1])).toEqual(round(withFloat[1])); - - // utilize cache - withInt = pxToLL([200, 200], 4, 256); - withFloat = pxToLL([200, 200], 4.0000000001, 256); - - expect(round(withInt[0])).toEqual(round(withFloat[0])); - expect(round(withInt[1])).toEqual(round(withFloat[1])); -}); - -test('high precision float 512', () => { - let withInt = pxToLL([400, 400], 2, 512); - let withFloat = pxToLL([400, 400], 2.00000000001, 512); - - /** - * @param val - input - * @returns rounded - */ - function round(val: number): number { - return +parseFloat(String(val)).toFixed(6); - } - - expect(round(withInt[0])).toEqual(round(withFloat[0])); - expect(round(withInt[1])).toEqual(round(withFloat[1])); - - // utilize cache - withInt = pxToLL([200, 200], 4, 512); - withFloat = pxToLL([200, 200], 4.0000000001, 512); - - expect(round(withInt[0])).toEqual(round(withFloat[0])); - expect(round(withInt[1])).toEqual(round(withFloat[1])); -}); - -describe('llToTile', () => { - it('0-0-0: center point', () => { - const tile = llToTile([0, 0], 0, 512); - expect(tile).toEqual([0, 0]); - }); - - it('0-0-0: top left', () => { - const tile = llToTile([-180, 85.05], 0, 512); - expect(tile).toEqual([0, 0]); - }); - - // zoom 1 - it('1-0-0: center point', () => { - const tile = llToTile([0, 0], 1, 512); - expect(tile).toEqual([1, 1]); - }); - - it('1-0-0: top left', () => { - const tile = llToTile([-180, 85.05], 1, 512); - expect(tile).toEqual([0, 0]); - }); -}); - -describe('llToTilePx', () => { - it('0-0-0: center point', () => { - const tileOffset = llToTilePx([0, 0], [0, 0, 0], 512); - expect(tileOffset).toEqual([0.5, 0.5]); - }); - - it('2-3-3: center point', () => { - const tileOffset = llToTilePx([0, 0], [2, 3, 3], 512); - expect(tileOffset).toEqual([-1, -1]); - }); - - it('0-1-0: out of bounds tile with center point (used for world wrapping)', () => { - const tileOffset = llToTilePx([0, 0], [0, 2, 0], 512); - expect(tileOffset).toEqual([-1.5, 0.5]); - }); - - it('0-0-0: top left', () => { - const tileOffset = llToTilePx([-180, 85.05], [0, 0, 0], 512); - expect(tileOffset).toEqual([0, 0.00003634242909722474]); - }); - - it('0-0-0: top right', () => { - const tileOffset = llToTilePx([180, 85.05], [0, 0, 0], 512); - expect(tileOffset).toEqual([1, 0.00003634242909722474]); - }); - - it('0-0-0: bottom right', () => { - const tileOffset = llToTilePx([180, -85.05], [0, 0, 0], 512); - expect(tileOffset).toEqual([1, 0.9999636575709028]); - }); - - it('0-0-0: bottom left', () => { - const tileOffset = llToTilePx([-180, -85.05], [0, 0, 0], 512); - expect(tileOffset).toEqual([0, 0.9999636575709028]); - }); - - it('center for zoom 1 tiles', () => { - const tileOffset00 = llToTilePx([0, 0], [1, 0, 0], 512); - expect(tileOffset00).toEqual([1, 1]); - - const tileOffset10 = llToTilePx([0, 0], [1, 1, 0], 512); - expect(tileOffset10).toEqual([0, 1]); - - const tileOffset01 = llToTilePx([0, 0], [1, 0, 1], 512); - expect(tileOffset01).toEqual([1, 0]); - - const tileOffset11 = llToTilePx([0, 0], [1, 1, 1], 512); - expect(tileOffset11).toEqual([0, 0]); - }); -}); - -test('llToMerc', () => { - expect(llToMerc([0, 0])).toEqual([0, -7.081154551613622e-10]); - expect(llToMerc([-180, 90])).toEqual([-20037508.34278924, 20037508.342789244]); - expect(llToMerc([180, -90])).toEqual([20037508.34278924, -20037508.342789244]); -}); - -test('mercToLL', () => { - expect(mercToLL([0, -7.081154551613622e-10])).toEqual([0, 0]); - expect(mercToLL([-20037508.34278924, 20037508.342789244])).toEqual([ - -179.99999999999997, 85.05112877980659, - ]); - expect(mercToLL([20037508.34278924, -20037508.342789244])).toEqual([ - 179.99999999999997, -85.05112877980659, - ]); -}); - -test('pxToTile', () => { - expect(pxToTile([0, 0])).toEqual([0, 0]); - expect(pxToTile([600, 2_000])).toEqual([1, 3]); -}); - -test('tilePxBounds', () => { - expect(tilePxBounds([0, 0, 0])).toEqual([0, 0, 512, 512]); - expect(tilePxBounds([1, 0, 0])).toEqual([0, 0, 512, 512]); - expect(tilePxBounds([1, 1, 0])).toEqual([512, 0, 1024, 512]); - expect(tilePxBounds([2, 2, 2])).toEqual([1024, 1024, 1536, 1536]); -}); - -test('mercatorXfromLng', () => { - expect(mercatorXfromLng(0)).toEqual(0.5); - expect(mercatorXfromLng(-180)).toEqual(0); - expect(mercatorXfromLng(180)).toEqual(1); -}); - -test('mercatorYfromLat', () => { - expect(mercatorYfromLat(0)).toEqual(0.5); - expect(mercatorYfromLat(-85.05112877980659)).toEqual(0.9999999999999999); - expect(mercatorYfromLat(85.05112877980659)).toEqual(-7.894919286223336e-17); - // out of bounds numbers - expect(mercatorYfromLat(90)).toEqual(-5.441549447954536); - expect(mercatorYfromLat(-90)).toEqual(Infinity); -}); - -test('mercatorZfromAltitude', () => { - expect(mercatorZfromAltitude(0, 0)).toEqual(0); - expect(mercatorZfromAltitude(1_000_000, 0)).toEqual(0.0249811212145705); - expect(mercatorZfromAltitude(1_000_000, 60)).toEqual(0.04996224242914099); -}); - -test('lngFromMercatorX', () => { - expect(lngFromMercatorX(0.5)).toEqual(0); - expect(lngFromMercatorX(0)).toEqual(-180); - expect(lngFromMercatorX(1)).toEqual(180); -}); - -test('latFromMercatorY', () => { - expect(latFromMercatorY(0.5)).toEqual(0); - expect(latFromMercatorY(1)).toEqual(-85.05112877980659); - expect(latFromMercatorY(0)).toEqual(85.05112877980659); - // out of bounds numbers - expect(latFromMercatorY(2)).toEqual(-89.99075251648904); - expect(latFromMercatorY(-1)).toEqual(89.99075251648904); -}); - -test('altitudeFromMercatorZ', () => { - expect(altitudeFromMercatorZ(0, 0)).toEqual(0); - expect(altitudeFromMercatorZ(0.0249811212145705, 0)).toEqual(86_266.73833405455); - expect(altitudeFromMercatorZ(0.04996224242914099, 60)).toEqual(1.224646799147353e-10); -}); - -test('mercatorLatScale', () => { - expect(mercatorLatScale(0)).toEqual(1); - expect(mercatorLatScale(45)).toEqual(1.414213562373095); - expect(mercatorLatScale(-45)).toEqual(1.414213562373095); - expect(mercatorLatScale(85)).toEqual(11.47371324566986); - expect(mercatorLatScale(-85)).toEqual(11.47371324566986); -});