外部ライブラリを使わないKMLパーサーを書いてみた。Java SE 6 だけでKMLファイルをパースする。

kml/Document(name, description)/Placemark(name, description)/Point(coordinates) or LineString(coordinates) のあたりだけ対応。つまり、名称、説明、ポイント(点)とパス(点列)だけ。


import java.io.*;
import java.net.*;
import java.util.*;
import javax.xml.parsers.*;
import javax.xml.xpath.*;
import org.w3c.dom.*;
import org.xml.sax.*;
 
public class Kml {
 
  private static final String LS = System.getProperty("line.separator");
  public List<Document> documents;
 
  public Kml() {
    ; // 何もしない
  }
 
  public static class Document {
    public String name;
    public String description;
    public List<Placemark> placemarks;
  }
 
  public static class Placemark {
    public String name;
    public String description;
    public Point point;
    public LineString lineString;
  }
 
  public static class Point {
    public GeoPoint coordinates;
    public String toString() {
      return "coordinates=" + coordinates;
    }
  }
 
  public static class LineString {
    public List<GeoPoint> coordinates;
    public String toString() {
      if (coordinates != null) {
        StringBuilder sb = new StringBuilder();
        sb.append("coordinates=");
        if (coordinates.size() > 0) {
          for (int i = 0; i < coordinates.size(); i++) {
            sb.append(coordinates.get(i));
            sb.append(",");
          }
          sb.deleteCharAt(sb.length() - 1); // 最後のカンマを削除
        }
        return sb.toString();
      } else {
        return "coordinates=null";
      }
    }
  }
 
  public static class GeoPoint {
    public double lat;
    public double lon;
 
    public GeoPoint(double lat, double lon) {
      this.lat = lat;
      this.lon = lon;
    }
 
    public String toString() {
      return lat + "," + lon;
    }
  }
 
  public static Kml parse(org.w3c.dom.Document doc) throws Exception {
    XPath xpath = XPathFactory.newInstance().newXPath();
    Kml kml = new Kml();
    kml.documents = new ArrayList<Document>();
    NodeList documents = (NodeList) xpath.evaluate("kml/Document", doc, XPathConstants.NODESET);
    for (int i = 0; i < documents.getLength(); i++) {
      Document d = new Document();
      Node document = documents.item(i);
      d.placemarks = new ArrayList<Placemark>();
      d.name = xpath.evaluate("name", document);
      d.description = xpath.evaluate("description", document);
      NodeList placemarks = (NodeList) xpath.evaluate("Placemark", document, XPathConstants.NODESET);
      for (int j = 0; j < placemarks.getLength(); j++) {
        Placemark p = new Placemark();
        Node placemark = placemarks.item(j);
        p.name = xpath.evaluate("name", placemark);
        p.description = xpath.evaluate("description", placemark);
        List<GeoPoint> point = parseLineStringCoordinates(xpath.evaluate("Point/coordinates", placemark));
        if (point != null) {
          p.point = new Point();
          p.point.coordinates = point.get(0);
        }
        List<GeoPoint> lineString = parseLineStringCoordinates(xpath.evaluate("LineString/coordinates", placemark));
        if (lineString != null) {
          p.lineString = new LineString();
          p.lineString.coordinates = lineString;
        }
        d.placemarks.add(p);
      }
      kml.documents.add(d);
    }
    return kml;
  }
 
  public static Kml parse(String xmlContentData) throws Exception {
    StringReader sr = new StringReader(xmlContentData);
    InputSource is = new InputSource(sr);
    org.w3c.dom.Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
    return parse(doc);
  }
 
  public static Kml parse(URL url) throws Exception {
    org.w3c.dom.Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(url.toString());
    return parse(doc);
  }
 
  private static List<GeoPoint> parseLineStringCoordinates(String coordinates) {
    try {
      if (coordinates == null || "".equals(coordinates)) {
        return null;
      }
      List<GeoPoint> list = new ArrayList<GeoPoint>();
      String[] a = coordinates.split("\\s");
      for (int i = 0; i < a.length; i++) {
        String[] lonlat = a[i].split(","); // lon.lat,alt
        if (lonlat.length >= 2) {
          list.add(new GeoPoint(Double.parseDouble(lonlat[1]), Double.parseDouble(lonlat[0])));
        }
      }
      if (list.size() < 1) {
        return null;
      }
      return list;
    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }
  }
 
  public String toString() {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < documents.size(); i++) {
      Document d = documents.get(i);
      sb.append("{" + LS);
      sb.append("  Document.name=" + d.name + LS);
      sb.append("  Document.description=" + d.description + LS);
      for (int j = 0; j < d.placemarks.size(); j++) {
        Placemark p = d.placemarks.get(j);
        sb.append("  {" + LS);
        sb.append("  Placemark.name=" + p.name + LS);
        sb.append("  Placemark.description=" + p.description + LS);
        sb.append("  Placemark.Point=" + p.point + LS);
        sb.append("  Placemark.LineString=" + p.lineString + LS);
        sb.append("  }" + LS);
      }
      sb.append("}" + LS);
    }
    return sb.toString();
  }
 
  // テスト的なサンプル
  public static void main(String[] args) throws Exception {
 
    String kml1 =
      "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + LS +
      "<kml xmlns=\"http://earth.google.com/kml/2.2\">" + LS +
      "<Document>" + LS +
      "  <Placemark>" + LS +
      "  <Point>" + LS +
      "    <coordinates>136.881638,35.170689,0.000000</coordinates>" + LS +
      "  </Point>" + LS +
      "  </Placemark>" + LS +
      "  <Placemark>" + LS +
      "  <LineString>" + LS +
      "    <coordinates>" + LS +
      "    136.883499,35.171009,0.000000" + LS +
      "    136.883835,35.171600,0.000000" + LS +
      "    136.884735,35.171452,0.000000" + LS +
      "    136.889755,35.180210,0.000000" + LS +
      "    136.893860,35.182690,0.000000" + LS +
      "    136.895767,35.182911,0.000000" + LS +
      "    136.896133,35.184799,0.000000" + LS +
      "    136.898987,35.187531,0.000000" + LS +
      "    </coordinates>" + LS +
      "  </LineString>" + LS +
      "  </Placemark>" + LS +
      "</Document>" + LS +
      "</kml>" + LS;
    System.out.println("***** kml1 *****");
    System.out.println(Kml.parse(kml1).toString());
 
    // [ヅ] Google マイマップで作ったKMLファイル
    // http://www.nilab.info/z3/20110925_03.html
    String kml2 = "http://maps.google.co.jp/maps/ms?ie=UTF8&msa=0&output=kml&msid=207660430330299538596.0004ad806d2079defe369";
    System.out.println("***** kml2 *****");
    System.out.println(Kml.parse(new URL(kml2)).toString());
 
    // [ヅ] 刈谷ハイウェイオアシス・岩ケ池公園にあるゴーカートに乗ったときのGPSログ
    // http://www.nilab.info/z3/20100918_zlashdot_001138.html
    String kml3 = "http://latlonglab.yahoo.co.jp/route/get?id=5e884ddfb06cab015e5de860e552aa98&format=kml";
    System.out.println("***** kml3 *****");
    System.out.println(Kml.parse(new URL(kml3)).toString());
 
    // [ヅ] 刈谷ハイウェイオアシス・岩ケ池公園にあるゴーカートに乗ったときのGPSログ
    // http://www.nilab.info/z3/20100918_zlashdot_001138.html
    String kml4 = "file:" + System.getProperty("user.home") + "/doc.kml";
    System.out.println("***** kml4 *****");
    System.out.println(Kml.parse(new URL(kml4)).toString());
  }
}

実行結果。


***** kml1 *****
{
  Document.name=
  Document.description=
  {
    Placemark.name=
    Placemark.description=
    Placemark.Point=coordinates=35.170689,136.881638
    Placemark.LineString=null
  }
  {
    Placemark.name=
    Placemark.description=
    Placemark.Point=null
    Placemark.LineString=coordinates=35.171009,136.883499,35.1716,136.883835,35.171452,136.884735,35.18021,136.889755,35.18269,136.89386,35.182911,136.895767,35.184799,136.896133,35.187531,136.898987
  }
}
 
***** kml2 *****
{
  Document.name=名古屋駅から
  Document.description=名古屋駅から名古屋城と白川公園へ。
  {
    Placemark.name=名古屋駅(愛知)
    Placemark.description=
    Placemark.Point=coordinates=35.170689,136.881638
    Placemark.LineString=null
  }
  {
    Placemark.name=名古屋駅から名古屋城へ
    Placemark.description=<div dir="ltr">名古屋駅から名古屋城へ道なりに線を引く機能で引いてみた。</div>
    Placemark.Point=null
    Placemark.LineString=coordinates=35.171009,136.883499,35.171108,136.883499,35.171211,136.883575,35.171471,136.883545,35.171539,136.883621,35.1716,136.883835,35.1716,136.883835,35.171612,136.884247,35.171551,136.884415,35.17136,136.884567,35.171452,136.884735,35.172112,136.888565,35.17223,136.889389,35.17218,136.889633,35.17218,136.889633,35.18021,136.889755,35.18354,136.890228,35.18354,136.890228,35.18367,136.890472,35.18367,136.890472,35.18269,136.89386,35.181931,136.895096,35.181931,136.895096,35.182598,136.89534,35.182911,136.895767,35.182911,136.895767,35.183109,136.895767,35.183331,136.895859,35.18478,136.895782,35.18478,136.895782,35.184799,136.896133,35.184891,136.89621,35.187302,136.895905,35.187302,136.895905,35.187531,136.898987
  }
  {
    Placemark.name=名古屋城
    Placemark.description=
    Placemark.Point=coordinates=35.18557,136.899139
    Placemark.LineString=null
  }
  {
    Placemark.name=名古屋駅から白川公園へ
    Placemark.description=<div dir="ltr">名古屋駅から白川公園へ手動で直線で引いてみた。</div>
    Placemark.Point=null
    Placemark.LineString=coordinates=35.170898,136.882599,35.171211,136.884277,35.167496,136.885345,35.168194,136.889175,35.167896,136.892136,35.168194,136.897629,35.165161,136.89801,35.165234,136.898865,35.165092,136.898895,35.165024,136.898773,35.164906,136.898621,35.164635,136.898926,35.164471,136.899139,35.164509,136.900223,35.164604,136.900558,35.165215,136.900558,35.165432,136.900803,35.165436,136.90126,35.164608,136.901184,35.163994,136.901321,35.16391,136.901382,35.163883,136.90126,35.163639,136.901352,35.163631,136.901352
  }
}
 
***** kml3 *****
{
  Document.name=刈谷ハイウェイオアシス 岩ケ池公園 ゴーカート
  Document.description=刈谷ハイウェイオアシス 岩ケ池公園のゴーカートコース全長520m。
iPhoneアプリのMotion-X GPS Liteでログを取ったもの。

[ヅラド] 刈谷ハイウェイオアシス・岩ケ池公園にあるゴーカートに乗ったときのGPSログ
http://www.nilab.info/zurazure2/001138.html
  {
    Placemark.name=
    Placemark.description=
    Placemark.Point=null
    Placemark.LineString=coordinates=35.0430591666667,137.048205555556,35.0431005555556,137.048205,35.0432666666667,137.04827,35.0433377777778,137.048362777778,35.0433777777778,137.048485555556,35.0433277777778,137.048578888889,35.043225,137.048616666667,35.0431266666667,137.048577777778,35.0431405555556,137.048408888889,35.0431705555556,137.048283888889,35.043185,137.048237777778,35.0431438888889,137.048219166667,35.0430766666667,137.048205,35.04298,137.048205555556,35.0428827777778,137.048245,35.0427888888889,137.048297777778,35.04269,137.048387777778,35.0426227777778,137.04845,35.0425188888889,137.048526666667,35.0425188888889,137.048526666667,35.0424138888889,137.04855,35.0423438888889,137.048530555556,35.0422516666667,137.048478888889,35.0421505555556,137.048452777778,35.0420755555556,137.048515555556,35.0420405555556,137.04861,35.0420738888889,137.048655555556,35.0420738888889,137.048655555556,35.0421527777778,137.048820555556,35.0421505555556,137.048892777778,35.0420816666667,137.048840555556,35.042035,137.048780555556,35.04198,137.048740555556,35.0419805555556,137.048551666667,35.0419916666667,137.048422777778,35.0420538888889,137.048355555556,35.0421927777778,137.048322777778,35.0422741666667,137.048368888889,35.0423441666667,137.048409166667,35.0423705555556,137.048435,35.0425588888889,137.048451666667,35.0425588888889,137.048451666667,35.0427066666667,137.048285
  }
}
 
***** kml4 *****
{
  Document.name=
  Document.description=
  {
    Placemark.name=Start
    Placemark.description=<div xmlns="http://www.opengis.net/kml/2.2">
  <b>Start Time:</b>
  2010-09-18T04:48:34Z
</div>
    Placemark.Point=coordinates=35.04306,137.048206
    Placemark.LineString=null
  }
  {
    Placemark.name=End
    Placemark.description=<div xmlns="http://www.opengis.net/kml/2.2">
  <b>End Time:</b>
  2010-09-18T04:52:33Z
</div>
    Placemark.Point=coordinates=35.042707,137.048285
    Placemark.LineString=null
  }
  {
    Placemark.name=Track 001
    Placemark.description=<div xmlns="http://www.opengis.net/kml/2.2" style="font-size: 11px; min-height: 240px; min-width: 440px">
  <div style="width: 260px; float:left;">
    <table style="width: 100%; margin: 8px 0" border="0">
      <tr>
        <td style="font-weight: bold; width: 90px;">Date:</td>
        <td>2010/09/18  1:48 pm</td>
      </tr>
      <tr>
        <td style="font-weight: bold; width: 90px;">Distance:</td>
        <td>0.43 kilometers</td>
      </tr>
      <tr>
        <td style="font-weight: bold; width: 90px;">Elapsed Time:</td>
        <td>03:58.5</td>
      </tr>
      <tr>
        <td style="font-weight: bold; width: 90px;">Avg Speed:</td>
        <td>6.5 km/h</td>
      </tr>
      <tr>
        <td style="font-weight: bold; width: 90px;">Max Speed:</td>
        <td>15.6 km/h</td>
      </tr>
      <tr>
        <td style="font-weight: bold; width: 90px;">Avg Pace:</td>
        <td>09' 13" per km</td>
      </tr>
      <tr>
        <td style="font-weight: bold; width: 90px;">Min Altitude:</td>
        <td>0 m</td>
      </tr>
      <tr>
        <td style="font-weight: bold; width: 90px;">Max Altitude:</td>
        <td>16 m</td>
      </tr>
    </table>
    <table style="width: 100%; margin: 8px 0" border="0">
      <tr>
        <td style="font-weight: bold; width: 90px;">Start Time:</td>
        <td>2010-09-18T04:48:34Z</td>
      </tr>
      <tr>
        <td colspan="2" style="font-weight: bold; width: 90px;">Start Location:</td>
      </tr>
      <tr>
        <td style="font-weight: bold; padding-left: 20px; width: 70px;">Latitude:</td>
        <td>35.043060? N</td>
      </tr>
      <tr>
        <td style="font-weight: bold; padding-left: 20px; width: 70px;">Longitude:</td>
        <td>137.048206? E</td>
      </tr>
    </table>
    <table style="width: 100%; margin: 8px 0" border="0">
      <tr>
        <td style="font-weight: bold; width: 90px;">End Time:</td>
        <td>2010-09-18T04:52:33Z</td>
      </tr>
      <tr>
        <td style="font-weight: bold; padding-left: 20px; width: 70px;">Latitude:</td>
        <td>35.042707? N</td>
      </tr>
      <tr>
        <td style="font-weight: bold; padding-left: 20px; width: 70px;">Longitude:</td>
        <td>137.048285? E</td>
      </tr>
    </table>
  </div>
  <div style="width: 160px; float: right" id="imagesDiv">
    <p>2010/09/18  1:48 pm</p>
  </div>
</div>
    Placemark.Point=null
    Placemark.LineString=coordinates=35.04306,137.048206,35.043101,137.048205,35.043267,137.04827,35.043338,137.048363,35.043378,137.048486,35.043328,137.048579,35.043225,137.048617,35.043127,137.048578,35.043141,137.048409,35.043171,137.048284,35.043185,137.048238,35.043144,137.04822,35.043077,137.048205,35.04298,137.048206,35.042883,137.048245,35.042789,137.048298,35.04269,137.048388,35.042623,137.04845,35.042519,137.048527,35.042519,137.048527,35.042414,137.04855,35.042344,137.048531,35.042252,137.048479,35.042151,137.048453,35.042076,137.048516,35.042041,137.04861,35.042074,137.048656,35.042074,137.048656,35.042153,137.048821,35.042151,137.048893,35.042082,137.048841,35.042035,137.048781,35.04198,137.048741,35.041981,137.048552,35.041992,137.048423,35.042054,137.048356,35.042193,137.048323,35.042275,137.048369,35.042345,137.04841,35.042371,137.048435,35.042559,137.048452,35.042559,137.048452,35.042707,137.048285
  }
}

Ref. Keyhole Markup Language — Google Developers

tags: java kml

Posted by NI-Lab. (@nilab)