diff --git a/README.md b/README.md index 4f82d30..d7dce53 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,8 @@ Includes: - 📦 Extent class for working with bounding boxes - 🀄️ Tiler class for splitting the world into rectangular tiles -- 🕹️ Transform class for managing translation, scale, rotation -- 📺 Viewport class for managing view state and converting between Lon/Lat (λ,φ) and Cartesian (x,y) coordinates +- 🕹️ Transform class for managing translation, zoom, rotation +- 📺 Viewport class for managing view state and converting between Lon/Lat [λ,φ] and Cartesian [x,y] coordinates ### util diff --git a/bun.lock b/bun.lock index ee1a9f8..d7d4559 100644 --- a/bun.lock +++ b/bun.lock @@ -4,9 +4,6 @@ "workspaces": { "": { "name": "rapid-sdk", - "dependencies": { - "@types/geojson": "^7946.0.16", - }, "devDependencies": { "@eslint/js": "^10.0.1", "@types/bun": "^1.3.13", @@ -21,6 +18,7 @@ "version": "1.0.0-pre.4", "dependencies": { "@types/d3-polygon": "^3.0.2", + "@types/geojson": "^7946.0.16", "d3-polygon": "^3.0.1", }, }, diff --git a/docs/classes/_rapid_sdk_math.Viewport.html b/docs/classes/_rapid_sdk_math.Viewport.html index 23fe196..a31eb3c 100644 --- a/docs/classes/_rapid_sdk_math.Viewport.html +++ b/docs/classes/_rapid_sdk_math.Viewport.html @@ -1,7 +1,7 @@
Unprojects a coordinate from given Cartesian [x,y] to Lon/Lat [λ,φ]
+Cartesian [x,y]
+OptionalincludeRotation: booleanLon/Lat [λ,φ]
const v = new Viewport();
v.unproject([0, 0]); // returns [0, 0]
v.unproject([256, 256]); // returns [180, -85.0511287798]
v.unproject([-256, -256]); // returns [-180, 85.0511287798]
diff --git a/docs/index.html b/docs/index.html
index 4acce04..0b55e22 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -33,7 +33,7 @@
🕹️ Transform class for managing translation, scale, rotation
📺 Viewport class for managing view state and converting between Lon/Lat (λ,φ) and Cartesian (x,y) coordinates
+📺 Viewport class for managing view state and converting between Lon/Lat [λ,φ] and Cartesian [x,y] coordinates
🕹️ Transform class for managing translation, scale, rotation
📺 Viewport class for managing view state and converting between Lon/Lat (λ,φ) and Cartesian (x,y) coordinates
+📺 Viewport class for managing view state and converting between Lon/Lat [λ,φ] and Cartesian [x,y] coordinates
This project is just getting started! 🌱
diff --git a/package.json b/package.json index 59446f5..22c26e1 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,6 @@ "packages/math", "packages/util" ], - "dependencies": { - "@types/geojson": "^7946.0.16" - }, "devDependencies": { "@eslint/js": "^10.0.1", "@types/bun": "^1.3.13", diff --git a/packages/math/README.md b/packages/math/README.md index a6b7c92..23caba4 100644 --- a/packages/math/README.md +++ b/packages/math/README.md @@ -31,8 +31,8 @@ import { Extent } from '@rapid-sdk/math'; // ESM import named - 📦 Extent class for working with bounding boxes - 🀄️ Tiler class for splitting the world into rectangular tiles -- 🕹️ Transform class for managing translation, scale, rotation -- 📺 Viewport class for managing view state and converting between Lon/Lat (λ,φ) and Cartesian (x,y) coordinates +- 🕹️ Transform class for managing translation, zoom, rotation +- 📺 Viewport class for managing view state and converting between Lon/Lat [λ,φ] and Cartesian [x,y] coordinates ## Contributing diff --git a/packages/math/package.json b/packages/math/package.json index 513135f..86d17ae 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -39,6 +39,7 @@ "test": "bun test --dots --coverage ./test" }, "dependencies": { + "@types/geojson": "^7946.0.16", "@types/d3-polygon": "^3.0.2", "d3-polygon": "^3.0.1" }, diff --git a/packages/math/src/Extent.ts b/packages/math/src/Extent.ts index f2e64b8..3b81193 100644 --- a/packages/math/src/Extent.ts +++ b/packages/math/src/Extent.ts @@ -4,7 +4,7 @@ */ import { geoMetersToLat, geoMetersToLon } from './geo'; -import { Vec2 } from './vector'; +import { Vec2, Vec4 } from './vector'; /** Bounding box containing minX, minY, maxX, maxY numbers */ export interface BBox { @@ -97,7 +97,7 @@ export class Extent { * @example * new Extent([0, 0], [5, 10]).rectangle(); // returns [0, 0, 5, 10] */ - rectangle(): number[] { + rectangle(): Vec4 { return [this.min[0], this.min[1], this.max[0], this.max[1]]; } @@ -243,11 +243,15 @@ export class Extent { */ extendSelf(other: Extent | Vec2): Extent { if (other instanceof Extent) { - this.min = [Math.min(other.min[0], this.min[0]), Math.min(other.min[1], this.min[1])]; - this.max = [Math.max(other.max[0], this.max[0]), Math.max(other.max[1], this.max[1])]; + this.min[0] = Math.min(other.min[0], this.min[0]); + this.min[1] = Math.min(other.min[1], this.min[1]); + this.max[0] = Math.max(other.max[0], this.max[0]); + this.max[1] = Math.max(other.max[1], this.max[1]); } else { - this.min = [Math.min(other[0], this.min[0]), Math.min(other[1], this.min[1])]; - this.max = [Math.max(other[0], this.max[0]), Math.max(other[1], this.max[1])]; + this.min[0] = Math.min(other[0], this.min[0]); + this.min[1] = Math.min(other[1], this.min[1]); + this.max[0] = Math.max(other[0], this.max[0]); + this.max[1] = Math.max(other[1], this.max[1]); } return this; } diff --git a/packages/math/src/Tiler.ts b/packages/math/src/Tiler.ts index f99a75a..478bfc8 100644 --- a/packages/math/src/Tiler.ts +++ b/packages/math/src/Tiler.ts @@ -5,11 +5,11 @@ * See: https://developers.google.com/maps/documentation/javascript/coordinates */ -import { MAX_Z, MIN_Z } from './constants'; +import { ANGLE_EPSILON, MAX_Z, MIN_Z, TAU, WORLD_SIZE } from './constants'; import { Extent } from './Extent'; import { Viewport } from './Viewport'; import { geomPathHasIntersections, geomPolygonIntersectsPolygon, geomRotatePoints } from './geom'; -import { numClamp } from './number'; +import { numClamp, numWrap } from './number'; import { Vec2, Vec3 } from './vector'; import type * as GeoJSON from 'geojson'; @@ -21,7 +21,7 @@ export interface Tile { id: string; /** tile coordinate array ex. [0,0,0] */ xyz: Vec3; - /** Extent in world coordinates (z16 scale, range 0..16_777_216) */ + /** Extent in world coordinates (z=WORLD_ZOOM scale, range 0..WORLD_SIZE) */ tileExtent: Extent; /** Extent in WGS84 coordinates(lon,lat) */ wgs84Extent: Extent; @@ -35,12 +35,6 @@ export interface TileResult { tiles: Tile[]; } - -function range(start: number, end: number): number[] { - return Array.from(Array(1 + end - start).keys()).map((v) => start + v); -} - - export class Tiler { private _tileSize = 256; private _zoomRange: Vec2 = [MIN_Z, MAX_Z]; @@ -145,9 +139,12 @@ export class Tiler { // ''--..__ / // ''G [vw,vh] - const ts = this._tileSize; // tile size in pizels + const ts = this._tileSize; // tile size in pixels const ms = this._margin * ts; // margin size in pixels const t = viewport.transform.props; + const wrappedRotation = numWrap(t.r ?? 0, 0, TAU); + const rotation = (wrappedRotation < ANGLE_EPSILON || (TAU - wrappedRotation) < ANGLE_EPSILON) ? 0 : wrappedRotation; + const hasRotation = rotation !== 0; const [sw, sh] = viewport.dimensions; let visiblePolygon = viewport.visiblePolygon() as Vec2[]; let screenPolygon = [[0, 0], [0, sh], [sw, sh], [sw, 0], [0, 0]] as Vec2[]; @@ -158,17 +155,17 @@ export class Tiler { let marginPolygon = _addMargin(screenPolygon, ms); // Un-rotate the polygons back to where they would be on a north-aligned grid. - if (t.r) { + if (hasRotation) { const center = viewport.center(); - screenPolygon = geomRotatePoints(screenPolygon, -t.r, center); - marginPolygon = geomRotatePoints(marginPolygon, -t.r, center); - visiblePolygon = geomRotatePoints(visiblePolygon, -t.r, center); + screenPolygon = geomRotatePoints(screenPolygon, -rotation, center); + marginPolygon = geomRotatePoints(marginPolygon, -rotation, center); + visiblePolygon = geomRotatePoints(visiblePolygon, -rotation, center); } if (ms) { // now that visible is un-rotated, we can apply margin to it if needed. visiblePolygon = _addMargin(visiblePolygon, ms); } - // Convert all polygons to world coordinates.. + // Convert all polygons to world coordinates. for (let i = 0; i < 5; i++) { visiblePolygon[i] = viewport.screenToWorld(visiblePolygon[i]); screenPolygon[i] = viewport.screenToWorld(screenPolygon[i]); @@ -184,24 +181,21 @@ export class Tiler { const adjust = log2ts - 8; // adjust zoom for tile sizes not 256px (log2(256) = 8) const zOrig = t.z - adjust; const z = numClamp(Math.round(zOrig), this._zoomRange[0], this._zoomRange[1]); - const pow2z = Math.pow(2, z); - const worldScale = pow2z / 16_777_216; - const tileScale = 16_777_216 / pow2z; + const pow2z = 2 ** z; + const worldScale = pow2z / WORLD_SIZE; + const tileScale = WORLD_SIZE / pow2z; const min = 0; const max = pow2z - 1; - const cols = range( - numClamp(Math.floor(worldMin[0] * worldScale), min, max), - numClamp(Math.floor(worldMax[0] * worldScale), min, max) - ); - const rows = range( - numClamp(Math.floor(worldMin[1] * worldScale), min, max), - numClamp(Math.floor(worldMax[1] * worldScale), min, max) - ); - - const tiles: Tile[] = []; - for (const y of rows) { - for (const x of cols) { + const minCol = numClamp(Math.floor(worldMin[0] * worldScale), min, max); + const maxCol = numClamp(Math.floor(worldMax[0] * worldScale), min, max); + const minRow = numClamp(Math.floor(worldMin[1] * worldScale), min, max); + const maxRow = numClamp(Math.floor(worldMax[1] * worldScale), min, max); + + const visibleTiles: Tile[] = []; + const marginTiles: Tile[] = []; + for (let y = minRow; y <= maxRow; y++) { + for (let x = minCol; x <= maxCol; x++) { if (this._skipNullIsland && Tiler.isNearNullIsland(x, y, z)) continue; // The tile bounds in world coordinates @@ -218,7 +212,7 @@ export class Tiler { // Note, for rotated viewports, we need the strict test, // because the tile corners may be rotated out of the viewport - see rapid-sdk#281 - if (!isIncluded && t.r !== 0) { + if (!isIncluded && hasRotation) { isIncluded = geomPathHasIntersections(marginPolygon, tilePolygon); } if (!isIncluded) continue; // no need to include this tile in the result @@ -228,7 +222,7 @@ export class Tiler { geomPolygonIntersectsPolygon(screenPolygon, tilePolygon, false) || geomPolygonIntersectsPolygon(tilePolygon, screenPolygon, false); - if (!isVisible && t.r !== 0) { + if (!isVisible && hasRotation) { isVisible = geomPathHasIntersections(screenPolygon, tilePolygon); } @@ -247,15 +241,15 @@ export class Tiler { }; if (isVisible) { - tiles.unshift(tile); // tiles in view at beginning + visibleTiles.push(tile); // tiles in view at beginning } else { - tiles.push(tile); // tiles in margin at the end + marginTiles.push(tile); // tiles in margin at the end } } } return { - tiles: tiles + tiles: visibleTiles.reverse().concat(marginTiles) }; @@ -279,7 +273,7 @@ export class Tiler { * @example * const t = new Tiler(); * const v = new Viewport(); - * v.transform = { x: 256, y: 256, k: 256 / Math.PI }; // z1 + * v.transform = { x: 256, y: 256, z: 1 }; * v.dimensions = [512, 512]; // entire world visible * const result = t.getTiles(v); * const gj = t.getGeoJSON(result); // returns a GeoJSON FeatureCollection @@ -400,8 +394,8 @@ export class Tiler { */ static isNearNullIsland(x: number, y: number, z: number): boolean { if (z >= 7) { - const center = Math.pow(2, z - 1); - const width = Math.pow(2, z - 6); + const center = 2 ** (z - 1); + const width = 2 ** (z - 6); const min = center - width / 2; const max = center + width / 2 - 1; return x >= min && x <= max && y >= min && y <= max; diff --git a/packages/math/src/Transform.ts b/packages/math/src/Transform.ts index 3353188..0507621 100644 --- a/packages/math/src/Transform.ts +++ b/packages/math/src/Transform.ts @@ -3,7 +3,7 @@ * @module */ -import { TAU, MIN_Z, MAX_Z } from './constants'; +import { TAU, MIN_Z, MAX_Z, ANGLE_EPSILON } from './constants'; import { numClamp, numWrap } from './number'; import { Vec2 } from './vector'; @@ -37,7 +37,7 @@ export class Transform { /** Constructs a new Transform * @param other */ - constructor(other?: Transform) { + constructor(other?: Partial
+and converting between Lon/Lat [λ,φ] and Cartesian [x,y] coordinatesViewportis a class for managing the state of the viewer -and converting between Lon/Lat (λ,φ) and Cartesian (x,y) coordinatesOriginal geographic coordinate data is in WGS84 (Lon,Lat) -and "projected" into screen space (x,y) using the Web Mercator projection +and "projected" into screen space [x,y] using the Web Mercator projection see: https://en.wikipedia.org/wiki/Web_Mercator_projection
The parameters of this projection are stored in
_transform@@ -57,15 +57,15 @@
Example
Example
-