トップOpenStreetMap > OSM陸地ポリゴンの分割

OSM陸地ポリゴンの分割

はじめに

低ズーム用はノードを間引くことによりポリゴンデータのサイズを小さくできるが、 高ズーム用の場合、そのままでは、スマホのメモリには載らないほどの大きなポリゴン(ユーラシア大陸など)がある。

このため、ポリゴンの分割が必須となる。また、メモリに載るとしても分割した方がレンダリング時間か短くなる。

分割プログラム

まず、水平分割プログラム splitPolygonHorizontally メソッドを作成、デバッグして、 スマホで地図を表示して問題がないことをチェックした。

実際には zoom 9か10以上で使うがチェックは zoom 5 で行った。 ハイズームではごく一部のチェックしかできないためである。

次に、垂直分割をチェックした。

南極と北米で分割エラーが出た。垂直分割線とポリゴンの交点の座標を求めている。 y1 < y2 となるはずが逆のケースがある。特に、X, Y座標は負の値をとらないのに 交点が負の値になっているケースが多い。

当然、ここでの分割は行っていないので、描画上のエラーは生じていないことを確認した。 しかし、気になるので、いずれ原因を究明したい。

【原因判明】

水平分割で海岸線が偶然水平分割線と重なった場合、交点は算出できない。 同様に、垂直分割で海岸線が垂直分割線と重なった場合、交点は算出できない。 南極大陸でエラーが多いのはこのためである。

取りあえず、ここでは分割をやめればいい。

procCoastLine antarctica
Error y1=835864880 y2=-1812152464
Error y1=912725841 y2=-1029607766
Error y1=912970227 y2=-360144960
Error y1=913196947 y2=309317846
Error y1=913789055 y2=-1931044030
Error y1=914006026 y2=-545901416
Error y1=914536049 y2=839241197
Error y1=915884784 y2=-2070583485
Error y1=917100957 y2=-685440872
Error y1=917407864 y2=699701742

procCoastLine north-america
Error y1=206627837 y2=206530715
    List<Polygon> splitPolygonHorizontally(int span) {
        List<Polygon> dst = new ArrayList<>();

        rotate(iYmax);
//System.out.printf("iYmax=%d\n", iYmax);
        int bgn = ((ymin + span) / span) * span;
        boolean fSplit = false;
        for (int y = bgn; y < ymax; y += span) {  // y : 水平分割線のY座標
            int cntCross = 0;
            int i1 = -1, i2 = -1;
            int k = iYmax;
            do {
                if ((ys[k] - (long)y) * (ys[next(k)] - y) < 0) {  // 交差判定
                    cntCross++;
                    if (i1 < 0) i1 = k;     // 最初の交点
                    else i2 = k;            // 最後の交点
                } 
                k = next(k);
            } while (k != iYmax) ;          // 一巡するまで

            //System.out.printf("%d ", cntCross);
            if (cntCross == 2 && i2 - i1 >= 2 && i2+1 < xs.length) { // 水平分割可能
                int x1 = crossHorizonX(xs[i1], ys[i1], xs[i1+1], ys[i1+1], y);
                int x2 = crossHorizonX(xs[i2], ys[i2], xs[i2+1], ys[i2+1], y);
                // (x1,y): 線分i1ーi1+1と水平線yの交点, (x2,y): 線分i2ーi2+1と水平線yの交点
                if (x2 > x1) {
                    System.out.printf("Error x1=%d x2=%d\n", x1, x2);
                    continue;   // 分割しない
                }

                //分割で生まれるポリゴン x1, i1+1, ... , i2, x2, x1
                Polygon pN = new Polygon(i2 - i1 + 3);
                pN.add(x1, y);
                for (int i = i1+1; i <= i2; i++) {
                    pN.add(xs[i], ys[i]);
                }
                pN.add(x2, y);
                pN.add(x1, y);
                pN.setMinMax();
                pN.fSplit = true;
                fSplit = true;      // 分割が起きた
   //System.out.printf("assign=%d length=%d\n", i2 - i1 + 3, pN.length); ここでの矛盾はない

                dst.add(pN);

                // 元のポリゴン(小さくなる)
                set(i1+1, x1, y);
                set(i2, x2, y);
                remove(i1+2, i2);  // i1+2, i1+3, ... , i2-1 を削除
                setMinMax();
            }
            //System.out.println();
        }
        setMinMax();
        dst.add(this);
        return dst;
    }

    List<Polygon> splitPolygonVertically(int span) {
        List<Polygon> dst = new ArrayList<>();

        rotate(iXmax);
//System.out.printf("iYmax=%d\n", iYmax);
        int bgn = ((xmin + span) / span) * span;
        boolean fSplit = false;
        for (int x = bgn; x < xmax; x += span) {  // y : 水平分割線のY座標
            int cntCross = 0;
            int i1 = -1, i2 = -1;
            int k = iXmax;
            do {
                if ((xs[k] - (long)x) * (xs[next(k)] - x) < 0) {  // 交差判定
                    cntCross++;
                    if (i1 < 0) i1 = k;     // 最初の交点
                    else i2 = k;            // 最後の交点
                } 
                k = next(k);
            } while (k != iXmax) ;          // 一巡するまで

            //System.out.printf("%d ", cntCross);
            if (cntCross == 2 && i2 - i1 >= 2 && i2+1 < ys.length) { // 垂直分割可能
                int y1 = crossVertY(xs[i1], ys[i1], xs[i1+1], ys[i1+1], x);
                int y2 = crossVertY(xs[i2], ys[i2], xs[i2+1], ys[i2+1], x);
                // (x,y1): 線分i1ーi1+1と水平線yの交点, (x,y2): 線分i2ーi2+1と垂直線xの交点
                if (y2 < y1) {
                    System.out.printf("Error y1=%d y2=%d\n", y1, y2);
                    continue;   // 分割しない
                }

                //分割で生まれるポリゴン y1, i1+1, ... , i2, y2, y1
                Polygon pN = new Polygon(i2 - i1 + 3);
                pN.add(x, y1);
                for (int i = i1+1; i <= i2; i++) {
                    pN.add(xs[i], ys[i]);
                }
                pN.add(x, y2);
                pN.add(x, y1);
                pN.setMinMax();
                pN.fSplit = true;
                fSplit = true;      // 分割が起きた
   //System.out.printf("assign=%d length=%d\n", i2 - i1 + 3, pN.length); ここでの矛盾はない

                dst.add(pN);

                // 元のポリゴン(小さくなる)
                set(i1+1, x, y1);
                set(i2, x, y2);
                remove(i1+2, i2);  // i1+2, i1+3, ... , i2-1 を削除
                setMinMax();
            }
            //System.out.println();
        }
        setMinMax();
        dst.add(this);
        return dst;
    }

    // newStart が配列の先頭になるように回転する
    void rotate(int newStart) {
        int[] xsNew = xs.clone();
        int[] ysNew = ys.clone();
        for (int i = 0; i < length; i++) {
            xsNew[i] = xs[(i+newStart)%length];
            ysNew[i] = ys[(i+newStart)%length];
        }
        xs = xsNew;
        ys = ysNew;
        iXmax = (iXmax + length - newStart) % length;
        iYmax = (iYmax + length - newStart) % length;
    }

水平・垂直分割

最初に水平分割を行い、その結果のポリゴンを垂直分割する。

エラーが多く出た。垂直分割にバグがあるかも知れない。吟味しよう。

リファレンス

[1] OSM: line、polygonの簡略化