mirror of https://gitee.com/antv-l7/antv-l7
feat: 栅格瓦片的多波段计算 (#1367)
* fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * feat: 增加多波段瓦片的 operation 操作 * style: lint style * feat: 增加波段指定逻辑 * style: lint style * feat: 优化多波段的请求操作路径代码 * style: lint style * feat: multi raster layer support express operation 1.0 * style: lint style * feat: 增加栅格图层的多波段分析能力 * chore: add raster layer demo * style: lint style * style: lint style * style: lint style * style: lint style * style: lint style * feat: 补充栅格数据表达式计算 * chore: 调整栅格计算方法的位置 * chore: 优化栅格计算代码结构 * chore: 优化数据栅格代码 * style: lint style * style: lint style * style: lint style * feat: band operation handle empty data * feat: 支持彩色多通道栅格 * feat: 数据栅格瓦片支持彩色多通道波段计算 * style: lint style * style: lint style * style: lint style Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>
This commit is contained in:
parent
36dcd9928c
commit
5540d4c610
|
@ -24,7 +24,17 @@ async function getTiffData() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addLayer() {
|
export default () => {
|
||||||
|
useEffect(() => {
|
||||||
|
const scene = new Scene({
|
||||||
|
id: 'map',
|
||||||
|
map: new GaodeMap({
|
||||||
|
center: [121.268, 30.3628],
|
||||||
|
zoom: 3,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
scene.on('loaded', async () => {
|
||||||
const tiffdata = await getTiffData();
|
const tiffdata = await getTiffData();
|
||||||
|
|
||||||
const layer = new RasterLayer({});
|
const layer = new RasterLayer({});
|
||||||
|
@ -55,26 +65,9 @@ async function addLayer() {
|
||||||
positions: [0, 0.2, 0.4, 0.6, 0.8, 1.0],
|
positions: [0, 0.2, 0.4, 0.6, 0.8, 1.0],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return layer;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
const scene = new Scene({
|
|
||||||
id: 'map',
|
|
||||||
map: new GaodeMap({
|
|
||||||
center: [121.268, 30.3628],
|
|
||||||
zoom: 3,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const layer = await addLayer();
|
|
||||||
|
|
||||||
scene.on('loaded', () => {
|
|
||||||
scene.addLayer(layer);
|
scene.addLayer(layer);
|
||||||
});
|
});
|
||||||
})();
|
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
// @ts-ignore
|
||||||
|
import { RasterLayer, Scene } from '@antv/l7';
|
||||||
|
// @ts-ignore
|
||||||
|
import { GaodeMap } from '@antv/l7-maps';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import * as GeoTIFF from 'geotiff';
|
||||||
|
|
||||||
|
async function getTiffData() {
|
||||||
|
const response = await fetch(
|
||||||
|
'https://gw.alipayobjects.com/os/rmsportal/XKgkjjGaAzRyKupCBiYW.dat',
|
||||||
|
);
|
||||||
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
|
const tiff = await GeoTIFF.fromArrayBuffer(arrayBuffer);
|
||||||
|
const image = await tiff.getImage();
|
||||||
|
const width = image.getWidth();
|
||||||
|
const height = image.getHeight();
|
||||||
|
const values = await image.readRasters();
|
||||||
|
return {
|
||||||
|
data: values[0],
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
min: 0,
|
||||||
|
max: 8000,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
useEffect(() => {
|
||||||
|
const scene = new Scene({
|
||||||
|
id: 'map',
|
||||||
|
map: new GaodeMap({
|
||||||
|
center: [121.268, 30.3628],
|
||||||
|
zoom: 3,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
scene.on('loaded', async () => {
|
||||||
|
|
||||||
|
const tiffdata = await getTiffData();
|
||||||
|
|
||||||
|
const layer = new RasterLayer({});
|
||||||
|
layer
|
||||||
|
.source(tiffdata.data, {
|
||||||
|
parser: {
|
||||||
|
type: 'raster',
|
||||||
|
width: tiffdata.width,
|
||||||
|
height: tiffdata.height,
|
||||||
|
min: 0,
|
||||||
|
max: 80,
|
||||||
|
extent: [73.482190241, 3.82501784112, 135.106618732, 57.6300459963],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.style({
|
||||||
|
heightRatio: 100,
|
||||||
|
opacity: 0.8,
|
||||||
|
domain: [0, 2000],
|
||||||
|
rampColors: {
|
||||||
|
colors: [
|
||||||
|
'#FF4818',
|
||||||
|
'#F7B74A',
|
||||||
|
'#FFF598',
|
||||||
|
'#91EABC',
|
||||||
|
'#2EA9A1',
|
||||||
|
'#206C7C',
|
||||||
|
].reverse(),
|
||||||
|
positions: [0, 0.2, 0.4, 0.6, 0.8, 1.0],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
scene.addLayer(layer);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id="map"
|
||||||
|
style={{
|
||||||
|
height: '500px',
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,93 @@
|
||||||
|
// @ts-ignore
|
||||||
|
import { RasterLayer, Scene } from '@antv/l7';
|
||||||
|
// @ts-ignore
|
||||||
|
import { GaodeMap } from '@antv/l7-maps';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import * as GeoTIFF from 'geotiff';
|
||||||
|
|
||||||
|
async function getTiffData() {
|
||||||
|
const response = await fetch(
|
||||||
|
'https://gw.alipayobjects.com/os/rmsportal/XKgkjjGaAzRyKupCBiYW.dat',
|
||||||
|
// 'http://127.0.0.1:3333/p.tif',
|
||||||
|
);
|
||||||
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
|
return arrayBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
useEffect(() => {
|
||||||
|
const scene = new Scene({
|
||||||
|
id: 'map',
|
||||||
|
map: new GaodeMap({
|
||||||
|
center: [121.268, 30.3628],
|
||||||
|
zoom: 3,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
scene.on('loaded', async () => {
|
||||||
|
|
||||||
|
const tiffdata = await getTiffData();
|
||||||
|
|
||||||
|
const layer = new RasterLayer({})
|
||||||
|
layer.source({
|
||||||
|
data: tiffdata,
|
||||||
|
bands: [0]
|
||||||
|
}, {
|
||||||
|
parser: {
|
||||||
|
type: 'raster',
|
||||||
|
// width: tiffdata.width,
|
||||||
|
// height: tiffdata.height,
|
||||||
|
format: async (data, bands) => {
|
||||||
|
|
||||||
|
const tiff = await GeoTIFF.fromArrayBuffer(data);
|
||||||
|
const imageCount = await tiff.getImageCount();
|
||||||
|
console.log('imageCount', imageCount)
|
||||||
|
|
||||||
|
const image = await tiff.getImage();
|
||||||
|
const width = image.getWidth();
|
||||||
|
const height = image.getHeight();
|
||||||
|
const values = await image.readRasters();
|
||||||
|
return { rasterData: values[0], width, height };
|
||||||
|
|
||||||
|
},
|
||||||
|
// operation: (allBands) => {
|
||||||
|
// return allBands[0].rasterData;
|
||||||
|
// },
|
||||||
|
operation: ['+', ['band', 0], 1],
|
||||||
|
min: 0,
|
||||||
|
max: 80,
|
||||||
|
extent: [73.482190241, 3.82501784112, 135.106618732, 57.6300459963],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.style({
|
||||||
|
heightRatio: 100,
|
||||||
|
opacity: 0.8,
|
||||||
|
domain: [0, 2000],
|
||||||
|
rampColors: {
|
||||||
|
colors: [
|
||||||
|
'#FF4818',
|
||||||
|
'#F7B74A',
|
||||||
|
'#FFF598',
|
||||||
|
'#91EABC',
|
||||||
|
'#2EA9A1',
|
||||||
|
'#206C7C',
|
||||||
|
].reverse(),
|
||||||
|
positions: [0, 0.2, 0.4, 0.6, 0.8, 1.0],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
scene.addLayer(layer);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id="map"
|
||||||
|
style={{
|
||||||
|
height: '500px',
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,94 @@
|
||||||
|
// @ts-ignore
|
||||||
|
import { RasterLayer, Scene } from '@antv/l7';
|
||||||
|
// @ts-ignore
|
||||||
|
import { GaodeMap } from '@antv/l7-maps';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import * as GeoTIFF from 'geotiff';
|
||||||
|
|
||||||
|
async function getTiffData(url: string) {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
|
return arrayBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
useEffect(() => {
|
||||||
|
const scene = new Scene({
|
||||||
|
id: 'map',
|
||||||
|
map: new GaodeMap({
|
||||||
|
center: [110, 30.3628],
|
||||||
|
zoom: 3,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
scene.on('loaded', async () => {
|
||||||
|
//
|
||||||
|
const url1 = 'https://gw.alipayobjects.com/os/rmsportal/XKgkjjGaAzRyKupCBiYW.dat';
|
||||||
|
// 全国夜光图
|
||||||
|
const url2 = 'https://gw.alipayobjects.com/zos/antvdemo/assets/light_clip/lightF182013.tiff'
|
||||||
|
const tiffdata = await getTiffData(url1);
|
||||||
|
const tiffdata2 = await getTiffData(url2);
|
||||||
|
// const rasterData = { data: tiffdata }
|
||||||
|
const rasterData = [
|
||||||
|
{ data: tiffdata },
|
||||||
|
{ data: tiffdata2 }
|
||||||
|
];
|
||||||
|
|
||||||
|
const layer = new RasterLayer({})
|
||||||
|
layer.source(rasterData, {
|
||||||
|
parser: {
|
||||||
|
type: 'raster',
|
||||||
|
format: async (data, bands) => {
|
||||||
|
// console.log('bands', bands)
|
||||||
|
const tiff = await GeoTIFF.fromArrayBuffer(data);
|
||||||
|
// const imageCount = await tiff.getImageCount();
|
||||||
|
|
||||||
|
const image = await tiff.getImage();
|
||||||
|
const width = image.getWidth();
|
||||||
|
const height = image.getHeight();
|
||||||
|
const values = await image.readRasters();
|
||||||
|
return { rasterData: values[0], width, height };
|
||||||
|
|
||||||
|
},
|
||||||
|
// operation: (allBands) => {
|
||||||
|
// console.log(allBands)
|
||||||
|
// return allBands[0].rasterData;
|
||||||
|
// },
|
||||||
|
operation: ['+', ['+', ['band', 0], 90], ['*', ['band', 1], 50]],
|
||||||
|
min: 0,
|
||||||
|
max: 80,
|
||||||
|
extent: [73.482190241, 3.82501784112, 135.106618732, 57.6300459963],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.style({
|
||||||
|
heightRatio: 100,
|
||||||
|
opacity: 0.8,
|
||||||
|
domain: [0, 4000],
|
||||||
|
rampColors: {
|
||||||
|
colors: [
|
||||||
|
'#FF4818',
|
||||||
|
'#F7B74A',
|
||||||
|
'#FFF598',
|
||||||
|
'#333',
|
||||||
|
'#222',
|
||||||
|
'#000',
|
||||||
|
].reverse(),
|
||||||
|
positions: [0, 0.2, 0.4, 0.6, 0.8, 1.0],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
scene.addLayer(layer);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id="map"
|
||||||
|
style={{
|
||||||
|
height: '500px',
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,85 @@
|
||||||
|
// @ts-ignore
|
||||||
|
import { RasterLayer, Scene } from '@antv/l7';
|
||||||
|
// @ts-ignore
|
||||||
|
import { GaodeMap } from '@antv/l7-maps';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import * as GeoTIFF from 'geotiff';
|
||||||
|
|
||||||
|
async function getTiffData(url: string) {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
|
return arrayBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
useEffect(() => {
|
||||||
|
const scene = new Scene({
|
||||||
|
id: 'map',
|
||||||
|
map: new GaodeMap({
|
||||||
|
center: [121.268, 30.3628],
|
||||||
|
zoom: 3,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
scene.on('loaded', async () => {
|
||||||
|
const url1 = 'https://gw.alipayobjects.com/os/rmsportal/XKgkjjGaAzRyKupCBiYW.dat';
|
||||||
|
const url2 = 'https://gw.alipayobjects.com/zos/antvdemo/assets/light_clip/lightF182013.tiff'
|
||||||
|
const tiffdata = await getTiffData(url1);
|
||||||
|
const tiffdata2 = await getTiffData(url2);
|
||||||
|
|
||||||
|
const layer = new RasterLayer({})
|
||||||
|
layer.source([
|
||||||
|
{
|
||||||
|
data: tiffdata,
|
||||||
|
bands: [0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: tiffdata2,
|
||||||
|
bands: [0],
|
||||||
|
}
|
||||||
|
], {
|
||||||
|
parser: {
|
||||||
|
type: 'rasterRgb',
|
||||||
|
format: async (data, bands) => {
|
||||||
|
// console.log(bands, )
|
||||||
|
const tiff = await GeoTIFF.fromArrayBuffer(data);
|
||||||
|
const imageCount = await tiff.getImageCount();
|
||||||
|
console.log('imageCount', imageCount, bands)
|
||||||
|
|
||||||
|
const image = await tiff.getImage();
|
||||||
|
const width = image.getWidth();
|
||||||
|
const height = image.getHeight();
|
||||||
|
const values = await image.readRasters();
|
||||||
|
return { rasterData: values[0], width, height };
|
||||||
|
},
|
||||||
|
// operation: (allBands) => {
|
||||||
|
// return allBands[0].rasterData;
|
||||||
|
// },
|
||||||
|
// operation: ['+', ['band', 0], 1],
|
||||||
|
operation: {
|
||||||
|
r: ['*', ['band', 1], 1],
|
||||||
|
g: ['*', ['band', 1], 1],
|
||||||
|
b: ['*', ['band', 1], 1],
|
||||||
|
},
|
||||||
|
extent: [73.482190241, 3.82501784112, 135.106618732, 57.6300459963],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.style({
|
||||||
|
// opacity: 0.8,
|
||||||
|
channelRMax: 100,
|
||||||
|
channelGMax: 100,
|
||||||
|
channelBMax: 100
|
||||||
|
});
|
||||||
|
scene.addLayer(layer);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id="map"
|
||||||
|
style={{
|
||||||
|
height: '500px',
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,118 @@
|
||||||
|
// @ts-ignore
|
||||||
|
import { RasterLayer, Scene } from '@antv/l7';
|
||||||
|
// @ts-ignore
|
||||||
|
import { GaodeMap } from '@antv/l7-maps';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
|
async function getTiffData(url: string) {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
|
return arrayBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
useEffect(() => {
|
||||||
|
const scene = new Scene({
|
||||||
|
id: 'map',
|
||||||
|
map: new GaodeMap({
|
||||||
|
center: [110, 30.3628],
|
||||||
|
zoom: 3,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const canvasSize = 256;
|
||||||
|
canvas.width = canvasSize;
|
||||||
|
canvas.height = canvasSize;
|
||||||
|
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
|
||||||
|
|
||||||
|
scene.on('loaded', async () => {
|
||||||
|
const url1 = 'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*sV6gSYSdpl4AAAAAAAAAAAAAARQnAQ';
|
||||||
|
const tiffdata = await getTiffData(url1);
|
||||||
|
|
||||||
|
// Gray = R*0.299 + G*0.587 + B*0.114
|
||||||
|
const grayExp = ['+',
|
||||||
|
['*', ['band', 0], 0.299],
|
||||||
|
[
|
||||||
|
'+',
|
||||||
|
['*', ['band', 1], 0.587],
|
||||||
|
['*', ['band', 2], 0.114]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
const layer = new RasterLayer({})
|
||||||
|
layer.source([
|
||||||
|
{
|
||||||
|
data: tiffdata,
|
||||||
|
bands: [0],
|
||||||
|
},
|
||||||
|
], {
|
||||||
|
parser: {
|
||||||
|
type: 'rasterRgb',
|
||||||
|
format: async (data, bands) => {
|
||||||
|
const blob: Blob = new Blob([new Uint8Array(data)], {
|
||||||
|
type: 'image/png',
|
||||||
|
});
|
||||||
|
const img = await createImageBitmap(blob);
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, canvasSize, canvasSize);
|
||||||
|
ctx.drawImage(img, 0, 0, canvasSize, canvasSize);
|
||||||
|
const imgData = ctx.getImageData(0, 0, canvasSize, canvasSize).data;
|
||||||
|
const channelR: number[] = [];
|
||||||
|
const channelG: number[] = [];
|
||||||
|
const channelB: number[] = [];
|
||||||
|
for (let i = 0; i < imgData.length; i += 4) {
|
||||||
|
const R = imgData[i];
|
||||||
|
const G = imgData[i + 1];
|
||||||
|
const B = imgData[i + 2];
|
||||||
|
|
||||||
|
channelR.push(R);
|
||||||
|
channelG.push(G);
|
||||||
|
channelB.push(B);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{ rasterData: channelR, width: canvasSize, height: canvasSize },
|
||||||
|
{ rasterData: channelG, width: canvasSize, height: canvasSize },
|
||||||
|
{ rasterData: channelB, width: canvasSize, height: canvasSize }
|
||||||
|
];
|
||||||
|
},
|
||||||
|
// operation: (allBands) => {
|
||||||
|
// return allBands[0].rasterData;
|
||||||
|
// },
|
||||||
|
// operation: ['+', ['band', 0], 1],
|
||||||
|
operation: {
|
||||||
|
r: ['band', 0],
|
||||||
|
g: ['band', 1],
|
||||||
|
b: ['band', 2],
|
||||||
|
// r: ['*', ['band', 0], 1],
|
||||||
|
// g: ['*', ['band', 1], 1],
|
||||||
|
// b: ['*', ['band', 2], 1],
|
||||||
|
// r: grayExp,
|
||||||
|
// g: grayExp,
|
||||||
|
// b: grayExp,
|
||||||
|
},
|
||||||
|
extent: [73.482190241, 3.82501784112, 135.106618732, 57.6300459963],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.style({
|
||||||
|
// opacity: 0.8,
|
||||||
|
// channelRMax: 100,
|
||||||
|
// channelGMax: 100,
|
||||||
|
// channelBMax: 100
|
||||||
|
});
|
||||||
|
scene.addLayer(layer);
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
scene.destroy();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id="map"
|
||||||
|
style={{
|
||||||
|
height: '500px',
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,3 @@
|
||||||
|
### RasterLayer - raster
|
||||||
|
#### 普通 rasterLayer
|
||||||
|
<code src="./demos/rasterData.tsx"></code>
|
|
@ -0,0 +1,4 @@
|
||||||
|
### RasterLayer - rasterFile
|
||||||
|
#### raster file data
|
||||||
|
加载单个栅格文件的二进制流数据,自己提供栅格波段数据的提取方法。
|
||||||
|
<code src="./demos/rasterFile.tsx"></code>
|
|
@ -0,0 +1,4 @@
|
||||||
|
### RasterLayer - rasterFile2
|
||||||
|
#### raster file data2
|
||||||
|
加载多个栅格文件的二进制流数据,自己提供栅格波段数据的提取方法。
|
||||||
|
<code src="./demos/rasterFile2.tsx"></code>
|
|
@ -0,0 +1,3 @@
|
||||||
|
### RasterLayer - rasterRgb
|
||||||
|
#### 普通 rgb rasterLayer
|
||||||
|
<code src="./demos/rasterRgb.tsx"></code>
|
|
@ -0,0 +1,3 @@
|
||||||
|
### RasterLayer - rasterRgb2
|
||||||
|
#### 普通 rgb2 rasterLayer
|
||||||
|
<code src="./demos/rasterRgb2.tsx"></code>
|
|
@ -28,7 +28,8 @@ export default () => {
|
||||||
'https://a.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/{z}/{x}/{y}.vector.pbf?access_token=';
|
'https://a.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/{z}/{x}/{y}.vector.pbf?access_token=';
|
||||||
const token =
|
const token =
|
||||||
// 'pk.eyJ1IjoiMTg5Njk5NDg2MTkiLCJhIjoiY2s5OXVzdHlzMDVneDNscDVjdzVmeXl0dyJ9.81SQ5qaJS0xExYLbDZAGpQ';
|
// 'pk.eyJ1IjoiMTg5Njk5NDg2MTkiLCJhIjoiY2s5OXVzdHlzMDVneDNscDVjdzVmeXl0dyJ9.81SQ5qaJS0xExYLbDZAGpQ';
|
||||||
'pk.eyJ1IjoiMTg5Njk5NDg2MTkiLCJhIjoiY2w3dHk3dnN4MDYzaDNycDkyMDl2bzh6NiJ9.YIrG9kwUpayLj01f6W23Gw';
|
// 'pk.eyJ1IjoiMTg5Njk5NDg2MTkiLCJhIjoiY2w3dHk3dnN4MDYzaDNycDkyMDl2bzh6NiJ9.YIrG9kwUpayLj01f6W23Gw';
|
||||||
|
'pk.eyJ1IjoiMTg5Njk5NDg2MTkiLCJhIjoiY2w4bXNyeHgzMGl0cjNvbXlmeHFjeDBwZCJ9.05W7JfyT6BVkpu12dYL58w';
|
||||||
const source = new Source(url + token, {
|
const source = new Source(url + token, {
|
||||||
parser: {
|
parser: {
|
||||||
type: 'mvt',
|
type: 'mvt',
|
||||||
|
|
|
@ -27,7 +27,8 @@ export default () => {
|
||||||
'https://a.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/{z}/{x}/{y}.vector.pbf?access_token=';
|
'https://a.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/{z}/{x}/{y}.vector.pbf?access_token=';
|
||||||
const token =
|
const token =
|
||||||
// 'pk.eyJ1IjoiMTg5Njk5NDg2MTkiLCJhIjoiY2s5OXVzdHlzMDVneDNscDVjdzVmeXl0dyJ9.81SQ5qaJS0xExYLbDZAGpQ';
|
// 'pk.eyJ1IjoiMTg5Njk5NDg2MTkiLCJhIjoiY2s5OXVzdHlzMDVneDNscDVjdzVmeXl0dyJ9.81SQ5qaJS0xExYLbDZAGpQ';
|
||||||
'pk.eyJ1IjoiMTg5Njk5NDg2MTkiLCJhIjoiY2w3dHk3dnN4MDYzaDNycDkyMDl2bzh6NiJ9.YIrG9kwUpayLj01f6W23Gw';
|
// 'pk.eyJ1IjoiMTg5Njk5NDg2MTkiLCJhIjoiY2w3dHk3dnN4MDYzaDNycDkyMDl2bzh6NiJ9.YIrG9kwUpayLj01f6W23Gw';
|
||||||
|
'pk.eyJ1IjoiMTg5Njk5NDg2MTkiLCJhIjoiY2w4bXNyeHgzMGl0cjNvbXlmeHFjeDBwZCJ9.05W7JfyT6BVkpu12dYL58w';
|
||||||
const source = new Source(url + token, {
|
const source = new Source(url + token, {
|
||||||
parser: {
|
parser: {
|
||||||
type: 'mvt',
|
type: 'mvt',
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
### Raster - HillShade
|
### Raster - HillShade
|
||||||
山体阴影
|
山体阴影
|
||||||
<code src="./rasterData/hillshade.tsx"></code>
|
<code src="./rasterData/hillShading.tsx"></code>
|
||||||
|
|
|
@ -12,7 +12,7 @@ export default () => {
|
||||||
center: [-119.5591, 37.715],
|
center: [-119.5591, 37.715],
|
||||||
zoom: 9,
|
zoom: 9,
|
||||||
style: 'mapbox://styles/mapbox/cjaudgl840gn32rnrepcb9b9g',
|
style: 'mapbox://styles/mapbox/cjaudgl840gn32rnrepcb9b9g',
|
||||||
token: 'pk.eyJ1IjoiMTg5Njk5NDg2MTkiLCJhIjoiY2w3dHk3dnN4MDYzaDNycDkyMDl2bzh6NiJ9.YIrG9kwUpayLj01f6W23Gw'
|
token: 'pk.eyJ1IjoiMTg5Njk5NDg2MTkiLCJhIjoiY2w4bXNyeHgzMGl0cjNvbXlmeHFjeDBwZCJ9.05W7JfyT6BVkpu12dYL58w'
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ export default () => {
|
||||||
layer
|
layer
|
||||||
.source(
|
.source(
|
||||||
// 'https://api.mapbox.com/v4/mapbox.terrain-rgb/{z}/{x}/{y}.pngraw?access_token=pk.eyJ1IjoiMTg5Njk5NDg2MTkiLCJhIjoiY2w3dHk3dnN4MDYzaDNycDkyMDl2bzh6NiJ9.YIrG9kwUpayLj01f6W23Gw',
|
// 'https://api.mapbox.com/v4/mapbox.terrain-rgb/{z}/{x}/{y}.pngraw?access_token=pk.eyJ1IjoiMTg5Njk5NDg2MTkiLCJhIjoiY2w3dHk3dnN4MDYzaDNycDkyMDl2bzh6NiJ9.YIrG9kwUpayLj01f6W23Gw',
|
||||||
'https://api.mapbox.com/raster/v1/mapbox.mapbox-terrain-dem-v1/{z}/{x}/{y}.webp?sku=101xrmKJip7uQ&access_token=pk.eyJ1IjoiMTg5Njk5NDg2MTkiLCJhIjoiY2w3dHk3dnN4MDYzaDNycDkyMDl2bzh6NiJ9.YIrG9kwUpayLj01f6W23Gw',
|
'https://api.mapbox.com/raster/v1/mapbox.mapbox-terrain-dem-v1/{z}/{x}/{y}.webp?sku=101xrmKJip7uQ&access_token=pk.eyJ1IjoiMTg5Njk5NDg2MTkiLCJhIjoiY2w4bXNyeHgzMGl0cjNvbXlmeHFjeDBwZCJ9.05W7JfyT6BVkpu12dYL58w',
|
||||||
// 'https://b.tiles.mapbox.com/v3/aj.sf-dem/12/657/1589.png',
|
// 'https://b.tiles.mapbox.com/v3/aj.sf-dem/12/657/1589.png',
|
||||||
// https://b.tiles.mapbox.com/v3/aj.sf-dem/12/659/1589.png
|
// https://b.tiles.mapbox.com/v3/aj.sf-dem/12/659/1589.png
|
||||||
{
|
{
|
|
@ -1,205 +0,0 @@
|
||||||
//@ts-ignore
|
|
||||||
import { Scene, RasterLayer } from '@antv/l7';
|
|
||||||
//@ts-ignore
|
|
||||||
import { Mapbox } from '@antv/l7-maps';
|
|
||||||
import React, { useEffect } from 'react';
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
useEffect(() => {
|
|
||||||
const scene = new Scene({
|
|
||||||
id: 'map',
|
|
||||||
map: new Mapbox({
|
|
||||||
center: [-119.5591, 37.715],
|
|
||||||
zoom: 9,
|
|
||||||
style: 'mapbox://styles/mapbox/cjaudgl840gn32rnrepcb9b9g',
|
|
||||||
token: 'pk.eyJ1IjoiMTg5Njk5NDg2MTkiLCJhIjoiY2w3dHk3dnN4MDYzaDNycDkyMDl2bzh6NiJ9.YIrG9kwUpayLj01f6W23Gw'
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = 256;
|
|
||||||
canvas.height = 256;
|
|
||||||
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
|
|
||||||
|
|
||||||
scene.on('loaded', () => {
|
|
||||||
const layer = new RasterLayer();
|
|
||||||
layer
|
|
||||||
.source(
|
|
||||||
// 'https://api.mapbox.com/v4/mapbox.terrain-rgb/{z}/{x}/{y}.pngraw?access_token=pk.eyJ1IjoiMTg5Njk5NDg2MTkiLCJhIjoiY2w3dHk3dnN4MDYzaDNycDkyMDl2bzh6NiJ9.YIrG9kwUpayLj01f6W23Gw',
|
|
||||||
'https://api.mapbox.com/raster/v1/mapbox.mapbox-terrain-dem-v1/{z}/{x}/{y}.webp?sku=101xrmKJip7uQ&access_token=pk.eyJ1IjoiMTg5Njk5NDg2MTkiLCJhIjoiY2w3dHk3dnN4MDYzaDNycDkyMDl2bzh6NiJ9.YIrG9kwUpayLj01f6W23Gw',
|
|
||||||
// 'https://b.tiles.mapbox.com/v3/aj.sf-dem/12/657/1589.png',
|
|
||||||
// https://b.tiles.mapbox.com/v3/aj.sf-dem/12/659/1589.png
|
|
||||||
{
|
|
||||||
parser: {
|
|
||||||
type: 'rasterTile',
|
|
||||||
dataType: 'arraybuffer',
|
|
||||||
tileSize: 256,
|
|
||||||
// extent: [-180, -85.051129, 179, 85.051129],
|
|
||||||
format: async (data: any) => {
|
|
||||||
const blob: Blob = new Blob([new Uint8Array(data)], {
|
|
||||||
type: 'image/png',
|
|
||||||
});
|
|
||||||
const img = await createImageBitmap(blob);
|
|
||||||
ctx.clearRect(0, 0, 256, 256);
|
|
||||||
ctx.drawImage(img, 0, 0, 256, 256);
|
|
||||||
|
|
||||||
const shadeOptions = {
|
|
||||||
resolution: 256,
|
|
||||||
sunEl: 0,
|
|
||||||
sunAz: 131,
|
|
||||||
vert: 2
|
|
||||||
}
|
|
||||||
const elevationImage = ctx.getImageData(0, 0, 256, 256);
|
|
||||||
const width = elevationImage.width;
|
|
||||||
const height = elevationImage.height;
|
|
||||||
const elevationData = elevationImage.data;
|
|
||||||
const shadeData = new Uint8ClampedArray(elevationData.length);
|
|
||||||
const dp = shadeOptions.resolution * 2;
|
|
||||||
const maxX = width - 1;
|
|
||||||
const maxY = height - 1;
|
|
||||||
const pixel = [0, 0, 0, 0];
|
|
||||||
const twoPi = 2 * Math.PI;
|
|
||||||
const halfPi = Math.PI / 2;
|
|
||||||
|
|
||||||
const sunEl = (Math.PI * shadeOptions.sunEl) / 180;
|
|
||||||
const sunAz = (Math.PI * shadeOptions.sunAz) / 180;
|
|
||||||
|
|
||||||
const cosSunEl = Math.cos(sunEl);
|
|
||||||
const sinSunEl = Math.sin(sunEl);
|
|
||||||
let pixelX,
|
|
||||||
pixelY,
|
|
||||||
x0,
|
|
||||||
x1,
|
|
||||||
y0,
|
|
||||||
y1,
|
|
||||||
offset,
|
|
||||||
z0,
|
|
||||||
z1,
|
|
||||||
dzdx,
|
|
||||||
dzdy,
|
|
||||||
slope,
|
|
||||||
aspect,
|
|
||||||
cosIncidence,
|
|
||||||
scaled;
|
|
||||||
function calculateElevation(pixel) {
|
|
||||||
// The method used to extract elevations from the DEM.
|
|
||||||
// In this case the format used is
|
|
||||||
// red + green * 2 + blue * 3
|
|
||||||
//
|
|
||||||
// Other frequently used methods include the Mapbox format
|
|
||||||
// (red * 256 * 256 + green * 256 + blue) * 0.1 - 10000
|
|
||||||
// and the Terrarium format
|
|
||||||
// (red * 256 + green + blue / 256) - 32768
|
|
||||||
//
|
|
||||||
// return pixel[0] + pixel[1] * 2 + pixel[2] * 3;
|
|
||||||
|
|
||||||
return -10000 + (pixel[0] * 256 * 256 + pixel[1] * 2 * 256 + pixel[2]) * 0.1;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const arr2: number[] = [];
|
|
||||||
|
|
||||||
for (pixelY = 0; pixelY <= maxY; ++pixelY) {
|
|
||||||
y0 = pixelY === 0 ? 0 : pixelY - 1;
|
|
||||||
y1 = pixelY === maxY ? maxY : pixelY + 1;
|
|
||||||
for (pixelX = 0; pixelX <= maxX; ++pixelX) {
|
|
||||||
x0 = pixelX === 0 ? 0 : pixelX - 1;
|
|
||||||
x1 = pixelX === maxX ? maxX : pixelX + 1;
|
|
||||||
|
|
||||||
// determine elevation for (x0, pixelY)
|
|
||||||
offset = (pixelY * width + x0) * 4;
|
|
||||||
pixel[0] = elevationData[offset];
|
|
||||||
pixel[1] = elevationData[offset + 1];
|
|
||||||
pixel[2] = elevationData[offset + 2];
|
|
||||||
pixel[3] = elevationData[offset + 3];
|
|
||||||
z0 = shadeOptions.vert * calculateElevation(pixel);
|
|
||||||
|
|
||||||
// determine elevation for (x1, pixelY)
|
|
||||||
offset = (pixelY * width + x1) * 4;
|
|
||||||
pixel[0] = elevationData[offset];
|
|
||||||
pixel[1] = elevationData[offset + 1];
|
|
||||||
pixel[2] = elevationData[offset + 2];
|
|
||||||
pixel[3] = elevationData[offset + 3];
|
|
||||||
z1 = shadeOptions.vert * calculateElevation(pixel);
|
|
||||||
|
|
||||||
dzdx = (z1 - z0) / dp;
|
|
||||||
|
|
||||||
// determine elevation for (pixelX, y0)
|
|
||||||
offset = (y0 * width + pixelX) * 4;
|
|
||||||
pixel[0] = elevationData[offset];
|
|
||||||
pixel[1] = elevationData[offset + 1];
|
|
||||||
pixel[2] = elevationData[offset + 2];
|
|
||||||
pixel[3] = elevationData[offset + 3];
|
|
||||||
z0 = shadeOptions.vert * calculateElevation(pixel);
|
|
||||||
|
|
||||||
// determine elevation for (pixelX, y1)
|
|
||||||
offset = (y1 * width + pixelX) * 4;
|
|
||||||
pixel[0] = elevationData[offset];
|
|
||||||
pixel[1] = elevationData[offset + 1];
|
|
||||||
pixel[2] = elevationData[offset + 2];
|
|
||||||
pixel[3] = elevationData[offset + 3];
|
|
||||||
z1 = shadeOptions.vert * calculateElevation(pixel);
|
|
||||||
|
|
||||||
dzdy = (z1 - z0) / dp;
|
|
||||||
|
|
||||||
slope = Math.atan(Math.sqrt(dzdx * dzdx + dzdy * dzdy));
|
|
||||||
|
|
||||||
aspect = Math.atan2(dzdy, -dzdx);
|
|
||||||
if (aspect < 0) {
|
|
||||||
aspect = halfPi - aspect;
|
|
||||||
} else if (aspect > halfPi) {
|
|
||||||
aspect = twoPi - aspect + halfPi;
|
|
||||||
} else {
|
|
||||||
aspect = halfPi - aspect;
|
|
||||||
}
|
|
||||||
|
|
||||||
cosIncidence =
|
|
||||||
sinSunEl * Math.cos(slope) +
|
|
||||||
cosSunEl * Math.sin(slope) * Math.cos(sunAz - aspect);
|
|
||||||
|
|
||||||
offset = (pixelY * width + pixelX) * 4;
|
|
||||||
scaled = 255 * cosIncidence;
|
|
||||||
shadeData[offset + 1] = scaled;
|
|
||||||
shadeData[offset + 2] = scaled;
|
|
||||||
shadeData[offset + 3] = elevationData[offset + 3];
|
|
||||||
|
|
||||||
const r = shadeData[offset]
|
|
||||||
const g = shadeData[offset + 1]
|
|
||||||
const b = shadeData[offset + 2]
|
|
||||||
// (0.30*R)+(0.59*G)+(0.11*B)
|
|
||||||
arr2.push( 0.30 * r + 0.59 * g + 0.11 * b );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {rasterData: arr2, width: width, height: height};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.style({
|
|
||||||
opacity: 0.5,
|
|
||||||
domain: [0, 255],
|
|
||||||
rampColors: {
|
|
||||||
colors: [
|
|
||||||
'#fff',
|
|
||||||
'#000',
|
|
||||||
],
|
|
||||||
positions: [0, 1.0],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
scene.addLayer(layer)
|
|
||||||
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
id="map"
|
|
||||||
style={{
|
|
||||||
height: '500px',
|
|
||||||
position: 'relative',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -25,8 +25,7 @@ export default () => {
|
||||||
const layer = new RasterLayer();
|
const layer = new RasterLayer();
|
||||||
layer
|
layer
|
||||||
.source(
|
.source(
|
||||||
'https://api.mapbox.com/v4/mapbox.terrain-rgb/{z}/{x}/{y}.pngraw?access_token=pk.eyJ1IjoiMTg5Njk5NDg2MTkiLCJhIjoiY2w3dHk3dnN4MDYzaDNycDkyMDl2bzh6NiJ9.YIrG9kwUpayLj01f6W23Gw',
|
'http://webst0{1-4}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
|
||||||
|
|
||||||
{
|
{
|
||||||
parser: {
|
parser: {
|
||||||
type: 'rasterTile',
|
type: 'rasterTile',
|
||||||
|
@ -46,10 +45,7 @@ export default () => {
|
||||||
const arr: number[] = [];
|
const arr: number[] = [];
|
||||||
for (let i = 0; i < imgData.length; i += 4) {
|
for (let i = 0; i < imgData.length; i += 4) {
|
||||||
const R = imgData[i];
|
const R = imgData[i];
|
||||||
const G = imgData[i + 1];
|
arr.push(R);
|
||||||
const B = imgData[i + 2];
|
|
||||||
const d = -10000 + (R * 256 * 256 + G * 256 + B) * 0.1;
|
|
||||||
arr.push(d);
|
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
rasterData: arr,
|
rasterData: arr,
|
||||||
|
@ -61,7 +57,7 @@ export default () => {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.style({
|
.style({
|
||||||
domain: [0, 1014],
|
domain: [0, 255],
|
||||||
clampLow: true,
|
clampLow: true,
|
||||||
rampColors: {
|
rampColors: {
|
||||||
colors: [
|
colors: [
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
//@ts-ignore
|
||||||
|
import { Scene, RasterLayer } from '@antv/l7';
|
||||||
|
//@ts-ignore
|
||||||
|
import { Map } from '@antv/l7-maps';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
useEffect(() => {
|
||||||
|
const scene = new Scene({
|
||||||
|
id: 'map2',
|
||||||
|
map: new Map({
|
||||||
|
center: [105.732421875, 32.24997445586331],
|
||||||
|
pitch: 0,
|
||||||
|
style: 'dark',
|
||||||
|
zoom: 2,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = 256;
|
||||||
|
canvas.height = 256;
|
||||||
|
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
|
||||||
|
|
||||||
|
scene.on('loaded', () => {
|
||||||
|
const layer = new RasterLayer();
|
||||||
|
layer
|
||||||
|
.source(
|
||||||
|
'http://webst0{1-4}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
|
||||||
|
{
|
||||||
|
parser: {
|
||||||
|
type: 'rasterTile',
|
||||||
|
// dataType: 'arraybuffer',
|
||||||
|
dataType: 'rgb',
|
||||||
|
tileSize: 256,
|
||||||
|
zoomOffset: 0,
|
||||||
|
extent: [-180, -85.051129, 179, 85.051129],
|
||||||
|
minZoom: 0,
|
||||||
|
format: async (data: any, bands) => {
|
||||||
|
// console.log(bands)
|
||||||
|
const blob: Blob = new Blob([new Uint8Array(data)], {
|
||||||
|
type: 'image/png',
|
||||||
|
});
|
||||||
|
const img = await createImageBitmap(blob);
|
||||||
|
ctx.clearRect(0, 0, 256, 256);
|
||||||
|
ctx.drawImage(img, 0, 0, 256, 256);
|
||||||
|
const imgData = ctx.getImageData(0, 0, 256, 256).data;
|
||||||
|
const channelR: number[] = [];
|
||||||
|
const channelG: number[] = [];
|
||||||
|
const channelB: number[] = [];
|
||||||
|
for (let i = 0; i < imgData.length; i += 4) {
|
||||||
|
const R = imgData[i];
|
||||||
|
const G = imgData[i + 1];
|
||||||
|
const B = imgData[i + 2];
|
||||||
|
channelR.push(R);
|
||||||
|
channelG.push(G);
|
||||||
|
channelB.push(B);
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{ rasterData: channelR, width: 256, height: 256 },
|
||||||
|
{ rasterData: channelG, width: 256, height: 256 },
|
||||||
|
{ rasterData: channelB, width: 256, height: 256 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// operation: ['band', 0]
|
||||||
|
// operation: (allBands) => {
|
||||||
|
// // console.log(allBands)
|
||||||
|
// return allBands[0].rasterData
|
||||||
|
// }
|
||||||
|
operation: {
|
||||||
|
r: ['band', 0],
|
||||||
|
g: ['band', 1],
|
||||||
|
b: ['band', 2],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.style({
|
||||||
|
});
|
||||||
|
|
||||||
|
scene.addLayer(layer)
|
||||||
|
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id="map2"
|
||||||
|
style={{
|
||||||
|
height: '500px',
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,4 +1,6 @@
|
||||||
|
// @ts-ignore
|
||||||
import { RasterLayer, Scene, Source } from '@antv/l7';
|
import { RasterLayer, Scene, Source } from '@antv/l7';
|
||||||
|
// @ts-ignore
|
||||||
import { GaodeMap } from '@antv/l7-maps';
|
import { GaodeMap } from '@antv/l7-maps';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import * as GeoTIFF from 'geotiff';
|
import * as GeoTIFF from 'geotiff';
|
||||||
|
@ -80,9 +82,19 @@ export default () => {
|
||||||
maskfence: maskData
|
maskfence: maskData
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// const urls = [
|
||||||
|
// 'https://ganos.oss-cn-hangzhou.aliyuncs.com/m2/l7/tiff_jx/{z}/{x}/{y}.tiff',
|
||||||
|
// 'https://ganos.oss-cn-hangzhou.aliyuncs.com/m2/l7/tiff_jx/{z}/{x}/{y}.tiff',
|
||||||
|
// ]
|
||||||
const urls = [
|
const urls = [
|
||||||
'https://ganos.oss-cn-hangzhou.aliyuncs.com/m2/l7/tiff_jx/{z}/{x}/{y}.tiff',
|
{
|
||||||
'https://ganos.oss-cn-hangzhou.aliyuncs.com/m2/l7/tiff_jx/{z}/{x}/{y}.tiff',
|
url: 'https://ganos.oss-cn-hangzhou.aliyuncs.com/m2/l7/tiff_jx/{z}/{x}/{y}.tiff',
|
||||||
|
bands: [0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: 'https://ganos.oss-cn-hangzhou.aliyuncs.com/m2/l7/tiff_jx/{z}/{x}/{y}.tiff'
|
||||||
|
// default bands: [0]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
const tileSource = new Source(urls,
|
const tileSource = new Source(urls,
|
||||||
{
|
{
|
||||||
|
@ -91,29 +103,38 @@ export default () => {
|
||||||
dataType: 'arraybuffer',
|
dataType: 'arraybuffer',
|
||||||
tileSize: 256,
|
tileSize: 256,
|
||||||
maxZoom: 13.1,
|
maxZoom: 13.1,
|
||||||
format: async data => {
|
format: async (data, bands) => {
|
||||||
|
// current raster file bands
|
||||||
|
console.log('bands', bands)
|
||||||
|
|
||||||
const tiff = await GeoTIFF.fromArrayBuffer(data[0]);
|
const tiff = await GeoTIFF.fromArrayBuffer(data);
|
||||||
const image = await tiff.getImage();
|
const image = await tiff.getImage(bands[0]);
|
||||||
const width = image.getWidth();
|
const width = image.getWidth();
|
||||||
const height = image.getHeight();
|
const height = image.getHeight();
|
||||||
const values = await image.readRasters();
|
const values = await image.readRasters();
|
||||||
const rasterData = values[0];
|
const rasterData = values[0];
|
||||||
|
|
||||||
|
// return bandData | bandData[]
|
||||||
|
// return [{ rasterData, width, height }];
|
||||||
|
return { rasterData, width, height };
|
||||||
|
},
|
||||||
|
// bands: [0, 1]
|
||||||
|
operation: ['*', ['band', 0], 2],
|
||||||
|
// operation: (allBands) => {
|
||||||
|
// const rasterData: number[] = [];
|
||||||
|
// const { width, height } = allBands[0];
|
||||||
|
// const length = width * height;
|
||||||
|
// const band0 = allBands[0];
|
||||||
|
// const band1 = allBands[1];
|
||||||
|
|
||||||
const tiff2 = await GeoTIFF.fromArrayBuffer(data[1]);
|
// for(let i = 0;i < length; i++) {
|
||||||
const image2 = await tiff2.getImage();
|
// const v1 = band0.rasterData[i] | 0;
|
||||||
const values2 = await image2.readRasters();
|
// const v2 = band1.rasterData[i] | 0;
|
||||||
const rasterData2 = values2[0];
|
// rasterData.push(v1 * (1/2) + v2 * (1/2))
|
||||||
|
// }
|
||||||
const r = rasterData.map((d, i) => {
|
// return rasterData;
|
||||||
|
// // return bands[0].rasterData;
|
||||||
const d2 = rasterData2[i]
|
// }
|
||||||
return d/2 + d2/3
|
|
||||||
})
|
|
||||||
|
|
||||||
return { rasterData: r, width, height };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
### Raster - RasterData RGB
|
||||||
|
彩色数据栅格(多通道)
|
||||||
|
|
||||||
|
#### 加载 image
|
||||||
|
<code src="./rasterData/loadImageRGB.tsx"></code>
|
|
@ -579,7 +579,7 @@ export default class BaseLayer<ChildLayerStyleOptions = {}>
|
||||||
}
|
}
|
||||||
|
|
||||||
public source(data: any, options?: ISourceCFG): ILayer {
|
public source(data: any, options?: ISourceCFG): ILayer {
|
||||||
if (data?.data) {
|
if (data?.type === 'source') {
|
||||||
// 判断是否为source
|
// 判断是否为source
|
||||||
this.setSource(data);
|
this.setSource(data);
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -279,4 +279,8 @@ export interface IRasterLayerStyleOptions extends IBaseLayerStyleOptions {
|
||||||
clampHigh: boolean;
|
clampHigh: boolean;
|
||||||
rampColors: IColorRamp;
|
rampColors: IColorRamp;
|
||||||
rampColorsData?: ImageData | IImagedata;
|
rampColorsData?: ImageData | IImagedata;
|
||||||
|
|
||||||
|
channelRMax?: number;
|
||||||
|
channelGMax?: number;
|
||||||
|
channelBMax?: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ export default class RaterLayer extends BaseLayer<IRasterLayerStyleOptions> {
|
||||||
const type = this.getModelType();
|
const type = this.getModelType();
|
||||||
const defaultConfig = {
|
const defaultConfig = {
|
||||||
raster: {},
|
raster: {},
|
||||||
|
rasterRgb: {},
|
||||||
raster3d: {},
|
raster3d: {},
|
||||||
rasterTile: {},
|
rasterTile: {},
|
||||||
};
|
};
|
||||||
|
@ -42,6 +43,8 @@ export default class RaterLayer extends BaseLayer<IRasterLayerStyleOptions> {
|
||||||
switch (parserType) {
|
switch (parserType) {
|
||||||
case 'raster':
|
case 'raster':
|
||||||
return 'raster';
|
return 'raster';
|
||||||
|
case 'rasterRgb':
|
||||||
|
return 'rasterRgb';
|
||||||
case 'rasterTile':
|
case 'rasterTile':
|
||||||
return 'rasterTile';
|
return 'rasterTile';
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import RasterTileModel from '../../tile/models/tileModel';
|
import RasterTileModel from '../../tile/models/tileModel';
|
||||||
import RasterModel from './raster';
|
import RasterModel from './raster';
|
||||||
export type RasterModelType = 'raster' | 'raster3d' | 'rasterTile';
|
import RasterRgbModel from './rasterRgb';
|
||||||
|
export type RasterModelType = 'raster' | 'raster3d' | 'rasterTile' | 'rasterRgb';
|
||||||
|
|
||||||
const RasterModels: { [key in RasterModelType]: any } = {
|
const RasterModels: { [key in RasterModelType]: any } = {
|
||||||
raster: RasterModel,
|
raster: RasterModel,
|
||||||
|
rasterRgb: RasterRgbModel,
|
||||||
raster3d: RasterModel,
|
raster3d: RasterModel,
|
||||||
rasterTile: RasterTileModel,
|
rasterTile: RasterTileModel,
|
||||||
};
|
};
|
||||||
|
|
|
@ -41,7 +41,26 @@ export default class RasterModel extends BaseModel {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public initModels(callbackModel: (models: IModel[]) => void) {
|
private async getRasterData(parserDataItem: any) {
|
||||||
|
if(Array.isArray(parserDataItem.data)) {
|
||||||
|
// 直接传入波段数据
|
||||||
|
return {
|
||||||
|
data: parserDataItem.data,
|
||||||
|
width: parserDataItem.width,
|
||||||
|
height: parserDataItem.height,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 多波段形式、需要进行处理
|
||||||
|
const { rasterData, width, height } = await parserDataItem.data;
|
||||||
|
return {
|
||||||
|
data: Array.from(rasterData),
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async initModels(callbackModel: (models: IModel[]) => void) {
|
||||||
const {
|
const {
|
||||||
mask = false,
|
mask = false,
|
||||||
maskInside = true,
|
maskInside = true,
|
||||||
|
@ -51,10 +70,13 @@ export default class RasterModel extends BaseModel {
|
||||||
const source = this.layer.getSource();
|
const source = this.layer.getSource();
|
||||||
const { createTexture2D } = this.rendererService;
|
const { createTexture2D } = this.rendererService;
|
||||||
const parserDataItem = source.data.dataArray[0];
|
const parserDataItem = source.data.dataArray[0];
|
||||||
|
|
||||||
|
const {data, width, height} = await this.getRasterData(parserDataItem);
|
||||||
|
|
||||||
this.texture = createTexture2D({
|
this.texture = createTexture2D({
|
||||||
data: parserDataItem.data,
|
data,
|
||||||
width: parserDataItem.width,
|
width,
|
||||||
height: parserDataItem.height,
|
height,
|
||||||
format: gl.LUMINANCE,
|
format: gl.LUMINANCE,
|
||||||
type: gl.FLOAT,
|
type: gl.FLOAT,
|
||||||
// aniso: 4,
|
// aniso: 4,
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
import {
|
||||||
|
AttributeType,
|
||||||
|
gl,
|
||||||
|
IEncodeFeature,
|
||||||
|
IModel,
|
||||||
|
ITexture2D,
|
||||||
|
} from '@antv/l7-core';
|
||||||
|
import { getMask } from '@antv/l7-utils';
|
||||||
|
import BaseModel from '../../core/BaseModel';
|
||||||
|
import { IRasterLayerStyleOptions } from '../../core/interface';
|
||||||
|
import { RasterImageTriangulation } from '../../core/triangulation';
|
||||||
|
import rasterFrag from '../shaders/raster_rgb_frag.glsl';
|
||||||
|
import rasterVert from '../shaders/raster_2d_vert.glsl';
|
||||||
|
export default class RasterModel extends BaseModel {
|
||||||
|
protected texture: ITexture2D;
|
||||||
|
protected channelRMax: number = 256;
|
||||||
|
protected channelGMax: number = 256;
|
||||||
|
protected channelBMax: number = 256;
|
||||||
|
|
||||||
|
public getUninforms() {
|
||||||
|
const {
|
||||||
|
opacity = 1,
|
||||||
|
channelRMax,
|
||||||
|
channelGMax,
|
||||||
|
channelBMax
|
||||||
|
} = this.layer.getLayerConfig() as IRasterLayerStyleOptions;
|
||||||
|
return {
|
||||||
|
u_opacity: opacity || 1,
|
||||||
|
u_texture: this.texture,
|
||||||
|
u_channelRMax: channelRMax !== undefined ? channelRMax : this.channelRMax,
|
||||||
|
u_channelGMax: channelGMax !== undefined ? channelGMax : this.channelGMax,
|
||||||
|
u_channelBMax: channelBMax !== undefined ? channelBMax : this.channelBMax,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getRasterData(parserDataItem: any) {
|
||||||
|
if(Array.isArray(parserDataItem.data)) {
|
||||||
|
// 直接传入波段数据
|
||||||
|
return {
|
||||||
|
data: parserDataItem.data,
|
||||||
|
width: parserDataItem.width,
|
||||||
|
height: parserDataItem.height,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 多波段形式、需要进行处理
|
||||||
|
// 支持彩色栅格(多通道)
|
||||||
|
const { rasterData, width, height, channelR, channelG, channelB } = await parserDataItem.data;
|
||||||
|
this.channelRMax = channelR;
|
||||||
|
this.channelGMax = channelG;
|
||||||
|
this.channelBMax = channelB;
|
||||||
|
return {
|
||||||
|
data: Array.from(rasterData),
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async initModels(callbackModel: (models: IModel[]) => void) {
|
||||||
|
const {
|
||||||
|
mask = false,
|
||||||
|
maskInside = true,
|
||||||
|
} = this.layer.getLayerConfig() as IRasterLayerStyleOptions;
|
||||||
|
const source = this.layer.getSource();
|
||||||
|
const { createTexture2D } = this.rendererService;
|
||||||
|
const parserDataItem = source.data.dataArray[0];
|
||||||
|
|
||||||
|
const {data, width, height} = await this.getRasterData(parserDataItem);
|
||||||
|
this.texture = createTexture2D({
|
||||||
|
// @ts-ignore
|
||||||
|
data,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
format: gl.RGB,
|
||||||
|
type: gl.FLOAT,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.layer
|
||||||
|
.buildLayerModel({
|
||||||
|
moduleName: 'rasterImageDataRGBA',
|
||||||
|
vertexShader: rasterVert,
|
||||||
|
fragmentShader: rasterFrag,
|
||||||
|
triangulation: RasterImageTriangulation,
|
||||||
|
primitive: gl.TRIANGLES,
|
||||||
|
depth: { enable: false },
|
||||||
|
stencil: getMask(mask, maskInside),
|
||||||
|
pick: false,
|
||||||
|
})
|
||||||
|
.then((model) => {
|
||||||
|
callbackModel([model]);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.warn(err);
|
||||||
|
callbackModel([]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public buildModels(callbackModel: (models: IModel[]) => void) {
|
||||||
|
this.initModels(callbackModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public clearModels(): void {
|
||||||
|
this.texture?.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected registerBuiltinAttributes() {
|
||||||
|
// point layer size;
|
||||||
|
this.styleAttributeService.registerStyleAttribute({
|
||||||
|
name: 'uv',
|
||||||
|
type: AttributeType.Attribute,
|
||||||
|
descriptor: {
|
||||||
|
name: 'a_Uv',
|
||||||
|
buffer: {
|
||||||
|
// give the WebGL driver a hint that this buffer may change
|
||||||
|
usage: gl.DYNAMIC_DRAW,
|
||||||
|
data: [],
|
||||||
|
type: gl.FLOAT,
|
||||||
|
},
|
||||||
|
size: 2,
|
||||||
|
update: (
|
||||||
|
feature: IEncodeFeature,
|
||||||
|
featureIdx: number,
|
||||||
|
vertex: number[],
|
||||||
|
) => {
|
||||||
|
return [vertex[3], vertex[4]];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
precision mediump float;
|
||||||
|
uniform float u_opacity: 1.0;
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_channelRMax: 256.;
|
||||||
|
uniform float u_channelGMax: 256.;
|
||||||
|
uniform float u_channelBMax: 256.;
|
||||||
|
varying vec2 v_texCoord;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec3 rgb = texture2D(u_texture,vec2(v_texCoord.x,v_texCoord.y)).rgb;
|
||||||
|
gl_FragColor = vec4(rgb.r/u_channelRMax, rgb.g/u_channelGMax, rgb.b/u_channelBMax, u_opacity);
|
||||||
|
}
|
|
@ -22,7 +22,6 @@ export default class RasterTiffTile extends TileFactory {
|
||||||
clampLow,
|
clampLow,
|
||||||
mask,
|
mask,
|
||||||
} = initOptions;
|
} = initOptions;
|
||||||
|
|
||||||
const rasterData = tile.data;
|
const rasterData = tile.data;
|
||||||
if (!rasterData.data) {
|
if (!rasterData.data) {
|
||||||
console.warn('raster data not exist!');
|
console.warn('raster data not exist!');
|
||||||
|
@ -31,13 +30,15 @@ export default class RasterTiffTile extends TileFactory {
|
||||||
layerIDList: [],
|
layerIDList: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
const dataType = this.parentLayer?.getSource()?.parser?.dataType;
|
||||||
const layer = new RasterDataLayer({
|
const layer = new RasterDataLayer({
|
||||||
visible: tile.isVisible,
|
visible: tile.isVisible,
|
||||||
mask,
|
mask,
|
||||||
})
|
})
|
||||||
.source(rasterData.data, {
|
.source(rasterData.data, {
|
||||||
parser: {
|
parser: {
|
||||||
type: 'raster',
|
// 数据栅格分为单通道栅格和多通道彩色栅格
|
||||||
|
type: dataType === 'rgb' ? 'rasterRgb': 'raster',
|
||||||
width: rasterData.width,
|
width: rasterData.width,
|
||||||
height: rasterData.height,
|
height: rasterData.height,
|
||||||
extent: tile.bboxPolygon.bbox,
|
extent: tile.bboxPolygon.bbox,
|
||||||
|
@ -46,6 +47,7 @@ export default class RasterTiffTile extends TileFactory {
|
||||||
.style({
|
.style({
|
||||||
colorTexture,
|
colorTexture,
|
||||||
opacity,
|
opacity,
|
||||||
|
// TODO: 目前从 domain 从父瓦片图层的 style 进行配置,后续考虑从每个时机请求的栅格文件中进行配置
|
||||||
domain,
|
domain,
|
||||||
clampHigh,
|
clampHigh,
|
||||||
clampLow,
|
clampLow,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import BaseLayer from '../../core/BaseLayer';
|
import BaseLayer from '../../core/BaseLayer';
|
||||||
import { IRasterLayerStyleOptions } from '../../core/interface';
|
import { IRasterLayerStyleOptions } from '../../core/interface';
|
||||||
import RasterModel from '../../raster/models/rasterTile';
|
import RasterModel from '../../raster/models/rasterTile';
|
||||||
|
import RasterRgbModel from '../../raster/models/rasterRgb';
|
||||||
|
|
||||||
export default class RasterTiffLayer extends BaseLayer<
|
export default class RasterTiffLayer extends BaseLayer<
|
||||||
Partial<IRasterLayerStyleOptions>
|
Partial<IRasterLayerStyleOptions>
|
||||||
|
@ -21,8 +22,12 @@ export default class RasterTiffLayer extends BaseLayer<
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getModelType() {
|
protected getModelType() {
|
||||||
|
if(this.layerSource.parser.type === 'rasterRgb') {
|
||||||
|
return RasterRgbModel;
|
||||||
|
} else {
|
||||||
return RasterModel;
|
return RasterModel;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
protected getConfigSchema() {
|
protected getConfigSchema() {
|
||||||
return {
|
return {
|
||||||
properties: {
|
properties: {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import json from './parser/json';
|
||||||
import mapboxVectorTile from './parser/mvt';
|
import mapboxVectorTile from './parser/mvt';
|
||||||
import geojsonVTTile from './parser/geojsonvt';
|
import geojsonVTTile from './parser/geojsonvt';
|
||||||
import raster from './parser/raster';
|
import raster from './parser/raster';
|
||||||
|
import rasterRgb from './parser/rasterRgb';
|
||||||
import rasterTile, { rasterDataTypes } from './parser/raster-tile';
|
import rasterTile, { rasterDataTypes } from './parser/raster-tile';
|
||||||
import testTile from './parser/testTile';
|
import testTile from './parser/testTile';
|
||||||
import Source from './source';
|
import Source from './source';
|
||||||
|
@ -25,6 +26,7 @@ registerParser('image', image);
|
||||||
registerParser('csv', csv);
|
registerParser('csv', csv);
|
||||||
registerParser('json', json);
|
registerParser('json', json);
|
||||||
registerParser('raster', raster);
|
registerParser('raster', raster);
|
||||||
|
registerParser('rasterRgb', rasterRgb);
|
||||||
registerTransform('cluster', cluster);
|
registerTransform('cluster', cluster);
|
||||||
registerTransform('filter', filter);
|
registerTransform('filter', filter);
|
||||||
registerTransform('join', join);
|
registerTransform('join', join);
|
||||||
|
|
|
@ -26,6 +26,7 @@ export interface IParserData {
|
||||||
export enum RasterTileType {
|
export enum RasterTileType {
|
||||||
IMAGE = 'image',
|
IMAGE = 'image',
|
||||||
ARRAYBUFFER = 'arraybuffer',
|
ARRAYBUFFER = 'arraybuffer',
|
||||||
|
RGB = 'rgb',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IGeojsonvtOptions {
|
export interface IGeojsonvtOptions {
|
||||||
|
@ -58,9 +59,41 @@ export interface ITileParserCFG {
|
||||||
geojsonvtOptions?: IGeojsonvtOptions;
|
geojsonvtOptions?: IGeojsonvtOptions;
|
||||||
|
|
||||||
format?: any;
|
format?: any;
|
||||||
|
operation?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IJsonItem {
|
export interface IJsonItem {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
export type IJsonData = IJsonItem[];
|
export type IJsonData = IJsonItem[];
|
||||||
|
|
||||||
|
export interface IRasterData {
|
||||||
|
rasterData: HTMLImageElement | Uint8Array| ImageBitmap | null | undefined;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
export type IRasterFormat = (imageData: ArrayBuffer, bands: number[], channels?: string[]) => Promise<IRasterData|IRasterData[]>;
|
||||||
|
export interface IRasterFileData {
|
||||||
|
data: ArrayBuffer;
|
||||||
|
bands: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IRgbOperation = {
|
||||||
|
r?: any[];
|
||||||
|
g?: any[]
|
||||||
|
b?: any[]
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IBandsOperation = ((bands: IRasterData[]) => Uint8Array | Array<number>) | any[] | IRgbOperation;
|
||||||
|
|
||||||
|
export type IRasterLayerData = number[] | IRasterFileData | IRasterFileData[];
|
||||||
|
|
||||||
|
export interface IRasterCfg {
|
||||||
|
format?: IRasterFormat;
|
||||||
|
operation?: IBandsOperation;
|
||||||
|
extent: [number, number, number, number];
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
max: number;
|
||||||
|
min: number;
|
||||||
|
}
|
|
@ -1,6 +1,15 @@
|
||||||
import { Tile, TileLoadParams, TilesetManagerOptions } from '@antv/l7-utils';
|
import {
|
||||||
|
Tile,
|
||||||
|
TileLoadParams,
|
||||||
|
TilesetManagerOptions,
|
||||||
|
ITileBand,
|
||||||
|
} from '@antv/l7-utils';
|
||||||
import { IParserData, ITileParserCFG, RasterTileType } from '../interface';
|
import { IParserData, ITileParserCFG, RasterTileType } from '../interface';
|
||||||
import { defaultFormat, getTileBuffer, getTileImage } from '../utils/getTile';
|
import {
|
||||||
|
defaultFormat,
|
||||||
|
getTileBuffer,
|
||||||
|
getTileImage,
|
||||||
|
} from '../utils/tile/getRasterTile';
|
||||||
|
|
||||||
const DEFAULT_CONFIG: Partial<TilesetManagerOptions> = {
|
const DEFAULT_CONFIG: Partial<TilesetManagerOptions> = {
|
||||||
tileSize: 256,
|
tileSize: 256,
|
||||||
|
@ -9,7 +18,13 @@ const DEFAULT_CONFIG: Partial<TilesetManagerOptions> = {
|
||||||
zoomOffset: 0,
|
zoomOffset: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const rasterDataTypes = [RasterTileType.ARRAYBUFFER];
|
export const rasterDataTypes = [RasterTileType.ARRAYBUFFER, RasterTileType.RGB];
|
||||||
|
|
||||||
|
function isUrlError(url: string | string[] | ITileBand[]) {
|
||||||
|
if (Array.isArray(url) && url.length === 0) return true;
|
||||||
|
if (!Array.isArray(url) && typeof url !== 'string') return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param data
|
* @param data
|
||||||
|
@ -17,23 +32,29 @@ export const rasterDataTypes = [RasterTileType.ARRAYBUFFER];
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export default function rasterTile(
|
export default function rasterTile(
|
||||||
data: string | string[],
|
data: string | string[] | ITileBand[],
|
||||||
cfg?: ITileParserCFG,
|
cfg?: ITileParserCFG,
|
||||||
): IParserData {
|
): IParserData {
|
||||||
const tileDataType: RasterTileType = cfg?.dataType || RasterTileType.IMAGE;
|
if (isUrlError(data)) throw new Error('tile server url is error');
|
||||||
|
|
||||||
|
let tileDataType: RasterTileType = cfg?.dataType || RasterTileType.IMAGE;
|
||||||
|
// Tip: RasterTileType.RGB 是彩色多通道的数据纹理,同样走数据纹理的请求
|
||||||
|
if (tileDataType === RasterTileType.RGB)
|
||||||
|
tileDataType = RasterTileType.ARRAYBUFFER;
|
||||||
const getTileData = (tileParams: TileLoadParams, tile: Tile) => {
|
const getTileData = (tileParams: TileLoadParams, tile: Tile) => {
|
||||||
switch (tileDataType) {
|
switch (tileDataType) {
|
||||||
case RasterTileType.IMAGE:
|
case RasterTileType.IMAGE:
|
||||||
return getTileImage(data, tileParams, tile);
|
return getTileImage(data as string | string[], tileParams, tile);
|
||||||
case RasterTileType.ARRAYBUFFER:
|
case RasterTileType.ARRAYBUFFER:
|
||||||
return getTileBuffer(
|
return getTileBuffer(
|
||||||
data,
|
data,
|
||||||
tileParams,
|
tileParams,
|
||||||
tile,
|
tile,
|
||||||
cfg?.format || defaultFormat,
|
cfg?.format || defaultFormat,
|
||||||
|
cfg?.operation,
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return getTileImage(data, tileParams, tile);
|
return getTileImage(data as string | string[], tileParams, tile);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,36 @@
|
||||||
import { IParserData, IRasterCfg } from '@antv/l7-core';
|
import { IParserData } from '@antv/l7-core';
|
||||||
export default function raster(data: number[], cfg: IRasterCfg): IParserData {
|
import { IRasterLayerData, IRasterCfg, IRasterFileData } from '../interface';
|
||||||
const { extent, width, height, min, max } = cfg;
|
import { bandsOperation } from '../utils/bandOperation/bands';
|
||||||
|
import { isNumberArray } from '../utils/util';
|
||||||
|
|
||||||
|
export default function raster(
|
||||||
|
data: IRasterLayerData,
|
||||||
|
cfg: IRasterCfg,
|
||||||
|
): IParserData {
|
||||||
|
const { extent, width, height, min, max, format, operation } = cfg;
|
||||||
|
let bandData, rasterWidth, rasterHeight;
|
||||||
|
if (format === undefined || isNumberArray(data)) {
|
||||||
|
// 兼容写法 - 用户直接传入解析完的波段数据
|
||||||
|
bandData = Array.from(data as number[]);
|
||||||
|
rasterWidth = width;
|
||||||
|
rasterHeight = height;
|
||||||
|
} else {
|
||||||
|
// 用户传入为解析的栅格数据 - arraybuffer
|
||||||
|
// 将数据统一为 IRasterFileData[]
|
||||||
|
const imageDataList = (Array.isArray(data)
|
||||||
|
? data
|
||||||
|
: [data]) as IRasterFileData[];
|
||||||
|
bandData = bandsOperation(imageDataList, format, operation);
|
||||||
|
}
|
||||||
|
|
||||||
const resultData = {
|
const resultData = {
|
||||||
_id: 1,
|
_id: 1,
|
||||||
dataArray: [
|
dataArray: [
|
||||||
{
|
{
|
||||||
_id: 1,
|
_id: 1,
|
||||||
data: Array.from(data),
|
data: bandData,
|
||||||
width,
|
width: rasterWidth,
|
||||||
height,
|
height: rasterHeight,
|
||||||
min,
|
min,
|
||||||
max,
|
max,
|
||||||
coordinates: [
|
coordinates: [
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { IParserData } from '@antv/l7-core';
|
||||||
|
import { IRasterLayerData, IRasterCfg, IRasterFileData } from '../interface';
|
||||||
|
import { bandsOperation } from '../utils/bandOperation/bands';
|
||||||
|
import { isNumberArray } from '../utils/util';
|
||||||
|
|
||||||
|
export default function rasterRgb(
|
||||||
|
data: IRasterLayerData,
|
||||||
|
cfg: IRasterCfg,
|
||||||
|
): IParserData {
|
||||||
|
const { extent, width, height, min, max, format, operation } = cfg;
|
||||||
|
let bandData, rasterWidth, rasterHeight;
|
||||||
|
if (format === undefined || isNumberArray(data)) {
|
||||||
|
// 兼容写法 - 用户直接传入解析完的波段数据
|
||||||
|
bandData = Array.from(data as number[]);
|
||||||
|
rasterWidth = width;
|
||||||
|
rasterHeight = height;
|
||||||
|
} else {
|
||||||
|
// 用户传入为解析的栅格数据 - arraybuffer
|
||||||
|
// 将数据统一为 IRasterFileData[]
|
||||||
|
const imageDataList = (Array.isArray(data)
|
||||||
|
? data
|
||||||
|
: [data]) as IRasterFileData[];
|
||||||
|
bandData = bandsOperation(imageDataList, format, operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultData = {
|
||||||
|
_id: 1,
|
||||||
|
dataArray: [
|
||||||
|
{
|
||||||
|
_id: 1,
|
||||||
|
data: bandData,
|
||||||
|
width: rasterWidth,
|
||||||
|
height: rasterHeight,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
coordinates: [
|
||||||
|
[extent[0], extent[1]],
|
||||||
|
[extent[2], extent[3]],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
return resultData;
|
||||||
|
}
|
|
@ -33,6 +33,7 @@ function mergeCustomizer(objValue: any, srcValue: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Source extends EventEmitter implements ISource {
|
export default class Source extends EventEmitter implements ISource {
|
||||||
|
public type: string = 'source';
|
||||||
public inited: boolean = false;
|
public inited: boolean = false;
|
||||||
public data: IParserData;
|
public data: IParserData;
|
||||||
public center: [number, number];
|
public center: [number, number];
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
import { IRasterFileData, IRasterFormat, IBandsOperation, IRgbOperation, IRasterData } from '../../interface';
|
||||||
|
import { calculate } from './math';
|
||||||
|
import {
|
||||||
|
ResponseCallback,
|
||||||
|
} from '@antv/l7-utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行波段计算 format + operation
|
||||||
|
* @param imageDataList
|
||||||
|
* @param rasterFormat
|
||||||
|
* @param operation
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function bandsOperation(imageDataList: IRasterFileData[], rasterFormat: IRasterFormat, operation: IBandsOperation|undefined) {
|
||||||
|
if(imageDataList.length === 0) {
|
||||||
|
return {
|
||||||
|
rasterData: [0],
|
||||||
|
width: 1,
|
||||||
|
heigh: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let bandsData = (await Promise.all(
|
||||||
|
imageDataList.map(({ data, bands = [0] }) => rasterFormat(data, bands)),
|
||||||
|
)) as IRasterData[];
|
||||||
|
// @ts-ignore
|
||||||
|
bandsData = bandsData.flat();
|
||||||
|
// Tip: rasterFormat 返回值 rasterData|rasterData[]
|
||||||
|
|
||||||
|
// 多个栅格数据必须是相同大小才能进行相互之间的运算
|
||||||
|
const { width, height } = bandsData[0];
|
||||||
|
|
||||||
|
type IOperationResult = HTMLImageElement | Uint8Array | ImageBitmap | null | undefined
|
||||||
|
let rasterData: IOperationResult|IOperationResult[];
|
||||||
|
let channelR = undefined;
|
||||||
|
let channelG = undefined;
|
||||||
|
let channelB = undefined;
|
||||||
|
switch (typeof operation) {
|
||||||
|
case 'function':
|
||||||
|
rasterData = operation(bandsData) as Uint8Array;
|
||||||
|
break;
|
||||||
|
case 'object':
|
||||||
|
// 波段计算表达式 - operation
|
||||||
|
// operation: ['+', ['band', 0], 1]
|
||||||
|
/**
|
||||||
|
* operation: {
|
||||||
|
* r: ['+', ['band', 0], 1],
|
||||||
|
* g: ['+', ['band', 0], 1],
|
||||||
|
* b: ['+', ['band', 0], 1],
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
if(!Array.isArray(operation)) {
|
||||||
|
const rgbBands = getRgbBands(operation, bandsData);
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
channelRMax,
|
||||||
|
channelGMax,
|
||||||
|
channelBMax
|
||||||
|
} = combineRGBChannels(rgbBands);
|
||||||
|
rasterData = data as unknown as Uint8Array;
|
||||||
|
channelR = channelRMax;
|
||||||
|
channelG = channelGMax;
|
||||||
|
channelB = channelBMax;
|
||||||
|
} else {
|
||||||
|
rasterData = calculate(operation as any[], bandsData);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
rasterData = bandsData[0].rasterData;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
rasterData,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
channelR,
|
||||||
|
channelG,
|
||||||
|
channelB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRgbBands(operation: IRgbOperation, bandsData: IRasterData[]) {
|
||||||
|
if(operation.r === undefined) console.warn('Channel R lost in Operation! Use band[0] to fill!');
|
||||||
|
if(operation.g === undefined) console.warn('Channel G lost in Operation! Use band[0] to fill!');
|
||||||
|
if(operation.b === undefined) console.warn('Channel B lost in Operation! Use band[0] to fill!');
|
||||||
|
const r = calculate(operation.r || ['band', 0], bandsData);
|
||||||
|
const g = calculate(operation.g || ['band', 0], bandsData);
|
||||||
|
const b = calculate(operation.b || ['band', 0], bandsData);
|
||||||
|
return [r, g, b];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将波段数据进行合并操作(彩色多通道)
|
||||||
|
* @param bandsData
|
||||||
|
*/
|
||||||
|
function combineRGBChannels(bandsData: Uint8Array[]) {
|
||||||
|
const channelR = bandsData[0];
|
||||||
|
const channelG = bandsData[1];
|
||||||
|
const channelB = bandsData[2];
|
||||||
|
const data = [];
|
||||||
|
let channelRMax = -Infinity;
|
||||||
|
let channelGMax = -Infinity;
|
||||||
|
let channelBMax = -Infinity;
|
||||||
|
for(let i = 0;i < channelR.length;i++) {
|
||||||
|
data.push(channelR[i]);channelRMax = Math.max(channelRMax, channelR[i]);
|
||||||
|
data.push((channelG)[i]);channelGMax = Math.max(channelGMax, channelR[i]);
|
||||||
|
data.push((channelB)[i]);channelBMax = Math.max(channelBMax, channelR[i]);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
channelRMax,
|
||||||
|
channelGMax,
|
||||||
|
channelBMax
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理每个请求得到的栅格文件数据
|
||||||
|
*/
|
||||||
|
export async function handleRasterFiles(
|
||||||
|
rasterFiles: IRasterFileData[],
|
||||||
|
rasterFormat: IRasterFormat,
|
||||||
|
operation: IBandsOperation | undefined,
|
||||||
|
callback: ResponseCallback<any>
|
||||||
|
) {
|
||||||
|
const { rasterData, width, height } = await bandsOperation(rasterFiles, rasterFormat, operation)
|
||||||
|
// 目前 max|min 没有生效
|
||||||
|
const defaultMIN = 0;
|
||||||
|
const defaultMAX = 8000;
|
||||||
|
callback(null, {
|
||||||
|
data: rasterData,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
min: defaultMIN,
|
||||||
|
max: defaultMAX,
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
|
||||||
|
import { IRasterData } from '../../interface';
|
||||||
|
|
||||||
|
/** 数学运算 根据计算表达式进行数学运算
|
||||||
|
* * * Math operators:
|
||||||
|
* `['*', value1, value2]` multiplies `value1` by `value2`
|
||||||
|
* `['/', value1, value2]` divides `value1` by `value2`
|
||||||
|
* `['+', value1, value2]` adds `value1` and `value2`
|
||||||
|
* `['-', value1, value2]` subtracts `value2` from `value1`
|
||||||
|
* `['%', value1, value2]` returns the result of `value1 % value2` (modulo)
|
||||||
|
* `['^', value1, value2]` returns the value of `value1` raised to the `value2` power
|
||||||
|
* `['abs', value1]` returns the absolute value of `value1`
|
||||||
|
* `['floor', value1]` returns the nearest integer less than or equal to `value1`
|
||||||
|
* `['round', value1]` returns the nearest integer to `value1`
|
||||||
|
* `['ceil', value1]` returns the nearest integer greater than or equal to `value1`
|
||||||
|
* `['sin', value1]` returns the sine of `value1`
|
||||||
|
* `['cos', value1]` returns the cosine of `value1`
|
||||||
|
* `['atan', value1, value2]` returns `atan2(value1, value2)`. If `value2` is not provided, returns `atan(value1)`
|
||||||
|
*/
|
||||||
|
export function mathematical(symbol: string, n1: number, n2: number) {
|
||||||
|
switch(symbol) {
|
||||||
|
case '+': return n1 + n2;
|
||||||
|
case '-': return n1 - n2;
|
||||||
|
case '*': return n1 * n2;
|
||||||
|
case '/': return n1 / n2;
|
||||||
|
case '%': return n1 % n2;
|
||||||
|
|
||||||
|
case '^': return Math.pow(n1, n2);
|
||||||
|
case 'abs': return Math.abs(n1);
|
||||||
|
case 'floor': return Math.floor(n1);
|
||||||
|
case 'round': return Math.round(n1);
|
||||||
|
case 'ceil': return Math.ceil(n1);
|
||||||
|
case 'sin': return Math.sin(n1);
|
||||||
|
case 'cos': return Math.cos(n1);
|
||||||
|
case 'atan': return (n2 === -1) ? Math.atan(n1): Math.atan2(n1, n2);
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.warn('Calculate symbol err! Return default 0');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据表达式计算
|
||||||
|
* @param express
|
||||||
|
* @param bandsData
|
||||||
|
*/
|
||||||
|
export function calculate(express: any[], bandsData: IRasterData[]) {
|
||||||
|
const {width, height} = bandsData[0];
|
||||||
|
const dataArray = bandsData.map(band => band.rasterData) as Uint8Array[];
|
||||||
|
const length = width * height;
|
||||||
|
const rasterData = [];
|
||||||
|
const originExp = JSON.stringify(express);
|
||||||
|
for(let i = 0;i < length; i++) {
|
||||||
|
const exp = JSON.parse(originExp);
|
||||||
|
// 将表达式中的 ['band', 0]、['band', 1] 等替换为实际的栅格数据
|
||||||
|
const expResult = spellExpress(exp, dataArray, i);
|
||||||
|
if(typeof expResult === 'number') {
|
||||||
|
// exp: ['band', 0] => exp: 2 ...
|
||||||
|
// exp 直接指定了波段值,替换完后直接就是数值了,无需计算
|
||||||
|
rasterData.push(expResult);
|
||||||
|
} else {
|
||||||
|
const result = calculateExpress(exp);
|
||||||
|
rasterData.push(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return rasterData as unknown as Uint8Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
type IExpress = any[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将表达式中的指定波段替换为对应波段的栅格数据
|
||||||
|
* @param express
|
||||||
|
* @param dataArray
|
||||||
|
* @param index
|
||||||
|
*/
|
||||||
|
export function spellExpress(express: IExpress, dataArray: Uint8Array[], index: number) {
|
||||||
|
/**
|
||||||
|
* 用户直接指定波段数值,无需计算
|
||||||
|
*/
|
||||||
|
if(express.length === 2 && express[0] === 'band' && typeof express[1] === 'number') {
|
||||||
|
try {
|
||||||
|
return dataArray[express[1]][index];
|
||||||
|
} catch(err) {
|
||||||
|
console.warn('Raster Data err!');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
express.map((e, i) => {
|
||||||
|
if(Array.isArray(e) && e.length > 0) {
|
||||||
|
switch(e[0]) {
|
||||||
|
case 'band':
|
||||||
|
try {
|
||||||
|
express[i] = dataArray[e[1]][index];
|
||||||
|
} catch(err) {
|
||||||
|
console.warn('Raster Data err!');
|
||||||
|
express[i] = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
spellExpress(e, dataArray, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatExpress(express: IExpress) {
|
||||||
|
const [symbol1, symbol2 = -1, symbol3 = -1] = express;
|
||||||
|
if(symbol1 === undefined) {
|
||||||
|
console.warn('Express err!')
|
||||||
|
return ['+', 0, 0];
|
||||||
|
}
|
||||||
|
const symbol = symbol1.replace(/\s+/g, '');
|
||||||
|
|
||||||
|
return [symbol, symbol2, symbol3];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calculateExpress(express: IExpress) {
|
||||||
|
const formatExp = formatExpress(express);
|
||||||
|
const str = formatExp[0];
|
||||||
|
let left = formatExp[1];
|
||||||
|
let right = formatExp[2];
|
||||||
|
|
||||||
|
if(Array.isArray(left)) {
|
||||||
|
left = calculateExpress(express[1]);
|
||||||
|
}
|
||||||
|
if(Array.isArray(right)) {
|
||||||
|
right = calculateExpress(express[2]);
|
||||||
|
}
|
||||||
|
return mathematical(str, left, right);
|
||||||
|
}
|
|
@ -1,151 +0,0 @@
|
||||||
import {
|
|
||||||
getImage,
|
|
||||||
makeXMLHttpRequestPromise,
|
|
||||||
ResponseCallback,
|
|
||||||
IRasterParser,
|
|
||||||
arrayBufferToTiffImage,
|
|
||||||
RequestParameters,
|
|
||||||
getArrayBuffer,
|
|
||||||
getURLFromTemplate,
|
|
||||||
Tile,
|
|
||||||
TileLoadParams,
|
|
||||||
} from '@antv/l7-utils';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用于获取 raster data 的瓦片,如 tiff、lerc、dem 等
|
|
||||||
* 支持多文件模式
|
|
||||||
* @param url
|
|
||||||
* @param tileParams
|
|
||||||
* @param tile
|
|
||||||
* @param rasterParser
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const getTileBuffer = async (
|
|
||||||
url: string | string[],
|
|
||||||
tileParams: TileLoadParams,
|
|
||||||
tile: Tile,
|
|
||||||
rasterParser: (imageData: ArrayBuffer) => Promise<IRasterParser>,
|
|
||||||
): Promise<HTMLImageElement | ImageBitmap> => {
|
|
||||||
const requestParameters = {
|
|
||||||
url: getTileUrl(url, tileParams),
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
getTiffImage(
|
|
||||||
tile,
|
|
||||||
requestParameters,
|
|
||||||
(err, img) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else if (img) {
|
|
||||||
resolve(img);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rasterParser,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTiffImage = async (
|
|
||||||
tile: Tile,
|
|
||||||
requestParameters: RequestParameters,
|
|
||||||
callback: ResponseCallback<HTMLImageElement | ImageBitmap | null>,
|
|
||||||
rasterParser: any,
|
|
||||||
) => {
|
|
||||||
if (Array.isArray(requestParameters.url)) {
|
|
||||||
const imageDataList = [];
|
|
||||||
const xhrList: any[] = [];
|
|
||||||
const errList = [];
|
|
||||||
const urls = requestParameters.url;
|
|
||||||
|
|
||||||
for (let i = 0; i < urls.length; i++) {
|
|
||||||
const params = {
|
|
||||||
...requestParameters,
|
|
||||||
url: urls[i],
|
|
||||||
};
|
|
||||||
const { err, data, xhr } = await makeXMLHttpRequestPromise({
|
|
||||||
...params,
|
|
||||||
type: 'arrayBuffer',
|
|
||||||
});
|
|
||||||
if (err) {
|
|
||||||
errList.push(err);
|
|
||||||
}
|
|
||||||
xhrList.push(xhr);
|
|
||||||
imageDataList.push(data);
|
|
||||||
}
|
|
||||||
setTileXHRCancelFunc(tile, xhrList);
|
|
||||||
|
|
||||||
if (errList.length > 0) {
|
|
||||||
callback(errList as Error[], null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { rasterData, width, height } = await rasterParser(imageDataList);
|
|
||||||
const defaultMIN = 0;
|
|
||||||
const defaultMAX = 8000;
|
|
||||||
callback(null, {
|
|
||||||
// @ts-ignore
|
|
||||||
data: rasterData,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
min: defaultMIN,
|
|
||||||
max: defaultMAX,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const xhr = getArrayBuffer(requestParameters, (err, imgData) => {
|
|
||||||
if (err) {
|
|
||||||
callback(err);
|
|
||||||
} else if (imgData) {
|
|
||||||
arrayBufferToTiffImage(imgData, callback, rasterParser);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setTileXHRCancelFunc(tile, [xhr]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function setTileXHRCancelFunc(tile: Tile, xhrList: any[]) {
|
|
||||||
tile.xhrCancel = () => {
|
|
||||||
xhrList.map((xhr) => {
|
|
||||||
xhr.abort();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTileUrl(url: string | string[], tileParams: TileLoadParams) {
|
|
||||||
if (Array.isArray(url)) {
|
|
||||||
return url.map((src) => getURLFromTemplate(src, tileParams));
|
|
||||||
} else {
|
|
||||||
return getURLFromTemplate(url, tileParams);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getTileImage = async (
|
|
||||||
url: string | string[],
|
|
||||||
tileParams: TileLoadParams,
|
|
||||||
tile: Tile,
|
|
||||||
): Promise<HTMLImageElement | ImageBitmap> => {
|
|
||||||
// TODO: 后续考虑支持加载多服务
|
|
||||||
const imgUrl = getURLFromTemplate(
|
|
||||||
Array.isArray(url) ? url[0] : url,
|
|
||||||
tileParams,
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const xhr = getImage({ url: imgUrl }, (err, img) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else if (img) {
|
|
||||||
resolve(img);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
tile.xhrCancel = () => xhr.abort();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const defaultFormat = () => {
|
|
||||||
return {
|
|
||||||
rasterData: new Uint8Array([0]),
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { IRasterFormat, IBandsOperation, IRasterFileData } from '../../interface';
|
||||||
|
import { getTileBandParams, bindCancel } from './request';
|
||||||
|
import {
|
||||||
|
makeXMLHttpRequestPromise,
|
||||||
|
ResponseCallback,
|
||||||
|
ITileBand,
|
||||||
|
RequestParameters,
|
||||||
|
getArrayBuffer,
|
||||||
|
Tile,
|
||||||
|
} from '@antv/l7-utils';
|
||||||
|
import { handleRasterFiles } from '../bandOperation/bands';
|
||||||
|
|
||||||
|
export const getRasterFile = async (
|
||||||
|
tile: Tile,
|
||||||
|
requestParameters: RequestParameters,
|
||||||
|
callback: ResponseCallback<HTMLImageElement | ImageBitmap | null>,
|
||||||
|
rasterFormat: IRasterFormat,
|
||||||
|
operation?: IBandsOperation,
|
||||||
|
) => {
|
||||||
|
// Tip: 至少存在一个请求文件的 url,处理得到标准的 ITileBand[] url 路径和 bands 参数
|
||||||
|
const tileBandParams: ITileBand[] = getTileBandParams(requestParameters.url);
|
||||||
|
|
||||||
|
if(tileBandParams.length > 1) {// 同时请求多文件
|
||||||
|
const { rasterFiles, xhrList, errList } = await getMultiArrayBuffer(tileBandParams, requestParameters);
|
||||||
|
bindCancel(tile, xhrList);
|
||||||
|
if (errList.length > 0) {
|
||||||
|
callback(errList as Error[], null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRasterFiles(rasterFiles, rasterFormat, operation, callback);
|
||||||
|
} else {
|
||||||
|
const xhr = getArrayBuffer(requestParameters, (err, imgData) => {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
} else if (imgData) {
|
||||||
|
const rasterFiles = [{
|
||||||
|
data: imgData,
|
||||||
|
bands: tileBandParams[0].bands
|
||||||
|
}];
|
||||||
|
handleRasterFiles(rasterFiles, rasterFormat, operation, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
bindCancel(tile, [xhr]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get multi raster files async
|
||||||
|
* @param tileBandParams
|
||||||
|
* @param requestParameters
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async function getMultiArrayBuffer(tileBandParams: ITileBand[], requestParameters: RequestParameters) {
|
||||||
|
const rasterFiles: IRasterFileData[] = [];
|
||||||
|
const xhrList: any[] = [];
|
||||||
|
const errList = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < tileBandParams.length; i++) {
|
||||||
|
const tileBandParam = tileBandParams[i]
|
||||||
|
const params = {
|
||||||
|
...requestParameters,
|
||||||
|
url: tileBandParam.url,
|
||||||
|
};
|
||||||
|
|
||||||
|
const bands = tileBandParam.bands;
|
||||||
|
const { err, data, xhr } = await makeXMLHttpRequestPromise({
|
||||||
|
...params,
|
||||||
|
type: 'arrayBuffer',
|
||||||
|
});
|
||||||
|
if (err) {
|
||||||
|
errList.push(err);
|
||||||
|
}
|
||||||
|
xhrList.push(xhr);
|
||||||
|
rasterFiles.push({
|
||||||
|
data,
|
||||||
|
bands,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { rasterFiles, xhrList, errList }
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
import {
|
||||||
|
getImage,
|
||||||
|
ITileBand,
|
||||||
|
getURLFromTemplate,
|
||||||
|
Tile,
|
||||||
|
TileLoadParams,
|
||||||
|
} from '@antv/l7-utils';
|
||||||
|
import { getTileUrl } from './request';
|
||||||
|
import { IRasterFormat, IBandsOperation } from '../../interface';
|
||||||
|
import { getRasterFile } from './getRasterData';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于获取 raster data 的瓦片,如 tiff、lerc、dem 等
|
||||||
|
* 支持多文件模式
|
||||||
|
* @param url
|
||||||
|
* @param tileParams
|
||||||
|
* @param tile
|
||||||
|
* @param rasterFormat
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getTileBuffer = async (
|
||||||
|
url: string | string[] | ITileBand[],
|
||||||
|
tileParams: TileLoadParams,
|
||||||
|
tile: Tile,
|
||||||
|
rasterFormat: IRasterFormat,
|
||||||
|
operation?: IBandsOperation,
|
||||||
|
): Promise<HTMLImageElement | ImageBitmap> => {
|
||||||
|
const requestParameters = {
|
||||||
|
// getTileUrl 将原始的 url 路径进行转化(多服务器)
|
||||||
|
url: getTileUrl(url, tileParams),
|
||||||
|
};
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
getRasterFile(
|
||||||
|
tile,
|
||||||
|
requestParameters,
|
||||||
|
(err, img) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else if (img) {
|
||||||
|
resolve(img);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rasterFormat,
|
||||||
|
operation,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 获取图片格式的文件 jpg、png 等
|
||||||
|
* @param url
|
||||||
|
* @param tileParams
|
||||||
|
* @param tile
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getTileImage = async (
|
||||||
|
url: string | string[],
|
||||||
|
tileParams: TileLoadParams,
|
||||||
|
tile: Tile,
|
||||||
|
): Promise<HTMLImageElement | ImageBitmap> => {
|
||||||
|
// TODO: 后续考虑支持加载多服务
|
||||||
|
const imgUrl = getURLFromTemplate(
|
||||||
|
Array.isArray(url) ? url[0] : url,
|
||||||
|
tileParams,
|
||||||
|
);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const xhr = getImage({ url: imgUrl }, (err, img) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else if (img) {
|
||||||
|
resolve(img);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tile.xhrCancel = () => xhr.abort();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultFormat = () => {
|
||||||
|
return {
|
||||||
|
rasterData: new Uint8Array([0]),
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,98 @@
|
||||||
|
import {
|
||||||
|
ITileBand,
|
||||||
|
getURLFromTemplate,
|
||||||
|
TileLoadParams,
|
||||||
|
Tile
|
||||||
|
} from '@antv/l7-utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据不同的输入,将对应的 url 路径进行转化
|
||||||
|
* https://{a-c}.xxx.ccc => https://a.xxx.ccc
|
||||||
|
* https://{a-c}.xxx.ccc => https://b.xxx.ccc
|
||||||
|
* https://{a-c}.xxx.ccc => https://c.xxx.ccc
|
||||||
|
* @param url
|
||||||
|
* 'https://a.xxx.ccc '
|
||||||
|
* or
|
||||||
|
* ['https://a.xxx.ccc', 'https://c.ddd.ccc']
|
||||||
|
* or
|
||||||
|
* [
|
||||||
|
* {
|
||||||
|
* url: 'https://a.xxx.ccc',
|
||||||
|
* bands: [0]
|
||||||
|
* },
|
||||||
|
* ...
|
||||||
|
* ]
|
||||||
|
* @param tileParams
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getTileUrl(
|
||||||
|
url: string | string[] | ITileBand[],
|
||||||
|
tileParams: TileLoadParams,
|
||||||
|
) {
|
||||||
|
if (Array.isArray(url)) {
|
||||||
|
if(typeof url[0] === 'string') {
|
||||||
|
return (url as string[]).map((src) =>
|
||||||
|
getURLFromTemplate(src, tileParams),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (url as ITileBand[]).map((o) => {
|
||||||
|
return {
|
||||||
|
url: getURLFromTemplate(o.url, tileParams),
|
||||||
|
bands: o.bands || [0],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return getURLFromTemplate(url, tileParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理 url 中的波段参数,将不同的 url 格式处理成统一格式
|
||||||
|
* @param urlBandParam
|
||||||
|
* 'https://a.bb.xxx'
|
||||||
|
* or
|
||||||
|
* [
|
||||||
|
* 'https://a.bb.xxx',
|
||||||
|
* 'https://a.bb.xxx'
|
||||||
|
* ]
|
||||||
|
* or
|
||||||
|
* [
|
||||||
|
* {
|
||||||
|
* url: 'https://a.bb.xxx',
|
||||||
|
* bands: [0, 1]
|
||||||
|
* },
|
||||||
|
* ...
|
||||||
|
* ]
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getTileBandParams(urlBandParam: string | string[] | ITileBand[]): ITileBand[] {
|
||||||
|
if(typeof urlBandParam === 'string') {
|
||||||
|
return [{
|
||||||
|
url: urlBandParam,
|
||||||
|
bands: [0]
|
||||||
|
}]
|
||||||
|
} else if(typeof urlBandParam[0] === 'string') {
|
||||||
|
return urlBandParam.map(param => {
|
||||||
|
return {
|
||||||
|
url: param as string,
|
||||||
|
bands: [0]
|
||||||
|
};
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return urlBandParam as ITileBand[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 tile 文件请求的取消函数
|
||||||
|
* @param tile
|
||||||
|
* @param xhrList
|
||||||
|
*/
|
||||||
|
export function bindCancel(tile: Tile, xhrList: any[]) {
|
||||||
|
tile.xhrCancel = () => {
|
||||||
|
xhrList.map((xhr) => {
|
||||||
|
xhr.abort();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,8 +1,45 @@
|
||||||
|
import { IRasterLayerData, IRasterFileData } from '../interface';
|
||||||
|
|
||||||
interface IDataItem {
|
interface IDataItem {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getColumn(data: IDataItem[], columnName: string) {
|
export function getColumn(data: IDataItem[], columnName: string) {
|
||||||
return data.map((item: IDataItem) => {
|
return data.map((item: IDataItem) => {
|
||||||
return item[columnName] * 1;
|
return item[columnName] * 1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isRasterFileData(data?: IRasterLayerData) {
|
||||||
|
if (data === undefined) return false;
|
||||||
|
if (!Array.isArray(data) && data.data !== undefined) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isRasterFileDataArray(data: IRasterLayerData) {
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
if (data.length === 0) return false;
|
||||||
|
if (isRasterFileData(data[0] as IRasterFileData)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNumberArray(data: IRasterLayerData) {
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
if (data.length === 0) return true;
|
||||||
|
if (typeof data[0] === 'number') {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
import { getReferrer } from './env';
|
import { getReferrer } from './env';
|
||||||
import { $window, $XMLHttpRequest } from './mini-adapter';
|
import { $window, $XMLHttpRequest } from './mini-adapter';
|
||||||
|
|
||||||
|
export interface ITileBand {
|
||||||
|
url: string;
|
||||||
|
bands: number[];
|
||||||
|
}
|
||||||
|
|
||||||
export type RequestParameters = {
|
export type RequestParameters = {
|
||||||
url: string | string[];
|
url: string | string[] | ITileBand[];
|
||||||
headers?: any;
|
headers?: any;
|
||||||
method?: 'GET' | 'POST' | 'PUT';
|
method?: 'GET' | 'POST' | 'PUT';
|
||||||
body?: string;
|
body?: string;
|
||||||
|
@ -55,7 +60,7 @@ function makeFetchRequest(
|
||||||
callback: ResponseCallback<any>,
|
callback: ResponseCallback<any>,
|
||||||
) {
|
) {
|
||||||
const url = Array.isArray(requestParameters.url) ? requestParameters.url[0] : requestParameters.url;
|
const url = Array.isArray(requestParameters.url) ? requestParameters.url[0] : requestParameters.url;
|
||||||
const request = new Request(url, {
|
const request = new Request(url as string, {
|
||||||
method: requestParameters.method || 'GET',
|
method: requestParameters.method || 'GET',
|
||||||
body: requestParameters.body,
|
body: requestParameters.body,
|
||||||
credentials: requestParameters.credentials,
|
credentials: requestParameters.credentials,
|
||||||
|
@ -96,7 +101,7 @@ function makeFetchRequest(
|
||||||
new AJAXError(
|
new AJAXError(
|
||||||
response.status,
|
response.status,
|
||||||
response.statusText,
|
response.statusText,
|
||||||
url,
|
url.toString(),
|
||||||
body,
|
body,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -157,7 +162,7 @@ function makeXMLHttpRequest(
|
||||||
type: xhr.getResponseHeader('Content-Type'),
|
type: xhr.getResponseHeader('Content-Type'),
|
||||||
});
|
});
|
||||||
callback(
|
callback(
|
||||||
new AJAXError(xhr.status, xhr.statusText, url, body),
|
new AJAXError(xhr.status, xhr.statusText, url.toString(), body),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -305,44 +310,3 @@ export const getImage = (
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const arrayBufferToTiffImage = async (
|
|
||||||
data: ArrayBuffer,
|
|
||||||
callback: (err?: Error | null, image?: any) => void,
|
|
||||||
rasterParser: any,
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const { rasterData, width, height } = await rasterParser(data);
|
|
||||||
const defaultMIN = 0;
|
|
||||||
const defaultMAX = 8000;
|
|
||||||
callback(null, {
|
|
||||||
data: rasterData,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
min: defaultMIN,
|
|
||||||
max: defaultMAX,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
callback(null, new Error('' + err));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getTiffImage = (
|
|
||||||
requestParameters: RequestParameters,
|
|
||||||
callback: ResponseCallback<HTMLImageElement | ImageBitmap | null>,
|
|
||||||
rasterParser: any,
|
|
||||||
) => {
|
|
||||||
return getArrayBuffer(requestParameters, (err, imgData) => {
|
|
||||||
if (err) {
|
|
||||||
callback(err);
|
|
||||||
} else if (imgData) {
|
|
||||||
arrayBufferToTiffImage(imgData, callback, rasterParser);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface IRasterParser {
|
|
||||||
rasterData: HTMLImageElement | ImageBitmap | null | undefined;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
}
|
|
Loading…
Reference in New Issue