如何计算2个相邻H3 Cells之间的平均距离?

How to calculate the average distance between 2 neighboring H3 Cells?

我正在为 Java https://github.com/uber/h3-java

使用 Uber H3 库

我想知道分辨率为 15 的 2 个相邻 H3 单元之间的平均距离是多少(它们各自中心点之间的距离,或 2 x 中心点)。

根据我尝试计算的方式,我得到了 3 个截然不同的结果:

哪个是正确的?为什么我会得到如此不同的结果?

public static final long RES_15_HEXAGON = 644569124665188486L; // some random res 15 H3 index (as Long)

private final H3Core h3;

H3UtilsTest() throws IOException {
   h3 = H3Core.newInstance();
}

@Test
void calculateDistanceBetweenNeighborsFromRealEdges() {
    final long h3Index = RES_15_HEXAGON;
    final GeoCoord centerPoint = h3.h3ToGeo(h3Index);
    assertEquals(46.94862876826281, centerPoint.lat);
    assertEquals(7.4404471879141205, centerPoint.lng);
    assertEquals(15, h3.h3GetResolution(h3Index));
    final List<GeoCoord> hexagon = this.h3.h3ToGeoBoundary(h3Index);
    assertEquals(6, hexagon.size());
    List<Double> edgeLengths = new ArrayList<>();
    for (int i = 0; i < hexagon.size(); i++) {
        edgeLengths.add(h3.pointDist(hexagon.get(i), hexagon.get((i + 1) % hexagon.size()), LengthUnit.m));
    }

    final double apothem = edgeLengths.stream().mapToDouble(Double::doubleValue).average().getAsDouble();
    assertEquals(0.5739852101653261, apothem);

    final double distanceBetweenNeighbors = 2 * apothem;
    assertEquals(1.1479704203306522, distanceBetweenNeighbors);
}

@Test
void calculateDistanceBetweenNeighborsFromAverageEdges() {
    final double averageEdgeLength = h3.edgeLength(15, LengthUnit.m);
    assertEquals(0.509713273, averageEdgeLength);
    assertEquals(1.019426546, 2 * averageEdgeLength);

    final double apothem = averageEdgeLength * Math.sqrt(3) / 2;
    assertEquals(0.4414246430641128, apothem);

    final double distanceBetweenNeighbors = 2 * apothem;
    assertEquals(0.8828492861282256, distanceBetweenNeighbors);
}

@Test
void calculateDistanceBetweenNeighborsFromNeighbors() {
    final GeoCoord origin = h3.h3ToGeo(RES_15_HEXAGON);
    final List<Long> neighbors = h3.kRing(RES_15_HEXAGON, 1);
    assertEquals(7, neighbors.size()); // contains the center hexagon as well
    neighbors.forEach(neighbor -> assertEquals(6, h3.h3ToGeoBoundary(neighbor).size())); // they are really 6-sided hexagons !
    final List<Double> distances = neighbors.stream().filter(neighbor -> neighbor != RES_15_HEXAGON).map(neighbor -> h3.pointDist(origin, h3.h3ToGeo(neighbor), LengthUnit.m)).toList();
    assertEquals(6, distances.size());

    final Double distanceBetweenNeighbors = distances.stream().mapToDouble(Double::doubleValue).average().getAsDouble();
    assertEquals(0.9941567117250641, distanceBetweenNeighbors);
}

我假设由于网格中“六边形”形状的变形,您对这些不同的方法会得到不同的答案。这是您选择的单元格及其邻居:

您可以看到六边形不是完全规则的 - 虽然 H3 试图尽量减少变形,但我们确实有在全球范围内变化的形状变形。单元格大小和形状会根据您选择的采样位置略有不同。

这里的“正确”答案在很大程度上取决于您的用例。我们可能可以计算出整个地球像元中心之间的平均距离,但您实际上可能更关心局部区域。或者平均值可能不是您真正需要的 - 您可能希望在 per-cell 的基础上进行计算。在大多数情况下,我发现最好是:

  • 使用细胞中心之间的 pointDist
  • 计算感兴趣细胞的实际距离
  • 感兴趣区域内的样本,例如应用程序视口,并计算样本的平均值。

为了它的价值,我实现了一种创建更大样本的方法,灵感来自@nrabinowitz 的建议:

public static final long RES_15_HEXAGON = 644569124665188486L;

private final H3Core h3;

H3UtilsTest() throws IOException {
   h3 = H3Core.newInstance();
}

@Test
void calculateDistanceBetweenNeighborsUsingLargerSample() {
    final Set<Long> origins = new HashSet<>(Set.of(RES_15_HEXAGON));
    final Set<Long> visitedOrigins = new HashSet<>();
    final Set<Long> nextOrigins = new HashSet<>();

    final List<Double> distances = new ArrayList<>();
    final int nbIterations = 10;
    for (int i = 0; i < nbIterations; i++) {
        for (Long origin : origins) {
            visitedOrigins.add(origin);
            final Set<Long> destinations = new HashSet<>(h3.kRing(origin, 1));
            destinations.removeAll(visitedOrigins);
            for (Long destination : destinations) {
                distances.add(h3.pointDist(h3.h3ToGeo(origin), h3.h3ToGeo(destination), LengthUnit.m));
            }
            destinations.removeAll(origins); // we don't need the hexagons (anymore) that are on the same ring as origins
            nextOrigins.addAll(destinations);
        }
        origins.clear();
        origins.addAll(nextOrigins);
        nextOrigins.clear();
    }

    final double averageDistanceBetweenNeighbors = distances.stream().mapToDouble(Double::doubleValue).average().getAsDouble();
    assertEquals(0.9942, averageDistanceBetweenNeighbors, 0.0001);
    assertEquals(870, distances.size());
}