目录
该文档是根据本人做的项目进行的总结,可能存在知识的不准确性,仅做参考;
前端代码实现
功能:根据提供一个瓦片服务地址,解析服务的元数据,获取到范围,根据范围,计算出行列号
根据xml配置文件计算出行列号
1、xml配置文件信息样例
2、代码实现
1、解析xml数据为json格式的数据,借助插件x2js
2、本例中,只做了3857和4326坐标系的支持;请求用axios进行请求
3、因为文档中的比例尺分母使用的是米,所以4236中,如果没有提供level时,将度数转为米进行匹配层级信息的
4、4326坐标系的地球半径取值为:6371004;3857坐标系的地球半径取值为:6378137;
5、可以直接copy代码使用,使用中注意度和米的单位问题
import X2JS from 'x2js';
// 1、请求配置文件路径,获取元数据,通过插件x2js解析xml数据为json格式
let url = 'http://xxxxx';
axios.get(url).then((res) => {
if (res.status != 200) {
alert('请检查您的配置文件路径');
return false;
}
var x2js = new X2JS();
var json = x2js.xml2js(res.data);
getColRowLevel({data:json,dpi:90.714});
});
// 2、 根据获取的元数据,获取行列号和层级
const getColRowLevel = (json) => {
let { data, dpi, level } = json;
let Contents = data.Capabilities.Contents;
let TileMatrixSet = Contents.TileMatrixSet;
// 元数据中的crs坐标系
let SupportedCRS = TileMatrixSet.SupportedCRS.__text || TileMatrixSet.SupportedCRS;
// 默认crs是3857
//默认dpi为90.714,dpi一般为90.714或者96;如果不确定dpi请咨询提供服务的后台
dpi = dpi ? Number(dpi) : 90.714;
level = level ? Number(level) : null;
if (SupportedCRS.toLowerCase().includes('epsg::4326')) {
// 4326
return colRowLevel4326(Contents, dpi, level);
} else {
// 3857
return colRowLevel3857(Contents, dpi, level);
}
};
const colRowLevel4326 = (Contents, dpi, level) => {
let Layer = Contents.Layer;
// 服务范围,如果给指定范围,则去掉下面四行,直接let extentObj = {minX,minY,maxX,maxY}
let WGS84BoundingBox = Layer.WGS84BoundingBox;
let LowerCorner = WGS84BoundingBox.LowerCorner.__text;
let UpperCorner = WGS84BoundingBox.UpperCorner.__text;
let extentObj = getExtent4326(LowerCorner, UpperCorner);
let { minX, maxX, minY, maxY } = extentObj;
let canvasWidth = 1000;
let tileSize = 256;
let realTileWidthDegree = (maxX - minX) / (canvasWidth / tileSize);
let realTileWidthMeter = (realTileWidthDegree / 180) * (Math.PI * 6371004);
let resolutions = realTileWidthMeter / tileSize;
let scaleDenominator = resolutions / (0.0254 / dpi);
// // Identifier ScaleDenominator TileHeight TopLeftCorner
let TileMatrixSet = Contents.TileMatrixSet;
let TileMatrixArr = TileMatrixSet.TileMatrix;
// 当前比例尺最接近的对象
let curScaleObj = getScaleInfo(TileMatrixArr,scaleDenominator,level);
if (curScaleObj) {
// Identifier ScaleDenominator TileWidth TopLeftCorner
let { ScaleDenominator, TopLeftCorner, Identifier, TileWidth } = curScaleObj;
Identifier = Identifier.__text;
ScaleDenominator = eToNumber(ScaleDenominator);
let originObj = TopLeftCorner.split(' ');
let originX = originObj[1];
let originY = originObj[0];
var resolution = (((0.0254 / dpi) * ScaleDenominator) / (Math.PI * 6371004)) * 180;
let minCol = Math.floor(Math.abs(originX - minX) / (resolution * tileSize));
let minRow = Math.floor(Math.abs(originY - maxY) / (resolution * tileSize));
let maxCol = Math.ceil(Math.abs(originX - maxX) / (resolution * tileSize));
let maxRow = Math.ceil(Math.abs(originY - minY) / (resolution * tileSize));
return {
minCol,
minRow,
maxCol,
maxRow,
level: Identifier
};
}
return false;
};
const colRowLevel3857 = (Contents, dpi, level) => {
let Layer = Contents.Layer;
// 服务范围,如果给指定范围,则去掉下面四行,直接let extentObj = {minX,minY,maxX,maxY}
let WGS84BoundingBox = Layer.WGS84BoundingBox;
let LowerCorner = WGS84BoundingBox.LowerCorner.__text;
let UpperCorner = WGS84BoundingBox.UpperCorner.__text;
let extentObj = getExtent3857(LowerCorner, UpperCorner);
let { minX, maxX, minY, maxY } = extentObj;
let canvasWidth = 1000;
let tileSize = 256;
let realTileWidth = (maxX - minX) / (canvasWidth / tileSize);
let resolutions = realTileWidth / tileSize;
let scaleDenominator = resolutions / (0.0254 / dpi);
// Identifier ScaleDenominator TileHeight TopLeftCorner
let TileMatrixSet = Contents.TileMatrixSet;
let TileMatrixArr = TileMatrixSet.TileMatrix;
// 当前比例尺最接近的对象
let curScaleObj = getScaleInfo(TileMatrixArr,scaleDenominator,level);
if (curScaleObj) {
// Identifier ScaleDenominator TileWidth TopLeftCorner
let { ScaleDenominator, TopLeftCorner, Identifier, TileWidth } = curScaleObj;
Identifier = Identifier.__text;
ScaleDenominator = eToNumber(ScaleDenominator);
let originObj = getOrigin(TopLeftCorner);
let originX = originObj.x;
let originY = originObj.y;
let resolution = (0.0254 / dpi) * ScaleDenominator;
let minCol = Math.floor(Math.abs(originX - minX) / (resolution * tileSize));
let minRow = Math.floor(Math.abs(originY - maxY) / (resolution * tileSize));
let maxCol = Math.ceil(Math.abs(originX - maxX) / (resolution * tileSize));
let maxRow = Math.ceil(Math.abs(originY - minY) / (resolution * tileSize));
return {
minCol,
minRow,
maxCol,
maxRow,
level: Identifier
};
}
return false;
};
// 得到当前比例尺信息
const getScaleInfo = (TileMatrixArr,scaleDenominator,level)=>{
let curScaleObj = null;
let neastScaleD = undefined;
if (level) {
curScaleObj = TileMatrixArr.find((item) => item.Identifier.__text == level);
}
if (!curScaleObj) {
TileMatrixArr.forEach((item) => {
let ScaleDenominator = eToNumber(item.ScaleDenominator);
let dist = Math.abs(scaleDenominator - ScaleDenominator);
if (neastScaleD == undefined) {
neastScaleD = dist;
curScaleObj = item;
} else if (dist < neastScaleD) {
neastScaleD = dist;
curScaleObj = item;
}
});
}
return curScaleObj;
}
// 得到origin
const getOrigin = (TopLeftCorner) => {
let arr = TopLeftCorner.split(' ');
return { x: eToNumber(arr[0]), y: eToNumber(arr[1]) };
};
// 将自然计数转化为数字
const eToNumber = (eNum) => {
eNum = (eNum + '').toLowerCase();
if (eNum.includes('e')) {
let arr = eNum.split('e');
return Number(arr[0]) * Math.pow(10, arr[1]);
}
return Number(eNum);
};
// 得到范围的坐标点
const getExtent3857 = (LowerCorner, UpperCorner) => {
let lowerArr = LowerCorner.split(' ');
let upperArr = UpperCorner.split(' ');
let min = WGS84ToMercator({ lng: lowerArr[0], lat: lowerArr[1] });
let max = WGS84ToMercator({ lng: upperArr[0], lat: upperArr[1] });
return {
minX: min.lng,
minY: min.lat,
maxX: max.lng,
maxY: max.lat
};
};
const getExtent4326 = (LowerCorner, UpperCorner) => {
let lowerArr = LowerCorner.split(' ');
let upperArr = UpperCorner.split(' ');
return {
minX: lowerArr[0],
minY: lowerArr[1],
maxX: upperArr[0],
maxY: upperArr[1]
};
};
运用到的知识
1、像素和米的换算
1英寸 = 2.54厘米 = 0.0254米
1英寸 = 96像素
所以:1像素 = (0.0254/dpi)米
dpi一般取值96或者90.714,dpi的值不确定的话,需要咨询提供瓦片服务的人
2、分辨率的计算
分辨率 = (0.0254/dpi)*比例尺分母;
3、经纬度和长度米的换算
这种换算方式,我测试后验证应该是在4326坐标系下的换算;
换算时,注意地球半径的取值:4326坐标系的地球半径取值为:6371004;3857坐标系的地球半径取值为:6378137;
长度转经纬度:
degree = meter / (2 * Math.PI * 6371004) * 360 = meter / (Math.PI * 6371004) * 180;degree = meter / (Math.PI * 6371004) * 180;
meter = degree/180* (Math.PI * 6371004)
米转度 粗暴:
degree = meter/111194
米转度 6371000 为地球赤道半径(待证)
Math.asin(radius / 6371000) * 180 / Math.PI
1度=π/180弧度
瓦片行列号的计算逻辑
已知数据:通过各种途径可以获取到的
originX,originY:原点坐标,(可以在配置文件中获取,一般3857的为[-2.003750834E7 2.003750834E7],4326的为[-180,90])
canvasWidth,canvasHeight:画布的宽高
tileSize: 瓦片的宽,在画布上的瓦片的宽
minGeoX,minGeoY,maxGeoX,maxGeoY:要计算的实际坐标的范围,两个坐标点的最大值和最小值 (要计算行列号给的范围)
1、计算出比例尺分母;
(如果告知了层级(level),就可以直接获取比例尺分母,忽略此步骤)
realTileWidth:实际瓦片的宽度
realTileWidth = (minGeoX - originX) / (canvasWidth / tileSize);
realTileWidth = 分辨率 * tileSize;
分辨路 = (0.0254/dpi)*比例尺分母;
上述三个公式,可以推到比例尺分母
比例尺分母 = (minGeoX - originX) / (canvasWidth / tileSize)/tileSize/(0.0254/dpi);
2、 算出层级和比例尺分母
根据步骤1中获取的比例尺分母,循环配置中的瓦片的层级矩阵数据,得到最接近层级(level),并取得那个层级对应的比例尺分母ScaleDenominator
3、计算出分辨率resolution
分辨路的公式是: (0.0254 / dpi) * 比例尺分母
使用中,注意 单位 度和米的转换;
// 4326中,如果获取的比例尺分母单位是米,需要这样转化一下,如果是度,和3857一样就可以了
var resolution = (((0.0254 / dpi) * ScaleDenominator) / (Math.PI * 6371004)) * 180;
// 3857中
let resolution = (0.0254 / dpi) * ScaleDenominator;
4、根据上述步骤的取值,计算出行列号
最小行列号数据向下取整,最大行列号数据向上取整,以获取包含完整坐标范围的行列号
// 最小列号
let minCol = Math.floor(Math.abs(originX - minX) / (resolution * tileSize));
// 最小行号
let minRow = Math.floor(Math.abs(originY - maxY) / (resolution * tileSize));
// 最大列号
let maxCol = Math.ceil(Math.abs(originX - maxX) / (resolution * tileSize));
// 最大行号
let maxRow = Math.ceil(Math.abs(originY - minY) / (resolution * tileSize));
下面这张是在网上看到的图,标注的非常清楚,可以帮助理解