jinglun-cloud
1 天以前 e59a23054bbaf71124ca1e7e642f1108b5c68786
src/main/webapp/static/js/basMap/mapTrackGeometry.js
@@ -1,7 +1,7 @@
/**
 * 环穿 / 平滑轨道几何:直角多边形转圆弧路径、PIXI 绘制、设备朝向等。
 * 环穿持久化的 pathList 为中轨;外圈、内圈仅在绘制时由 buildAnnulusPaths 计算。
 * 供 basMap 编辑器与监控 MapCanvas 共用。需在页面中先于 editor.js / MapCanvas.js 引入。
 * 设备外观(CRN / RGV)绘制与 MapCanvas 贴图一致,见 drawCrnDeviceGraphics / drawRgvDeviceGraphics。
 */
(function (global) {
  'use strict';
@@ -258,7 +258,6 @@
        return deviceForm;
      }
      var allDistance = getAllDistance(pathList);
      var ab = annulusBandContext(rect, rect.shape || 'rect');
      deviceForm.deviceList.forEach(function (item) {
        var deltaDistance = (allDistance * item.progress) / 100;
        var startPoint = {
@@ -271,16 +270,8 @@
          pathIndex: 0,
          deltaDistance: deltaDistance
        });
        var centered = shiftAnnulusPointToBandCenter(
          moved.x,
          moved.y,
          moved.path,
          ab.inset,
          ab.refInside
        );
        item.x = centered.x;
        item.y = centered.y;
        item.x = moved.x;
        item.y = moved.y;
        item.path = moved.path;
        item.width = item.deviceLength;
        item.height = item.deviceWidth;
@@ -396,69 +387,6 @@
  }
  /**
   * 射线法判点是否在简单多边形内(用于找参考内点,算内向法向)。
   * @param {{ x: number, y: number }} pt
   * @param {{ x: number, y: number }[]} ring
   * @returns {boolean}
   */
  function pointInPolygon(pt, ring) {
    var inside = false;
    var n = ring.length;
    var i;
    for (i = 0; i < n; i++) {
      var a = ring[i];
      var b = ring[(i + 1) % n];
      var intersects =
        a.y > pt.y !== b.y > pt.y &&
        pt.x < ((b.x - a.x) * (pt.y - a.y)) / (b.y - a.y + 1e-12) + a.x;
      if (intersects) {
        inside = !inside;
      }
    }
    return inside;
  }
  /**
   * 在尖点环包围盒内采样,得到多边形内部一点,供直线段上判断「指向内侧」的法向。
   * @param {{ x: number, y: number }[]} pointList
   * @returns {{ x: number, y: number }}
   */
  function findInteriorRefPoint(pointList) {
    var minX = Infinity;
    var minY = Infinity;
    var maxX = -Infinity;
    var maxY = -Infinity;
    var i;
    for (i = 0; i < pointList.length; i++) {
      var p = pointList[i];
      minX = Math.min(minX, p.x);
      minY = Math.min(minY, p.y);
      maxX = Math.max(maxX, p.x);
      maxY = Math.max(maxY, p.y);
    }
    var cx = (minX + maxX) / 2;
    var cy = (minY + maxY) / 2;
    if (pointInPolygon({ x: cx, y: cy }, pointList)) {
      return { x: cx, y: cy };
    }
    var g;
    for (g = 1; g <= 6; g++) {
      var sj;
      for (sj = 1; sj < g; sj++) {
        var si;
        for (si = 1; si < g; si++) {
          var tx = minX + ((maxX - minX) * si) / g;
          var ty = minY + ((maxY - minY) * sj) / g;
          if (pointInPolygon({ x: tx, y: ty }, pointList)) {
            return { x: tx, y: ty };
          }
        }
      }
    }
    return { x: cx, y: cy };
  }
  /**
   * 有向边 (ax,ay)→(bx,by) 沿环前进方向的单位内向法向量(逆时针环时内侧在前进方向左侧)。
   * @param {number} ax
   * @param {number} ay
@@ -552,6 +480,7 @@
    return out;
  }
  // 暂时注释掉。以后再考虑加上。先不删除。
  /**
   * 解析环穿单侧内缩像素距离;若传入 `precomputedSharp` 则不再重复求尖点环。
   * @param {{ width: number, height: number, shape?: string, annulusBandInset?: number, value?: string|object }} sprite
@@ -603,17 +532,16 @@
  // }
  /**
   * 环穿共用:尖点环、内侧参考点、inset(只算一遍尖点,避免多处重复 getSharpCornerList)。
   * 环穿共用:尖点环、inset(只算一遍尖点)。
   * @param {object} sprite x/y/width/height/shape/turningPoint/annulusBandInset
   * @param {string} defaultShape
   * @returns {{ sharp: object[], refInside: {x:number,y:number}, inset: number }}
   * @returns {{ sharp: object[], inset: number }}
   */
  function annulusBandContext(sprite, defaultShape) {
    sprite.shape = sprite.shape || defaultShape;
    var sharp = getSharpCornerList(sprite, defaultShape);
    return {
      sharp: sharp,
      refInside: findInteriorRefPoint(sharp),
      inset: 6
    };
  }
@@ -871,12 +799,12 @@
  }
  /**
   * 生成外圈与内圈 smooth pathList;内圈由正交尖点环 offset 后再 setRadius/smooth。
   * 外圈、中轨、内圈:外/内仅用于绘制;中轨为 pathList 与运动学所用。
   * @param {{ x: number, y: number, width: number, height: number, shape?: string, turningPoint?: object, annulusBandInset?: number }} sprite
   * @param {string} defaultShape
   * @returns {{ outerPath: object[], innerPath: object[]|null, inset: number }}
   * @returns {{ outerPath: object[], innerPath: object[]|null, centerPath: object[], inset: number }}
   */
  function buildOuterAndInnerPaths(sprite, defaultShape) {
  function buildAnnulusPaths(sprite, defaultShape) {
    var ctx = annulusBandContext(sprite, defaultShape);
    var sharp = ctx.sharp;
    var sharpClone = sharp.map(function (p) {
@@ -887,112 +815,32 @@
    var inset = ctx.inset;
    var minSide = Math.min(sprite.width, sprite.height);
    if (inset <= 0 || minSide <= inset * 2 + 4) {
      return { outerPath: outerPath, innerPath: null, inset: inset };
      return { outerPath: outerPath, innerPath: null, centerPath: outerPath, inset: inset };
    }
    var centerSharp = offsetOrthogonalSharpRing(
      sharpClone.map(function (p) {
        return { x: p.x, y: p.y, direction: p.direction };
      }),
      inset / 2
    );
    var centerMeta = {};
    setRadiusInPoint(centerSharp, centerMeta);
    var centerPath = smoothRightAnglePath(centerSharp);
    var innerSharp = offsetOrthogonalSharpRing(sharpClone, inset);
    var innerMeta = {};
    setRadiusInPoint(innerSharp, innerMeta);
    var innerPath = smoothRightAnglePath(innerSharp);
    return { outerPath: outerPath, innerPath: innerPath, inset: inset };
    return { outerPath: outerPath, innerPath: innerPath, centerPath: centerPath, inset: inset };
  }
  /**
   * 外轮廓上一点沿法向(直线段)或径向(圆弧段)内移 half,落在双轨几何中线。
   * @param {number} x
   * @param {number} y
   * @param {object} path 当前段 line|arc
   * @param {number} inset 单侧带宽
   * @param {{ x: number, y: number }} refInside 多边形内参考点
   * @returns {{ x: number, y: number }}
   */
  function shiftAnnulusPointToBandCenter(x, y, path, inset, refInside) {
    var half = inset / 2;
    if (half <= 0) {
      return { x: x, y: y };
    }
    if (path.type === 'line') {
      var tx = path.x - path.startX;
      var ty = path.y - path.startY;
      var tlen = Math.sqrt(tx * tx + ty * ty) || 1;
      var nx = -ty / tlen;
      var ny = tx / tlen;
      var mx = (path.startX + path.x) / 2;
      var my = (path.startY + path.y) / 2;
      if ((refInside.x - mx) * nx + (refInside.y - my) * ny < 0) {
        nx = -nx;
        ny = -ny;
      }
      return { x: x + nx * half, y: y + ny * half };
    }
    if (path.type === 'arc') {
      var ox = x - path.x;
      var oy = y - path.y;
      var olen = Math.sqrt(ox * ox + oy * oy) || 1;
      return { x: x - (ox / olen) * half, y: y - (oy / olen) * half };
    }
    return { x: x, y: y };
  }
  /**
   * 将外圈 path 上坐标映射到轨道带中线(监控/条码与编辑器一致)。
   * @param {{ type: string, x: number, y: number, width: number, height: number, shape?: string, turningPoint?: object, annulusBandInset?: number }} element
   * @param {number} x
   * @param {number} y
   * @param {object} path
   * @returns {{ x: number, y: number }}
   */
  function centerAnnulusBandPoint(element, x, y, path) {
    if (!element || element.type !== 'annulus' || path == null) {
      return { x: x, y: y };
    }
    var ctx = annulusBandContext(element, element.shape || 'rect');
    return shiftAnnulusPointToBandCenter(x, y, path, ctx.inset, ctx.refInside);
  }
  /**
   * 环穿:把 getPositionAfterMove 等返回的 position 压到轨带中线;非环穿或无效 position 原样返回。
   * @param {{ type: string }} trackInfo
   * @param {{ x: number, y: number, path?: object }|null|undefined} position
   * @returns {typeof position}
   */
  function applyAnnulusBandCenterToPosition(trackInfo, position) {
    if (!position || !trackInfo || trackInfo.type !== 'annulus') {
      return position;
    }
    var c = centerAnnulusBandPoint(trackInfo, position.x, position.y, position.path);
    return Object.assign({}, position, { x: c.x, y: c.y });
  }
  /**
   * 沿 pathList 移动后再对环穿做轨带中线修正(与 getPositionAfterMove + applyAnnulusBandCenterToPosition 等价)。
   * @param {{ type: string }} trackInfo
   * @param {{ x: number, y: number }} point 起点;环穿上应在外圈 path 上
   * @param {object[]} pathList
   * @param {object} path
   * @param {number} deltaDistance
   * @param {number} [angle]
   * @returns {{ x: number, y: number, path: object, angle?: number }} 与 getPositionAfterMove 返回形态一致
   */
  function computeFinalPosition(trackInfo, point, pathList, path, deltaDistance, angle) {
    var position = getPositionAfterMove({
      point: point,
      pathList: pathList,
      path: path,
      deltaDistance: deltaDistance,
      angle: angle
    });
    return applyAnnulusBandCenterToPosition(trackInfo, position);
  }
  /**
   * 将坐标压回环穿外圈 path(直线段投影、圆弧段落到半径上)。
   * sprite 已做 centerAnnulusBandPoint 时不能直接作为 getPositionAfterMove 的起点,否则会沿「点→段终点」斜移漂移。
   * 将坐标压回环穿 pathList(中轨)当前段上。
   * @param {number} x
   * @param {number} y
   * @param {{ type: string, startX?: number, startY?: number, x?: number, y?: number, radius?: number }|null|undefined} path
   * @returns {{ x: number, y: number }}
   */
  function snapToAnnulusOuterPath(trackInfo, x, y, path) {
  function snapToAnnulusPath(trackInfo, x, y, path) {
    if (!path || trackInfo.type !== 'annulus') {
      return { x: x, y: y };
    }
@@ -1025,16 +873,12 @@
  }
  /**
   * 监控轨道层:已保存的 pathList 描外圈 + 按几何重算内圈再描一圈。
   * 监控轨道层:按几何算外圈、内圈并描边(pathList 为中轨,不用于此描边)。
   * @param {PIXI.Graphics} smoothedGraphics
   * @param {{ pathList: object[], x: number, y: number, width: number, height: number, shape?: string, turningPoint?: object, annulusBandInset?: number }} item
   * @param {string} defaultShape
   */
  function strokeAnnulusDualOutline(smoothedGraphics, item, defaultShape) {
    var outerPath = item && item.pathList;
    if (!outerPath || !outerPath.length) {
      return;
    }
    var tmp = {
      x: item.x,
      y: item.y,
@@ -1044,24 +888,22 @@
      turningPoint: item.turningPoint,
      annulusBandInset: item.annulusBandInset
    };
    var pair = buildOuterAndInnerPaths(tmp, item.shape || defaultShape);
    traceSmoothedPath(smoothedGraphics, outerPath);
    var pair = buildAnnulusPaths(tmp, item.shape || defaultShape);
    traceSmoothedPath(smoothedGraphics, pair.outerPath);
    if (pair.innerPath && pair.innerPath.length) {
      traceSmoothedPath(smoothedGraphics, pair.innerPath);
    }
  }
  /**
   * 编辑器环穿填充:写回 sprite.pathList(外圈),绘制外填+内洞。
   * PIXI 中带洞填充通常不会给洞边界描边,故在 endFill 后再沿内圈 trace 一遍,与监控轨 strokeAnnulusDualOutline 一致,
   * 使 trackLayer / guideLayer / selectionLayer 均能看到内圈轮廓。
   * 编辑器环穿:写回 sprite.pathList(中轨),绘制外填+内洞。
   * @param {PIXI.Graphics} smoothedGraphics
   * @param {object} sprite 元素或预览 rect
   * @param {string} defaultShape 如 annulusShape
   */
  function startDrawSmoothedPath(smoothedGraphics, sprite, defaultShape) {
    var pair = buildOuterAndInnerPaths(sprite, defaultShape);
    sprite.pathList = pair.outerPath;
    var pair = buildAnnulusPaths(sprite, defaultShape);
    sprite.pathList = pair.centerPath;
    if (!pair.innerPath || !pair.innerPath.length) {
      drawSmoothedPath(smoothedGraphics, pair.outerPath);
      return;
@@ -1171,12 +1013,9 @@
    drawSmoothedPath: drawSmoothedPath,
    traceSmoothedPath: traceSmoothedPath,
    strokeAnnulusDualOutline: strokeAnnulusDualOutline,
    centerAnnulusBandPoint: centerAnnulusBandPoint,
    snapToAnnulusOuterPath: snapToAnnulusOuterPath,
    snapToAnnulusPath: snapToAnnulusPath,
    startDrawSmoothedPath: startDrawSmoothedPath,
    getRotate: getRotate,
    computeFinalPosition:computeFinalPosition,
    applyAnnulusBandCenterToPosition:applyAnnulusBandCenterToPosition,
    drawCrnDeviceGraphics: drawCrnDeviceGraphics,
    drawRgvDeviceGraphics: drawRgvDeviceGraphics
  };