Здравствуйте!
В этом сообщении рассмотрим использование карт из различных источников (в том числе Yandex карты) в приложении под Android.
Для решения задачи будем использовать библиотеку 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
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. Для преобразования координат в тайлы и обратно в сети был найден класс. Вот этот немного доработанный класс
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
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;
}
}
}
Осталось вывести карты на форме
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);
}
}
}
Результат
Автор: constv
Здравствуйте, у Вас при использовании YandexTilesOverlay не возникает проблем с анимацией зума? У меня при таком способе отрисовки, тайлы предыдущего уровня перестают быть видимыми, а на небольшой части экрана появляются тайлы из явно другого места.
Спасибо.