You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用JS/HTML5 Canvas检测六边形内像素以划分陆水网格?

嘿,我来帮你搞定这个六边形网格的像素检测问题!你已经搞定了六边形的绘制,接下来核心就是判断哪些像素属于六边形内部,然后统计水域占比对吧?咱们一步步来:

第一步:改造六边形绘制函数,保存顶点信息

你的原hex函数只负责绘制,我们需要把六边形的顶点坐标存下来,这样后面才能判断点是否在内部。修改后的函数会返回顶点数组,同时别忘了闭合路径:

function hex(x = 0, y = 0, size = 10) {
  const vertices = [];
  ctx.beginPath();
  for (let side = 0; side < 6; side++) {
    const angle = side * 2 * Math.PI / 6;
    const px = x + size * Math.cos(angle);
    const py = y + size * Math.sin(angle);
    vertices.push({x: px, y: py});
    side === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py);
  }
  ctx.closePath(); // 闭合路径,确保填充完整
  return vertices;
}
第二步:判断单个像素是否在六边形内

因为六边形是凸多边形,用叉积法判断效率很高——只要点在所有边的同一侧(内部方向),就属于六边形内部。先写个叉积计算的辅助函数:

// 计算向量AB与AC的叉积,用于判断点C相对于AB的位置
function crossProduct(a, b, c) {
  return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
}

然后实现点在六边形内的判断函数:

function isPointInHex(point, hexVertices) {
  let sign = null;
  for (let i = 0; i < hexVertices.length; i++) {
    const a = hexVertices[i];
    const b = hexVertices[(i + 1) % 6]; // 循环取下一个顶点
    const cross = crossProduct(a, b, point);
    
    // 点刚好在边上,直接算内部
    if (cross === 0) return true;
    
    // 记录第一个非零叉积的符号
    if (sign === null) {
      sign = cross > 0 ? 1 : -1;
    } else if ((cross > 0 ? 1 : -1) !== sign) {
      // 符号不一致,说明点在外部
      return false;
    }
  }
  return true;
}
第三步:确定需要检测的像素范围

没必要遍历整个画布,只需要遍历六边形的外接矩形内的像素就行,能大幅减少计算量。写个函数获取外接矩形的边界:

function getHexBoundingBox(hexVertices) {
  let minX = Infinity, maxX = -Infinity;
  let minY = Infinity, maxY = -Infinity;
  
  hexVertices.forEach(v => {
    minX = Math.min(minX, v.x);
    maxX = Math.max(maxX, v.x);
    minY = Math.min(minY, v.y);
    maxY = Math.max(maxY, v.y);
  });
  
  // 转换成整数像素坐标(像素是离散的)
  return {
    left: Math.floor(minX),
    right: Math.ceil(maxX),
    top: Math.floor(minY),
    bottom: Math.ceil(maxY)
  };
}
第四步:像素颜色检测与统计

首先要获取地图的像素数据,然后判断每个六边形内的像素是否属于水域,最后计算占比:

1. 获取地图像素数据

假设你的地图在另一个Canvas上(比如mapCanvas),先一次性获取所有像素数据:

const mapCtx = mapCanvas.getContext('2d');
const mapImageData = mapCtx.getImageData(0, 0, mapCanvas.width, mapCanvas.height);
const pixelData = mapImageData.data; // 每个像素占4位:r, g, b, a

2. 获取单个像素的颜色

function getPixelColor(x, y, imageData) {
  const index = (y * imageData.width + x) * 4;
  return {
    r: pixelData[index],
    g: pixelData[index + 1],
    b: pixelData[index + 2],
    a: pixelData[index + 3]
  };
}

3. 判断是否为水域颜色

你需要根据自己地图的实际蓝色调调整阈值,这里给个参考:

function isWaterColor(color) {
  // 示例阈值:偏蓝的颜色,可根据你的地图采样调整
  return color.r < 100 && color.g < 150 && color.b > 150;
}

4. 统计六边形内的水域占比并着色

function processHex(x, y, size, waterThreshold = 0.5) {
  const hexVertices = hex(x, y, size);
  const bbox = getHexBoundingBox(hexVertices);
  
  let waterCount = 0;
  let totalCount = 0;
  
  // 遍历外接矩形内的所有像素
  for (let px = bbox.left; px <= bbox.right; px++) {
    for (let py = bbox.top; py <= bbox.bottom; py++) {
      // 跳过地图外的像素
      if (px < 0 || px >= mapImageData.width || py < 0 || py >= mapImageData.height) continue;
      
      // 判断像素是否在六边形内
      if (isPointInHex({x: px, y: py}, hexVertices)) {
        totalCount++;
        const color = getPixelColor(px, py, mapImageData);
        if (isWaterColor(color)) waterCount++;
      }
    }
  }
  
  // 计算占比并设置颜色
  const waterRatio = totalCount === 0 ? 0 : waterCount / totalCount;
  ctx.fillStyle = waterRatio >= waterThreshold ? '#4a90e2' : '#2ecc71';
  ctx.fill();
  ctx.strokeStyle = '#000';
  ctx.stroke(); // 描边方便看网格
  
  return waterRatio >= waterThreshold;
}
第五步:生成整个六边形网格

六边形网格的排列有规律:奇数行的六边形会比偶数行偏移半个六边形宽度。我们来实现网格生成:

function generateHexGrid(mapWidth, mapHeight, hexSize) {
  const hexWidth = hexSize * Math.sqrt(3); // 六边形的宽度(两个对边的距离)
  const hexHeight = hexSize * 2; // 六边形的高度(两个对顶点的距离)
  
  // 遍历所有行,行间距是高度的3/4(六边形紧密排列的间距)
  for (let y = hexSize; y < mapHeight + hexSize; y += hexHeight * 0.75) {
    // 奇数行偏移半个宽度
    const xOffset = Math.floor(y / (hexHeight * 0.75)) % 2 === 1 ? hexWidth / 2 : 0;
    
    // 遍历该行的所有六边形
    for (let x = xOffset + hexWidth/2; x < mapWidth + hexWidth; x += hexWidth) {
      processHex(x, y, hexSize);
    }
  }
}
一些优化小建议
  • 提前获取ImageData:不要每次调用processHex都重新获取,提前获取一次能大幅提升性能。
  • 调整颜色阈值:用取色器工具采样你地图里的水域颜色,精确调整isWaterColor的参数。
  • 性能优化:如果网格很大,考虑用Web Worker处理像素统计,避免阻塞主线程导致页面卡顿。
  • 边界处理:地图边缘的六边形可能部分超出范围,统计时记得跳过这些像素。

内容的提问来源于stack exchange,提问作者Durakken

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:53:22