init
This commit is contained in:
25
src/Obj/Analysis/CircleViewShed/_element.js
Normal file
25
src/Obj/Analysis/CircleViewShed/_element.js
Normal file
@ -0,0 +1,25 @@
|
||||
function html() {
|
||||
return `
|
||||
<span class="custom-divider"></span>
|
||||
<div class="div-item">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<span class="label">视点高度</span>
|
||||
<div class="input-number input-number-unit-1">
|
||||
<input class="input" type="number" title="" min="0" max="999999" step="0.1" @model="viewPointHeight">
|
||||
<span class="unit">m</span>
|
||||
<span class="arrow"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<span class="label">采样精度</span>
|
||||
<input class="input" type="number" title="" min="1" max="100" step="1" @model="precision">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
export { html }
|
512
src/Obj/Analysis/CircleViewShed/_index.js
Normal file
512
src/Obj/Analysis/CircleViewShed/_index.js
Normal file
@ -0,0 +1,512 @@
|
||||
/*
|
||||
* @Author: Wang jianLei
|
||||
* @Date: 2022-05-17 21:49:28
|
||||
* @Last Modified by: Wang JianLei
|
||||
* @Last Modified time: 2022-05-19 22:08:14
|
||||
*/
|
||||
|
||||
let ViewShed = function (sdk, canvasEleId) {
|
||||
if (!sdk.viewer) throw new Error("no viewer object!");
|
||||
alert(canvasEleId)
|
||||
let canvasEle = document.getElementById(canvasEleId);
|
||||
if (!canvasEle) throw new Error("the canvas element is not exist");
|
||||
this.canvasEle = canvasEle;
|
||||
this.viewer = sdk.viewer;
|
||||
this.handler = undefined;
|
||||
this.lightCamera;
|
||||
this.pyramid;
|
||||
this.frustumPrimitive;
|
||||
this.viewershedPolygon;
|
||||
};
|
||||
ViewShed.prototype = {
|
||||
/**
|
||||
* 初始化handler
|
||||
*/
|
||||
initHandler() {
|
||||
if (this.handler) {
|
||||
this.handler.destroy();
|
||||
this.handler = undefined;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 开始执行视域分析
|
||||
* @param {number} precision 精度,值越大创建耗时越长,建议在10~20之间
|
||||
*/
|
||||
createViewshed: function (precision) {
|
||||
let $this = this;
|
||||
let scene = $this.viewer.scene;
|
||||
$this.initHandler();
|
||||
$this.clearAll();
|
||||
$this.handler = new Cesium.ScreenSpaceEventHandler($this.viewer.canvas);
|
||||
$this.handler.setInputAction((event) => {
|
||||
// 禁止地球旋转和缩放,地球的旋转会对鼠标移动监听有影响,所以需要禁止
|
||||
scene.screenSpaceCameraController.enableRotate = false;
|
||||
scene.screenSpaceCameraController.enableZoom = false;
|
||||
scene.globe.depthTestAgainstTerrain = true;
|
||||
let earthPosition = scene.pickPosition(event.position);
|
||||
let pos = $this.cartesian3ToDegree(earthPosition);
|
||||
$this.handler.setInputAction(function (event) {
|
||||
let newPosition = scene.pickPosition(event.endPosition);
|
||||
if (Cesium.defined(newPosition)) {
|
||||
let pos1 = $this.cartesian3ToDegree(newPosition);
|
||||
let distance = Cesium.Cartesian3.distance(newPosition, earthPosition);
|
||||
let angle = $this.getAngle(pos[0], pos[1], pos1[0], pos1[1]);
|
||||
let pitch = $this.getPitch(earthPosition, newPosition);
|
||||
$this.ViewShedOptions = {
|
||||
viewPosition: earthPosition, //观测点 笛卡尔坐标
|
||||
endPosition: newPosition, //目标点 笛卡尔坐标
|
||||
direction: angle, //观测方位角 默认为`0`,范围`0~360`
|
||||
pitch: pitch, //俯仰角,radius,默认为`0`
|
||||
horizontalViewAngle: 90, //可视域水平夹角,默认为 `90`,范围`0~360`
|
||||
verticalViewAngle: 60, //可视域垂直夹角,默认为`60`,范围`0~180`
|
||||
visibleAreaColor: Cesium.Color.GREEN, //可视区域颜色,默认为`green`
|
||||
invisibleAreaColor: Cesium.Color.RED, //不可见区域颜色,默认为`red`
|
||||
visualRange: distance, //距离,单位`米`
|
||||
};
|
||||
$this.updateViewShed();
|
||||
}
|
||||
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
|
||||
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);
|
||||
|
||||
$this.handler.setInputAction(() => {
|
||||
$this.initHandler();
|
||||
// 开启地球旋转和缩放
|
||||
scene.screenSpaceCameraController.enableRotate = true;
|
||||
scene.screenSpaceCameraController.enableZoom = true;
|
||||
$this.drawViewershed(precision);
|
||||
}, Cesium.ScreenSpaceEventType.LEFT_UP);
|
||||
},
|
||||
|
||||
ReturnDistance(pos0, pos1) {
|
||||
let distance = 0;
|
||||
let point1cartographic = Cesium.Cartographic.fromCartesian(pos0);
|
||||
let point2cartographic = Cesium.Cartographic.fromCartesian(pos1);
|
||||
/**根据经纬度计算出距离**/
|
||||
let geodesic = new Cesium.EllipsoidGeodesic();
|
||||
geodesic.setEndPoints(point1cartographic, point2cartographic);
|
||||
let s = geodesic.surfaceDistance;
|
||||
return s;
|
||||
},
|
||||
getHeight(x, y, objectsToExclude) {
|
||||
let endCartographic = Cesium.Cartographic.fromDegrees(x, y);
|
||||
let endHeight = this.viewer.scene.sampleHeight(
|
||||
endCartographic,
|
||||
objectsToExclude
|
||||
);
|
||||
return endHeight;
|
||||
},
|
||||
|
||||
cartesian3ToDegree: function (Cartesian3) {
|
||||
let _ellipsoid = this.viewer.scene.globe.ellipsoid;
|
||||
let _cartographic = _ellipsoid.cartesianToCartographic(Cartesian3);
|
||||
let _lat = Cesium.Math.toDegrees(_cartographic.latitude);
|
||||
let _lng = Cesium.Math.toDegrees(_cartographic.longitude);
|
||||
let _alt = _cartographic.height;
|
||||
return [_lng, _lat, _alt];
|
||||
},
|
||||
getAngle: function (lng1, lat1, lng2, lat2) {
|
||||
let dRotateAngle = Math.atan2(Math.abs(lng1 - lng2), Math.abs(lat1 - lat2));
|
||||
if (lng2 >= lng1) {
|
||||
dRotateAngle = lat2 < lat1 ? Math.PI - dRotateAngle : dRotateAngle;
|
||||
} else {
|
||||
dRotateAngle =
|
||||
lat2 >= lat1 ? 2 * Math.PI - dRotateAngle : Math.PI + dRotateAngle;
|
||||
}
|
||||
dRotateAngle = (dRotateAngle * 180) / Math.PI;
|
||||
return dRotateAngle;
|
||||
},
|
||||
|
||||
getPitch(pointA, pointB) {
|
||||
let transfrom = Cesium.Transforms.eastNorthUpToFixedFrame(pointA);
|
||||
const vector = Cesium.Cartesian3.subtract(
|
||||
pointB,
|
||||
pointA,
|
||||
new Cesium.Cartesian3()
|
||||
);
|
||||
let direction = Cesium.Matrix4.multiplyByPointAsVector(
|
||||
Cesium.Matrix4.inverse(transfrom, transfrom),
|
||||
vector,
|
||||
vector
|
||||
);
|
||||
Cesium.Cartesian3.normalize(direction, direction);
|
||||
return Cesium.Math.PI_OVER_TWO - Cesium.Math.acosClamped(direction.z);
|
||||
},
|
||||
|
||||
updateViewShed: function () {
|
||||
this.clear();
|
||||
this.setLightCamera();
|
||||
this.addVisualPyramid();
|
||||
this.createFrustum();
|
||||
},
|
||||
clear: function () {
|
||||
if (this.pyramid) {
|
||||
this.viewer.entities.removeById(this.pyramid.id);
|
||||
this.pyramid = undefined;
|
||||
}
|
||||
if (this.frustumPrimitive) {
|
||||
this.viewer.scene.primitives.remove(this.frustumPrimitive);
|
||||
this.frustumPrimitive = undefined;
|
||||
}
|
||||
if (this.debugModelMatrixPrimitive) {
|
||||
this.viewer.scene.primitives.remove(this.debugModelMatrixPrimitive);
|
||||
this.debugModelMatrixPrimitive = undefined;
|
||||
}
|
||||
},
|
||||
clearAll: function () {
|
||||
this.clear();
|
||||
if (this.viewershedPolygon) {
|
||||
this.viewer.scene.primitives.remove(this.viewershedPolygon);
|
||||
this.viewershedPolygon = undefined;
|
||||
}
|
||||
},
|
||||
addVisualPyramid: function () {
|
||||
let options = this.ViewShedOptions;
|
||||
let position = options.viewPosition;
|
||||
let visualRange = Number(options.visualRange);
|
||||
let transform = Cesium.Transforms.eastNorthUpToFixedFrame(position);
|
||||
this.debugModelMatrixPrimitive = this.viewer.scene.primitives.add(
|
||||
new Cesium.DebugModelMatrixPrimitive({
|
||||
modelMatrix: transform,
|
||||
length: 5.0,
|
||||
})
|
||||
);
|
||||
const halfClock = options.horizontalViewAngle / 2;
|
||||
const halfCone = options.verticalViewAngle / 2;
|
||||
const pitch = Cesium.Math.toDegrees(options.pitch);
|
||||
const ellipsoid = new Cesium.EllipsoidGraphics({
|
||||
radii: new Cesium.Cartesian3(visualRange, visualRange, visualRange),
|
||||
minimumClock: Cesium.Math.toRadians(90 - options.direction - halfClock),
|
||||
maximumClock: Cesium.Math.toRadians(90 - options.direction + halfClock),
|
||||
minimumCone: Cesium.Math.toRadians(90 - pitch - halfCone),
|
||||
maximumCone: Cesium.Math.toRadians(90 - pitch + halfCone),
|
||||
fill: false,
|
||||
outline: true,
|
||||
subdivisions: 256,
|
||||
stackPartitions: 64,
|
||||
slicePartitions: 64,
|
||||
outlineColor: Cesium.Color.YELLOWGREEN.withAlpha(0.5),
|
||||
});
|
||||
const pyramidEntity = new Cesium.Entity({
|
||||
position: position,
|
||||
ellipsoid,
|
||||
});
|
||||
this.pyramid = this.viewer.entities.add(pyramidEntity);
|
||||
},
|
||||
setLightCamera: function () {
|
||||
if (!this.lightCamera) {
|
||||
this.lightCamera = new Cesium.Camera(this.viewer.scene);
|
||||
}
|
||||
let options = this.ViewShedOptions;
|
||||
let visualRange = Number(options.visualRange);
|
||||
this.lightCamera.position = options.viewPosition;
|
||||
this.lightCamera.frustum.near = 0.1;
|
||||
this.lightCamera.frustum.far = visualRange;
|
||||
const hr = Cesium.Math.toRadians(options.horizontalViewAngle);
|
||||
const vr = Cesium.Math.toRadians(options.verticalViewAngle);
|
||||
this.lightCamera.frustum.aspectRatio =
|
||||
(visualRange * Math.tan(hr / 2) * 2) /
|
||||
(visualRange * Math.tan(vr / 2) * 2);
|
||||
this.lightCamera.frustum.fov = hr > vr ? hr : vr;
|
||||
this.lightCamera.setView({
|
||||
destination: options.viewPosition,
|
||||
orientation: {
|
||||
heading: Cesium.Math.toRadians(options.direction || 0),
|
||||
pitch: options.pitch || 0,
|
||||
roll: 0,
|
||||
},
|
||||
});
|
||||
},
|
||||
createFrustum: function () {
|
||||
const scratchRight = new Cesium.Cartesian3();
|
||||
const scratchRotation = new Cesium.Matrix3();
|
||||
const scratchOrientation = new Cesium.Quaternion();
|
||||
const direction = this.lightCamera.directionWC;
|
||||
const up = this.lightCamera.upWC;
|
||||
let right = this.lightCamera.rightWC;
|
||||
right = Cesium.Cartesian3.negate(right, scratchRight);
|
||||
let rotation = scratchRotation;
|
||||
Cesium.Matrix3.setColumn(rotation, 0, right, rotation);
|
||||
Cesium.Matrix3.setColumn(rotation, 1, up, rotation);
|
||||
Cesium.Matrix3.setColumn(rotation, 2, direction, rotation);
|
||||
let orientation = Cesium.Quaternion.fromRotationMatrix(
|
||||
rotation,
|
||||
scratchOrientation
|
||||
);
|
||||
let instanceOutline = new Cesium.GeometryInstance({
|
||||
geometry: new Cesium.FrustumOutlineGeometry({
|
||||
frustum: this.lightCamera.frustum,
|
||||
origin: this.ViewShedOptions.viewPosition,
|
||||
orientation: orientation,
|
||||
}),
|
||||
id: "视椎体轮廓线" + Math.random().toString(36).substr(2),
|
||||
attributes: {
|
||||
color: Cesium.ColorGeometryInstanceAttribute.fromColor(
|
||||
new Cesium.Color(0.0, 1.0, 0.0, 1.0)
|
||||
),
|
||||
show: new Cesium.ShowGeometryInstanceAttribute(true),
|
||||
},
|
||||
});
|
||||
this.frustumPrimitive = this.viewer.scene.primitives.add(
|
||||
new Cesium.Primitive({
|
||||
geometryInstances: instanceOutline,
|
||||
appearance: new Cesium.PerInstanceColorAppearance({
|
||||
flat: true,
|
||||
translucent: false,
|
||||
closed: true,
|
||||
}),
|
||||
})
|
||||
);
|
||||
},
|
||||
createPoint: function (firstPos, secondPos) {
|
||||
let entity4FirstPos = new Cesium.Entity({
|
||||
name: "firstPos",
|
||||
show: true,
|
||||
position: firstPos,
|
||||
point: {
|
||||
show: true,
|
||||
pixelSize: 20,
|
||||
color: Cesium.Color.RED,
|
||||
outlineColor: Cesium.Color.YELLOW,
|
||||
outlineWidth: 5,
|
||||
},
|
||||
description: `
|
||||
<p>这是绘制的视椎体起点</p>`,
|
||||
});
|
||||
this.viewer.entities.add(entity4FirstPos);
|
||||
let entity4SecondPos = new Cesium.Entity({
|
||||
name: "secondPos",
|
||||
show: true,
|
||||
position: secondPos,
|
||||
point: {
|
||||
show: true,
|
||||
pixelSize: 30,
|
||||
color: Cesium.Color.YELLOW,
|
||||
outlineColor: Cesium.Color.RED,
|
||||
outlineWidth: 8,
|
||||
},
|
||||
description: `
|
||||
<p>这是绘制的视椎体视角终点</p>`,
|
||||
});
|
||||
this.viewer.entities.add(entity4SecondPos);
|
||||
},
|
||||
|
||||
//绘制可视域
|
||||
add(positionArr) {
|
||||
let polygon = new Cesium.PolygonGeometry({
|
||||
polygonHierarchy: new Cesium.PolygonHierarchy(
|
||||
Cesium.Cartesian3.fromDegreesArray(positionArr)
|
||||
),
|
||||
height: 0.0,
|
||||
extrudedHeight: 0.0,
|
||||
vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT,
|
||||
stRotation: 0.0, // 纹理的旋转坐标(以弧度为单位),正旋转是逆时针方向
|
||||
ellipsoid: Cesium.Ellipsoid.WGS84,
|
||||
granularity: Cesium.Math.RADIANS_PER_DEGREE, // 每个纬度和经度之间的距离(以弧度为单位),确定缓冲区中的位置数
|
||||
perPositionHeight: false, // 每个位置点使用的高度
|
||||
closeTop: true,
|
||||
closeBottom: true,
|
||||
// NONE 与椭圆表面不符的直线;GEODESIC 遵循测地路径;RHUMB 遵循大黄蜂或恶魔般的道路。
|
||||
arcType: Cesium.ArcType.GEODESIC, // 多边形边缘线型
|
||||
});
|
||||
|
||||
let polygonInstance = new Cesium.GeometryInstance({
|
||||
geometry: polygon,
|
||||
name: "ViewershedPolygon",
|
||||
attributes: {
|
||||
color: Cesium.ColorGeometryInstanceAttribute.fromColor(
|
||||
Cesium.Color.BLUE.withAlpha(0.6)
|
||||
),
|
||||
show: new Cesium.ShowGeometryInstanceAttribute(true), //显示或者隐藏
|
||||
},
|
||||
});
|
||||
this.viewershedPolygon = this.viewer.scene.primitives.add(
|
||||
new Cesium.GroundPrimitive({
|
||||
geometryInstances: polygonInstance,
|
||||
appearance: new Cesium.EllipsoidSurfaceAppearance({
|
||||
aboveGround: true,
|
||||
material: new Cesium.Material({
|
||||
fabric: {
|
||||
type: "Image",
|
||||
uniforms: {
|
||||
image: this.returnImgae(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
})
|
||||
);
|
||||
},
|
||||
drawViewershed(precision) {
|
||||
const pos = this.cartesian3ToDegree(this.ViewShedOptions.viewPosition);
|
||||
const radius = this.ViewShedOptions.visualRange;
|
||||
const direction = this.ViewShedOptions.direction;
|
||||
let boundary = this.computeBoundaryOptions(pos, radius, direction);
|
||||
const bbox = boundary.bbox;
|
||||
let mask = turf.polygon([boundary.boundaryPoints]);
|
||||
const dis = this.ViewShedOptions.visualRange / (precision * 1000);
|
||||
let gridPoints = turf.pointGrid(bbox, dis, { mask: mask });
|
||||
|
||||
let pointsResult = this.createTargetPoints(gridPoints, dis, pos);
|
||||
let variogram = kriging.train(
|
||||
pointsResult.values,
|
||||
pointsResult.lngs,
|
||||
pointsResult.lats,
|
||||
"exponential",
|
||||
0,
|
||||
100
|
||||
);
|
||||
let grid = kriging.grid([boundary.boundaryPoints], variogram, dis / 1000);
|
||||
const colors = [
|
||||
"#ff000080",
|
||||
"#ff000080",
|
||||
"#ff000080",
|
||||
"#ff000080",
|
||||
"#ff000080",
|
||||
"#ff000080",
|
||||
"#00ff0080",
|
||||
"#00ff0080",
|
||||
"#00ff0080",
|
||||
"#00ff0080",
|
||||
"#00ff0080",
|
||||
"#00ff0080",
|
||||
];
|
||||
|
||||
this.canvasEle.width = 3840;
|
||||
this.canvasEle.height = 2160;
|
||||
kriging.plot(
|
||||
this.canvasEle,
|
||||
grid,
|
||||
[bbox[0], bbox[2]],
|
||||
[bbox[1], bbox[3]],
|
||||
colors
|
||||
);
|
||||
this.add(boundary.positionArr);
|
||||
},
|
||||
computeBoundaryOptions(pos, radius, angle) {
|
||||
let Ea = 6378137; // 赤道半径
|
||||
let Eb = 6356725; // 极半径
|
||||
const lng = pos[0],
|
||||
lat = pos[1];
|
||||
const bbox = [lng, lat, lng, lat]; //[minX, minY, maxX, maxY]
|
||||
let positionArr = [];
|
||||
let boundaryPoints = [];
|
||||
positionArr.push(lng, lat);
|
||||
boundaryPoints.push([lng, lat]);
|
||||
//正北是0°
|
||||
let start = angle + 45 > 360 ? angle - 45 - 360 : angle - 45;
|
||||
let end = start + 90;
|
||||
for (let i = start; i <= end; i++) {
|
||||
let dx = radius * Math.sin((i * Math.PI) / 180.0);
|
||||
let dy = radius * Math.cos((i * Math.PI) / 180.0);
|
||||
let ec = Eb + ((Ea - Eb) * (90.0 - lat)) / 90.0;
|
||||
let ed = ec * Math.cos((lat * Math.PI) / 180);
|
||||
let BJD = lng + ((dx / ed) * 180.0) / Math.PI;
|
||||
let BWD = lat + ((dy / ec) * 180.0) / Math.PI;
|
||||
positionArr.push(BJD, BWD);
|
||||
boundaryPoints.push([BJD, BWD]);
|
||||
this.refreshBBox(bbox, BJD, BWD);
|
||||
}
|
||||
boundaryPoints.push([lng, lat]);
|
||||
return {
|
||||
positionArr,
|
||||
boundaryPoints,
|
||||
bbox,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* 更新外围矩形 Bbox
|
||||
* @param {Array} result 外围矩形Bbox-[minX, minY, maxX, maxY]
|
||||
* @param {Number} x 经度
|
||||
* @param {Number} y 纬度
|
||||
*/
|
||||
refreshBBox(result, x, y) {
|
||||
result[0] = x < result[0] ? x : result[0];
|
||||
result[1] = y < result[1] ? y : result[1];
|
||||
result[2] = x > result[2] ? x : result[2];
|
||||
result[3] = y > result[3] ? y : result[3];
|
||||
},
|
||||
/**
|
||||
* 插值点用射线判断通视性
|
||||
* @param {*} gridPoints 网格点
|
||||
* @param {*} step 步长,可以理解成是精度
|
||||
* @param {*} sourcePos 视域分析起点
|
||||
* @returns kriging插值所需的参数对象{ values:[], lngs:[], lats:[]}
|
||||
*/
|
||||
createTargetPoints(gridPoints, step, sourcePos) {
|
||||
let positionArr = [];
|
||||
let objectsToExclude = [
|
||||
this.frustumPrimitive,
|
||||
this.pyramid,
|
||||
this.debugModelMatrixPrimitive,
|
||||
];
|
||||
let values = [],
|
||||
lngs = [],
|
||||
lats = [];
|
||||
let height = this.getHeight(sourcePos[0], sourcePos[1], objectsToExclude);
|
||||
positionArr.push({
|
||||
x: sourcePos[0],
|
||||
y: sourcePos[1],
|
||||
z: height,
|
||||
});
|
||||
let viewPoint = this.ViewShedOptions.viewPosition;
|
||||
for (let index = 0; index < gridPoints.features.length; index++) {
|
||||
const feature = gridPoints.features[index];
|
||||
const coords = feature.geometry.coordinates;
|
||||
const x = coords[0],
|
||||
y = coords[1];
|
||||
let h = this.getHeight(x, y, objectsToExclude);
|
||||
let endPoint = Cesium.Cartesian3.fromDegrees(x, y, h);
|
||||
let direction = Cesium.Cartesian3.normalize(
|
||||
Cesium.Cartesian3.subtract(
|
||||
endPoint,
|
||||
viewPoint,
|
||||
new Cesium.Cartesian3()
|
||||
),
|
||||
new Cesium.Cartesian3()
|
||||
);
|
||||
// 建立射线
|
||||
let ray = new Cesium.Ray(viewPoint, direction);
|
||||
let result = this.viewer.scene.pickFromRay(ray, objectsToExclude); // 计算交互点,返回第一个
|
||||
if (result) {
|
||||
let buffer = this.ReturnDistance(endPoint, result.position);
|
||||
// let M_color = Cesium.Color.GREEN;
|
||||
if (buffer > step) {
|
||||
// M_color = Cesium.Color.RED;
|
||||
values.push(0);
|
||||
} else {
|
||||
values.push(1);
|
||||
}
|
||||
lngs.push(x);
|
||||
lats.push(y);
|
||||
// this.viewer.entities.add(
|
||||
// new Cesium.Entity({
|
||||
// name: "插值点哦",
|
||||
// show: true,
|
||||
// position: endPoint,
|
||||
// point: {
|
||||
// show: true,
|
||||
// pixelSize: 10,
|
||||
// color: M_color,
|
||||
// outlineWidth: 2,
|
||||
// outlineColor: Cesium.Color.YELLOW,
|
||||
// },
|
||||
// })
|
||||
// );
|
||||
}
|
||||
}
|
||||
return {
|
||||
values,
|
||||
lngs,
|
||||
lats,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* canvas转image图片
|
||||
* @returns base64图片
|
||||
*/
|
||||
returnImgae() {
|
||||
return this.canvasEle.toDataURL("image/png");
|
||||
},
|
||||
};
|
||||
|
||||
export default ViewShed;
|
131
src/Obj/Analysis/CircleViewShed/glsl.js
Normal file
131
src/Obj/Analysis/CircleViewShed/glsl.js
Normal file
@ -0,0 +1,131 @@
|
||||
export default `
|
||||
#define USE_CUBE_MAP_SHADOW true
|
||||
uniform sampler2D colorTexture;
|
||||
uniform sampler2D depthTexture;
|
||||
varying vec2 v_textureCoordinates;
|
||||
uniform mat4 camera_projection_matrix;
|
||||
uniform mat4 camera_view_matrix;
|
||||
uniform samplerCube shadowMap_textureCube;
|
||||
uniform mat4 shadowMap_matrix;
|
||||
uniform vec4 shadowMap_lightPositionEC;
|
||||
uniform vec4 shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness;
|
||||
uniform vec4 shadowMap_texelSizeDepthBiasAndNormalShadingSmooth;
|
||||
uniform float helsing_viewDistance;
|
||||
uniform vec4 helsing_visibleAreaColor;
|
||||
uniform vec4 helsing_invisibleAreaColor;
|
||||
|
||||
struct zx_shadowParameters
|
||||
{
|
||||
vec3 texCoords;
|
||||
float depthBias;
|
||||
float depth;
|
||||
float nDotL;
|
||||
vec2 texelStepSize;
|
||||
float normalShadingSmooth;
|
||||
float darkness;
|
||||
};
|
||||
|
||||
float czm_shadowVisibility(samplerCube shadowMap, zx_shadowParameters shadowParameters)
|
||||
{
|
||||
float depthBias = shadowParameters.depthBias;
|
||||
float depth = shadowParameters.depth;
|
||||
float nDotL = shadowParameters.nDotL;
|
||||
float normalShadingSmooth = shadowParameters.normalShadingSmooth;
|
||||
float darkness = shadowParameters.darkness;
|
||||
vec3 uvw = shadowParameters.texCoords;
|
||||
depth -= depthBias;
|
||||
float visibility = czm_shadowDepthCompare(shadowMap, uvw, depth);
|
||||
return czm_private_shadowVisibility(visibility, nDotL, normalShadingSmooth, darkness);
|
||||
}
|
||||
|
||||
vec4 getPositionEC(){
|
||||
return czm_windowToEyeCoordinates(gl_FragCoord);
|
||||
}
|
||||
|
||||
vec3 getNormalEC(){
|
||||
return vec3(1.);
|
||||
}
|
||||
|
||||
vec4 toEye(in vec2 uv,in float depth){
|
||||
vec2 xy=vec2((uv.x*2.-1.),(uv.y*2.-1.));
|
||||
vec4 posInCamera=czm_inverseProjection*vec4(xy,depth,1.);
|
||||
posInCamera=posInCamera/posInCamera.w;
|
||||
return posInCamera;
|
||||
}
|
||||
|
||||
vec3 pointProjectOnPlane(in vec3 planeNormal,in vec3 planeOrigin,in vec3 point){
|
||||
vec3 v01=point-planeOrigin;
|
||||
float d=dot(planeNormal,v01);
|
||||
return(point-planeNormal*d);
|
||||
}
|
||||
|
||||
float getDepth(in vec4 depth){
|
||||
float z_window=czm_unpackDepth(depth);
|
||||
z_window=czm_reverseLogDepth(z_window);
|
||||
float n_range=czm_depthRange.near;
|
||||
float f_range=czm_depthRange.far;
|
||||
return(2.*z_window-n_range-f_range)/(f_range-n_range);
|
||||
}
|
||||
|
||||
float shadow(in vec4 positionEC){
|
||||
vec3 normalEC=getNormalEC();
|
||||
zx_shadowParameters shadowParameters;
|
||||
shadowParameters.texelStepSize=shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.xy;
|
||||
shadowParameters.depthBias=shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.z;
|
||||
shadowParameters.normalShadingSmooth=shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.w;
|
||||
shadowParameters.darkness=shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness.w;
|
||||
vec3 directionEC=positionEC.xyz-shadowMap_lightPositionEC.xyz;
|
||||
float distance=length(directionEC);
|
||||
directionEC=normalize(directionEC);
|
||||
float radius=shadowMap_lightPositionEC.w;
|
||||
if(distance>radius)
|
||||
{
|
||||
return 2.0;
|
||||
}
|
||||
vec3 directionWC=czm_inverseViewRotation*directionEC;
|
||||
shadowParameters.depth=distance/radius-0.0003;
|
||||
shadowParameters.nDotL=clamp(dot(normalEC,-directionEC),0.,1.);
|
||||
shadowParameters.texCoords=directionWC;
|
||||
float visibility=czm_shadowVisibility(shadowMap_textureCube,shadowParameters);
|
||||
return visibility;
|
||||
}
|
||||
|
||||
bool visible(in vec4 result)
|
||||
{
|
||||
result.x/=result.w;
|
||||
result.y/=result.w;
|
||||
result.z/=result.w;
|
||||
return result.x>=-1.&&result.x<=1.
|
||||
&&result.y>=-1.&&result.y<=1.
|
||||
&&result.z>=-1.&&result.z<=1.;
|
||||
}
|
||||
|
||||
void main(){
|
||||
// 釉色 = 结构二维(颜色纹理, 纹理坐标)
|
||||
gl_FragColor = texture2D(colorTexture, v_textureCoordinates);
|
||||
// 深度 = 获取深度(结构二维(深度纹理, 纹理坐标))
|
||||
float depth = getDepth(texture2D(depthTexture, v_textureCoordinates));
|
||||
// 视角 = (纹理坐标, 深度)
|
||||
vec4 viewPos = toEye(v_textureCoordinates, depth);
|
||||
// 世界坐标
|
||||
vec4 wordPos = czm_inverseView * viewPos;
|
||||
// 虚拟相机中坐标
|
||||
vec4 vcPos = camera_view_matrix * wordPos;
|
||||
float near = .001 * helsing_viewDistance;
|
||||
float dis = length(vcPos.xyz);
|
||||
if(dis > near && dis < helsing_viewDistance){
|
||||
// 透视投影
|
||||
vec4 posInEye = camera_projection_matrix * vcPos;
|
||||
// 可视区颜色
|
||||
// vec4 helsing_visibleAreaColor=vec4(0.,1.,0.,.5);
|
||||
// vec4 helsing_invisibleAreaColor=vec4(1.,0.,0.,.5);
|
||||
if(visible(posInEye)){
|
||||
float vis = shadow(viewPos);
|
||||
if(vis > 0.3){
|
||||
// gl_FragColor = mix(gl_FragColor,helsing_visibleAreaColor,.5);
|
||||
} else {
|
||||
gl_FragColor = mix(gl_FragColor,helsing_invisibleAreaColor,.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}`;
|
380
src/Obj/Analysis/CircleViewShed/index.js
Normal file
380
src/Obj/Analysis/CircleViewShed/index.js
Normal file
@ -0,0 +1,380 @@
|
||||
// ViewShed.js
|
||||
import Event from '../../../Event'
|
||||
import MouseTip from '../../../MouseTip'
|
||||
import Tools from '../../../Tools'
|
||||
import EventBinding from '../../Element/Dialog/eventBinding'
|
||||
import Dialog from '../../../BaseDialog'
|
||||
import { html } from './_element'
|
||||
/**
|
||||
* @constructor
|
||||
* @description 可视域分析
|
||||
* @param sdk
|
||||
* @param {Object} options 选项。
|
||||
* @param {Number} options.viewPointHeight=1.8 视点高度(m)。
|
||||
* @param {Number} options.precision=20 精度。
|
||||
* @param {String} options.visibleAreaColor=#008000 可视区域颜色。
|
||||
* @param {String} options.invisibleAreaColor=#FF0000 不可视区域颜色。
|
||||
*/
|
||||
class CircleViewShed extends Tools {
|
||||
#intervalEvents = new Map()
|
||||
|
||||
constructor(sdk, options = {}, _Dialog = {}) {
|
||||
super(sdk, options)
|
||||
|
||||
this.viewer = sdk.viewer
|
||||
this.options = {}
|
||||
this.options.visibleAreaColor = options.visibleAreaColor || '#008000'
|
||||
this.options.invisibleAreaColor = options.invisibleAreaColor || '#FF0000'
|
||||
this.ids = []
|
||||
this.primitives = []
|
||||
this.viewpointPrimitive = null
|
||||
|
||||
this._elms = {}
|
||||
this.precision = options.precision
|
||||
this.viewPointHeight = options.viewPointHeight
|
||||
this.Dialog = _Dialog
|
||||
this._EventBinding = new EventBinding()
|
||||
this.html = null
|
||||
YJ.Analysis.AnalysesResults.push(this)
|
||||
CircleViewShed.edit(this)
|
||||
// CircleViewShed.create(this)
|
||||
}
|
||||
|
||||
get viewPointHeight() {
|
||||
return this.options.viewPointHeight
|
||||
}
|
||||
|
||||
set viewPointHeight(v) {
|
||||
let viewPointHeight = Math.floor(Number(v) * 10) / 10
|
||||
if (isNaN(viewPointHeight)) {
|
||||
viewPointHeight = 1.8
|
||||
}
|
||||
if (viewPointHeight < 0) {
|
||||
viewPointHeight = 0
|
||||
}
|
||||
this.options.viewPointHeight = viewPointHeight
|
||||
this._elms.viewPointHeight &&
|
||||
this._elms.viewPointHeight.forEach(item => {
|
||||
item.value = viewPointHeight
|
||||
})
|
||||
}
|
||||
|
||||
get precision() {
|
||||
return this.options.precision
|
||||
}
|
||||
|
||||
set precision(v) {
|
||||
let precision = Math.floor(Number(v))
|
||||
if (isNaN(precision)) {
|
||||
precision = 20
|
||||
} else if (precision < 1) {
|
||||
precision = 1
|
||||
}
|
||||
this.options.precision = precision
|
||||
this._elms.precision &&
|
||||
this._elms.precision.forEach(item => {
|
||||
item.value = precision
|
||||
})
|
||||
}
|
||||
|
||||
static create(that) {
|
||||
let count = 0
|
||||
if (!YJ.Measure.GetMeasureStatus()) {
|
||||
if (that._DialogObject && that._DialogObject.close) {
|
||||
that._DialogObject.close()
|
||||
that._DialogObject = null
|
||||
}
|
||||
let Draw = new YJ.Draw.DrawCircle(that.sdk)
|
||||
Draw.start(async (a, options) => {
|
||||
// that.center = options.center
|
||||
if(!options) {
|
||||
return
|
||||
}
|
||||
that.radius = options.radius
|
||||
let positions = await Cesium.sampleTerrainMostDetailed(
|
||||
that.sdk.viewer.terrainProvider,
|
||||
[Cesium.Cartographic.fromDegrees(options.center.lng, options.center.lat)]
|
||||
);
|
||||
that.center = {
|
||||
lng: options.center.lng,
|
||||
lat: options.center.lat,
|
||||
alt: positions[0].height
|
||||
}
|
||||
await that.analyse()
|
||||
})
|
||||
} else {
|
||||
console.log('上一次测量未结束')
|
||||
}
|
||||
}
|
||||
|
||||
static async edit(that) {
|
||||
if (that._DialogObject && that._DialogObject.close) {
|
||||
that._DialogObject.close()
|
||||
that._DialogObject = null
|
||||
}
|
||||
that._DialogObject = await new Dialog(that.sdk.viewer._container, {
|
||||
title: '圆形视域分析',
|
||||
left: '180px',
|
||||
top: '100px',
|
||||
closeCallBack: () => {
|
||||
that.Dialog.closeCallBack && that.Dialog.closeCallBack()
|
||||
YJ.Measure.SetMeasureStatus(false)
|
||||
}
|
||||
})
|
||||
await that._DialogObject.init()
|
||||
that._DialogObject._element.body.className =
|
||||
that._DialogObject._element.body.className + ' circle-view-shed'
|
||||
let contentElm = document.createElement('div')
|
||||
contentElm.innerHTML = html()
|
||||
that._DialogObject.contentAppChild(contentElm)
|
||||
|
||||
let drawElm = document.createElement('button')
|
||||
drawElm.innerHTML = '绘制'
|
||||
drawElm.addEventListener('click', () => {
|
||||
let terrainAvailability = that.viewer.terrainProvider.availability;
|
||||
if (!terrainAvailability) {
|
||||
window.ELEMENT && window.ELEMENT.Message({
|
||||
message: '未加载地形数据!',
|
||||
type: 'warning',
|
||||
duration: 1500
|
||||
});
|
||||
return
|
||||
}
|
||||
CircleViewShed.create(that)
|
||||
})
|
||||
that._DialogObject.footAppChild(drawElm)
|
||||
|
||||
let all_elm = contentElm.getElementsByTagName('*')
|
||||
that._EventBinding.on(that, all_elm)
|
||||
that._elms = that._EventBinding.element
|
||||
}
|
||||
|
||||
analyse() {
|
||||
// this.destroy()
|
||||
let center = [this.center.lng, this.center.lat]
|
||||
let radius = this.radius / 1000
|
||||
let circle = turf.circle(center, radius, {
|
||||
steps: 180,
|
||||
units: 'kilometers',
|
||||
properties: { foo: 'bar' }
|
||||
})
|
||||
if (!this.viewpointPrimitive) {
|
||||
this.viewpointPrimitive = this.viewer.scene.primitives.add(
|
||||
new Cesium.PointPrimitiveCollection()
|
||||
)
|
||||
}
|
||||
if (!this.viewBillboardPrimitive) {
|
||||
this.viewBillboardPrimitive = this.viewer.scene.primitives.add(
|
||||
new Cesium.BillboardCollection()
|
||||
)
|
||||
}
|
||||
|
||||
let array = []
|
||||
let distance = radius / this.precision
|
||||
for (let i = 1; i < circle.geometry.coordinates[0].length; i++) {
|
||||
let line = turf.lineString([center, circle.geometry.coordinates[0][i]])
|
||||
let array2 = []
|
||||
for (let j = 1; j <= this.precision; j++) {
|
||||
let sliced = turf.lineSliceAlong(line, 0, distance * j, {
|
||||
units: 'kilometers'
|
||||
})
|
||||
array2.push([
|
||||
sliced.geometry.coordinates[1][0],
|
||||
sliced.geometry.coordinates[1][1]
|
||||
])
|
||||
}
|
||||
array.push(array2)
|
||||
}
|
||||
|
||||
let viewPoint = Cesium.Cartesian3.fromDegrees(
|
||||
this.center.lng,
|
||||
this.center.lat,
|
||||
this.center.alt + this.viewPointHeight
|
||||
)
|
||||
let instances = []
|
||||
CircleViewShed.getcanvas(this).then(canvas =>
|
||||
this.viewBillboardPrimitive.add({
|
||||
position: viewPoint,
|
||||
image: canvas,
|
||||
width: 200,
|
||||
height: 140,
|
||||
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
|
||||
disableDepthTestDistance: Number.POSITIVE_INFINITY
|
||||
})
|
||||
)
|
||||
this.viewpointPrimitive.add({
|
||||
position: viewPoint,
|
||||
color: Cesium.Color.AQUA.withAlpha(1),
|
||||
pixelSize: 6
|
||||
})
|
||||
|
||||
let m = 0
|
||||
let _this = this
|
||||
let key = this.randomString()
|
||||
let intervalEvent = setInterval(() => {
|
||||
if (m >= array.length) {
|
||||
let item = this.#intervalEvents.get(key)
|
||||
item && clearInterval(item.event)
|
||||
return
|
||||
}
|
||||
InBatches(m)
|
||||
m += 1
|
||||
}, 0)
|
||||
this.#intervalEvents.set(key, { event: intervalEvent })
|
||||
|
||||
function InBatches(k) {
|
||||
let instances = []
|
||||
let i = k
|
||||
for (let j = 0; j < array[i].length; j++) {
|
||||
let pt1 = array[i][j]
|
||||
let pt2
|
||||
let pt3
|
||||
let pt4 = array[i][j - 1]
|
||||
if (i == array.length - 1) {
|
||||
pt2 = array[0][j]
|
||||
pt3 = array[0][j - 1]
|
||||
} else {
|
||||
pt2 = array[i + 1][j]
|
||||
pt3 = array[i + 1][j - 1]
|
||||
}
|
||||
if (j == 0) {
|
||||
pt3 = [...center]
|
||||
pt4 = []
|
||||
}
|
||||
let cpt = [(pt1[0] + pt3[0]) / 2, (pt1[1] + pt3[1]) / 2]
|
||||
let cartographic = Cesium.Cartographic.fromDegrees(cpt[0], cpt[1])
|
||||
let height = _this.viewer.scene.globe.getHeight(cartographic)
|
||||
let targetPoint = Cesium.Cartesian3.fromDegrees(cpt[0], cpt[1], height)
|
||||
|
||||
let direction = Cesium.Cartesian3.normalize(
|
||||
Cesium.Cartesian3.subtract(
|
||||
targetPoint,
|
||||
viewPoint,
|
||||
new Cesium.Cartesian3()
|
||||
),
|
||||
new Cesium.Cartesian3()
|
||||
)
|
||||
let ray = new Cesium.Ray(viewPoint, direction)
|
||||
let pickedObjects = _this.viewer.scene.drillPickFromRay(
|
||||
ray,
|
||||
_this.primitives
|
||||
)
|
||||
let result
|
||||
for (let i = 0; i < pickedObjects.length; i++) {
|
||||
if (pickedObjects[i].position) {
|
||||
result = pickedObjects[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
let color = Cesium.Color.LIME
|
||||
if (
|
||||
result &&
|
||||
Math.abs(result.position.x - targetPoint.x) > 0.01 &&
|
||||
Math.abs(result.position.y - targetPoint.y) > 0.01 &&
|
||||
Math.abs(result.position.z - targetPoint.z) > 0.01
|
||||
) {
|
||||
color = Cesium.Color.RED
|
||||
}
|
||||
let polyline = new Cesium.GroundPolylineGeometry({
|
||||
positions: Cesium.Cartesian3.fromDegreesArray([
|
||||
...pt1,
|
||||
...pt2,
|
||||
...pt3,
|
||||
...pt4,
|
||||
...pt1
|
||||
]),
|
||||
width: 2
|
||||
})
|
||||
|
||||
let polygonInstance = new Cesium.GeometryInstance({
|
||||
geometry: polyline,
|
||||
name: 'ViewershedPolygon',
|
||||
attributes: {
|
||||
color: Cesium.ColorGeometryInstanceAttribute.fromColor(color),
|
||||
show: new Cesium.ShowGeometryInstanceAttribute(true) //显示或者隐藏
|
||||
}
|
||||
})
|
||||
instances.push(polygonInstance)
|
||||
}
|
||||
|
||||
_this.primitives.push(
|
||||
_this.viewer.scene.primitives.add(
|
||||
new Cesium.GroundPolylinePrimitive({
|
||||
geometryInstances: instances,
|
||||
appearance: new Cesium.PolylineColorAppearance()
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
static getcanvas(that) {
|
||||
const canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d')
|
||||
canvas.width = 220
|
||||
canvas.height = 140
|
||||
canvas.style.background = '#000000'
|
||||
let img = new Image()
|
||||
const data = [
|
||||
{
|
||||
images: that.getSourceRootPath() + '/img/bubble/lng.png',
|
||||
text: '经度:' + parseFloat(that.center.lng.toFixed(10)) + '°'
|
||||
},
|
||||
{
|
||||
images: that.getSourceRootPath() + '/img/bubble/lat.png',
|
||||
text: '纬度:' + parseFloat(that.center.lat.toFixed(10)) + '°'
|
||||
},
|
||||
{
|
||||
images: that.getSourceRootPath() + '/img/bubble/h.png',
|
||||
text: '视高:' + that.viewPointHeight + ' m'
|
||||
},
|
||||
{
|
||||
images: that.getSourceRootPath() + '/img/bubble/radius.png',
|
||||
text: '半径:' + that.radius + ' m'
|
||||
}
|
||||
]
|
||||
img.src = that.getSourceRootPath() + '/img/bubble/bubble.png'
|
||||
let imagesLoaded = 0
|
||||
return new Promise(async (resolve, reject) => {
|
||||
img.onload = () => {
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
|
||||
data.forEach((item, index) => {
|
||||
const img = new Image()
|
||||
img.src = item.images
|
||||
img.onload = () => {
|
||||
ctx.drawImage(img, 12, 12 + index * 26)
|
||||
ctx.fillStyle = '#fff'
|
||||
ctx.font = '12px Arial'
|
||||
ctx.fillText(item.text, 44, 28 + index * 26)
|
||||
imagesLoaded++
|
||||
if (imagesLoaded === data.length) {
|
||||
resolve(canvas)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
destroy() {
|
||||
for (const [key, value] of this.#intervalEvents) {
|
||||
clearInterval(value.event)
|
||||
}
|
||||
this.#intervalEvents = new Map()
|
||||
for (let i = 0; i < this.primitives.length; i++) {
|
||||
this.viewer.scene.primitives.remove(this.primitives[i])
|
||||
}
|
||||
this.primitives = []
|
||||
if (this.viewpointPrimitive) {
|
||||
this.viewer.scene.primitives.remove(this.viewpointPrimitive)
|
||||
this.viewpointPrimitive = null
|
||||
}
|
||||
if (this.viewBillboardPrimitive) {
|
||||
this.viewer.scene.primitives.remove(this.viewBillboardPrimitive)
|
||||
this.viewBillboardPrimitive = null
|
||||
}
|
||||
YJ.Measure.SetMeasureStatus(false)
|
||||
}
|
||||
}
|
||||
|
||||
export default CircleViewShed
|
Reference in New Issue
Block a user