|
@@ -0,0 +1,499 @@
|
|
|
+const SunshineAnalysisByJS = {
|
|
|
+ /**
|
|
|
+ *
|
|
|
+ * @param {*} postions 经纬度 要分析的点的位置(是世界坐标)
|
|
|
+ * @param {*} selDate 日期
|
|
|
+ * @param {*} startTime 开始时间
|
|
|
+ * @param {*} endTime 结束时间
|
|
|
+ * @returns
|
|
|
+ */
|
|
|
+ PointSunshineAnalysis(
|
|
|
+ postions,
|
|
|
+ selDate,
|
|
|
+ startTime,
|
|
|
+ endTime,
|
|
|
+ HourSegmentation = 1
|
|
|
+ ) {
|
|
|
+ let framebufferCopy = viewer.scene._view.globeDepth._copyDepthFramebuffer;
|
|
|
+ let context = viewer.scene.context;
|
|
|
+ let width = viewer.scene.drawingBufferWidth;
|
|
|
+ let height = viewer.scene.drawingBufferHeight;
|
|
|
+ let pixels = context.readPixels({
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ width: width,
|
|
|
+ height: height,
|
|
|
+ framebuffer: framebufferCopy,
|
|
|
+ });
|
|
|
+ //根据传入时间计算实际日照时间(传入时间可能存在日落之前或之后的时间,应该去除)
|
|
|
+ var rcrlTime = SunshineAnalysisByJS.calculateTime(
|
|
|
+ selDate,
|
|
|
+ startTime,
|
|
|
+ endTime - 1
|
|
|
+ );
|
|
|
+ for (
|
|
|
+ var Timenumber = rcrlTime.jssunriseHours;
|
|
|
+ Timenumber <= rcrlTime.jssunsetHours;
|
|
|
+ Timenumber++
|
|
|
+ ) {
|
|
|
+ // var HourIsLineOfSightClear = false;
|
|
|
+ //增加精度,每小时分多时段采集
|
|
|
+ for (var fzs = 1; fzs <= HourSegmentation; fzs++) {
|
|
|
+ var date = new Date(
|
|
|
+ new Date(selDate).setHours(
|
|
|
+ Timenumber,
|
|
|
+ fzs * (60 / HourSegmentation),
|
|
|
+ 0
|
|
|
+ )
|
|
|
+ );
|
|
|
+ //获取太阳的世界坐标,
|
|
|
+ let transforMatrix = Cesium.Transforms.computeTemeToPseudoFixedMatrix(
|
|
|
+ Cesium.JulianDate.fromDate(date)
|
|
|
+ );
|
|
|
+ let sunposition2 =
|
|
|
+ Cesium.Simon1994PlanetaryPositions.computeSunPositionInEarthInertialFrame(
|
|
|
+ Cesium.JulianDate.fromDate(date)
|
|
|
+ );
|
|
|
+ Cesium.Matrix3.multiplyByVector(
|
|
|
+ transforMatrix,
|
|
|
+ sunposition2,
|
|
|
+ sunposition2
|
|
|
+ );
|
|
|
+
|
|
|
+ for (let index = 0; index < postions.length; index++) {
|
|
|
+ const postion = postions[index];
|
|
|
+ var fx = new Cesium.Cartesian3(
|
|
|
+ sunposition2.x - postion.x,
|
|
|
+ sunposition2.y - postion.y,
|
|
|
+ sunposition2.z - postion.z
|
|
|
+ );
|
|
|
+ var dec = Math.sqrt(fx.x * fx.x + fx.y * fx.y + fx.z * fx.z);
|
|
|
+ var fxxl = new Cesium.Cartesian3(fx.x / dec, fx.y / dec, fx.z / dec); //方向向量
|
|
|
+ var changdu = 200;
|
|
|
+ var zd = new Cesium.Cartesian3(
|
|
|
+ postion.x + changdu * fxxl.x,
|
|
|
+ postion.y + changdu * fxxl.y,
|
|
|
+ postion.z + changdu * fxxl.z
|
|
|
+ );
|
|
|
+ //如果线之间被隔断,就认为该点被阻挡了
|
|
|
+ var isLineOfSightClear = SunshineAnalysisByJS.jiance(
|
|
|
+ scene,
|
|
|
+ context,
|
|
|
+ pixels,
|
|
|
+ postion,
|
|
|
+ zd
|
|
|
+ );
|
|
|
+ // true 不在阴影中 false 在阴影中
|
|
|
+ if (isLineOfSightClear) {
|
|
|
+ // HourIsLineOfSightClear = true;
|
|
|
+ postion.rzsc += 60 / HourSegmentation / 60;
|
|
|
+ if (postion.rzsc > 5) {
|
|
|
+ postion.rzsc = 5;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return postions;
|
|
|
+ },
|
|
|
+
|
|
|
+ jiance(scene, context, pixels, start, end) {
|
|
|
+ var num = 100;
|
|
|
+ var isshowders = false;
|
|
|
+ var rad1 = Cesium.Cartographic.fromCartesian(start);
|
|
|
+ var rad2 = Cesium.Cartographic.fromCartesian(end); //开始节点和结束节点转换为以弧度计算的经纬度
|
|
|
+ var points = [];
|
|
|
+ //这里表示插值函数,start和end是起始点,然后进行插值,检测高程,如果获取的地面点比差值点的高程高的话就代表有焦点
|
|
|
+ //坐标转经纬度
|
|
|
+ let startpoint = {};
|
|
|
+ startpoint.longitude = (rad1.longitude / Math.PI) * 180;
|
|
|
+ startpoint.latitude = (rad1.latitude / Math.PI) * 180;
|
|
|
+ startpoint.czheight = rad1.height; //将开始节点转换为角度的点
|
|
|
+ let endpoint = {};
|
|
|
+ endpoint.longitude = (rad2.longitude / Math.PI) * 180;
|
|
|
+ endpoint.latitude = (rad2.latitude / Math.PI) * 180;
|
|
|
+ endpoint.czheight = rad2.height; //将结束节点节点转换为角度的点
|
|
|
+ for (var i = 0; i < num; i++) {
|
|
|
+ let point = {};
|
|
|
+ point.longitude = Cesium.Math.lerp(
|
|
|
+ startpoint.longitude,
|
|
|
+ endpoint.longitude,
|
|
|
+ 0.01 * (i + 1)
|
|
|
+ );
|
|
|
+ point.latitude = Cesium.Math.lerp(
|
|
|
+ startpoint.latitude,
|
|
|
+ endpoint.latitude,
|
|
|
+ 0.01 * (i + 1)
|
|
|
+ );
|
|
|
+ point.czheight =
|
|
|
+ startpoint.czheight -
|
|
|
+ (startpoint.czheight - endpoint.czheight) * 0.01 * (i + 1);
|
|
|
+ points.push(point);
|
|
|
+ }
|
|
|
+
|
|
|
+ var pointDepths = SunshineAnalysisByJS.getHighPrecisionGeoDepths(
|
|
|
+ context,
|
|
|
+ scene,
|
|
|
+ points,
|
|
|
+ pixels
|
|
|
+ );
|
|
|
+ for (let index = 0; index < pointDepths.length; index++) {
|
|
|
+ const element = pointDepths[index];
|
|
|
+ if (element) {
|
|
|
+ // var cartographic = Cesium.Cartesian3.fromDegrees(
|
|
|
+ // Cesium.Math.toDegrees(element.longitude),
|
|
|
+ // Cesium.Math.toDegrees(element.latitude),
|
|
|
+ // element.height
|
|
|
+ // ); //经纬度转世界坐标
|
|
|
+ // viewer.entities.add({
|
|
|
+ // position: cartographic,
|
|
|
+ // point: {
|
|
|
+ // pixelSize: 4,
|
|
|
+ // color: Cesium.Color.RED,
|
|
|
+ // outlineColor: Cesium.Color.BLACK,
|
|
|
+ // outlineWidth: 2,
|
|
|
+ // },
|
|
|
+ // });
|
|
|
+ points[index].zsheight = element.height; //真实点的高程
|
|
|
+ if (points[index].zsheight > points[index].czheight) {
|
|
|
+ isshowders = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isshowders) {
|
|
|
+ return false;
|
|
|
+ // console.log("在阴影中");
|
|
|
+ } else {
|
|
|
+ return true;
|
|
|
+ // console.log("不在阴影中");
|
|
|
+ }
|
|
|
+ // console.log(points);
|
|
|
+ },
|
|
|
+
|
|
|
+ getHighPrecisionGeoDepths(context, scene, positions, pixels, options = {}) {
|
|
|
+ const { enableSubPixel = true } = options;
|
|
|
+
|
|
|
+ const packedDepthScale = new Cesium.Cartesian4(
|
|
|
+ 1.0,
|
|
|
+ 1 / 255,
|
|
|
+ 1 / 65025,
|
|
|
+ 1 / 16581375
|
|
|
+ );
|
|
|
+
|
|
|
+ // if (!Cesium.defined(framebuffer)) return undefined;
|
|
|
+ if (!positions || positions.length == 0) return [];
|
|
|
+
|
|
|
+ // 高精度坐标转换
|
|
|
+ const preciseCoords = positions.map((pos) => {
|
|
|
+ const cartesian = Cesium.Cartesian3.fromDegrees(
|
|
|
+ pos.longitude,
|
|
|
+ pos.latitude,
|
|
|
+ pos.height || 20,
|
|
|
+ scene.globe.ellipsoid
|
|
|
+ );
|
|
|
+ // 转换为屏幕像素坐标
|
|
|
+ const screenPos = scene.cartesianToCanvasCoordinates(cartesian);
|
|
|
+ //屏幕坐标转深度图坐标
|
|
|
+ screenPos.y = scene.canvas.height - 1 - screenPos.y;
|
|
|
+ if (
|
|
|
+ !screenPos ||
|
|
|
+ screenPos.x < 0 ||
|
|
|
+ screenPos.x >= scene.canvas.width ||
|
|
|
+ screenPos.y < 0 ||
|
|
|
+ screenPos.y >= scene.canvas.height
|
|
|
+ ) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ x: screenPos.x, // 保留浮点精度
|
|
|
+ y: screenPos.y,
|
|
|
+ dx: screenPos.x - Math.floor(screenPos.x), // 小数部分
|
|
|
+ dy: screenPos.y - Math.floor(screenPos.y),
|
|
|
+ };
|
|
|
+ });
|
|
|
+
|
|
|
+ // 构建采样点集合
|
|
|
+ const samples = [];
|
|
|
+ preciseCoords.forEach((coord, index) => {
|
|
|
+ if (!coord) return;
|
|
|
+
|
|
|
+ const baseX = Math.floor(coord.x);
|
|
|
+ const baseY = Math.floor(coord.y);
|
|
|
+
|
|
|
+ // 根据插值模式收集采样点
|
|
|
+ const points = enableSubPixel
|
|
|
+ ? [
|
|
|
+ { x: baseX, y: baseY, weight: (1 - coord.dx) * (1 - coord.dy) },
|
|
|
+ { x: baseX + 1, y: baseY, weight: coord.dx * (1 - coord.dy) },
|
|
|
+ { x: baseX, y: baseY + 1, weight: (1 - coord.dx) * coord.dy },
|
|
|
+ { x: baseX + 1, y: baseY + 1, weight: coord.dx * coord.dy },
|
|
|
+ ]
|
|
|
+ : [{ x: Math.round(coord.x), y: Math.round(coord.y), weight: 1 }];
|
|
|
+
|
|
|
+ points.forEach((point) => {
|
|
|
+ samples.push({
|
|
|
+ sourceIndex: index,
|
|
|
+ x: point.x,
|
|
|
+ y: point.y,
|
|
|
+ weight: point.weight,
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // 计算采样区域
|
|
|
+
|
|
|
+ let minX = 0,
|
|
|
+ minY = 0;
|
|
|
+ let maxX = scene.canvas.width,
|
|
|
+ maxY = scene.canvas.height;
|
|
|
+ // let minX = Infinity,
|
|
|
+ // minY = Infinity;
|
|
|
+ // let maxX = -Infinity,
|
|
|
+ // maxY = -Infinity;
|
|
|
+ // samples.forEach((s) => {
|
|
|
+ // minX = Math.min(minX, s.x);
|
|
|
+ // minY = Math.min(minY, s.y);
|
|
|
+ // maxX = Math.max(maxX, s.x);
|
|
|
+ // maxY = Math.max(maxY, s.y);
|
|
|
+ // });
|
|
|
+
|
|
|
+ // const width = maxX - minX + 1;
|
|
|
+ // const height = maxY - minY + 1;
|
|
|
+ const width = maxX - minX;
|
|
|
+ const height = maxY - minY;
|
|
|
+
|
|
|
+ // // 批量读取像素数据
|
|
|
+ // let framebufferCopy = viewer.scene._view.globeDepth._copyDepthFramebuffer;
|
|
|
+ // let context = viewer.scene.context;
|
|
|
+ // const pixels = context.readPixels({
|
|
|
+ // x: minX,
|
|
|
+ // y: minY,
|
|
|
+ // width,
|
|
|
+ // height,
|
|
|
+ // framebufferCopy,
|
|
|
+ // });
|
|
|
+
|
|
|
+ // 初始化结果容器
|
|
|
+ const results = new Array(positions.length).fill(undefined);
|
|
|
+ if (!pixels || pixels.length < width * height * 4) return results;
|
|
|
+
|
|
|
+ // 创建深度缓存
|
|
|
+ const depthCache = new Map();
|
|
|
+ samples.forEach((sample) => {
|
|
|
+ const key = `${sample.x},${sample.y}`;
|
|
|
+ if (!depthCache.has(key)) {
|
|
|
+ const dx = sample.x - minX;
|
|
|
+ const dy = sample.y - minY;
|
|
|
+ if (dx < 0 || dx >= width || dy < 0 || dy >= height) return;
|
|
|
+
|
|
|
+ const pixelIndex = (dy * width + dx) * 4;
|
|
|
+ const packed = new Cesium.Cartesian4(
|
|
|
+ pixels[pixelIndex],
|
|
|
+ pixels[pixelIndex + 1],
|
|
|
+ pixels[pixelIndex + 2],
|
|
|
+ pixels[pixelIndex + 3]
|
|
|
+ );
|
|
|
+ Cesium.Cartesian4.divideByScalar(packed, 255, packed);
|
|
|
+ depthCache.set(key, Cesium.Cartesian4.dot(packed, packedDepthScale));
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 计算加权深度值
|
|
|
+ const depthAccumulator = new Map();
|
|
|
+ samples.forEach((sample) => {
|
|
|
+ const key = `${sample.x},${sample.y}`;
|
|
|
+ const depth = depthCache.get(key);
|
|
|
+ if (depth === undefined) return;
|
|
|
+
|
|
|
+ const current = depthAccumulator.get(sample.sourceIndex) || {
|
|
|
+ sum: 0,
|
|
|
+ weight: 0,
|
|
|
+ };
|
|
|
+ current.sum += depth * sample.weight;
|
|
|
+ current.weight += sample.weight;
|
|
|
+ depthAccumulator.set(sample.sourceIndex, current);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 生成最终结果
|
|
|
+ depthAccumulator.forEach((value, index) => {
|
|
|
+ var v = value.weight > 0 ? value.sum / value.weight : 0;
|
|
|
+ //这里直接用深度图坐标转世界坐标
|
|
|
+ let res = Cesium.SceneTransforms.drawingBufferToWgs84Coordinates(
|
|
|
+ scene,
|
|
|
+ new Cesium.Cartesian2(preciseCoords[index].x, preciseCoords[index].y),
|
|
|
+ v,
|
|
|
+ new Cesium.Cartesian3()
|
|
|
+ );
|
|
|
+ let carp = Cesium.Cartographic.fromCartesian(res);
|
|
|
+ results[index] = carp; //value.weight > 0 ? value.sum / value.weight : undefined;
|
|
|
+ });
|
|
|
+
|
|
|
+ return results;
|
|
|
+ },
|
|
|
+ //计算日照时间范围
|
|
|
+ calculateTime(selDate, startTime, endTime) {
|
|
|
+ var sunriseDate = SunriseSunsetJS.getSunrise(
|
|
|
+ 18.31723463241332,
|
|
|
+ 109.5112252162011,
|
|
|
+ selDate
|
|
|
+ );
|
|
|
+ var sunsetdate = SunriseSunsetJS.getSunset(
|
|
|
+ 18.31723463241332,
|
|
|
+ 109.5112252162011,
|
|
|
+ selDate
|
|
|
+ );
|
|
|
+ //参与计算的日出日落时间
|
|
|
+ var jssunriseHours = 0;
|
|
|
+ var jssunsetHours = 0;
|
|
|
+ //日出时间
|
|
|
+ var sunriseHours = sunriseDate.getHours() + sunriseDate.getMinutes() / 60;
|
|
|
+ //日落时间
|
|
|
+ var sunsetHours = sunsetdate.getHours() + sunsetdate.getMinutes() / 60;
|
|
|
+
|
|
|
+ if (startTime > sunriseHours) {
|
|
|
+ jssunriseHours = startTime;
|
|
|
+ } else jssunriseHours = sunriseHours;
|
|
|
+
|
|
|
+ if (endTime > sunsetHours) jssunsetHours = sunsetHours;
|
|
|
+ else jssunsetHours = endTime;
|
|
|
+ return { jssunriseHours: jssunriseHours, jssunsetHours: jssunsetHours };
|
|
|
+ },
|
|
|
+
|
|
|
+ //以下为测试例子
|
|
|
+ Pointxyz() {
|
|
|
+ let framebufferCopy = viewer.scene._view.globeDepth._copyDepthFramebuffer;
|
|
|
+ let context = viewer.scene.context;
|
|
|
+ let scene = viewer.scene;
|
|
|
+
|
|
|
+ let depth = SunshineAnalysisByJS.getDepth(context, 0, 0, framebufferCopy);
|
|
|
+ let res = Cesium.SceneTransforms.drawingBufferToWgs84Coordinates(
|
|
|
+ scene,
|
|
|
+ new Cesium.Cartesian2(0, 0),
|
|
|
+ depth,
|
|
|
+ new Cesium.Cartesian3()
|
|
|
+ );
|
|
|
+ let carp = Cesium.Cartographic.fromCartesian(res);
|
|
|
+
|
|
|
+ var cartographic = Cesium.Cartesian3.fromDegrees(
|
|
|
+ Cesium.Math.toDegrees(carp.longitude),
|
|
|
+ Cesium.Math.toDegrees(carp.latitude),
|
|
|
+ 20
|
|
|
+ ); //经纬度转世界坐标
|
|
|
+ var labelentity = viewer.entities.add({
|
|
|
+ position: cartographic,
|
|
|
+ point: {
|
|
|
+ pixelSize: 4,
|
|
|
+ color: Cesium.Color.RED,
|
|
|
+ outlineColor: Cesium.Color.BLACK,
|
|
|
+ outlineWidth: 2,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ // viewer.flyTo(labelentity);
|
|
|
+ console.log(depth, res, carp.height);
|
|
|
+ console.log(
|
|
|
+ Cesium.Math.toDegrees(carp.longitude),
|
|
|
+ Cesium.Math.toDegrees(carp.latitude),
|
|
|
+ carp.height
|
|
|
+ );
|
|
|
+
|
|
|
+ var terCartographic = new Cesium.Cartographic(
|
|
|
+ carp.longitude,
|
|
|
+ carp.latitude,
|
|
|
+ 0
|
|
|
+ ); //转经纬度对像
|
|
|
+ var cartographichight = viewer.scene.sampleHeight(terCartographic); //坐标点获取建筑物高程
|
|
|
+
|
|
|
+ var cartographic1 = Cesium.Cartesian3.fromDegrees(
|
|
|
+ Cesium.Math.toDegrees(carp.longitude),
|
|
|
+ Cesium.Math.toDegrees(carp.latitude),
|
|
|
+ cartographichight
|
|
|
+ ); //经纬度转世界坐标
|
|
|
+
|
|
|
+ console.log(cartographichight);
|
|
|
+
|
|
|
+ let res11 = SunshineAnalysisByJS.getDepthArray(context, framebufferCopy);
|
|
|
+ console.log(res11);
|
|
|
+ },
|
|
|
+ getDepth(context, x, y, framebuffer) {
|
|
|
+ const scratchPackedDepth = new Cesium.Cartesian4();
|
|
|
+ const packedDepthScale = new Cesium.Cartesian4(
|
|
|
+ 1.0,
|
|
|
+ 1.0 / 255.0,
|
|
|
+ 1.0 / 65025.0,
|
|
|
+ 1.0 / 16581375.0
|
|
|
+ );
|
|
|
+ // If this function is called before the framebuffer is created, the depth is undefined.
|
|
|
+ if (!Cesium.defined(framebuffer)) {
|
|
|
+ return undefined;
|
|
|
+ }
|
|
|
+ const pixels = context.readPixels({
|
|
|
+ x: x,
|
|
|
+ y: y,
|
|
|
+ width: 1,
|
|
|
+ height: 1,
|
|
|
+ framebuffer: framebuffer,
|
|
|
+ });
|
|
|
+ const packedDepth = Cesium.Cartesian4.unpack(pixels, 0, scratchPackedDepth);
|
|
|
+ Cesium.Cartesian4.divideByScalar(packedDepth, 255.0, packedDepth);
|
|
|
+ return Cesium.Cartesian4.dot(packedDepth, packedDepthScale);
|
|
|
+ },
|
|
|
+ getDepthArray(context, framebuffer) {
|
|
|
+ // If this function is called before the framebuffer is created, the depth is undefined
|
|
|
+ if (!Cesium.defined(framebuffer)) {
|
|
|
+ return undefined;
|
|
|
+ }
|
|
|
+ let width = viewer.scene.drawingBufferWidth;
|
|
|
+ let height = viewer.scene.drawingBufferHeight;
|
|
|
+ const pixels = context.readPixels({
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ width: width,
|
|
|
+ height: height,
|
|
|
+ framebuffer: framebuffer,
|
|
|
+ });
|
|
|
+ let k = 0;
|
|
|
+ let rest = [];
|
|
|
+ const packedDepthScale = new Cesium.Cartesian4(
|
|
|
+ 1.0,
|
|
|
+ 1.0 / 255.0,
|
|
|
+ 1.0 / 65025.0,
|
|
|
+ 1.0 / 16581375.0
|
|
|
+ );
|
|
|
+ for (let index = 0; index < width; index += 100) {
|
|
|
+ for (let j = 0; j < height; j += 100) {
|
|
|
+ const scratchPackedDepth = new Cesium.Cartesian4();
|
|
|
+
|
|
|
+ let arr = [
|
|
|
+ pixels[4 * k],
|
|
|
+ pixels[4 * k + 1],
|
|
|
+ pixels[4 * k + 2],
|
|
|
+ pixels[4 * k + 3],
|
|
|
+ ];
|
|
|
+ const packedDepth = Cesium.Cartesian4.unpack(
|
|
|
+ arr,
|
|
|
+ 0,
|
|
|
+ scratchPackedDepth
|
|
|
+ );
|
|
|
+ Cesium.Cartesian4.divideByScalar(packedDepth, 255.0, packedDepth);
|
|
|
+ let dp = Cesium.Cartesian4.dot(packedDepth, packedDepthScale);
|
|
|
+
|
|
|
+ let res = Cesium.SceneTransforms.drawingBufferToWgs84Coordinates(
|
|
|
+ scene,
|
|
|
+ new Cesium.Cartesian2(index, j),
|
|
|
+ dp,
|
|
|
+ new Cesium.Cartesian3()
|
|
|
+ );
|
|
|
+ let carp = Cesium.Cartographic.fromCartesian(res);
|
|
|
+ carp.longitude = Cesium.Math.toDegrees(carp.longitude);
|
|
|
+ carp.latitude = Cesium.Math.toDegrees(carp.latitude);
|
|
|
+ rest.push(carp);
|
|
|
+ k++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return rest;
|
|
|
+ },
|
|
|
+};
|
|
|
+export default SunshineAnalysisByJS;
|