Turf处理等压线

        Turf是一个用于空间分析的JavaScript库。它包括传统的空间操作、用于创建GeoJSON数据的辅助函数以及数据分类和统计工具。Turf可以作为客户端插件添加到您的网站,也可以使用Node.js在服务器端运行Turf。

Turf处理等压线_第1张图片

Turf处理等压线_第2张图片

        Turf是一个模块化的js类库,所有的模块都是在packages下面,每个模块下面都包含一个index.ts的源码文件,这个源码是使用typescript编写.

        Turf处理等压线_第3张图片

        对于上面这个实例,本章将通过源码的方式进行解析,首先调用的是turf.pointGrid这个函数,

Turf处理等压线_第4张图片

这个模块中的index.ts文件,代码中定义了pointGrid函数,这个函数接受如下几个参数

// 基于box边界,通过细分的方式插入点
function pointGrid

( bbox: BBox, // 东南西北作为边界 cellSide: number,// 细分时的步长 options: { units?: Units; // 单位 mask?: Feature; // 多边形掩码,插值点在这个范围内才算 properties?: P; // 属性 } = {} ): FeatureCollection { // Default parameters // 默认使用千米 if (options.mask && !options.units) options.units = "kilometers"; // Containers // 结果 var results = []; // 东南西北四个边界(经纬度) var west = bbox[0]; var south = bbox[1]; var east = bbox[2]; var north = bbox[3]; // 计算两点之间的球面距离 // 一个单元占整个距离的百分比 var xFraction = cellSide / distance([west, south], [east, south], options); // 一个单元的角度制下的距离 var cellWidth = xFraction * (east - west); // 一个单元占整个距离的百分比 var yFraction = cellSide / distance([west, south], [west, north], options); // 一个单元的角度制下的距离 var cellHeight = yFraction * (north - south); // 宽度(角度) var bboxWidth = east - west; // 高度(角度) var bboxHeight = north - south; // 列数 var columns = Math.floor(bboxWidth / cellWidth); // 行数 var rows = Math.floor(bboxHeight / cellHeight); // adjust origin of the grid // 多出来的一部分的中间 var deltaX = (bboxWidth - columns * cellWidth) / 2; var deltaY = (bboxHeight - rows * cellHeight) / 2; // 当前x var currentX = west + deltaX; // 计算插值点 while (currentX <= east) { // 当前y var currentY = south + deltaY; while (currentY <= north) { // 封装要素点 var cellPt = point([currentX, currentY], options.properties); if (options.mask) { // 点是否在掩码范围被内,只有在这个范围内才添加 if (within(cellPt, options.mask)) results.push(cellPt); } else { // 添加到结果中 results.push(cellPt); } currentY += cellHeight; } currentX += cellWidth; } // 封装成要素集合 return featureCollection(results); }

 计算distance的类是turf-distance文件夹下的index.ts

// 计算距离
function distance(
  from: Coord | Point,
  to: Coord | Point,
  options: {
    units?: Units;
  } = {}
) {
  // 获取坐标数组
  var coordinates1 = getCoord(from);
  // 获取坐标数组
  var coordinates2 = getCoord(to);
  // 计算维度
  var dLat = degreesToRadians(coordinates2[1] - coordinates1[1]);
  // 计算精度
  var dLon = degreesToRadians(coordinates2[0] - coordinates1[0]);
  // 角度转换为弧度
  var lat1 = degreesToRadians(coordinates1[1]);
  var lat2 = degreesToRadians(coordinates2[1]);

  // 计算两点之间的角度
  var a =
    Math.pow(Math.sin(dLat / 2), 2) +
    Math.pow(Math.sin(dLon / 2), 2) * Math.cos(lat1) * Math.cos(lat2);

  // 弧长公式Rθ
  return radiansToLength(
    2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)),
    options.units
  );
}

  计算等值线的类是turf-isolines文件夹下的index.ts

// 生成等值线
function isolines(
  pointGrid: FeatureCollection,// 网格点
  breaks: number[],
  options?: {
    zProperty?: string;
    commonProperties?: GeoJsonProperties;
    breaksProperties?: GeoJsonProperties[];
  }
) {
  // Optional parameters
  options = options || {};
  if (!isObject(options)) throw new Error("options is invalid");
  const zProperty = options.zProperty || "elevation";
  const commonProperties = options.commonProperties || {};
  const breaksProperties = options.breaksProperties || [];

  // Input validation
  collectionOf(pointGrid, "Point", "Input must contain Points");
  if (!breaks) throw new Error("breaks is required");
  if (!Array.isArray(breaks)) throw new Error("breaks must be an Array");
  if (!isObject(commonProperties))
    throw new Error("commonProperties must be an Object");
  if (!Array.isArray(breaksProperties))
    throw new Error("breaksProperties must be an Array");

  // Isoline methods
  // 等值线方法

  // 将网格点,按照z值构建一个只包含z值的矩阵
  const matrix = gridToMatrix(pointGrid, { zProperty: zProperty, flip: true });
  // 创建等值线
  const createdIsoLines = createIsoLines(
    matrix,
    breaks,
    zProperty,
    commonProperties,
    breaksProperties
  );
  // 重新缩放等值线
  const scaledIsolines = rescaleIsolines(createdIsoLines, matrix, pointGrid);

  // 封装成要素
  return featureCollection(scaledIsolines);
}

将所有的点构建一个z值的矩阵

Turf处理等压线_第5张图片

export default function gridToMatrix(grid, options) {
  // Optional parameters
  options = options || {};
  if (!isObject(options)) throw new Error("options is invalid");
  var zProperty = options.zProperty || "elevation";
  var flip = options.flip;
  var flags = options.flags;

  // validation
  collectionOf(grid, "Point", "input must contain Points");

  // 按照经纬度排序
  var pointsMatrix = sortPointsByLatLng(grid, flip);

  var matrix = [];
  // create property matrix from sorted points
  // looping order matters here
  // 从排序点创建属性矩阵循环此处的顺序问题
  for (var r = 0; r < pointsMatrix.length; r++) {
    var pointRow = pointsMatrix[r];
    var row = [];
    for (var c = 0; c < pointRow.length; c++) {
      var point = pointRow[c];
      // Check if zProperty exist
      if (point.properties[zProperty]) row.push(point.properties[zProperty]);
      else row.push(0);
      // add flags
      if (flags === true) point.properties.matrixPosition = [r, c];
    }
    matrix.push(row);
  }

  return matrix;
}

 

// 创建等值线
function createIsoLines(
  matrix: number[][],
  breaks: number[],
  zProperty: string,
  commonProperties: GeoJsonProperties,
  breaksProperties: GeoJsonProperties[]
): Feature[] {
  const results = [];
  // 遍历中断点
  for (let i = 1; i < breaks.length; i++) {
    // 作为阈值
    const threshold = +breaks[i]; // make sure it's a number

    const properties = { ...commonProperties, ...breaksProperties[i] };
    properties[zProperty] = threshold;
    // 应该是按照矩阵中的四个点计算是否可以计算出这个阈值的插值点
    const isoline = multiLineString(isoContours(matrix, threshold), properties);

    results.push(isoline);
  }
  return results;
}
// 等压线
export default function isoContours(data, threshold, options) {
  /* process options */
  options = options ? options : {};

  var optionKeys = Object.keys(defaultSettings);

  for (var i = 0; i < optionKeys.length; i++) {
    var key = optionKeys[i];
    var val = options[key];
    val =
      typeof val !== "undefined" && val !== null ? val : defaultSettings[key];

    settings[key] = val;
  }

  if (settings.verbose)
    console.log(
      "MarchingSquaresJS-isoContours: computing isocontour for " + threshold
    );

    // 计算等压线网格
  var ret = contourGrid2Paths(computeContourGrid(data, threshold));

  if (typeof settings.successCallback === "function")
    settings.successCallback(ret);

  return ret;
}
/* assume that x1 == 1 &&  x0 == 0 */
// 线性插值
function interpolateX(y, y0, y1) {
  return (y - y0) / (y1 - y0);
}

/* compute the isocontour 4-bit grid */
// 计算等压线4为的网格
function computeContourGrid(data, threshold) {
  // 行数、列数
  var rows = data.length - 1;
  var cols = data[0].length - 1;
  // 
  var ContourGrid = { rows: rows, cols: cols, cells: [] };

  // 遍历行数
  for (var j = 0; j < rows; ++j) {
    ContourGrid.cells[j] = [];
    // 遍历列数
    for (var i = 0; i < cols; ++i) {
      /* compose the 4-bit corner representation */
      var cval = 0;

      //取出相邻的四个点
      var tl = data[j + 1][i];
      var tr = data[j + 1][i + 1];
      var br = data[j][i + 1];
      var bl = data[j][i];

      if (isNaN(tl) || isNaN(tr) || isNaN(br) || isNaN(bl)) {
        continue;
      }

      // 检索阈值是否在四个高度内部,还是外部,四个值都高于阈值8421=15,或者四个值都低于阈值在外部0
      cval |= tl >= threshold ? 8 : 0;
      cval |= tr >= threshold ? 4 : 0;
      cval |= br >= threshold ? 2 : 0;
      cval |= bl >= threshold ? 1 : 0;

      /* resolve ambiguity for cval == 5 || 10 via averaging */
      var flipped = false;
      // 在对角线上
      if (cval === 5 || cval === 10) {
        // 计算平均值
        var average = (tl + tr + br + bl) / 4;
        // 两个对角线上的中点值偏离比较远,
        if (cval === 5 && average < threshold) {
          cval = 10; // 取另外一个对角线来确定
          flipped = true;
        } else if (cval === 10 && average < threshold) {
          cval = 5;
          flipped = true;
        }
      }

      /* add cell to ContourGrid if it contains edges */
      // 如果单元格包含边,则将其添加到ContourGrid

      // 阈值在四个点内部
      if (cval !== 0 && cval !== 15) {

        var top, bottom, left, right;
        top = bottom = left = right = 0.5;
        /* interpolate edges of cell */
        if (cval === 1) {  // 一个点在上,其他三个点都在下面
          // 插值左边
          left = 1 - interpolateX(threshold, tl, bl);
          // 插值下边
          bottom = 1 - interpolateX(threshold, br, bl);
        } else if (cval === 2) {
          bottom = interpolateX(threshold, bl, br);
          right = 1 - interpolateX(threshold, tr, br);
        } else if (cval === 3) {
          left = 1 - interpolateX(threshold, tl, bl);
          right = 1 - interpolateX(threshold, tr, br);
        } else if (cval === 4) {
          top = interpolateX(threshold, tl, tr);
          right = interpolateX(threshold, br, tr);
        } else if (cval === 5) {
          top = interpolateX(threshold, tl, tr);
          right = interpolateX(threshold, br, tr);
          bottom = 1 - interpolateX(threshold, br, bl);
          left = 1 - interpolateX(threshold, tl, bl);
        } else if (cval === 6) {
          bottom = interpolateX(threshold, bl, br);
          top = interpolateX(threshold, tl, tr);
        } else if (cval === 7) {
          left = 1 - interpolateX(threshold, tl, bl);
          top = interpolateX(threshold, tl, tr);
        } else if (cval === 8) {
          left = interpolateX(threshold, bl, tl);
          top = 1 - interpolateX(threshold, tr, tl);
        } else if (cval === 9) {
          bottom = 1 - interpolateX(threshold, br, bl);
          top = 1 - interpolateX(threshold, tr, tl);
        } else if (cval === 10) {
          top = 1 - interpolateX(threshold, tr, tl);
          right = 1 - interpolateX(threshold, tr, br);
          bottom = interpolateX(threshold, bl, br);
          left = interpolateX(threshold, bl, tl);
        } else if (cval === 11) {
          top = 1 - interpolateX(threshold, tr, tl);
          right = 1 - interpolateX(threshold, tr, br);
        } else if (cval === 12) {
          left = interpolateX(threshold, bl, tl);
          right = interpolateX(threshold, br, tr);
        } else if (cval === 13) {
          bottom = 1 - interpolateX(threshold, br, bl);
          right = interpolateX(threshold, br, tr);
        } else if (cval === 14) {
          left = interpolateX(threshold, bl, tl);
          bottom = interpolateX(threshold, bl, br);
        } else {
          console.log(
            "MarchingSquaresJS-isoContours: Illegal cval detected: " + cval
          );
        }
        ContourGrid.cells[j][i] = {
          cval: cval,   // 代表的是top、right、bottom、left中哪两个值有效果
          flipped: flipped,
          top: top,
          right: right,
          bottom: bottom,
          left: left,
        };
      }
    }
  }

  return ContourGrid;
}
// 计算等值点组成的路径
function contourGrid2Paths(grid) {
  var paths = [];
  var path_idx = 0;
  var epsilon = 1e-7;

  grid.cells.forEach(function (g, j) {
    g.forEach(function (gg, i) {
      if (typeof gg !== "undefined" && !isSaddle(gg) && !isTrivial(gg)) {
        var p = tracePath(grid.cells, j, i);
        var merged = false;
        /* we may try to merge paths at this point */
        if (p.info === "mergeable") {
          /*
            search backwards through the path array to find an entry
            that starts with where the current path ends...
          */
          var x = p.path[p.path.length - 1][0],
            y = p.path[p.path.length - 1][1];

          for (var k = path_idx - 1; k >= 0; k--) {
            if (
              Math.abs(paths[k][0][0] - x) <= epsilon &&
              Math.abs(paths[k][0][1] - y) <= epsilon
            ) {
              for (var l = p.path.length - 2; l >= 0; --l) {
                paths[k].unshift(p.path[l]);
              }
              merged = true;
              break;
            }
          }
        }
        if (!merged) paths[path_idx++] = p.path;
      }
    });
  });

  return paths;
}

总结:

具体的做法是按照给定的采样点,插值(例如反距离权重)成矩阵,将高度值构建成矩阵,将矩阵中的四个相邻的值取出来,按照给定的阈值进行线性插值定位,计算z值对应的经纬度,这就构成一个插值后的xyz值,将这些值按照路径拼起来就是等直线了

Turf处理等压线_第6张图片

参考:

等值线 | Turf.js中文网

GitHub - Turfjs/turf: A modular geospatial engine written in JavaScript

等值线 | Turf.js中文网

你可能感兴趣的:(前端)