package net.osmand.plus.views;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Cap;
import android.graphics.Paint.Join;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.PointF;
import android.text.TextPaint;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import net.osmand.IndexConstants;
import net.osmand.binary.BinaryMapDataObject;
import net.osmand.binary.BinaryMapIndexReader;
import net.osmand.binary.BinaryMapIndexReader.TagValuePair;
import net.osmand.data.LatLon;
import net.osmand.data.PointDescription;
import net.osmand.data.RotatedTileBox;
import net.osmand.map.OsmandRegions;
import net.osmand.map.WorldRegion;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.activities.LocalIndexHelper;
import net.osmand.plus.activities.LocalIndexInfo;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.download.DownloadActivityType;
import net.osmand.plus.download.IndexItem;
import net.osmand.plus.mapcontextmenu.MapContextMenu;
import net.osmand.plus.mapcontextmenu.other.MapMultiSelectionMenu;
import net.osmand.plus.resources.ResourceManager;
import net.osmand.plus.views.ContextMenuLayer.IContextMenuProvider;
import net.osmand.plus.views.ContextMenuLayer.IContextMenuProviderSelection;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public class DownloadedRegionsLayer extends OsmandMapLayer implements IContextMenuProvider, IContextMenuProviderSelection {
private static final int ZOOM_THRESHOLD = 2;
private OsmandApplication app;
private OsmandMapTileView view;
private Paint paintDownloaded;
private Path pathDownloaded;
private Paint paintSelected;
private Path pathSelected;
private Paint paintBackuped;
private Path pathBackuped;
private OsmandRegions osmandRegions;
private LocalIndexHelper helper;
private TextPaint textPaint;
private ResourceManager rm;
private MapLayerData<List<BinaryMapDataObject>> data;
private List<BinaryMapDataObject> selectedObjects = new LinkedList<>();
private static int ZOOM_TO_SHOW_MAP_NAMES = 6;
private static int ZOOM_AFTER_BASEMAP = 12;
private static int ZOOM_TO_SHOW_BORDERS_ST = 4;
private static int ZOOM_TO_SHOW_BORDERS = 7;
private static int ZOOM_TO_SHOW_SELECTION_ST = 3;
private static int ZOOM_TO_SHOW_SELECTION = 8;
public static class DownloadMapObject {
private BinaryMapDataObject dataObject;
private WorldRegion worldRegion;
private IndexItem indexItem;
private LocalIndexInfo localIndexInfo;
public BinaryMapDataObject getDataObject() {
return dataObject;
}
public WorldRegion getWorldRegion() {
return worldRegion;
}
public IndexItem getIndexItem() {
return indexItem;
}
public LocalIndexInfo getLocalIndexInfo() {
return localIndexInfo;
}
public DownloadMapObject(BinaryMapDataObject dataObject, WorldRegion worldRegion,
IndexItem indexItem, LocalIndexInfo localIndexInfo) {
this.dataObject = dataObject;
this.worldRegion = worldRegion;
this.indexItem = indexItem;
this.localIndexInfo = localIndexInfo;
}
}
@Override
public void initLayer(final OsmandMapTileView view) {
this.view = view;
app = view.getApplication();
rm = app.getResourceManager();
osmandRegions = rm.getOsmandRegions();
helper = new LocalIndexHelper(app);
paintDownloaded = getPaint(view.getResources().getColor(R.color.region_uptodate));
paintSelected = getPaint(view.getResources().getColor(R.color.region_selected));
paintBackuped = getPaint(view.getResources().getColor(R.color.region_backuped));
textPaint = new TextPaint();
final WindowManager wmgr = (WindowManager) view.getApplication().getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
wmgr.getDefaultDisplay().getMetrics(dm);
textPaint.setStrokeWidth(21 * dm.scaledDensity);
textPaint.setAntiAlias(true);
textPaint.setTextAlign(Paint.Align.CENTER);
pathDownloaded = new Path();
pathSelected = new Path();
pathBackuped = new Path();
data = new MapLayerData<List<BinaryMapDataObject>>() {
@Override
public void layerOnPostExecute() {
view.refreshMap();
}
public boolean queriedBoxContains(final RotatedTileBox queriedData, final RotatedTileBox newBox) {
if (newBox.getZoom() < ZOOM_TO_SHOW_SELECTION) {
if (queriedData != null && queriedData.getZoom() < ZOOM_TO_SHOW_SELECTION) {
return queriedData.containsTileBox(newBox);
} else {
return false;
}
}
List<BinaryMapDataObject> queriedResults = getResults();
if(queriedData != null && queriedData.containsTileBox(newBox) && queriedData.getZoom() >= ZOOM_TO_SHOW_MAP_NAMES) {
if(queriedResults != null && ( queriedResults.isEmpty() ||
Math.abs(queriedData.getZoom() - newBox.getZoom()) <= 1)) {
return true;
}
}
return false;
}
@Override
protected List<BinaryMapDataObject> calculateResult(RotatedTileBox tileBox) {
return queryData(tileBox);
}
};
}
private Paint getPaint(int color) {
Paint paint = new Paint();
paint.setStyle(Style.FILL_AND_STROKE);
paint.setStrokeWidth(1);
paint.setColor(color);
paint.setAntiAlias(true);
paint.setStrokeCap(Cap.ROUND);
paint.setStrokeJoin(Join.ROUND);
return paint;
}
@Override
public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) {
final int zoom = tileBox.getZoom();
if(zoom < ZOOM_TO_SHOW_SELECTION_ST) {
return;
}
// draw objects
if (osmandRegions.isInitialized() && zoom >= ZOOM_TO_SHOW_SELECTION_ST && zoom < ZOOM_TO_SHOW_SELECTION) {
final List<BinaryMapDataObject> currentObjects = new LinkedList<>();
if (data.results != null) {
currentObjects.addAll(data.results);
}
final List<BinaryMapDataObject> selectedObjects = new LinkedList<>(this.selectedObjects);
if (selectedObjects.size() > 0) {
removeObjectsFromList(currentObjects, selectedObjects);
drawBorders(canvas, tileBox, selectedObjects, pathSelected, paintSelected);
}
if (zoom >= ZOOM_TO_SHOW_BORDERS_ST && zoom < ZOOM_TO_SHOW_BORDERS) {
if (currentObjects.size() > 0) {
List<BinaryMapDataObject> downloadedObjects = new ArrayList<>();
List<BinaryMapDataObject> backupedObjects = new ArrayList<>();
for (BinaryMapDataObject o : currentObjects) {
boolean downloaded = checkIfObjectDownloaded(osmandRegions.getDownloadName(o));
boolean backuped = checkIfObjectBackuped(osmandRegions.getDownloadName(o));
if (downloaded) {
downloadedObjects.add(o);
} else if (backuped) {
backupedObjects.add(o);
}
}
if (backupedObjects.size() > 0) {
drawBorders(canvas, tileBox, backupedObjects, pathBackuped, paintBackuped);
}
if (downloadedObjects.size() > 0) {
drawBorders(canvas, tileBox, downloadedObjects, pathDownloaded, paintDownloaded);
}
}
}
}
}
private void removeObjectsFromList(List<BinaryMapDataObject> list, List<BinaryMapDataObject> objects) {
Iterator<BinaryMapDataObject> it = list.iterator();
while (it.hasNext()) {
BinaryMapDataObject o = it.next();
for (BinaryMapDataObject obj : objects) {
if (o.getId() == obj.getId()) {
it.remove();
break;
}
}
}
}
private void drawBorders(Canvas canvas, RotatedTileBox tileBox, final List<BinaryMapDataObject> objects, Path path, Paint paint) {
path.reset();
for (BinaryMapDataObject o : objects) {
double lat = MapUtils.get31LatitudeY(o.getPoint31YTile(0));
double lon = MapUtils.get31LongitudeX(o.getPoint31XTile(0));
path.moveTo(tileBox.getPixXFromLonNoRot(lon), tileBox.getPixYFromLatNoRot(lat));
for (int j = 1; j < o.getPointsLength(); j++) {
lat = MapUtils.get31LatitudeY(o.getPoint31YTile(j));
lon = MapUtils.get31LongitudeX(o.getPoint31XTile(j));
path.lineTo(tileBox.getPixXFromLonNoRot(lon), tileBox.getPixYFromLatNoRot(lat));
}
}
canvas.drawPath(path, paint);
}
private boolean checkIfObjectDownloaded(String downloadName) {
final String regionName = Algorithms.capitalizeFirstLetterAndLowercase(downloadName)
+ IndexConstants.BINARY_MAP_INDEX_EXT;
final String roadsRegionName = Algorithms.capitalizeFirstLetterAndLowercase(downloadName) + ".road"
+ IndexConstants.BINARY_MAP_INDEX_EXT;
return rm.getIndexFileNames().containsKey(regionName) || rm.getIndexFileNames().containsKey(roadsRegionName);
}
private boolean checkIfObjectBackuped(String downloadName) {
File fileDir = app.getAppPath(IndexConstants.BACKUP_INDEX_DIR);
final String regionName = Algorithms.capitalizeFirstLetterAndLowercase(downloadName)
+ IndexConstants.BINARY_MAP_INDEX_EXT;
final String roadsRegionName = Algorithms.capitalizeFirstLetterAndLowercase(downloadName) + ".road"
+ IndexConstants.BINARY_MAP_INDEX_EXT;
return new File(fileDir, regionName).exists() || new File(fileDir, roadsRegionName).exists();
}
private List<BinaryMapDataObject> queryData(RotatedTileBox tileBox) {
if (tileBox.getZoom() >= ZOOM_AFTER_BASEMAP) {
if(!checkIfMapEmpty(tileBox)) {
return Collections.emptyList();
}
}
LatLon lt = tileBox.getLeftTopLatLon();
LatLon rb = tileBox.getRightBottomLatLon();
// if (tileBox.getZoom() >= ZOOM_TO_SHOW_MAP_NAMES) {
// lt = rb = tileBox.getCenterLatLon();
// }
List<BinaryMapDataObject> result = null;
int left = MapUtils.get31TileNumberX(lt.getLongitude());
int right = MapUtils.get31TileNumberX(rb.getLongitude());
int top = MapUtils.get31TileNumberY(lt.getLatitude());
int bottom = MapUtils.get31TileNumberY(rb.getLatitude());
try {
result = osmandRegions.queryBbox(left, right, top, bottom);
} catch (IOException e) {
return result;
}
Iterator<BinaryMapDataObject> it = result.iterator();
while (it.hasNext()) {
BinaryMapDataObject o = it.next();
if (tileBox.getZoom() < ZOOM_TO_SHOW_SELECTION) {
//
} else {
if (!osmandRegions.contain(o, left / 2 + right / 2, top / 2 + bottom / 2)) {
it.remove();
}
}
}
return result;
}
private boolean checkIfMapEmpty(RotatedTileBox tileBox) {
// RotatedTileBox cb = rm.getRenderer().getCheckedBox();
// old code to wait
// wait for image to be rendered
// int count = 0;
// while (cb == null || cb.getZoom() != tileBox.getZoom() ||
// !cb.containsLatLon(tileBox.getCenterLatLon())) {
// if (count++ > 5) {
// return false;
// }
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// return false;
// }
// cb = rm.getRenderer().getCheckedBox();
// }
// if (cb == null || cb.getZoom() != tileBox.getZoom() ||
// !cb.containsLatLon(tileBox.getCenterLatLon())) {
// return false;
// }
int cState = rm.getRenderer().getCheckedRenderedState();
final boolean empty;
if (tileBox.getZoom() < ZOOM_AFTER_BASEMAP) {
empty = cState == 0;
} else {
empty = cState <= 1;
}
return empty;
}
@Override
public void onDraw(Canvas canvas, RotatedTileBox tileBox, DrawSettings nightMode) {
if(view.getMainLayer() instanceof MapTileLayer) {
return;
}
// query from UI thread because of Android AsyncTask bug (Handler init)
data.queryNewData(tileBox);
}
public String getFilter(StringBuilder btnName) {
StringBuilder filter = new StringBuilder();
int zoom = view.getZoom();
RotatedTileBox queriedBox = data.getQueriedBox();
final List<BinaryMapDataObject> currentObjects = data.results;
if (osmandRegions.isInitialized() && queriedBox != null) {
if(zoom >= ZOOM_TO_SHOW_MAP_NAMES && Math.abs(queriedBox.getZoom() - zoom) <= ZOOM_THRESHOLD &&
currentObjects != null){
btnName.setLength(0);
btnName.append(view.getResources().getString(R.string.shared_string_download));
filter.setLength(0);
Set<String> set = new TreeSet<String>();
int cx = view.getCurrentRotatedTileBox().getCenter31X();
int cy = view.getCurrentRotatedTileBox().getCenter31Y();
if ((currentObjects.size() > 0)) {
for (int i = 0; i < currentObjects.size(); i++) {
final BinaryMapDataObject o = currentObjects.get(i);
if (!osmandRegions.contain(o, cx, cy)) {
continue;
}
String fullName = osmandRegions.getFullName(o);
WorldRegion rd = osmandRegions.getRegionData(fullName);
if (rd != null && rd.isRegionMapDownload() && rd.getRegionDownloadName() != null) {
String name = rd.getLocaleName();
if (checkIfObjectDownloaded(rd.getRegionDownloadName())) {
return null;
}
if (!set.add(name)) {
continue;
}
if (set.size() > 1) {
btnName.append(" ").append(view.getResources().getString(R.string.shared_string_or))
.append(" ");
filter.append(", ");
} else {
btnName.append(" ");
}
filter.append(name);
btnName.append(name);
}
}
}
}
}
if(filter.length() == 0) {
return null;
}
return filter.toString();
}
@Override
public boolean drawInScreenPixels() {
return false;
}
@Override
public boolean onLongPressEvent(PointF point, RotatedTileBox tileBox) {
return false;
}
@Override
public void destroyLayer() {
}
// IContextMenuProvider
@Override
public void collectObjectsFromPoint(PointF point, RotatedTileBox tileBox, List<Object> objects) {
boolean isMenuVisible = false;
if (view.getContext() instanceof MapActivity) {
MapActivity mapActivity = (MapActivity) view.getContext();
MapContextMenu menu = mapActivity.getContextMenu();
MapMultiSelectionMenu multiMenu = menu.getMultiSelectionMenu();
isMenuVisible = menu.isVisible() || multiMenu.isVisible();
}
if (!isMenuVisible) {
getWorldRegionFromPoint(tileBox, point, objects);
}
}
@Override
public LatLon getObjectLocation(Object o) {
if (o instanceof DownloadMapObject) {
DownloadMapObject mapObject = ((DownloadMapObject) o);
return mapObject.worldRegion.getRegionCenter();
}
return null;
}
@Override
public PointDescription getObjectName(Object o) {
if (o instanceof DownloadMapObject) {
DownloadMapObject mapObject = ((DownloadMapObject) o);
return new PointDescription(PointDescription.POINT_TYPE_WORLD_REGION,
view.getContext().getString(R.string.shared_string_map), mapObject.worldRegion.getLocaleName());
}
return new PointDescription(PointDescription.POINT_TYPE_WORLD_REGION,
view.getContext().getString(R.string.shared_string_map), "");
}
@Override
public boolean disableSingleTap() {
return false;
}
@Override
public boolean disableLongPressOnMap() {
return false;
}
@Override
public boolean isObjectClickable(Object o) {
return false;
}
private void getWorldRegionFromPoint(RotatedTileBox tb, PointF point, List<? super DownloadMapObject> dataObjects) {
int zoom = tb.getZoom();
if (zoom >= ZOOM_TO_SHOW_SELECTION_ST && zoom < ZOOM_TO_SHOW_SELECTION
&& data.results != null && osmandRegions.isInitialized()) {
LatLon pointLatLon = tb.getLatLonFromPixel(point.x, point.y);
int point31x = MapUtils.get31TileNumberX(pointLatLon.getLongitude());
int point31y = MapUtils.get31TileNumberY(pointLatLon.getLatitude());
List<BinaryMapDataObject> result = new LinkedList<>(data.results);
Iterator<BinaryMapDataObject> it = result.iterator();
while (it.hasNext()) {
BinaryMapDataObject o = it.next();
boolean isRegion = true;
for (int i = 0; i < o.getTypes().length; i++) {
TagValuePair tp = o.getMapIndex().decodeType(o.getTypes()[i]);
if ("boundary".equals(tp.value)) {
isRegion = false;
break;
}
}
if (!isRegion || !osmandRegions.contain(o, point31x, point31y) ) {
it.remove();
}
}
OsmandRegions osmandRegions = app.getRegions();
for (BinaryMapDataObject o : result) {
String fullName = osmandRegions.getFullName(o);
WorldRegion region = osmandRegions.getRegionData(fullName);
if (region != null && region.isRegionMapDownload()) {
List<IndexItem> indexItems = app.getDownloadThread().getIndexes().getIndexItems(region);
List<IndexItem> dataItems = new LinkedList<>();
IndexItem regularMapItem = null;
for (IndexItem item : indexItems) {
if (item.isDownloaded() || app.getDownloadThread().isDownloading(item)) {
dataItems.add(item);
if (item.getType() == DownloadActivityType.NORMAL_FILE) {
regularMapItem = item;
}
}
}
if (dataItems.isEmpty() && regularMapItem != null) {
dataItems.add(regularMapItem);
}
if (!dataItems.isEmpty()) {
for (IndexItem item : dataItems) {
dataObjects.add(new DownloadMapObject(o, region, item, null));
}
} else {
String downloadName = osmandRegions.getDownloadName(o);
List<LocalIndexInfo> infos = helper.getLocalIndexInfos(downloadName);
if (infos.size() == 0) {
dataObjects.add(new DownloadMapObject(o, region, null, null));
} else {
for (LocalIndexInfo info : infos) {
dataObjects.add(new DownloadMapObject(o, region, null, info));
}
}
}
}
}
}
}
@Override
public int getOrder(Object o) {
int order = 0;
if (o instanceof DownloadMapObject) {
DownloadMapObject mapObject = ((DownloadMapObject) o);
order = mapObject.worldRegion.getLevel() * 1000 - 100000;
if (mapObject.indexItem != null) {
order += mapObject.indexItem.getType().getOrderIndex();
} else if (mapObject.localIndexInfo != null) {
order += mapObject.localIndexInfo.getType().getOrderIndex(mapObject.localIndexInfo);
}
}
return order;
}
@Override
public void setSelectedObject(Object o) {
if (o instanceof DownloadMapObject) {
DownloadMapObject mapObject = ((DownloadMapObject) o);
List<BinaryMapDataObject> list = new LinkedList<>();
list.add(mapObject.dataObject);
selectedObjects = list;
}
}
@Override
public void clearSelectedObject() {
selectedObjects = new LinkedList<>();
}
}