高速公路監管系統大屏可視化

0x00 項目背景

該項目用於高速公路監管。高速公路監管包括:高速公路的設備運行情況,設備維護情況,道路維護情況;交通流量分析,交通擁堵分析,擁堵溯源;事故分析,事件信息發佈等。

0x01設計圖

該項目目前主要是一個預演形式的項目,所以設計圖層面主要還是用了客戶提供的圖片。 我們的設計團隊參與的並不多。下面是客戶的設計圖紙:

設計圖

0x02 繪製公路

公路的實際效果還是比較複雜的,比如公路上面有各種線(斑馬線,黃線,白線,實線,虛線等等)。但是這些在本系統不是最重要的要素,因此考慮忽略。因此我們使用帶邊線效果的路徑進行模擬,效果如下所示:

公路效果

繪製的邏輯其實也很簡單,首先用較大的線寬繪製路徑,然後改爲較小的線寬和不同顏色在繪製一次路徑。 大致的繪製邏輯如下:


ctx.save();
        ctx.beginPath();
        ctx.lineJoin = 'round';
        // ctx.lineCap = 'round';
        points.forEach(({
            x,
            y
        }, index) => {
            ctx[index ? 'lineTo' : 'moveTo'](x, y);
        })
       
        ctx.lineWidth = width;
        ctx.strokeStyle = sideColor;
        ctx.stroke();
        ctx.shadowBlur = 0;
        ctx.globalCompositeOperation = 'source-over';
        ctx.lineWidth = width * 0.5;
        ctx.strokeStyle = midColor;
        ctx.stroke();

在編輯器中,增加一個公路組件,點擊下公路組件,便可以開始繪製公路:
公路組件

通過打點編輯路徑,即可以對公路的走向進行編輯。 需要注意的是,技術上使用了自動平滑的技術,原本的尖銳的角都會變成平滑的效果。

export function createSmoothCurvePoints(
  points,
  tension = 0.5,
  closed = false,
  numberOfSegments = 16
) {
  if (points.length < 2) {
    return points;
  }
  //  展開數組
  points = expandPointArr(points);

  let ps = points.slice(0), // clone array so we don't change the original
    result = [], // result points
    x,
    y, // our x,y coords
    t1x,
    t2x,
    t1y,
    t2y, // tension vectors
    c1,
    c2,
    c3,
    c4, // cardinal points
    st,
    t,
    i; // steps based on number of segments

  // The algorithm require a previous and next point to the actual point array.
  // Check if we will draw closed or open curve.
  // If closed, copy end points to beginning and first points to end
  // If open, duplicate first points to befinning, end points to end
  if (closed) {
    ps.unshift(points[points.length - 1]);
    ps.unshift(points[points.length - 2]);
    ps.unshift(points[points.length - 1]);
    ps.unshift(points[points.length - 2]);
    ps.push(points[0]);
    ps.push(points[1]);
  } else {
    ps.unshift(points[1]); // copy 1st point and insert at beginning
    ps.unshift(points[0]);
    ps.push(points[points.length - 2]); // copy last point and append
    ps.push(points[points.length - 1]);
  }

  // 1. loop goes through point array
  // 2. loop goes through each segment between the 2 points + 1e point before and after
  for (i = 2; i < ps.length - 4; i += 2) {
    // calculate tension vectors
    t1x = (ps[i + 2] - ps[i - 2]) * tension;
    t2x = (ps[i + 4] - ps[i - 0]) * tension;
    t1y = (ps[i + 3] - ps[i - 1]) * tension;
    t2y = (ps[i + 5] - ps[i + 1]) * tension;

    for (t = 0; t <= numberOfSegments; t++) {
      // calculate step
      st = t / numberOfSegments;

      // calculate cardinals
      c1 = 2 * Math.pow(st, 3) - 3 * Math.pow(st, 2) + 1;
      c2 = -(2 * Math.pow(st, 3)) + 3 * Math.pow(st, 2);
      c3 = Math.pow(st, 3) - 2 * Math.pow(st, 2) + st;
      c4 = Math.pow(st, 3) - Math.pow(st, 2);

      // calculate x and y cords with common control vectors
      x = c1 * ps[i] + c2 * ps[i + 2] + c3 * t1x + c4 * t2x;
      y = c1 * ps[i + 1] + c2 * ps[i + 3] + c3 * t1y + c4 * t2y;

      //store points in array
      result.push(x);
      result.push(y);
    }
  }
return contractPointArr(result);

0x03 門架的繪製

門架的最終效果如下圖所示:
門架

可以看出門架是由幾個立方體組合而成的。我們只需要理解立方體的繪製邏輯,便可以很輕鬆理解門架的繪製邏輯。

繪製立方體

編輯器中本身也存在立方體組件:
立方體組件

其顯示效果如下:
立方體效果

繪製立方體的思路並不複雜,只是藉助了一些三維的思路。首先借助了三維的投影變換的思路,當然此處使用的是正投影:

/**
   * 3d座標轉2d座標
   *
   * @param {Object} point - 3d座標
   * @param {Object} offset - 一點偏移
   * @returns {Object} - 2d座標
   */
  getProjectionPoint(point) {
    const network = this._network,
      p = vec3.create(),
      itMat = network.getMVMatrix();
    vec3.transformMat4(
      p,
      [point.x, point.y, point.z],
      itMat
    );
    const {
      x,
      y
    } = this.getLocation()
    return {
      x: p[0] + x,
      y: -p[1] + y
    };
  }

對立方體的8個頂點計算出其在平面上的位置,並計算出其現在在外面的三個面(注意:最多隻會有三個面顯示在外面)。 然後把三個面繪製值出來:

 drawSide(ctx, points, isFill = false, color = "#00ccff") {
    ctx.save();
    ctx[isFill ? 'fillStyle' : 'strokeStyle'] = color;
    ctx.lineWidth = 1;
    ctx.beginPath();
    points.forEach(({
      x,
      y
    }, index) => {
      ctx[index ? 'lineTo' : 'moveTo'](x, y);
    })
    ctx.closePath();
    ctx[isFill ? 'fill' : 'stroke']();
    ctx.restore();
  }

最終繪製的效果如上圖所示。

門架的繪製,就是多個立方體的組合的繪製。需要注意的一點就是,要注意多個立方體繪製的順序,這會涉及到遮擋關係的正確性。

在編輯器中,可以通過調整其長寬高和y軸旋轉角度來改變其顯示形態:
門架

0x04 標誌牌的繪製

標誌牌是公路上面常見的對象。 用於各種提示,在本系統,標誌牌顯示效果如下:
標誌牌

其繪製思路其實和前面的門架類似,都是通過立方體組合而成的。因此此處不再贅述。

#0x05 山的繪製
由於山是比較複雜的模型,因此程序直接使用了設計人員的設計的圖形。如下圖所示:
山

使用設計人員設計的圖片作爲網元的圖片,直接拖拽進入場景即可。

0x05 圖表的繪製

編輯器中集成了常用的echarts圖表和擴展的圖表。應此可以直接拖拽到場景之中,比如下圖截出了部分的圖表,包括柱狀圖、餅圖、曲線圖:
圖表組件

把圖表直接拖到場景中即可生成圖表效果,如下圖所示:
圖表效果

並可以在屬性框配置圖表的數據,此處爲了演示,使用的是靜態數據;也可以對接動態的數據上倆。

0x06 最終效果

綜合上述所有的效果,最終編輯出來了一個演示頁面,如下圖所示:
最終效果

有興趣獲取demo的,請發郵件到:
[email protected]

另歡迎關注個人公衆號 “ITman彪叔”

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章