Pull to refresh

Карты Yandex без использования Yandex Map Kit или карты из различных источников в приложении под Android

Reading time 9 min
Views 27K
Здравствуйте!
В этом сообщении рассмотрим использование карт из различных источников (в том числе Yandex карты) в приложении под Android.
image
Для решения задачи будем использовать библиотеку osmdroid. Нам потребуется сама библиотека(ссылка не на последнюю версию, эта версия, как мне кажется, более стабильна и быстрее, чем более новые) и еще одна дополнительная. Добавим их в проект (использовался Eclipse под Windows) (Build Path->Configure Buil Path->Libraries->Add External JARs).
Пропишем необходимые разрешения в манифесте проекта
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> 
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Для добавления новых источников карт, например, Yandex и Google (Mapnik уже встроен в библиотеку) потребуется расширить класс XYTileSource
MyTileSource
public class MyTileSource extends XYTileSource {

	public MyTileSource(String aName, string aResourceId, int aZoomMinLevel,
			int aZoomMaxLevel, int aTileSizePixels,
			String aImageFilenameEnding, String... aBaseUrl) {
		super(aName, aResourceId, aZoomMinLevel, aZoomMaxLevel,
				aTileSizePixels, aImageFilenameEnding, aBaseUrl);

	}
	//переопределим метод getTileURLString, он будет возвращать ссылку на тайл карты
	@Override
	public String getTileURLString(MapTile aTile) {

		return String.format(getBaseUrl(), aTile.getX(), aTile.getY(),
				aTile.getZoomLevel());
	}

}


Проблема в том, что osmdroid не поддерживает проекцию Yandex карт. Поэтому для корректного отображения Yandex карт расширим класс TilesOverlay. Для преобразования координат в тайлы и обратно в сети был найден класс. Вот этот немного доработанный класс
YandexUtils
public class YandexUtils {
	public static double[] geoToMercator(double[] g) {
		double d = g[0] * Math.PI / 180, m = g[1] * Math.PI / 180, l = 6378137, k = 0.0818191908426, f = k
				* Math.sin(m);
		double h = Math.tan(Math.PI / 4 + m / 2), j = Math.pow(
				Math.tan(Math.PI / 4 + Math.asin(f) / 2), k), i = h / j;
		// return new DoublePoint(Math.round(l * d), Math.round(l *
		// Math.log(i)));
		return new double[] { l * d, l * Math.log(i) };
	}

	public static double[] mercatorToGeo(double[] e) {
		double j = Math.PI, f = j / 2, i = 6378137, n = 0.003356551468879694, k = 0.00000657187271079536, h = 1.764564338702e-8, m = 5.328478445e-11;
		double g = f - 2 * Math.atan(1 / Math.exp(e[1] / i));
		double l = g + n * Math.sin(2 * g) + k * Math.sin(4 * g) + h
				* Math.sin(6 * g) + m * Math.sin(8 * g);
		double d = e[0] / i;
		return new double[] { d * 180 / Math.PI, l * 180 / Math.PI };
	}

	public static double[] mercatorToTiles(double[] e) {
		double d = Math.round((20037508.342789 + e[0]) * 53.5865938), f = Math
				.round((20037508.342789 - e[1]) * 53.5865938);
		d = boundaryRestrict(d, 0, 2147483647);
		f = boundaryRestrict(f, 0, 2147483647);
		return new double[] { d, f };
	}

	public static double[] tileToMercator(long[] d) {
		return new double[] { Math.round(d[0] / 53.5865938 - 20037508.342789),
				Math.round(20037508.342789 - d[1] / 53.5865938) };
	}

	public static double[] tileCoordinatesToPixels(double[] i, int h) {
		double g = Math.pow(2, toScale(h));
		return new double[] { (int) i[0] / g, (int) i[1] / g };
	}

	public static double boundaryRestrict(double f, double e, double d) {
		return Math.max(Math.min(f, d), e);
	}

	public static int toScale(int i) {
		return 23 - i;
	}

	public static long[] getTile(double[] h, int i) {
		long e = 8;
		long j = toScale(i), g = (long) h[0] >> j, f = (long) h[1] >> j;
		return new long[] { g >> e, f >> e };
	}

	public static long[] getPxCoordFromTileCoord(double[] h, int i) {
		long j = toScale(i), g = (long) h[0] >> j, f = (long) h[1] >> j;
		return new long[] { g, f };
	}

	public static long[] getTileCoordFromPixCoord(long[] h, int i) {
		long j = toScale(i), g = h[0] << j, f = h[1] << j;
		return new long[] { g, f };

	}

	public static double[] ReGetTile(double[] h, int i) {
		long e = 8;
		long j = toScale(i);
		long g = (long) h[0] << (int) j;
		long f = (long) h[1] << (int) j;
		double ge = g << (int) e;
		double fe = f << (int) e;
		long g2 = (long) (h[0] + 1) << (int) j;
		long f2 = (long) (h[1] + 1) << (int) j;
		double ge2 = g2 << (int) e;
		double fe2 = f2 << (int) e;

		double ad_g = (ge2 - ge) * (h[0] - Math.floor(h[0]));
		double ad_f = (fe2 - fe) * (h[1] - Math.floor(h[1]));

		return new double[] { ge + ad_g, fe + ad_f };
	}

	public static double[] ReGetTile(long[] h, int i) {
		long e = 8;
		long j = toScale(i);
		long g = (long) h[0] << (int) j;
		long f = (long) h[1] << (int) j;
		return new double[] { g << (int) e, f << (int) e };
	}

	public static double[] getGeoFromTile(int x, int y, int zoom) {
		double a, c1, c2, c3, c4, g, z, mercX, mercY;
		a = 6378137;
		c1 = 0.00335655146887969;
		c2 = 0.00000657187271079536;
		c3 = 0.00000001764564338702;
		c4 = 0.00000000005328478445;
		mercX = (x * 256 * 2 ^ (23 - zoom)) / 53.5865938 - 20037508.342789;
		mercY = 20037508.342789 - (y * 256 * 2 ^ (23 - zoom)) / 53.5865938;

		g = Math.PI / 2 - 2 * Math.atan(1 / Math.exp(mercY / a));
		z = g + c1 * Math.sin(2 * g) + c2 * Math.sin(4 * g) + c3
				* Math.sin(6 * g) + c4 * Math.sin(8 * g);

		return new double[] { mercX / a * 180 / Math.PI, z * 180 / Math.PI };
	}

	public static long[] getTileFromGeo(double lat, double lon, int zoom) {
		double rLon, rLat, a, k, z;
		rLon = lon * Math.PI / 180;
		rLat = lat * Math.PI / 180;
		a = 6378137;
		k = 0.0818191908426;
		z = Math.pow(
				Math.tan(Math.PI / 4 + rLat / 2)
						/ (Math.tan(Math.PI / 4 + Math.asin(k * Math.sin(rLat))
								/ 2)), k);
		return new long[] {
				(int) (((20037508.342789 + a * rLon) * 53.5865938 / Math.pow(2,
						(23 - zoom))) / 256),
				(int) (((20037508.342789 - a * Math.log(z)) * 53.5865938 / Math
						.pow(2, (23 - zoom)))) / 256 };
	}

	public static double tile2lon(int x, int aZoom) {
		return (x / Math.pow(2.0, aZoom) * 360.0) - 180;
	}

	public static double tile2lat(int y, int aZoom) {

		final double MerkElipsK = 0.0000001;
		final long sradiusa = 6378137;
		final long sradiusb = 6356752;
		final double FExct = (double) Math.sqrt(sradiusa * sradiusa - sradiusb
				* sradiusb)
				/ sradiusa;
		final int TilesAtZoom = 1 << aZoom;
		double result = (y - TilesAtZoom / 2) / -(TilesAtZoom / (2 * Math.PI));
		result = (2 * Math.atan(Math.exp(result)) - Math.PI / 2) * 180
				/ Math.PI;
		double Zu = result / (180 / Math.PI);
		double yy = ((y) - TilesAtZoom / 2);

		double Zum1 = Zu;
		Zu = Math.asin(1
				- ((1 + Math.sin(Zum1)) * Math.pow(1 - FExct * Math.sin(Zum1),
						FExct))
				/ (Math.exp((2 * yy) / -(TilesAtZoom / (2 * Math.PI))) * Math
						.pow(1 + FExct * Math.sin(Zum1), FExct)));
		while (Math.abs(Zum1 - Zu) >= MerkElipsK) {
			Zum1 = Zu;
			Zu = Math
					.asin(1
							- ((1 + Math.sin(Zum1)) * Math.pow(
									1 - FExct * Math.sin(Zum1), FExct))
							/ (Math.exp((2 * yy)
									/ -(TilesAtZoom / (2 * Math.PI))) * Math
										.pow(1 + FExct * Math.sin(Zum1), FExct)));
		}

		result = Zu * 180 / Math.PI;

		return result;

	}

	public static int[] getMapTileFromCoordinates(final double aLat,
			final double aLon, final int zoom) {
		final int[] out = new int[2];

		final double E2 = (double) aLat * Math.PI / 180;
		final long sradiusa = 6378137;
		final long sradiusb = 6356752;
		final double J2 = (double) Math.sqrt(sradiusa * sradiusa - sradiusb
				* sradiusb)
				/ sradiusa;
		final double M2 = (double) Math.log((1 + Math.sin(E2))
				/ (1 - Math.sin(E2)))
				/ 2
				- J2
				* Math.log((1 + J2 * Math.sin(E2)) / (1 - J2 * Math.sin(E2)))
				/ 2;
		final double B2 = (double) (1 << zoom);
		out[0] = (int) Math.floor(B2 / 2 - M2 * B2 / 2 / Math.PI);

		out[1] = (int) Math.floor((aLon + 180) / 360 * (1 << zoom));

		return out;
	}
}


Теперь когда есть формулы для преобразования координат можем расширить TilesOverlay
YandexTilesOverlay
public class YandexTilesOverlay extends TilesOverlay {
	private final Rect mTileRect = new Rect();
	private final Point mTilePos = new Point();

	public YandexTilesOverlay(MapTileProviderBase aTileProvider,
			Context aContext) {
		super(aTileProvider, aContext);
	}
        //переопределим метод draw для отрисовки Yandex тайлов
	@Override
	protected void draw(Canvas c, MapView osmv, boolean shadow) {
		//текущий масштаб
		int zoom = osmv.getZoomLevel();
		final Projection pj = osmv.getProjection();
		//координаты углов видимой части карты
		BoundingBoxE6 bb = osmv.getBoundingBox();
		//получаем координаты верхнего левого и нижнего правого тайла
		double[] MercatorTL = YandexUtils.geoToMercator(new double[] {
				bb.getLonWestE6() / 1E6, bb.getLatNorthE6() / 1E6 });
		double[] TilesTL = YandexUtils.mercatorToTiles(MercatorTL);
		long[] TileTL = YandexUtils.getTile(TilesTL, zoom);

		double[] MercatorRB = YandexUtils.geoToMercator(new double[] {
				bb.getLonEastE6() / 1E6, bb.getLatSouthE6() / 1E6 });
		double[] TilesRB = YandexUtils.mercatorToTiles(MercatorRB);
		long[] TileRB = YandexUtils.getTile(TilesRB, zoom);
		mTileProvider
				.ensureCapacity((int) ((TileRB[1] - TileTL[1] + 1) * (TileRB[0]
						- TileTL[0] + 1)));
		//геокоординаты верхнего левого тайла Yandex
		double[] reTiles = YandexUtils.ReGetTile(new long[] { TileTL[0],
				TileTL[1] }, zoom);
		long xx = (long) reTiles[0];
		long yy = (long) reTiles[1];
		double[] reMercator = YandexUtils.tileToMercator(new long[] { xx, yy });
		double[] tmp = YandexUtils.mercatorToGeo(reMercator);
		//геокоординаты верхнего левого тайла Yandex переводим в экранные координаты osmdroid
		GeoPoint gp = new GeoPoint(tmp[1], tmp[0]);
		pj.toPixels(gp, mTilePos);
		//в цикле отрисовываем все видимые тайлы Yandex
		for (int y = (int) TileTL[1]; y <= TileRB[1]; y++) {
			int xcount = 0;

			for (int x = (int) TileTL[0]; x <= TileRB[0]; x++) {

				final MapTile tile = new MapTile(zoom, x, y);
				final Drawable currentMapTile = mTileProvider.getMapTile(tile);
				if (currentMapTile != null) {

					mTileRect.set(mTilePos.x, mTilePos.y, mTilePos.x + 256,
							mTilePos.y + 256);
					currentMapTile.setBounds(mTileRect);
					currentMapTile.draw(c);

				}
				xcount++;
				mTilePos.x += 256;
			}
			mTilePos.x -= xcount * 256;
			mTilePos.y += 256;
		}
	}

}


Осталось вывести карты на форме
BlogOsmYandexActivity
public class BlogOsmYandexActivity extends Activity {
	MapView mMap;
	MapController mMapController;
	YandexTilesOverlay tilesOverlayYandex;
	TilesOverlay tilesOverlayGoogle;
	TilesOverlay tilesOverlayMapnik;
	MapTileProviderBasic tileProviderYandex;
	MapTileProviderBasic tileProviderGoogle;
	MapTileProviderBasic tileProviderMapnik;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		//разметка
		RelativeLayout rl = new RelativeLayout(this);
		//карта
		mMap = new MapView(this, 256);
		//включить встроенные кнопки управления масштабом
		mMap.setBuiltInZoomControls(true);
		//добавляем карту в разметку
		rl.addView(mMap, new RelativeLayout.LayoutParams(
				LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
		setContentView(rl);
		//устанавливаем начальный масштаб и координаты центра карты
		mMapController = mMap.getController();
		mMapController.setZoom(15);
		mMapController.setCenter(new GeoPoint(55.751893, 37.617166));
		//добавляем источник тайлов Yandex
		//ссылку можно подсмотреть в firebug 
		//getTileURLString подставит вместо %s  координаты тайла и масштаб
		ITileSource tileSourceYandex = new MyTileSource(
				"YandexMap",
				null,
				0,
				23,
				256,
				".png",
				"http://vec04.maps.yandex.net/tiles?l=map&v=2.28.0&x=%s&y=%s&z=%s&lang=ru-RU",
				"http://vec03.maps.yandex.net/tiles?l=map&v=2.28.0&x=%s&y=%s&z=%s&lang=ru-RU",
				"http://vec02.maps.yandex.net/tiles?l=map&v=2.28.0&x=%s&y=%s&z=%s&lang=ru-RU",
				"http://vec01.maps.yandex.net/tiles?l=map&v=2.28.0&x=%s&y=%s&z=%s&lang=ru-RU");
		//добавляем источник тайлов Google
		ITileSource tileSourceGoogle = new MyTileSource(
				"Google-Map",
				null,
				0,
				23,
				256,
				".png",
				"http://mt0.google.com/vt/lyrs=m&hl=ru&x=%s&y=%s&z=%s&s=Galileo",
				"http://mt1.google.com/vt/lyrs=m&hl=ru&x=%s&y=%s&z=%s&s=Galileo",
				"http://mt2.google.com/vt/lyrs=m&hl=ru&x=%s&y=%s&z=%s&s=Galileo",
				"http://mt3.google.com/vt/lyrs=m&hl=ru&x=%s&y=%s&z=%s&s=Galileo");
		//создаем поставщика тайлов и задаем для него источник тайлов Yandex
		tileProviderYandex = new MapTileProviderBasic(getApplicationContext());
		tileProviderYandex.setTileSource(tileSourceYandex);
		tileProviderYandex.setTileRequestCompleteHandler(mMap
				.getTileRequestCompleteHandler());
		//создаем слой Yandex карты
		tilesOverlayYandex = new YandexTilesOverlay(tileProviderYandex,
				this.getBaseContext());
		//создаем поставщика тайлов и задаем для него источник тайлов Google
		tileProviderGoogle = new MapTileProviderBasic(getApplicationContext());
		tileProviderGoogle.setTileRequestCompleteHandler(mMap
				.getTileRequestCompleteHandler());
		tileProviderGoogle.setTileSource(tileSourceGoogle);
		//создаем слой Google карты
		tilesOverlayGoogle = new TilesOverlay(tileProviderGoogle,
				this.getBaseContext());
		//создаем поставщика тайлов и задаем для него источник тайлов Mapnik
		tileProviderMapnik = new MapTileProviderBasic(getApplicationContext());
		tileProviderMapnik.setTileRequestCompleteHandler(mMap
				.getTileRequestCompleteHandler());
		tileProviderMapnik.setTileSource(TileSourceFactory.MAPNIK);
		//создаем слой Mapnik карты
		tilesOverlayMapnik = new TilesOverlay(tileProviderMapnik,
				this.getBaseContext());
		//устанавливаем Yandex карты текущим слоем
		mMap.getOverlayManager().setTilesOverlay(tilesOverlayYandex);
	}
	//меню для смены карт/слоев
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		SubMenu sm = menu.addSubMenu("Выбор карты");
		sm.add(0, 111, Menu.NONE, "Яндекс");
		sm.add(0, 222, Menu.NONE, "Google");
		sm.add(0, 333, Menu.NONE, "Mapnik");
		return true;
	}
	//в зависимости от выбора устанавливаем текущую карту/слой
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
		case (111):
			mMap.getOverlayManager().setTilesOverlay(tilesOverlayYandex);
			mMap.invalidate();
			return true;
		case (222):
			mMap.getOverlayManager().setTilesOverlay(tilesOverlayGoogle);
			mMap.invalidate();
			return true;
		case (333):
			mMap.getOverlayManager().setTilesOverlay(tilesOverlayMapnik);
			mMap.invalidate();
			return true;
		default:
			return super.onOptionsItemSelected(item);
		}
	}
}


Результат
image
Tags:
Hubs:
+4
Comments 27
Comments Comments 27

Articles