package net.osmand.plus.views; import net.osmand.data.QuadRect; import net.osmand.data.RotatedTileBox; import net.osmand.map.ITileSource; import net.osmand.map.TileSourceManager; import net.osmand.map.TileSourceManager.TileSourceTemplate; import net.osmand.plus.OsmandPlugin; import net.osmand.plus.OsmandSettings; import net.osmand.plus.R; import net.osmand.plus.mapillary.MapillaryPlugin; import net.osmand.plus.rastermaps.OsmandRasterMapsPlugin; import net.osmand.plus.resources.ResourceManager; import net.osmand.util.MapUtils; import android.annotation.SuppressLint; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.widget.Toast; public class MapTileLayer extends BaseMapLayer { protected static final int emptyTileDivisor = 16; public static final int OVERZOOM_IN = 2; private final boolean mainMap; protected ITileSource map = null; protected MapTileAdapter mapTileAdapter = null; Paint paintBitmap; protected RectF bitmapToDraw = new RectF(); protected Rect bitmapToZoom = new Rect(); protected OsmandMapTileView view; protected ResourceManager resourceManager; private OsmandSettings settings; private boolean visible = true; public MapTileLayer(boolean mainMap) { this.mainMap = mainMap; } @Override public boolean drawInScreenPixels() { return false; } @Override public void initLayer(OsmandMapTileView view) { this.view = view; settings = view.getSettings(); resourceManager = view.getApplication().getResourceManager(); paintBitmap = new Paint(); paintBitmap.setFilterBitmap(true); paintBitmap.setAlpha(getAlpha()); if (mapTileAdapter != null && view != null) { mapTileAdapter.initLayerAdapter(this, view); } } @Override public void setAlpha(int alpha) { super.setAlpha(alpha); if (paintBitmap != null) { paintBitmap.setAlpha(alpha); } } public void setMapTileAdapter(MapTileAdapter mapTileAdapter) { if (this.mapTileAdapter == mapTileAdapter) { return; } if (this.mapTileAdapter != null) { this.mapTileAdapter.onClear(); } this.mapTileAdapter = mapTileAdapter; if (mapTileAdapter != null && view != null) { mapTileAdapter.initLayerAdapter(this, view); mapTileAdapter.onInit(); } } public void setMapForMapTileAdapter(ITileSource map, MapTileAdapter mapTileAdapter) { if (mapTileAdapter == this.mapTileAdapter) { this.map = map; } } public void setMap(ITileSource map) { MapTileAdapter target = null; if (map instanceof TileSourceTemplate) { if (TileSourceManager.RULE_YANDEX_TRAFFIC.equals(((TileSourceTemplate) map).getRule())) { map = null; target = new YandexTrafficAdapter(); } } this.map = map; setMapTileAdapter(target); } public MapTileAdapter getMapTileAdapter() { return mapTileAdapter; } @SuppressLint("WrongCall") @Override public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSettings drawSettings) { if ((map == null && mapTileAdapter == null) || !visible) { return; } if (mapTileAdapter != null) { mapTileAdapter.onDraw(canvas, tileBox, drawSettings); } drawTileMap(canvas, tileBox); } @Override public void onDraw(Canvas canvas, RotatedTileBox tileBox, DrawSettings drawSettings) { } public void drawTileMap(Canvas canvas, RotatedTileBox tileBox) { ITileSource map = this.map; if (map == null) { return; } ResourceManager mgr = resourceManager; int nzoom = tileBox.getZoom(); final QuadRect tilesRect = tileBox.getTileBounds(); // recalculate for ellipsoid coordinates float ellipticTileCorrection = 0; if (map.isEllipticYTile()) { ellipticTileCorrection = (float) (MapUtils.getTileEllipsoidNumberY(nzoom, tileBox.getLatitude()) - tileBox.getCenterTileY()); } int left = (int) Math.floor(tilesRect.left); int top = (int) Math.floor(tilesRect.top + ellipticTileCorrection); int width = (int) Math.ceil(tilesRect.right - left); int height = (int) Math.ceil(tilesRect.bottom + ellipticTileCorrection - top); boolean useInternet = (OsmandPlugin.getEnabledPlugin(OsmandRasterMapsPlugin.class) != null || OsmandPlugin.getEnabledPlugin(MapillaryPlugin.class) != null) && settings.USE_INTERNET_TO_DOWNLOAD_TILES.get() && settings.isInternetConnectionAvailable() && map.couldBeDownloadedFromInternet(); int maxLevel = map.getMaximumZoomSupported(); int tileSize = map.getTileSize(); boolean oneTileShown = false; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { int leftPlusI = left + i; int topPlusJ = top + j; int x1 = tileBox.getPixXFromTileXNoRot(leftPlusI); int x2 = tileBox.getPixXFromTileXNoRot(leftPlusI + 1); int y1 = tileBox.getPixYFromTileYNoRot(topPlusJ - ellipticTileCorrection); int y2 = tileBox.getPixYFromTileYNoRot(topPlusJ + 1 - ellipticTileCorrection); bitmapToDraw.set(x1, y1, x2 , y2); final int tileX = leftPlusI; final int tileY = topPlusJ; Bitmap bmp = null; String ordImgTile = mgr.calculateTileId(map, tileX, tileY, nzoom); // asking tile image async boolean imgExist = mgr.tileExistOnFileSystem(ordImgTile, map, tileX, tileY, nzoom); boolean originalWillBeLoaded = useInternet && nzoom <= maxLevel; if (imgExist || originalWillBeLoaded) { bmp = mgr.getTileImageForMapAsync(ordImgTile, map, tileX, tileY, nzoom, useInternet); } if (bmp == null) { int div = 1; boolean readFromCache = originalWillBeLoaded || imgExist; boolean loadIfExists = !readFromCache; // asking if there is small version of the map (in cache) int allowedScale = Math.min(OVERZOOM_IN + Math.max(0, nzoom - map.getMaximumZoomSupported()), 8); int kzoom = 1; for (; kzoom <= allowedScale; kzoom++) { div *= 2; String imgTileId = mgr.calculateTileId(map, tileX / div, tileY / div, nzoom - kzoom); if (readFromCache) { bmp = mgr.getTileImageFromCache(imgTileId); if (bmp != null) { break; } } else if (loadIfExists) { if (mgr.tileExistOnFileSystem(imgTileId, map, tileX / div, tileY / div, nzoom - kzoom) || (useInternet && nzoom - kzoom <= maxLevel)) { bmp = mgr.getTileImageForMapAsync(imgTileId, map, tileX / div, tileY / div, nzoom - kzoom, useInternet); break; } } } if (bmp != null) { int xZoom = (tileX % div) * tileSize / div; int yZoom = (tileY % div) * tileSize / div; // nice scale boolean useSampling = kzoom > 3; bitmapToZoom.set(Math.max(xZoom, 0), Math.max(yZoom, 0), Math.min(xZoom + tileSize / div, tileSize), Math.min(yZoom + tileSize / div, tileSize)); if (!useSampling) { canvas.drawBitmap(bmp, bitmapToZoom, bitmapToDraw, paintBitmap); } else { int margin = 1; int scaledSize = tileSize / div; float innerMargin = 0.5f; RectF src = new RectF(0, 0, scaledSize, scaledSize); if (bitmapToZoom.left >= margin) { bitmapToZoom.left -= margin; src.left = innerMargin; src.right += margin; } if (bitmapToZoom.top >= margin) { bitmapToZoom.top -= margin; src.top = innerMargin; src.bottom += margin; } if (bitmapToZoom.right + margin <= tileSize) { bitmapToZoom.right += margin; src.right += margin - innerMargin; } if (bitmapToZoom.bottom + margin <= tileSize) { bitmapToZoom.bottom += margin; src.bottom += margin - innerMargin; } Matrix m = new Matrix(); RectF dest = new RectF(0, 0, tileSize, tileSize); m.setRectToRect(src, dest, Matrix.ScaleToFit.FILL); Bitmap sampled = Bitmap.createBitmap(bmp, bitmapToZoom.left, bitmapToZoom.top, bitmapToZoom.width(), bitmapToZoom.height(), m, true); bitmapToZoom.set(0, 0, tileSize, tileSize); // very expensive that's why put in the cache mgr.putTileInTheCache(ordImgTile, sampled); canvas.drawBitmap(sampled, bitmapToZoom, bitmapToDraw, paintBitmap); } } } else { bitmapToZoom.set(0, 0, tileSize, tileSize); canvas.drawBitmap(bmp, bitmapToZoom, bitmapToDraw, paintBitmap); } if (bmp != null) { oneTileShown = true; } } } if (mainMap && !oneTileShown && !useInternet && warningToSwitchMapShown < 3) { if (resourceManager.getRenderer().containsLatLonMapData(view.getLatitude(), view.getLongitude(), nzoom)) { Toast.makeText(view.getContext(), R.string.switch_to_vector_map_to_see, Toast.LENGTH_LONG).show(); warningToSwitchMapShown++; } } } public int getSourceTileSize() { return map == null ? 256 : map.getTileSize(); } @Override public int getMaximumShownMapZoom() { return map == null ? 20 : map.getMaximumZoomSupported() + OVERZOOM_IN; } @Override public int getMinimumShownMapZoom() { return map == null ? 1 : map.getMinimumZoomSupported(); } @Override public void destroyLayer() { setMapTileAdapter(null); } public boolean isVisible() { return visible; } public void setVisible(boolean visible) { this.visible = visible; } public ITileSource getMap() { return map; } }