トップOpenStreetMap > OSM: line、polygonの簡略化

OSM: line、polygonの簡略化

1.はじめに

OSMデータ(xml形式)はシンプルだが巨大である

OSMデータ(xml形式)は node, way, relation という階層構造である。座標値は node にある。 way は line や polygon を表す。ここには座標値はなく node id 列がある。 relation のメンバーは node, way, relation である。

構造的には極めてシンプルである。全データをメモリに取り込める場合、 node、way、relation の構造体を定義して、そこにデータを取り込めばよい。 relation は メンバーに relation を含むため、再帰構造となる。

しかし、圧縮ファイルでも 50GB の大きさである。 上記の構造体に全データを取り込むには数百GB~数TBのメモリが必要となる。

つまり、構造はシンプルであるが、メモリに取り込めないため、その操作が非常に厄介である。

line、polygonデータの間引きは簡単だが、交差エラーの回避が厄介である

間引きでは Ramer-Douglas-Peuckerアルゴリズムが著名で、 PostGISのST_SimplifyPreserveTopologyでもこれが使われている。 PostGISには複数のpolygonが交差しているとき、交差のない部分だけを取り出す機能があり、 これを使って実質上交差を回避している。この実装は間引きよりも難しい。 特に multipolygon の交差回避が難しい。

2.line、polygonの簡単化

間引き

Ramer-Douglas-Peuckerアルゴリズム(またはDouglas-Peuckerアルゴリズム)は 下に示すように、極めてシンプルなものである。

simplySection関数は線分 points[i]-points[j] の間引きを行う関数である。 許容誤差を epsilon とする。

線分 points[i]-points[j]と途中の点 points[k] との垂直距離を調べて、 その最大値を maxDistance とする。

この最大値が epsilon を超えなければ、全ての中間点は間引いて、処理は終わりである。

最大値が許容誤差を超える場合には、 この最大値を持った点 points[maxIndex] は間引かないことに決定する。 この点を境として、ふたつの線分 points[i]-points[maxIndex]および points[maxIndex]-points[j] に分けて、 それぞれについて simplySection関数を再帰コールする。

[C/C++言語]

// Returns the distance from point p to the line between p1 and p2
double perpendicular_distance(point_t p, point_t p1, point_t p2) {
    double dx = p2.x - p1.x;
    double dy = p2.y - p1.y;
    double d = sqrt(dx * dx + dy * dy);
    return fabs(p.x * dy - p.y * dx + p2.x * p1.y - p2.y * p1.x)/d;
}

void simplifySection(point_t *points, size_t i, size_t j, double epsilon) {
    if ((i + 1) == j) return;

    double maxDistance = -1.0;
    size_t maxIndex = i;
    for (size_t k = i + 1; k < j; k++) {
        double distance = perpendicular_distance(points[k], points[i], points[j]);
        if (distance > maxDistance) {
            maxDistance = distance;
            maxIndex = k;
        }
    }
    if (maxDistance <= epsilon) {
        for (size_t k = i + 1; k < j; k++) {
            points[k].unused = true;   // 間引く
        }
    } else {
        simplifySection(points, i, maxIndex, epsilon);
        simplifySection(points, maxIndex, j, epsilon);
    }
}

[Java]

    // 1度=約100km ⇒ 1m=約0.00001度
    public static double PerpendicularDistance(long p, long p1, long p2) {
        double E7 = 10000000.0;
        double px = getX(p) / E7;
        double py = getY(p) / E7;
        double p1x = getX(p1) / E7;
        double p1y = getY(p1) / E7;
        double p2x = getX(p2) / E7;
        double p2y = getY(p2) / E7;

        double dx = p2x - p1x;
        double dy = p2y - p1y;
        double d = Math.sqrt(dx*dx + dy*dy);
        return Math.abs(px*dy - py*dx + p2x*p1y - p2y*p1x)/d;
    }

    public static void SimplifySection(long[] pnts, int i, int j, double epsilon) {
        if ((i + 1) == j) return;
        double maxDistance = -1.0;
        int maxIndex = i;
        for (int k = i + 1; k < j; k++) {
            double distance = PerpendicularDistance(pnts[k], pnts[i], pnts[j]);
            if (distance > maxDistance) {
                maxDistance = distance;
                maxIndex = k;
            }
        }
        if (maxDistance <= epsilon) {
            for (int k = i + 1; k < j; k++) {
                pnts[k] = Long.MIN_VALUE;   // 間引く
            }
        } else {
            SimplifySection(pnts, i, maxIndex, epsilon);
            SimplifySection(pnts, maxIndex, j, epsilon);
        }
    }

このように、再帰を使うことにより、効率よく間引きを行う。

A.リファレンス

[1] トラックポイントを間引くアルゴリズム
[2] Ramer-Douglas-Peucker algorithm
[3] simple_Polygon
[4] 線分交差判定
[5] Ramer-Douglas-Peucker line simplification
[6] Line Intersection using Bentley Ottmann Algorithm
[7] Bentley-Otmann Algorithm
[8] cairo-bentley-ottmann.c
[9] GEOSによる図形演算