6、7年前のことであるが、OSM(OpenStreetMap)の行政境界線の精度が非常に低かった。市街地(神奈川県と東京都の境界)で数10mの誤差があった。
国土地理院地図の場合、ズームレベル15~18に記載される地物の水平精度は、原則として、17.5m以内(標準偏差)となっている[1]。
行政境界線の精度については、基準省令第2条第2項で、都市計画区域内では平面位置の誤差が2.5m以内、都市計画区域外では25m以内と規定されている[2]。?
OSMの行政境界線は誤って変更されたり、消されることもある。
国土数値情報:行政区域データをダウンロードして、OSM地図上に行政境界線を描いて、精度を確認してみたい。
神奈川県のデータをダウンロードした。解凍したxmlファイルは 40MB弱となった。
ファイルの上段に Pointデータがある。市とか町の代表点であろうか?
<jps:GM_Point id="n00012"> <jps:GM_Point.position> <jps:DirectPosition> <DirectPosition.coordinate>35.56139800 139.63333100</DirectPosition.coordinate> <DirectPosition.dimension>2</DirectPosition.dimension> </jps:DirectPosition> </jps:GM_Point.position> </jps:GM_Point>
境界線データは次のようになっている。idから境界線が何かわかるのであろうが、 まずは、精度を確認したいので、極座標値列データで境界線を描いてみる。
<jps:GM_Curve id="c00234"> <jps:GM_Primitive.proxy idref="c00234"/> <jps:GM_Primitive.proxy idref="_c00234"/> <jps:GM_OrientablePrimitive.orientation>+</jps:GM_OrientablePrimitive.orientation> <jps:GM_OrientablePrimitive.primitive idref="c00234"/> <jps:GM_Curve.segment> <jps:GM_LineString> <jps:GM_CurveSegment.interpolation>linear</jps:GM_CurveSegment.interpolation> <jps:GM_LineString.controlPoint> <jps:GM_PointArray> <GM_PointArray.column> <jps:GM_Position.indirect> <GM_PointRef.point idref="n00236"/> </jps:GM_Position.indirect> </GM_PointArray.column> <GM_PointArray.column> <jps:GM_Position.direct> <DirectPosition.coordinate>35.26616500 139.72910900</DirectPosition.coordinate> <DirectPosition.dimension>2</DirectPosition.dimension> </jps:GM_Position.direct> </GM_PointArray.column> <GM_PointArray.column> <jps:GM_Position.direct> <DirectPosition.coordinate>35.26617600 139.72914600</DirectPosition.coordinate> <DirectPosition.dimension>2</DirectPosition.dimension> </jps:GM_Position.direct> </GM_PointArray.column> <GM_PointArray.column> <jps:GM_Position.direct> <DirectPosition.coordinate>35.26617300 139.72916500</DirectPosition.coordinate> <DirectPosition.dimension>2</DirectPosition.dimension> </jps:GM_Position.direct> </GM_PointArray.column> <GM_PointArray.column> <jps:GM_Position.direct> <DirectPosition.coordinate>35.26618900 139.72920300</DirectPosition.coordinate> <DirectPosition.dimension>2</DirectPosition.dimension> </jps:GM_Position.direct> </GM_PointArray.column> <GM_PointArray.column> <jps:GM_Position.direct> <DirectPosition.coordinate>35.26621400 139.72922900</DirectPosition.coordinate> <DirectPosition.dimension>2</DirectPosition.dimension> </jps:GM_Position.direct> </GM_PointArray.column> <GM_PointArray.column> <jps:GM_Position.direct> <DirectPosition.coordinate>35.26623100 139.72925600</DirectPosition.coordinate> <DirectPosition.dimension>2</DirectPosition.dimension> </jps:GM_Position.direct> </GM_PointArray.column> <GM_PointArray.column> <jps:GM_Position.direct> <DirectPosition.coordinate>35.26622800 139.72928400</DirectPosition.coordinate> <DirectPosition.dimension>2</DirectPosition.dimension> </jps:GM_Position.direct> </GM_PointArray.column> <GM_PointArray.column> <jps:GM_Position.direct> <DirectPosition.coordinate>35.26617800 139.72932000</DirectPosition.coordinate> <DirectPosition.dimension>2</DirectPosition.dimension> </jps:GM_Position.direct> </GM_PointArray.column> <GM_PointArray.column> <jps:GM_Position.direct> <DirectPosition.coordinate>35.26613700 139.72934900</DirectPosition.coordinate> <DirectPosition.dimension>2</DirectPosition.dimension> </jps:GM_Position.direct> </GM_PointArray.column> <GM_PointArray.column> <jps:GM_Position.direct> <DirectPosition.coordinate>35.26609200 139.72937300</DirectPosition.coordinate> <DirectPosition.dimension>2</DirectPosition.dimension> </jps:GM_Position.direct> </GM_PointArray.column> <GM_PointArray.column> <jps:GM_Position.direct> <DirectPosition.coordinate>35.26606900 139.72937000</DirectPosition.coordinate> <DirectPosition.dimension>2</DirectPosition.dimension> </jps:GM_Position.direct> </GM_PointArray.column> <GM_PointArray.column> <jps:GM_Position.direct> <DirectPosition.coordinate>35.26604000 139.72932100</DirectPosition.coordinate> <DirectPosition.dimension>2</DirectPosition.dimension> </jps:GM_Position.direct> </GM_PointArray.column> <GM_PointArray.column> <jps:GM_Position.direct> <DirectPosition.coordinate>35.26600700 139.72926200</DirectPosition.coordinate> <DirectPosition.dimension>2</DirectPosition.dimension> </jps:GM_Position.direct> </GM_PointArray.column> <GM_PointArray.column> <jps:GM_Position.direct> <DirectPosition.coordinate>35.26598900 139.72923600</DirectPosition.coordinate> <DirectPosition.dimension>2</DirectPosition.dimension> </jps:GM_Position.direct> </GM_PointArray.column> <GM_PointArray.column> <jps:GM_Position.direct> <DirectPosition.coordinate>35.26599500 139.72921100</DirectPosition.coordinate> <DirectPosition.dimension>2</DirectPosition.dimension> </jps:GM_Position.direct> </GM_PointArray.column> <GM_PointArray.column> <jps:GM_Position.direct> <DirectPosition.coordinate>35.26601000 139.72918000</DirectPosition.coordinate> <DirectPosition.dimension>2</DirectPosition.dimension> </jps:GM_Position.direct> </GM_PointArray.column> <GM_PointArray.column> <jps:GM_Position.direct> <DirectPosition.coordinate>35.26602400 139.72910800</DirectPosition.coordinate> <DirectPosition.dimension>2</DirectPosition.dimension> </jps:GM_Position.direct> </GM_PointArray.column> <GM_PointArray.column> <jps:GM_Position.direct> <DirectPosition.coordinate>35.26603900 139.72908700</DirectPosition.coordinate> <DirectPosition.dimension>2</DirectPosition.dimension> </jps:GM_Position.direct> </GM_PointArray.column> <GM_PointArray.column> <jps:GM_Position.indirect> <GM_PointRef.point idref="n00236"/> </jps:GM_Position.indirect> </GM_PointArray.column> </jps:GM_PointArray> </jps:GM_LineString.controlPoint> </jps:GM_LineString> </jps:GM_Curve.segment> </jps:GM_Curve>
まずは精度のチェックだけなので、当初、直接、xmlファイルを Android に読込むプログラムを作った。 プログラムは動作するが、緯度、経度のパースでしばしばエラーが起きた。
error: text=' 139.55460800' length=13 start=0そのためパソコンで、xmlをバイナリレコードに変換することにした。
ファイル形式は暫定的なものであるが、次のようにした。
緯度、経度は 107をかけて整数化した。
先頭に、そのファイル全体の境界ボックスを置き、次に、レコード数を置いた。
その後ろに、レコード毎の境界ボックス、境界線のノード数、座標値列データを置いた。
minlonAll, minlatAll, maxlonAll, maxlatAll num_records minlon, minlat, maxlon, maxlat num_nodes, lon0, lat0, lon1, lat1, ..., lonL, latL
とりあえず、東京都と神奈川県だけをダウンロードして変換した。実行時間はAndroidタブレットより、1桁速い。 東京は細分化され、島嶼部も多いためであろうか、神奈川県の約2倍のファイルサイズで、レコード数約5倍となった。
東京都 3300records 1.84MB 実行時間: 0.02分 神奈川県 613records 983KB 実行時間: 0.01分
OSM地図に境界線を描画して、OSMの boundary=administaitive と比較した。 OSM地図(日本の場合)の境界線は当初はこの国土数値情報をインポートしたようである。 その後、商用使用が禁止されたため、現在は、インポートを禁止されている。
精度が高いことを期待したが、残念ながらそうでもなかった。都市計画区域内の標準偏差は2.5m以内であったとしても、 最大誤差はもっと大きい。
OSM地図の場合、インポート後に年月を経て、手作業で修正が行われたようである。 ざっと見た限りでは、OSMの方が精度が高い。ただし、誤まって消された所があるかもしれない。 一から、手作業で書いた境界線(東京都23区の境界線)では、大きく間違えているところがあった。 恐らくマッパーの先入観で間違えたのであろう。
要するに、全体的には、OSM地図の境界線の方が精度が高い。 しかし、場所によっては、OSM地図の境界線は大きく間違っていたり、誤って、消されている可能性がある。
取り急ぎ、全都道府県の境界線データをダウンロードする必要はない。 気になったところだけ、ダウンロードして、境界線を比較する。
OSMデータから抽出した主要境界線は別ファイルとして管理する。誤って、消された境界線があった場合、 保存された境界線データを使用する。
座標値列データは差分コードにより、4割前後縮小される。通常はメモリに常駐させる必要はないが、 統計処理などで、全国の主要境界線データを使う場合、必要なメモリは多分 30MB程度で済むであろう。
import java.io.*; import java.nio.*; import java.util.*; import java.nio.file.Paths; import javax.xml.parsers.*; import org.xml.sax.*; import org.xml.sax.helpers.DefaultHandler; public class Parser extends DefaultHandler { static class PointArray { int minlon, minlat, maxlon, maxlat; int[] points; // lon, lat } static void parseAdmin(String name) throws Exception{ String path = "c:/map/admin/" + name + ".xml"; String dst = "c:/map/adm/" + name + ".dat"; long start = System.currentTimeMillis(); SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser parser = factory.newSAXParser(); Parser handler = new Parser(); parser.parse(Paths.get(path).toFile(), handler); PointArray[] pa = handler.listPA.toArray(new PointArray[0]); System.out.println(pa.length + "records"); DataOutputStream dos = new DataOutputStream(new BufferedOutputStream( new FileOutputStream(dst))); dos.writeInt(handler.minlonAll); dos.writeInt(handler.minlatAll); dos.writeInt(handler.maxlonAll); dos.writeInt(handler.maxlatAll); dos.writeInt(pa.length); for (PointArray rec : pa) { dos.writeInt(rec.minlon); dos.writeInt(rec.minlat); dos.writeInt(rec.maxlon); dos.writeInt(rec.maxlat); dos.writeInt(rec.points.length/2); for (int v : rec.points) { dos.writeInt(v); } } dos.close(); System.out.printf("実行時間: %.2f分\n", (System.currentTimeMillis()-start)/60000.0); } List<PointArray> listPA = new ArrayList<>(); List<Integer> list = new ArrayList<>(); int minlon, minlat, maxlon, maxlat; int minlonAll, minlatAll, maxlonAll, maxlatAll; Parser() { minlonAll = minlatAll = Integer.MAX_VALUE; maxlonAll = maxlatAll = Integer.MIN_VALUE; } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { super.startElement(uri, localName, qName, attributes); if (qName.equals("jps:GM_Curve.segment")) { //System.out.println("Bgn: " + qName); list.clear(); minlon = minlat = Integer.MAX_VALUE; maxlon = maxlat = Integer.MIN_VALUE; } else if (qName.equals("DirectPosition.coordinate")) { coord = true; //System.out.println("coord"); } } boolean coord; public void characters(char[] ch, int start, int length) throws SAXException { // 6. テキストが出現したなら、char配列をStringにしてフィールドへ保存する if (coord) { String text = new String(ch, start, length); String[] val = text.split(" "); if (val.length == 2) { try { int lon = (int) (Double.parseDouble(val[0]) * 10000000); int lat = (int) (Double.parseDouble(val[1]) * 10000000); if (lon < minlon) minlon = lon; if (lat < minlat) minlat = lat; if (lon > maxlon) maxlon = lon; if (lat > maxlat) maxlat = lat; list.add(lon); list.add(lat); //System.out.println(lon + ", " + lat); } catch (Exception ex) { System.out.println("error: text='" + text + "' length=" + length + " start=" + start); ex.printStackTrace(); } } } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { super.endElement(uri, localName, qName); if (qName.equals("jps:GM_Curve.segment")) { //System.out.println("End: " + qName); PointArray pa = new PointArray(); pa.points = list.stream().mapToInt(i -> i).toArray(); pa.minlon = minlon; pa.minlat = minlat; pa.maxlon = maxlon; pa.maxlat = maxlat; listPA.add(pa); if (minlon < minlonAll) minlonAll = minlon; if (minlat < minlatAll) minlatAll = minlat; if (maxlon > maxlonAll) maxlonAll = maxlon; if (maxlat > maxlatAll) maxlatAll = maxlat; } else if (qName.equals("DirectPosition.coordinate")) { coord = false; } else if (qName.equals("jps:GM_Curve")) { } } public static void main(String[] args) throws Exception { parseAdmin("N03-110331_13"); // 東京都 parseAdmin("N03-110331_14"); // 神奈川県 } }