読者です 読者をやめる 読者になる 読者になる

S_a_k_Uの日記みたいなDB

~サクゥーと呼ばないで~

地図の位置情報を取り扱う その2

技術メモ Java

んで、やりたかったのは、「緯度経度で地図上のあるエリアの範囲内かどうか?」というのを判定する、みたいなこと。

要素 役割
IGeoArea 地図エリアインターフェース 地図上のあるエリアのインターフェース
GeoCircleArea 地図エリア(円形)クラス 地図上のある円形エリアのクラス
GeoRectangleArea 地図エリア(矩形)クラス 地図上のある矩形エリアのクラス
矩形の開始・終了位置の経度の大小が逆転した場合は、反対方向への範囲を示す。
(必ず、開始位置が西側、終了位置が東側となるようなエリアとなる)


昨日のGeoLocation#distanceメソッドも、円形エリア内かどうかの判定(withinメソッド)で使いたかった訳で。
GeoRectangleArea#withinメソッドは、境界値の判定を見直してみたり。

IGeoAreaインターフェース

/**
 * 地図エリアインターフェース
 * @author saku.jp
 */
public interface IGeoArea {

	/**
	 * エリア内かどうか判定する。
	 * @param location エリア内か判定する位置
	 * @return エリア内かどうか
	 */
	public boolean within(GeoLocation location);

}

GeoCircleAreaクラス

/**
 * 地図エリア(円形)クラス
 * @author saku.jp
 */
public class GeoCircleArea implements IGeoArea {

	/**
	 * エリアの中心
	 */
	private GeoLocation location = null;

	/**
	 * エリアの半径[m]
	 */
	private int radius = 0;

	/**
	 * エリアの中心を取得する。
	 * @return エリアの中心
	 */
	public GeoLocation getLocation() {
		return this.location;
	}

	/**
	 * エリアの中心を設定する。
	 * @param location エリアの中心
	 */
	private void setLocation(GeoLocation location) {
		this.location = location;
	}

	/**
	 * エリアの半径[m]を取得する。
	 * @return エリアの半径[m]
	 */
	public int getRadius() {
		return this.radius;
	}

	/**
	 * エリアの半径[m]を設定する。
	 * @param radius エリアの半径[m]
	 */
	private void setRadius(int radius) {
		this.radius = radius;
	}

	/**
	 * コンストラクタ
	 * @param location エリアの中心
	 * @param radius エリアの半径[m]
	 */
	public GeoCircleArea(GeoLocation location, int radius) {
		super();
		setLocation(location);
		setRadius(radius );
	}

	@Override
	public boolean within(GeoLocation location) {
		// 指定された位置と中心との距離が半径以下であれば範囲内
		return (getLocation().distance(location) <= getRadius());
	}

}

GeoRectangleAreaクラス

/**
 * 地図エリア(矩形)クラス
 * 矩形の開始・終了位置の経度の大小が逆転した場合は、反対方向への範囲を示す。
 * @author saku.jp
 */
public class GeoRectangleArea implements IGeoArea {

	/**
	 * 矩形エリア開始位置
	 */
	private GeoLocation startLocation = null;

	/**
	 * 矩形エリア終了位置
	 */
	private GeoLocation endLocation = null;

	/**
	 * 矩形エリア開始位置を取得する。
	 * @return 矩形エリア開始位置
	 */
	public GeoLocation getStartLocation() {
		return this.startLocation;
	}

	/**
	 * 矩形エリア開始位置を設定する。
	 * @param startLocation 矩形エリア開始位置
	 */
	private void setStartLocation(GeoLocation startLocation) {
		this.startLocation = startLocation;
	}

	/**
	 * 矩形エリア終了位置を取得する。
	 * @return 矩形エリア終了位置
	 */
	public GeoLocation getEndLocation() {
		return this.endLocation;
	}

	/**
	 * 矩形エリア終了位置を設定する。
	 * @param endLocation 矩形エリア終了位置
	 */
	private void setEndLocationTo(GeoLocation endLocation) {
		this.endLocation = endLocation;
	}

	/**
	 * コンストラクタ
	 * @param startLocation 矩形エリア開始位置
	 * @param endLocation 矩形エリア終了位置
	 */
	public GeoRectangleArea(GeoLocation startLocation, GeoLocation endLocation) {
		super();
		setStartLocation(startLocation);
		setEndLocationTo(endLocation );
	}

	@Override
	public boolean within(GeoLocation location) {

		// 緯度の範囲
		double minLat = Math.min(getStartLocation().getLatitude(), getEndLocation().getLatitude());
		double maxLat = Math.max(getStartLocation().getLatitude(), getEndLocation().getLatitude());
		// 緯度が範囲外ならfalse
		if ((minLat > location.getLatitude()) || (maxLat < location.getLatitude())) {
			return false;
		}

		// 経度の範囲
		double minLng = getStartLocation().getLongitude();
		double maxLng = getEndLocation().getLongitude();
		// 経度が範囲外ならfalse
		if (minLng <= maxLng) {
			if ((minLng > location.getLongitude()) || (maxLng < location.getLongitude())) {
				return false;
			}
		} else {
			// 東経(+)から西経(-)に跨ぐ場合の判定
			if ((minLng > location.getLongitude()) && (maxLng < location.getLongitude())) {
				return false;
			}
		}

		// 範囲内
		return true;

	}

}

GeoCircleAreaクラスのテストクラス

public class GeoCircleAreaTest {

	@Test
	public void testWithin() {

		// 東京駅を基準にする
		GeoLocation tokyo = new GeoLocation(35.681257, 139.767138);
		GeoCircleArea area1      = new GeoCircleArea(tokyo, 1000);			// 半径      1[km]
		GeoCircleArea area10     = new GeoCircleArea(tokyo, 10000);			// 半径     10[km]
		GeoCircleArea area100    = new GeoCircleArea(tokyo, 100000);		// 半径    100[km]
		GeoCircleArea area1000   = new GeoCircleArea(tokyo, 1000000);		// 半径  1,000[km]
		GeoCircleArea area10000  = new GeoCircleArea(tokyo, 10000000);		// 半径 10,000[km]
		GeoCircleArea area100000 = new GeoCircleArea(tokyo, 100000000);		// 半径100,000[km]

		// 品川駅(東京駅から6,415[m]<6.41[km]>くらい)がエリア内かどうか
		GeoLocation shinagawa = new GeoLocation(35.628452, 139.738733);
		assertEquals(area1.within(shinagawa), false);
		assertEquals(area10.within(shinagawa), true);
		assertEquals(area100.within(shinagawa), true);
		assertEquals(area1000.within(shinagawa), true);
		assertEquals(area10000.within(shinagawa), true);
		assertEquals(area100000.within(shinagawa), true);

		// 岡山駅(東京駅から543,961[m]<543[km]>くらい)がエリア内かどうか
		GeoLocation okayama = new GeoLocation(34.666204, 133.918477);
		assertEquals(area1.within(okayama), false);
		assertEquals(area10.within(okayama), false);
		assertEquals(area100.within(okayama), false);
		assertEquals(area1000.within(okayama), true);
		assertEquals(area10000.within(okayama), true);
		assertEquals(area100000.within(okayama), true);

		// シドニー(東京駅から7,839,152[m]<7,839[km]>くらい)がエリア内かどうか
		GeoLocation sydney = new GeoLocation(-33.9277, 151.171885);
		assertEquals(area1.within(sydney), false);
		assertEquals(area10.within(sydney), false);
		assertEquals(area100.within(sydney), false);
		assertEquals(area1000.within(sydney), false);
		assertEquals(area10000.within(sydney), true);
		assertEquals(area100000.within(sydney), true);

		// 自由の女神(東京駅から10,859,371[m]<10,859[km]>くらい)がエリア内かどうか
		GeoLocation liberty = new GeoLocation(40.692874,-74.044962);
		assertEquals(area1.within(liberty), false);
		assertEquals(area10.within(liberty), false);
		assertEquals(area100.within(liberty), false);
		assertEquals(area1000.within(liberty), false);
		assertEquals(area10000.within(liberty), false);
		assertEquals(area100000.within(liberty), true);

		// リオデジャネイロ(東京駅から18,588,043[m]<18,588[km]>くらい)がエリア内かどうか
		GeoLocation rio = new GeoLocation(-22.912248,-43.166574);
		assertEquals(area1.within(rio), false);
		assertEquals(area10.within(rio), false);
		assertEquals(area100.within(rio), false);
		assertEquals(area1000.within(rio), false);
		assertEquals(area10000.within(rio), false);
		assertEquals(area100000.within(rio), true);

	}

}

GeoRectangleAreaクラスのテストクラス

import static org.junit.Assert.*;

import org.junit.Test;

public class GeoRectangleAreaTest {

	@Test
	public void testWithin() {

		// 長野駅
		GeoLocation nagano = new GeoLocation(36.642946, 138.188763);
		// 高松駅
		GeoLocation takamatsu = new GeoLocation(34.350682,134.046919);
		// 長野駅から高松駅の範囲(反対方向)
		GeoRectangleArea area1 = new GeoRectangleArea(nagano, takamatsu);
		// 高松駅から長野駅の範囲
		GeoRectangleArea area2 = new GeoRectangleArea(takamatsu, nagano);

		// 大阪駅がエリア内かどうか
		GeoLocation osaka = new GeoLocation(34.70191, 135.494977);
		assertEquals(area1.within(osaka), false);		// 経度が外れてエリア外
		assertEquals(area2.within(osaka), true);		// エリア内

		// 名古屋駅がエリア内かどうか
		GeoLocation naogoya = new GeoLocation(35.170693, 136.881628);
		assertEquals(area1.within(naogoya), false);		// 経度が外れてエリア外
		assertEquals(area2.within(naogoya), true);		// エリア内

		// 金沢駅がエリア内かどうか
		GeoLocation kanazawa = new GeoLocation(36.578252, 136.647771);
		assertEquals(area1.within(kanazawa), false);	// 経度が外れてエリア外
		assertEquals(area2.within(kanazawa), true);		// エリア内

		// 品川駅がエリア内かどうか
		GeoLocation shinagawa = new GeoLocation(35.628452, 139.738733);
		assertEquals(area1.within(shinagawa), true);	// 反対方向でエリア内
		assertEquals(area2.within(shinagawa), false);	// 経度が外れてエリア外

		// 岡山駅がエリア内かどうか
		GeoLocation okayama = new GeoLocation(34.666204, 133.918477);
		assertEquals(area1.within(okayama), true);		// 反対方向でエリア内
		assertEquals(area2.within(okayama), false);		// 経度が外れてエリア外

		// 富山駅がエリア内かどうか
		GeoLocation toyama = new GeoLocation(36.701221, 137.213179);
		assertEquals(area1.within(toyama), false);		// 緯度経度が外れてエリア外
		assertEquals(area2.within(toyama), false);		// 緯度が外れてエリア外

		// 徳島駅がエリア内かどうか
		GeoLocation tokushima = new GeoLocation(34.074662, 134.550761);
		assertEquals(area1.within(tokushima), false);	// 緯度経度が外れてエリア外
		assertEquals(area2.within(tokushima), false);	// 緯度が外れてエリア外


		// 札幌
		GeoLocation sapporo = new GeoLocation(43.062617, 141.35416);
		// ケープタウン
		GeoLocation capetown = new GeoLocation(-33.924631, 18.42423);
		// 札幌からケープタウンの範囲(反対方向)
		GeoRectangleArea area3 = new GeoRectangleArea(sapporo, capetown);
		// ケープタウンから札幌の範囲
		GeoRectangleArea area4 = new GeoRectangleArea(capetown, sapporo);

		// ニューデリーがエリア内かどうか
		GeoLocation newdelhi = new GeoLocation(28.635289, 77.224957);
		assertEquals(area3.within(newdelhi), false);	// 経度が外れてエリア外
		assertEquals(area4.within(newdelhi), true);		// エリア内

		// 北京がエリア内かどうか
		GeoLocation beijing = new GeoLocation(39.904033, 116.407527);
		assertEquals(area3.within(beijing), false);		// 経度が外れてエリア外
		assertEquals(area4.within(beijing), true);		// エリア内

		// メキシコシティがエリア内かどうか
		GeoLocation mexico = new GeoLocation(19.432606, -99.133207);
		assertEquals(area3.within(mexico), true);		// 反対方向でエリア内
		assertEquals(area4.within(mexico), false);		// 経度が外れてエリア外

		// ハワイがエリア内かどうか
		GeoLocation hawaii = new GeoLocation(19.897018, -155.582571);
		assertEquals(area3.within(hawaii), true);		// 反対方向でエリア内
		assertEquals(area4.within(hawaii), false);		// 経度が外れてエリア外

		// シドニーがエリア内かどうか
		GeoLocation sydney = new GeoLocation(-33.9277, 151.171885);
		assertEquals(area3.within(sydney), false);		// 緯度が外れてエリア外
		assertEquals(area4.within(sydney), false);		// 緯度経度が外れてエリア外

		// ロンドンがエリア内かどうか
		GeoLocation london = new GeoLocation(51.5073, -0.127645);
		assertEquals(area3.within(london), false);		// 緯度が外れてエリア外
		assertEquals(area4.within(london), false);		// 緯度経度が外れてエリア外

		// 札幌がエリア内かどうか
		assertEquals(area3.within(sapporo), true);		// 境界でエリア内
		assertEquals(area4.within(sapporo), true);		// 境界でエリア内

		// ケープタウンがエリア内かどうか
		assertEquals(area3.within(capetown), true);		// 境界でエリア内
		assertEquals(area4.within(capetown), true);		// 境界でエリア内

	}

}