Skip to content

Commit

Permalink
feat: web based reprojection(but slow)
Browse files Browse the repository at this point in the history
  • Loading branch information
hongfaqiu committed Sep 4, 2023
1 parent 22a58c1 commit 665de3a
Show file tree
Hide file tree
Showing 13 changed files with 589 additions and 107 deletions.
2 changes: 1 addition & 1 deletion example/src/utils/CesiumMap/CesiumMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export default class CesiumMap extends BaseMap {
renderOptions,
enablePickFeatures: true,
projFunc: (code) => {
if (![4326].includes(code)) {
if (![4326, 3857, 900913].includes(code)) {
{
try {
let prj = proj4("EPSG:4326", `EPSG:${code}`)
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
},
"devDependencies": {
"conventional-changelog-cli": "^2.2.2",
"esbuild": "^0.19.2",
"lerna": "^6.1.0",
"rimraf": "^5.0.1",
"rollup-plugin-dts": "^6.0.1",
"rollup-plugin-esbuild": "^5.0.0",
"turbo": "^1.10.13"
}
}
2 changes: 1 addition & 1 deletion packages/TIFFImageryProvider/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/node_modules
/lib
/dist
4 changes: 2 additions & 2 deletions packages/TIFFImageryProvider/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ Load GeoTIFF/COG(Cloud optimized GeoTIFF) on Cesium
- Three band rendering.
- Multi mode color rendering.
- Support identify TIFF value with cartographic position.
- Support any projected TIFF.
- Web Workers speed up.
- WebGL accelerated rendering.
- Band calculation.
- Support any projected TIFF (experimental).

## Install

Expand Down Expand Up @@ -57,7 +57,7 @@ provider.readyPromise.then(() => {
})
```

If TIFF's projection is not EPSG:4326, you can pass the ``projFunc`` to handle the projection
**Experimental** If TIFF's projection is not EPSG:4326 or EPSG:3857, you can pass the ``projFunc`` to handle the projection

```ts
import proj4 from 'proj4';
Expand Down
12 changes: 6 additions & 6 deletions packages/TIFFImageryProvider/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@
"cog",
"webgl"
],
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./lib/index.js",
"require": "./lib/index.js"
"import": "./dist/index.js",
"require": "./dist/index.js"
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/hongfaqiu/tiff-imagery-provider.git"
},
"scripts": {
"dev": "rollup -c -w",
"build": "rollup -c",
"dev": "rimraf dist && rollup -c --watch",
"build": "rimraf dist && rollup -c",
"prepublish": "pnpm build"
},
"author": "hongfaqiu",
Expand Down
62 changes: 41 additions & 21 deletions packages/TIFFImageryProvider/rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,54 @@
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import { readFileSync } from 'fs';
import webWorkerLoader from "rollup-plugin-web-worker-loader";
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const pkg = require("./package.json")
import esbuild from 'rollup-plugin-esbuild';
import path from 'path';
import dts from 'rollup-plugin-dts';

const pkg = JSON.parse(
readFileSync(new URL('./package.json', import.meta.url)).toString(),
);

const deps = { ...pkg.dependencies, ...pkg.peerDependencies };
const external = Object.keys(deps)
/**
* @type {import('rollup').RollupOptions}
*/
const config = {
input: 'src/index.ts',
output: {
file: pkg.main,
sourcemap: true,
exports: "auto",
const config = [
{
input: 'src/index.ts',
output: {
dir: path.dirname(pkg.main),
name: pkg.main,
format: 'esm',
sourcemap: true,
// preserveModules: true,
},
external,
plugins: [
webWorkerLoader({
inline: true,
targetPlatform: "browser",
extensions: ["ts", "js"],
external,
}),
esbuild({
target: 'node14',
}),
]
},
{
input: 'src/index.ts',
output: {
dir: path.dirname(pkg.types),
entryFileNames: '[name].d.ts',
format: 'esm',
},
plugins: [dts()],
},
external: Object.keys(deps),
plugins: [
resolve(),
commonjs(),
typescript(),
webWorkerLoader({
inline: true,
targetPlatform: "browser",
extensions: ["ts", "js"],
external: []
}),
]
};
];

export default config;
99 changes: 51 additions & 48 deletions packages/TIFFImageryProvider/src/TIFFImageryProvider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Event, GeographicTilingScheme, Credit, Rectangle, ImageryLayerFeatureInfo, Math as CesiumMath, DeveloperError, defined, Cartesian2 } from "cesium";
import { Event, GeographicTilingScheme, Credit, Rectangle, ImageryLayerFeatureInfo, Math as CesiumMath, DeveloperError, defined, Cartesian2, WebMercatorTilingScheme } from "cesium";
import GeoTIFF, { Pool, fromUrl, fromBlob, GeoTIFFImage } from 'geotiff';

import { addColorScale, plot } from './plotty'
Expand Down Expand Up @@ -135,7 +135,6 @@ export interface TIFFImageryProviderOptions {
resampleMethod?: 'nearest' | 'bilinear' | 'linear';
}
const canvas = document.createElement('canvas');

let workerPool: Pool;
function getWorkerPool() {
if (!workerPool) {
Expand All @@ -146,7 +145,7 @@ function getWorkerPool() {

export class TIFFImageryProvider {
ready: boolean;
tilingScheme: TIFFImageryProviderTilingScheme | GeographicTilingScheme;
tilingScheme: TIFFImageryProviderTilingScheme | GeographicTilingScheme | WebMercatorTilingScheme;
rectangle: Rectangle;
tileSize: number;
tileWidth: number;
Expand Down Expand Up @@ -235,16 +234,14 @@ export class TIFFImageryProvider {
rectangleSouthwestInMeters: new Cartesian2(west, south),
...this._proj
})
this.rectangle = this.tilingScheme.rectangle
} else if (prjCode === 3857 || prjCode === 900913) {
this.tilingScheme = new WebMercatorTilingScheme({
rectangleNortheastInMeters: new Cartesian2(east, north),
rectangleSouthwestInMeters: new Cartesian2(west, south),
})
} else if (prjCode === 4326) {
this.rectangle = Rectangle.fromDegrees(...this.bbox)
// 处理跨180度经线的情况
// https://github.com/CesiumGS/cesium/blob/da00d26473f663db180cacd8e662ca4309e09560/packages/engine/Source/Core/TileAvailability.js#L195
if (this.rectangle.east < this.rectangle.west) {
this.rectangle.east += CesiumMath.TWO_PI;
}
this.tilingScheme = new GeographicTilingScheme({
rectangle: this.rectangle,
rectangle: Rectangle.fromDegrees(...this.bbox),
numberOfLevelZeroTilesX: 1,
numberOfLevelZeroTilesY: 1
});
Expand All @@ -253,6 +250,12 @@ export class TIFFImageryProvider {
throw error;
}

this.rectangle = this.tilingScheme.rectangle
// 处理跨180度经线的情况
// https://github.com/CesiumGS/cesium/blob/da00d26473f663db180cacd8e662ca4309e09560/packages/engine/Source/Core/TileAvailability.js#L195
if (this.rectangle.east < this.rectangle.west) {
this.rectangle.east += CesiumMath.TWO_PI;
}
this._imageCount = await source.getImageCount();
this.tileSize = this.tileWidth = tileSize || (this._isTiled ? image.getTileWidth() : image.getWidth()) || 512;
this.tileHeight = tileSize || (this._isTiled ? image.getTileHeight() : image.getHeight()) || 512;
Expand Down Expand Up @@ -438,30 +441,35 @@ export class TIFFImageryProvider {

const width = image.getWidth();
const height = image.getHeight();
const lonlatRect = this.tilingScheme.tileXYToRectangle(x, y, z);

if (lonlatRect.east < lonlatRect.west) {
lonlatRect.east += CesiumMath.TWO_PI;
const tileXNum = this.tilingScheme.getNumberOfXTilesAtLevel(z);
const tileYNum = this.tilingScheme.getNumberOfYTilesAtLevel(z);
const tilePixel = {
xWidth: width / tileXNum,
yWidth: height / tileYNum
}

let targetRect: Rectangle = lonlatRect,
nativeRect: Rectangle = this.rectangle;

if (this.tilingScheme instanceof TIFFImageryProviderTilingScheme) {
targetRect = this.tilingScheme.tileXYToNativeRectangle(x, y, z);
nativeRect = this.tilingScheme.nativeRectangle;
let window = [
Math.round(x * tilePixel.xWidth),
Math.round(y * tilePixel.yWidth),
Math.round((x + 1) * tilePixel.xWidth),
Math.round((y + 1) * tilePixel.yWidth),
];

if (this._proj && this.tilingScheme instanceof TIFFImageryProviderTilingScheme) {
const targetRect = this.tilingScheme.tileXYToNativeRectangle2(x, y, z);
const nativeRect = this.tilingScheme.nativeRectangle;
targetRect.west -= (nativeRect.width / width)
targetRect.east += (nativeRect.width / width)
targetRect.south -= (nativeRect.height / height)
targetRect.north += (nativeRect.height / height)

window = [
~~((targetRect.west - nativeRect.west) / nativeRect.width * width),
~~((nativeRect.north - targetRect.north) / nativeRect.height * height),
~~((targetRect.east - nativeRect.west) / nativeRect.width * width),
~~((nativeRect.north - targetRect.south) / nativeRect.height * height),
]
}

let window = [
~~((targetRect.west - nativeRect.west) / nativeRect.width * width),
~~((nativeRect.north - targetRect.north) / nativeRect.height * height),
~~((targetRect.east - nativeRect.west) / nativeRect.width * width),
~~((nativeRect.north - targetRect.south) / nativeRect.height * height),
]

const options = {
window,
Expand All @@ -470,6 +478,7 @@ export class TIFFImageryProvider {
height: this.tileHeight,
samples: this.readSamples,
resampleMethod: this.options.resampleMethod,
fillValue: this.noData,
interleave: false,
}
let res: TypedArray[];
Expand All @@ -479,10 +488,12 @@ export class TIFFImageryProvider {
} else {
res = await image.readRasters(options) as TypedArray[];
}
if (this._proj.project) {
const sourceBBox = [targetRect.west, targetRect.south, targetRect.east, targetRect.north] as any;
if (this._proj?.project && this.tilingScheme instanceof TIFFImageryProviderTilingScheme) {
const sourceRect = this.tilingScheme.tileXYToNativeRectangle2(x, y, z);
const targetRect = this.tilingScheme.tileXYToRectangle(x, y, z);
const sourceBBox = [sourceRect.west, sourceRect.south, sourceRect.east, sourceRect.north] as any;

const targetBBox = [lonlatRect.west, lonlatRect.south, lonlatRect.east, lonlatRect.north].map(CesiumMath.toDegrees) as any
const targetBBox = [targetRect.west, targetRect.south, targetRect.east, targetRect.north].map(CesiumMath.toDegrees) as any

const result = [];
for (let i = 0; i < res.length; i++) {
Expand All @@ -494,7 +505,7 @@ export class TIFFImageryProvider {
targetHeight: this.tileHeight,
nodata: this.noData,
project: this._proj.project,
bbox: sourceBBox,
sourceBBox,
targetBBox
})
result.push(prjData)
Expand Down Expand Up @@ -609,24 +620,16 @@ export class TIFFImageryProvider {
const width = image.getWidth();
const height = image.getHeight();
let posX: number, posY: number;
if (this._proj?.project) {
const [west, south, east, north] = this.bbox;
const [x, y] = this._proj.project([longitude, latitude].map(CesiumMath.toDegrees));
const xWidth = east - west, yHeight = north - south;
posX = ~~(Math.abs((x - west) / xWidth) * width);
posY = ~~(Math.abs((y - south) / yHeight) * height);
} else {
const { west, south, north, width: lonWidth } = this.rectangle;
let lonGap = longitude - west;
// 处理跨180°经线的情况
if (longitude < west) {
lonGap += CesiumMath.TWO_PI;
}

posX = ~~(Math.abs(lonGap / lonWidth) * width);
posY = ~~(Math.abs((north - latitude) / (north - south)) * height);
const { west, south, north, width: lonWidth } = this.rectangle;
let lonGap = longitude - west;
// 处理跨180°经线的情况
if (longitude < west) {
lonGap += CesiumMath.TWO_PI;
}

posX = ~~(Math.abs(lonGap / lonWidth) * width);
posY = ~~(Math.abs((north - latitude) / (north - south)) * height);

const options = {
window: [posX, posY, posX + 1, posY + 1],
height: 1,
Expand Down
23 changes: 10 additions & 13 deletions packages/TIFFImageryProvider/src/TIFFImageryProviderTilingScheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class TIFFImageryProviderTilingScheme extends WebMercatorTilingScheme {
return Cartographic.fromDegrees(longitude, latitude, height, result);
},
};

const swMeters = new Cartesian3();
options.rectangleSouthwestInMeters.clone(swMeters);
const neMeters = new Cartesian3();
Expand All @@ -57,9 +57,10 @@ class TIFFImageryProviderTilingScheme extends WebMercatorTilingScheme {

// @ts-ignore
this._rectangle = Rectangle.fromCartographicArray([southwest, southeast, northwest, northeast])

}

tileXYToNativeRectangle(
tileXYToNativeRectangle2(
x: number,
y: number,
level: number,
Expand Down Expand Up @@ -88,19 +89,15 @@ class TIFFImageryProviderTilingScheme extends WebMercatorTilingScheme {
y: number,
level: number,
) {
const xTiles = this.getNumberOfXTilesAtLevel(level);
const yTiles = this.getNumberOfYTilesAtLevel(level);
const rect = this.tileXYToNativeRectangle(x, y, level);

const xTileWidth = this.rectangle.width / xTiles;
const west = this.rectangle.west + x * xTileWidth;
const east = this.rectangle.west + (x + 1) * xTileWidth;

const yTileHeight = this.rectangle.height / yTiles;
const north = this.rectangle.north - y * yTileHeight;
const south = this.rectangle.north - (y + 1) * yTileHeight;
const projection = this.projection;
const ws = projection.unproject(new Cartesian3(rect.west, rect.south));
const wn = projection.unproject(new Cartesian3(rect.west, rect.north));
const en = projection.unproject(new Cartesian3(rect.east, rect.north));
const es = projection.unproject(new Cartesian3(rect.east, rect.south));

const rectangle = new Rectangle(west, south, east, north);
return rectangle;
return Rectangle.fromCartographicArray([ws, wn, en, es]);
};
}

Expand Down
6 changes: 3 additions & 3 deletions packages/TIFFImageryProvider/src/helpers/reprojection.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export type ReprojectionOptions = {
project: (pos: number[]) => number[];
bbox: [minX: number, minY: number, maxX: number, maxY: number];
sourceBBox: [minX: number, minY: number, maxX: number, maxY: number];
targetBBox: [minX: number, minY: number, maxX: number, maxY: number];
data: number[];
sourceWidth: number;
Expand All @@ -19,10 +19,10 @@ function inRange(val: number, range: [number, number]) {
}

export function reprojection(options: ReprojectionOptions) {
const { data, bbox, targetBBox, project, sourceWidth, sourceHeight, nodata } = options;
const { data, sourceBBox, targetBBox, project, sourceWidth, sourceHeight, nodata } = options;
const { targetWidth = sourceWidth, targetHeight = sourceHeight } = options;

const [minX, minY, maxX, maxY] = bbox;
const [minX, minY, maxX, maxY] = sourceBBox;

const [minLon, minLat, maxLon, maxLat] = targetBBox;

Expand Down
Loading

0 comments on commit 665de3a

Please sign in to comment.