1.先安装依赖读取tif文件插件
yarn add geotiff
npm install geotiff
import GeoTIFF, { fromBlob, fromUrl, fromArrayBuffer } from 'geotiff';
import GeoTIFFImage from 'geotiff/dist-node/geotiffimage';
2.安装proj4坐标转换
yarn add proj4
npm install proj4
import proj4 from 'proj4'
proj4js 坐标转换,git代码库地址:https://github.com/proj4js/proj4js
另一个坐标系在线查询和坐标转换地址:EPSG.io: Coordinate Systems Worldwide
在PostGIS中有一个表 spatial_ref_sys ,可以查询所有的坐标系信息。
在Geoserver中也有所有坐标系的信息。在ArcGIS中也有。
以下介绍在node中使用proj4,以及在Cesium三维开发中使用。
读取储存在minio中的tif文件
/**
* 在Cesium中加载和处理GeoTIFF图像
* @param url - GeoTIFF文件的URL
* @returns Promise<void>
*/
async function geotiffinit(url: string): Promise<void> {
try {
console.log('Starting GeoTIFF processing for URL:', url);
// 1. 加载和解析GeoTIFF
const tiff = await fromUrl(url);
console.log('GeoTIFF loaded successfully');
const image = await tiff.getImage();
console.log('GeoTIFF image extracted');
// 2. 获取图像基本信息
const width = image.getWidth();
const height = image.getHeight();
const [west, south, east, north] = image.getBoundingBox();
console.log('Image dimensions:', width, 'x', height);
console.log('Bounding box:', { west, south, east, north });
// 3. 处理投影信息
const projectionInfo = await getProjectionInfo(image);
console.log('Projection info:', projectionInfo);
const {convertedSW, convertedNE} = await convertCoordinates(west, south, east, north, projectionInfo);
console.log('Converted coordinates:', { convertedSW, convertedNE });
// 4. 读取和处理图像数据
const imageData = await processImageData(image, width, height);
console.log('Image data processed');
// 5. 在Cesium中加载图像
await loadImageToCesium(imageData, convertedSW, convertedNE);
console.log('GeoTIFF successfully loaded and processed');
} catch (error) {
console.error('Error in geotiffinit:', error);
if (error instanceof Error) {
throw new Error(`Failed to load GeoTIFF: ${error.message}`);
} else {
throw new Error(`Failed to load GeoTIFF: Unknown error`);
}
}
}
/**
* 获取和设置投影信息
* @param image - GeoTIFF图像对象
*/
async function getProjectionInfo(image: GeoTIFFImage) {
// 检查并获取投影代码
let epsgCode = '';
try {
// 尝试获取投影代码
const projectedCSType = image.geoKeys?.ProjectedCSTypeGeoKey;
const geographicType = image.geoKeys?.GeographicTypeGeoKey;
// 如果投影代码是数字,转换为EPSG格式
if (typeof projectedCSType === 'number') {
epsgCode = `EPSG:${projectedCSType}`;
} else if (typeof geographicType === 'number') {
epsgCode = `EPSG:${geographicType}`;
} else if (projectedCSType) {
epsgCode = String(projectedCSType);
} else if (geographicType) {
epsgCode = String(geographicType);
} else {
epsgCode = 'EPSG:4326'; // 默认使用WGS84
}
console.log(`Detected projection code: ${epsgCode}`);
} catch (error) {
console.warn('Failed to get projection code:', error);
epsgCode = 'EPSG:4326'; // 出错时默认使用WGS84
}
// 注册常用UTM投影
// 注册UTM投影区域 (1-60)
for (let zone = 1; zone <= 60; zone++) {
const utmCode = `EPSG:${32600 + zone}`;
if (!proj4.defs(utmCode)) {
proj4.defs(utmCode, `+proj=utm +zone=${zone} +datum=WGS84 +units=m +no_defs`);
}
}
// 确保WGS84投影已注册
if (!proj4.defs('EPSG:4326')) {
proj4.defs('EPSG:4326', '+proj=longlat +datum=WGS84 +no_defs');
}
return epsgCode;
}
/**
* 转换坐标
* @param west - 西边界
* @param south - 南边界
* @param east - 东边界
* @param north - 北边界
* @param sourceProjection - 源投影
*/
async function convertCoordinates(
west: number,
south: number,
east: number,
north: number,
sourceProjection: string
) {
const convertedSW = proj4(sourceProjection, 'EPSG:4326', [west, south]);
const convertedNE = proj4(sourceProjection, 'EPSG:4326', [east, north]);
return { convertedSW, convertedNE };
}
/**
* 处理图像数据
* @param image - GeoTIFF图像对象
* @param width - 图像宽度
* @param height - 图像高度
*/
async function processImageData(image: GeoTIFFImage, width: number, height: number) {
// 读取光谱数据
const [red = [], green = [], blue = []] = await image.readRasters();
// 创建canvas并处理图像数据
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
if (!ctx) throw new Error('Failed to get canvas context');
const imageData = ctx.createImageData(width, height);
// 优化: 使用TypedArray提高性能
const data = new Uint8ClampedArray(width * height * 4);
for (let i = 0; i < width * height; i++) {
const offset = i * 4;
data[offset] = red[i];
data[offset + 1] = green[i] || 0;
data[offset + 2] = blue[i] || 0;
data[offset + 3] = red[i] === 0 ? 0 : 255;
}
imageData.data.set(data);
ctx.putImageData(imageData, 0, 0);
console.log(canvas.toDataURL())
return canvas.toDataURL();
}
/**
* 将处理后的图像加载到Cesium
* @param imageUrl - 图像数据URL
* @param convertedSW - 转换后的西南角坐标
* @param convertedNE - 转换后的东北角坐标
*/
async function loadImageToCesium(imageUrl: string, convertedSW: number[], convertedNE: number[]) {
// 创建Cesium矩形
const rectangle = new Cesium.Rectangle.fromDegrees(
convertedSW[0], // west
convertedSW[1], // south
convertedNE[0], // east
convertedNE[1] // north
);
// 添加图像到Cesium
const layer = root.$aMap.viewer.imageryLayers.addImageryProvider(
new Cesium.SingleTileImageryProvider({
url: imageUrl,
rectangle
})
);
// 调整视角
await root.$aMap.viewer.camera.flyTo({
destination: rectangle,
duration: 1.5
});
}
通过坐标转换获取位置很准确,加载效果也还可以,但是文件大的话加载会很慢或者程序计算崩溃,适合加载一些比较小文件.