逆に考えてみた。
略地図って場所や量を制限して描いてるけど、そうじゃなくて描いたあとに削ってみたらいいんじゃないかって。
たとえばこんな感じ。
名古屋の栄駅から巫女コスプレ居酒屋『月天』へのルート。
使った経路データは やすらぎ居酒屋 月天 - ルートラボ - LatLongLab のKMLファイル。
なかなか(・∀・)イイ感じじゃない?
比較用にYahoo!の経路地図APIを出力してみる。
他の経路もいくつか試してみる。
データ: [ヅ] RunKeeperのGPXとKMLをダウンロードする
切り取り地図:
経路地図:
切り取り地図:
経路地図:
データ: 三つの橋 {the three bridges} - ルートラボ - LatLongLab
切り取り地図:
経路地図:
データ: 名古屋栄駅 〜 Vie Sa Vie (ヴィ・サ・ヴィ) - ルートラボ - LatLongLab
切り取り地図:
経路地図:
データ: ALPSLAB route (名古屋駅〜フェリーチェ・ぎゃろりいの(Felice Giallolino)) のデータ
切り取り地図:
経路地図:
ALPSLAB 略地図(データとスクリーンショット取っといて良かった):
文字が途中で消えちゃうこともあったり、経路データが長すぎると地図画像が生成できないとか、いろいろあるけどまぁまぁいいかも。
以下、ソースコード。切り取り地図の生成まで書くのに1時間ぐらい。そこからKML処理とかテストデータ探すとかいろいろやってたら計3時間ぐらいかかった。。。
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.imageio.*;
import javax.xml.parsers.*;
import javax.xml.xpath.*;
import org.w3c.dom.*;
public class ClippedMap {
public static void main(String[] args) throws Exception {
// ALPSLAB route (名古屋駅〜フェリーチェ・ぎゃろりいの(Felice Giallolino)) のデータ
// http://www.nilab.info/lab/alpslab_route/felice_giallolino/
writeClippedMap("http://www.nilab.info/lab/alpslab_route/felice_giallolino/17ba55406169fdc50eac99ef474deeb7.kml", 400, 400);
// [ヅ] RunKeeperのGPXとKMLをダウンロードする
// http://www.nilab.info/z3/20110830_04.html
writeClippedMap("http://www.nilab.info/f/0/RK_kml_2010-09-28_0556PM.kml", 600, 600);
// 名古屋栄駅 〜 Vie Sa Vie (ヴィ・サ・ヴィ) - ルートラボ - LatLongLab
// http://latlonglab.yahoo.co.jp/route/watch?id=5f54f25756fc0242734dd5d8865ca2d4
writeClippedMap("http://latlonglab.yahoo.co.jp/route/get?id=5f54f25756fc0242734dd5d8865ca2d4&format=kml", 500, 500);
// 三つの橋 {the three bridges} - ルートラボ - LatLongLab
// http://latlonglab.yahoo.co.jp/route/watch?id=acb7164917ea4b80a5b125924f1ced2e
writeClippedMap("http://latlonglab.yahoo.co.jp/route/get?id=acb7164917ea4b80a5b125924f1ced2e&format=kml", 600, 600);
// やすらぎ居酒屋 月天 - ルートラボ - LatLongLab
// http://latlonglab.yahoo.co.jp/route/watch?id=43b640cbbb224e3d061da6a68f0e897c
writeClippedMap("http://latlonglab.yahoo.co.jp/route/get?id=43b640cbbb224e3d061da6a68f0e897c&format=kml", 600, 600);
// [Nagoya Castle's front gate] -> [a front of Nagoya Station] -> [Electrical Museum] -> [SANGETSU] -> [Meijo Park] - ルートラボ - LatLongLab
// http://latlonglab.yahoo.co.jp/route/watch?id=7609e509a4b0b0b420f78b5046f7f222
writeClippedMap("http://latlonglab.yahoo.co.jp/route/get?id=7609e509a4b0b0b420f78b5046f7f222&format=kml", 600, 600);
}
private static void writeClippedMap(String kmlUrl, int imageWidth, int imageHeight) throws Exception {
Route route = getRoute(kmlUrl);
// 地図の上下左右の緯度経度を取得
MapParams params = new MapParams(route, imageWidth, imageHeight);
String mapInfoUrl = getStaticMapUrl(params, "xml");
MapInfo mapinfo = getMapInfo(mapInfoUrl);
// 経路の緯度経度列をピクセル座標へ変換
GeoTranslator gt = new GeoTranslator(
mapinfo.minlat, mapinfo.maxlat,
mapinfo.minlon, mapinfo.maxlon,
0, imageWidth, 0, imageHeight);
Path2D pxroute = new Path2D.Double();
Point2D pos = gt.toPoint(route.lat(0), route.lon(0));
pxroute.moveTo(pos.getX(), pos.getY());
for(int i=0; i<route.size(); i++){
pos = gt.toPoint(route.lat(i), route.lon(i));
pxroute.lineTo(pos.getX(), pos.getY());
}
// 画像オブジェクトを生成
BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
// クリッピング領域を設定
float clippingWidth = 160.0f;
BasicStroke pen = new BasicStroke(clippingWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
Shape clippingShape = pen.createStrokedShape(pxroute);
g.setClip(clippingShape);
// 地図画像を取得して描画(経路は透明色に設定している、つまり描画しない)
String mapImageUrl = getStaticMapUrl(params, "png32");
BufferedImage mapImage = ImageIO.read(new URL(mapImageUrl));
g.drawImage(mapImage, 0, 0, null);
// 自前で経路を描画する(たとえば好きな画像のパターンを貼り付けることも可能になる)
g.setPaint(new Color(0, 0, 255, 127));
g.fill(new BasicStroke(10.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND).createStrokedShape(pxroute));
g.dispose();
String id = "" + System.currentTimeMillis();
ImageIO.write(image, "png", new File("20120216_clippedmap_" + id + ".png"));
// 比較用に経路地図APIを出力する
String routeMapImageUrl = getRouteMapUrl(params, "png32");
BufferedImage routeMapImage = ImageIO.read(new URL(routeMapImageUrl));
ImageIO.write(routeMapImage, "png", new File("20120216_routemap_" + id + ".png"));
}
// KMLから緯度経度を抜き出してRouteオブジェクトを生成
private static Route getRoute(String kmlUrl) throws Exception {
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(kmlUrl);
XPath xpath = XPathFactory.newInstance().newXPath();
String coordinates = xpath.evaluate("//coordinates", doc);
String[] a = coordinates.split("\\s");
Route route = new Route();
for(int i=0; i<a.length; i++){
String[] lonlat = a[i].split(","); // lon.lat,alt
if(lonlat.length >= 2){
route.add(new Double(lonlat[1]), new Double(lonlat[0]));
}
}
return route;
}
// 経路クラス
private static class Route{
// [緯度,経度,緯度,経度,緯度,経度,...]
ArrayList<Double> geopos = new ArrayList<Double>();
private void add(double lat, double lon){
geopos.add(lat);
geopos.add(lon);
}
private int size(){
return geopos.size() / 2;
}
private double lat(int index){
return geopos.get(index * 2);
}
private double lon(int index){
return geopos.get(index * 2 + 1);
}
private String getString(){
StringBuilder sb = new StringBuilder();
for(int i=0; i<geopos.size(); i++){
sb.append(geopos.get(i));
sb.append(",");
}
sb.deleteCharAt(sb.length() - 1); // 最後のカンマを削除
return sb.toString();
}
}
// 経路地図画像のURLを返す
private static String getRouteMapUrl(MapParams p, String output){
// Yahoo!デベロッパーネットワーク - YOLP(地図) - 経路地図
// http://developer.yahoo.co.jp/webapi/map/openlocalplatform/v1/routemap.html
String url =
"http://routemap.olp.yahooapis.jp/OpenLocalPlatform/V1/routeMap" +
"?appid=" + p.appid +
"&output=" + output +
"&width=" + p.width +
"&height=" + p.height +
"&route=search:off|" + p.route.getString();
return url;
}
// 地図画像のURLを返す
private static String getStaticMapUrl(MapParams p, String output){
// Yahoo!デベロッパーネットワーク - YOLP(地図) - スタティックマップ
// http://developer.yahoo.co.jp/webapi/map/openlocalplatform/v1/static.html
String url =
"http://map.olp.yahooapis.jp/OpenLocalPlatform/V1/static" +
"?appid=" + p.appid +
"&output=" + output +
"&width=" + p.width +
"&height=" + p.height +
"&l=" + p.r + "," + p.g + "," + p.b + "," + p.a + "," + p.w + "," +
p.route.getString();
return url;
}
// 地図の情報を返す
private static MapInfo getMapInfo(String url) throws Exception {
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(url);
XPath xpath = XPathFactory.newInstance().newXPath();
// datum="WGS84" format="lon,lat"
String[] ul = xpath.evaluate("ResultSet/Result/Coordinate-UL/Coordinates", doc).split(","); // 左上
String[] ur = xpath.evaluate("ResultSet/Result/Coordinate-UR/Coordinates", doc).split(","); // 右上
String[] dl = xpath.evaluate("ResultSet/Result/Coordinate-DL/Coordinates", doc).split(","); // 左下
String[] dr = xpath.evaluate("ResultSet/Result/Coordinate-DR/Coordinates", doc).split(","); // 右下
// 上下左右の緯度経度の平均を求める
MapInfo mi = new MapInfo();
mi.maxlat = (Double.parseDouble(ul[1]) + Double.parseDouble(ur[1])) / 2; // 上端
mi.minlat = (Double.parseDouble(dl[1]) + Double.parseDouble(dr[1])) / 2; // 下端
mi.minlon = (Double.parseDouble(ul[0]) + Double.parseDouble(dl[0])) / 2; // 左端
mi.maxlon = (Double.parseDouble(ur[0]) + Double.parseDouble(dr[0])) / 2; // 右端
return mi;
}
// 地図の情報クラス
private static class MapInfo{
double minlat;
double maxlat;
double minlon;
double maxlon;
}
// 地図生成パラメータクラス
private static class MapParams{
Route route;
String appid = "YOUR APPLICATION ID";
int r = 0;
int g = 0;
int b = 0;
int a = 127;
int w = 10;
int width;
int height;
MapParams(Route route, int width, int height){
this.route = route;
this.width = width;
this.height = height;
}
}
// 緯度経度をピクセル座標へ変換するクラス(単純な割合で計算。投影法とかわからんし・・・)
private static class GeoTranslator{
private final double fx;
private final double fy;
private final double minx;
private final double miny;
private final double minlon;
private final double maxlat;
// 緯度経度は右上方向が正
// ピクセル右下方向が正
private GeoTranslator(
double minlat, double maxlat, double minlon, double maxlon,
double minx, double maxx, double miny, double maxy){
this.fx = (maxx - minx) / (maxlon - minlon);
this.fy = (maxy - miny) / (maxlat - minlat);
this.minx = minx;
this.miny = miny;
this.minlon = minlon;
this.maxlat = maxlat;
}
// 緯度経度をピクセル座標へ変換する
private Point2D toPoint(double lat, double lon){
double x = (lon - minlon) * fx + minx;
double y = (maxlat - lat) * fy + miny;
return new Point2D.Double(x, y);
}
}
}
やっぱり、Javaの描画ライブラリは便利。 BasicStroke が無かったらこんなのサクっと書けない。。。
「制限して描く」んじゃなくて「消す」とか「削る」っていう逆転の発想は他にも使えないかな・・・
Ref.
- Yahoo!デベロッパーネットワーク - YOLP(地図) - スタティックマップ
- Yahoo!デベロッパーネットワーク - YOLP(地図) - 経路地図
tags: map yahoo_maps_api routemap java
Posted by NI-Lab. (@nilab)