package net.osmand.plus.resources;
import android.content.Context;
import android.content.res.AssetManager;
import android.database.sqlite.SQLiteException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.HandlerThread;
import android.text.format.DateFormat;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import net.osmand.AndroidUtils;
import net.osmand.GeoidAltitudeCorrection;
import net.osmand.IProgress;
import net.osmand.IndexConstants;
import net.osmand.Location;
import net.osmand.PlatformUtil;
import net.osmand.ResultMatcher;
import net.osmand.binary.BinaryMapIndexReader;
import net.osmand.binary.BinaryMapIndexReader.SearchPoiTypeFilter;
import net.osmand.binary.CachedOsmandIndexes;
import net.osmand.data.Amenity;
import net.osmand.data.RotatedTileBox;
import net.osmand.data.TransportStop;
import net.osmand.map.ITileSource;
import net.osmand.map.MapTileDownloader;
import net.osmand.map.MapTileDownloader.DownloadRequest;
import net.osmand.map.OsmandRegions;
import net.osmand.osm.MapPoiTypes;
import net.osmand.osm.PoiCategory;
import net.osmand.plus.AppInitializer;
import net.osmand.plus.AppInitializer.InitEvents;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.R;
import net.osmand.plus.SQLiteTileSource;
import net.osmand.plus.Version;
import net.osmand.plus.render.MapRenderRepositories;
import net.osmand.plus.render.NativeOsmandLibrary;
import net.osmand.plus.resources.AsyncLoadingThread.MapLoadRequest;
import net.osmand.plus.resources.AsyncLoadingThread.OnMapLoadedListener;
import net.osmand.plus.resources.AsyncLoadingThread.TileLoadDownloadRequest;
import net.osmand.plus.srtmplugin.SRTMPlugin;
import net.osmand.plus.views.OsmandMapLayer.DrawSettings;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
import org.apache.commons.logging.Log;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Resource manager is responsible to work with all resources
* that could consume memory (especially with file resources).
* Such as indexes, tiles.
* Also it is responsible to create cache for that resources if they
* can't be loaded fully into memory & clear them on request.
*/
public class ResourceManager {
public static final String VECTOR_MAP = "#vector_map"; //$NON-NLS-1$
private static final String INDEXES_CACHE = "ind.cache";
private static final Log log = PlatformUtil.getLog(ResourceManager.class);
protected static ResourceManager manager = null;
// it is not good investigated but no more than 64 (satellite images)
// Only 8 MB (from 16 Mb whole mem) available for images : image 64K * 128 = 8 MB (8 bit), 64 - 16 bit, 32 - 32 bit
// at least 3*9?
protected int maxImgCacheSize = 28;
protected Map<String, Bitmap> cacheOfImages = new LinkedHashMap<String, Bitmap>();
protected Map<String, Boolean> imagesOnFS = new LinkedHashMap<String, Boolean>() ;
protected File dirWithTiles ;
private final OsmandApplication context;
private List<ResourceListener> resourceListeners = new ArrayList<>();
public interface ResourceListener {
void onMapsIndexed();
}
// Indexes
public enum BinaryMapReaderResourceType {
POI,
REVERSE_GEOCODING,
STREET_LOOKUP,
TRANSPORT,
ADDRESS,
QUICK_SEARCH,
ROUTING
}
public static class BinaryMapReaderResource {
private BinaryMapIndexReader initialReader;
private File filename;
private List<BinaryMapIndexReader> readers = new ArrayList<>(BinaryMapReaderResourceType.values().length);
private boolean useForRouting;
public BinaryMapReaderResource(File f, BinaryMapIndexReader initialReader) {
this.filename = f;
this.initialReader = initialReader;
while(readers.size() < BinaryMapReaderResourceType.values().length) {
readers.add(null);
}
}
public BinaryMapIndexReader getReader(BinaryMapReaderResourceType type) {
BinaryMapIndexReader r = readers.get(type.ordinal());
if(r == null) {
try {
RandomAccessFile raf = new RandomAccessFile(filename, "r");
r = new BinaryMapIndexReader(raf, initialReader);
readers.set(type.ordinal(), r);
} catch (IOException e) {
log.error("Fail to initialize " + filename.getName(), e);
e.printStackTrace();
}
}
return r;
}
public String getFileName() {
return filename.getName();
}
// should not use methods to read from file!
public BinaryMapIndexReader getShallowReader() {
return initialReader;
}
public void close() {
close(initialReader);
for(BinaryMapIndexReader rr : readers) {
if(rr != null) {
close(rr);
}
}
initialReader = null;
}
public boolean isClosed() {
return initialReader == null;
}
private void close(BinaryMapIndexReader r) {
try {
r.close();
} catch (IOException e) {
log.error("Fail to close " + filename.getName(), e);
e.printStackTrace();
}
}
public void setUseForRouting(boolean useForRouting) {
this.useForRouting = useForRouting;
}
public boolean isUseForRouting() {
return useForRouting;
}
}
protected final Map<String, BinaryMapReaderResource> fileReaders = new ConcurrentHashMap<String, BinaryMapReaderResource>();
private final Map<String, RegionAddressRepository> addressMap = new ConcurrentHashMap<String, RegionAddressRepository>();
protected final Map<String, AmenityIndexRepository> amenityRepositories = new ConcurrentHashMap<String, AmenityIndexRepository>();
// protected final Map<String, BinaryMapIndexReader> routingMapFiles = new ConcurrentHashMap<String, BinaryMapIndexReader>();
protected final Map<String, TransportIndexRepository> transportRepositories = new ConcurrentHashMap<String, TransportIndexRepository>();
protected final Map<String, String> indexFileNames = new ConcurrentHashMap<String, String>();
protected final Map<String, String> basemapFileNames = new ConcurrentHashMap<String, String>();
protected final IncrementalChangesManager changesManager = new IncrementalChangesManager(this);
protected final MapRenderRepositories renderer;
protected final MapTileDownloader tileDownloader;
public final AsyncLoadingThread asyncLoadingThread = new AsyncLoadingThread(this);
private HandlerThread renderingBufferImageThread;
protected boolean internetIsNotAccessible = false;
private java.text.DateFormat dateFormat;
private boolean depthContours;
public ResourceManager(OsmandApplication context) {
this.context = context;
this.renderer = new MapRenderRepositories(context);
asyncLoadingThread.start();
renderingBufferImageThread = new HandlerThread("RenderingBaseImage");
renderingBufferImageThread.start();
tileDownloader = MapTileDownloader.getInstance(Version.getFullVersion(context));
dateFormat = DateFormat.getDateFormat(context);
resetStoreDirectory();
WindowManager mgr = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
mgr.getDefaultDisplay().getMetrics(dm);
// Only 8 MB (from 16 Mb whole mem) available for images : image 64K * 128 = 8 MB (8 bit), 64 - 16 bit, 32 - 32 bit
// at least 3*9?
float tiles = (dm.widthPixels / 256 + 2) * (dm.heightPixels / 256 + 2) * 3;
log.info("Tiles to load in memory : " + tiles);
maxImgCacheSize = (int) (tiles) ;
}
public MapTileDownloader getMapTileDownloader() {
return tileDownloader;
}
public HandlerThread getRenderingBufferImageThread() {
return renderingBufferImageThread;
}
public void addResourceListener(ResourceListener listener) {
if (!resourceListeners.contains(listener)) {
resourceListeners.add(listener);
}
}
public void removeResourceListener(ResourceListener listener) {
resourceListeners.remove(listener);
}
public void resetStoreDirectory() {
dirWithTiles = context.getAppPath(IndexConstants.TILES_INDEX_DIR);
dirWithTiles.mkdirs();
context.getAppPath(IndexConstants.GPX_INDEX_DIR).mkdirs();
// ".nomedia" indicates there are no pictures and no music to list in this dir for the Gallery app
try {
context.getAppPath(".nomedia").createNewFile(); //$NON-NLS-1$
} catch( Exception e ) {
}
}
public java.text.DateFormat getDateFormat() {
return dateFormat;
}
public OsmandApplication getContext() {
return context;
}
public boolean hasDepthContours() {
return depthContours;
}
////////////////////////////////////////////// Working with tiles ////////////////////////////////////////////////
public Bitmap getTileImageForMapAsync(String file, ITileSource map, int x, int y, int zoom, boolean loadFromInternetIfNeeded) {
return getTileImageForMap(file, map, x, y, zoom, loadFromInternetIfNeeded, false, true);
}
public synchronized Bitmap getTileImageFromCache(String file){
return cacheOfImages.get(file);
}
public synchronized void putTileInTheCache(String file, Bitmap bmp) {
cacheOfImages.put(file, bmp);
}
public Bitmap getTileImageForMapSync(String file, ITileSource map, int x, int y, int zoom, boolean loadFromInternetIfNeeded) {
return getTileImageForMap(file, map, x, y, zoom, loadFromInternetIfNeeded, true, true);
}
public synchronized void tileDownloaded(DownloadRequest request){
if(request instanceof TileLoadDownloadRequest){
TileLoadDownloadRequest req = ((TileLoadDownloadRequest) request);
imagesOnFS.put(req.tileId, Boolean.TRUE);
/* if(req.fileToSave != null && req.tileSource instanceof SQLiteTileSource){
try {
((SQLiteTileSource) req.tileSource).insertImage(req.xTile, req.yTile, req.zoom, req.fileToSave);
} catch (IOException e) {
log.warn("File "+req.fileToSave.getName() + " couldn't be read", e); //$NON-NLS-1$//$NON-NLS-2$
}
req.fileToSave.delete();
String[] l = req.fileToSave.getParentFile().list();
if(l == null || l.length == 0){
req.fileToSave.getParentFile().delete();
l = req.fileToSave.getParentFile().getParentFile().list();
if(l == null || l.length == 0){
req.fileToSave.getParentFile().getParentFile().delete();
}
}
}*/
}
}
public synchronized boolean tileExistOnFileSystem(String file, ITileSource map, int x, int y, int zoom){
if(!imagesOnFS.containsKey(file)){
boolean ex = false;
if(map instanceof SQLiteTileSource){
if(((SQLiteTileSource) map).isLocked()){
return false;
}
ex = ((SQLiteTileSource) map).exists(x, y, zoom);
} else {
if(file == null){
file = calculateTileId(map, x, y, zoom);
}
ex = new File(dirWithTiles, file).exists();
}
if (ex) {
imagesOnFS.put(file, Boolean.TRUE);
} else {
imagesOnFS.put(file, null);
}
}
return imagesOnFS.get(file) != null || cacheOfImages.get(file) != null;
}
public void clearTileImageForMap(String file, ITileSource map, int x, int y, int zoom){
getTileImageForMap(file, map, x, y, zoom, true, false, true, true);
}
/**
* @param file - null could be passed if you do not call very often with that param
*/
protected Bitmap getTileImageForMap(String file, ITileSource map, int x, int y, int zoom,
boolean loadFromInternetIfNeeded, boolean sync, boolean loadFromFs) {
return getTileImageForMap(file, map, x, y, zoom, loadFromInternetIfNeeded, sync, loadFromFs, false);
}
// introduce cache in order save memory
protected StringBuilder builder = new StringBuilder(40);
protected char[] tileId = new char[120];
private GeoidAltitudeCorrection geoidAltitudeCorrection;
private boolean searchAmenitiesInProgress;
public synchronized String calculateTileId(ITileSource map, int x, int y, int zoom) {
builder.setLength(0);
if (map == null) {
builder.append(IndexConstants.TEMP_SOURCE_TO_LOAD);
} else {
builder.append(map.getName());
}
if (map instanceof SQLiteTileSource) {
builder.append('@');
} else {
builder.append('/');
}
builder.append(zoom).append('/').append(x).append('/').append(y).
append(map == null ? ".jpg" : map.getTileFormat()).append(".tile"); //$NON-NLS-1$ //$NON-NLS-2$
return builder.toString();
}
protected synchronized Bitmap getTileImageForMap(String tileId, ITileSource map, int x, int y, int zoom,
boolean loadFromInternetIfNeeded, boolean sync, boolean loadFromFs, boolean deleteBefore) {
if (tileId == null) {
tileId = calculateTileId(map, x, y, zoom);
if(tileId == null){
return null;
}
}
if(deleteBefore){
cacheOfImages.remove(tileId);
if (map instanceof SQLiteTileSource) {
((SQLiteTileSource) map).deleteImage(x, y, zoom);
} else {
File f = new File(dirWithTiles, tileId);
if (f.exists()) {
f.delete();
}
}
imagesOnFS.put(tileId, null);
}
if (loadFromFs && cacheOfImages.get(tileId) == null && map != null) {
boolean locked = map instanceof SQLiteTileSource && ((SQLiteTileSource) map).isLocked();
if(!loadFromInternetIfNeeded && !locked && !tileExistOnFileSystem(tileId, map, x, y, zoom)){
return null;
}
String url = loadFromInternetIfNeeded ? map.getUrlToLoad(x, y, zoom) : null;
File toSave = null;
if (url != null) {
if (map instanceof SQLiteTileSource) {
toSave = new File(dirWithTiles, calculateTileId(((SQLiteTileSource) map).getBase(), x, y, zoom));
} else {
toSave = new File(dirWithTiles, tileId);
}
}
TileLoadDownloadRequest req = new TileLoadDownloadRequest(dirWithTiles, url, toSave,
tileId, map, x, y, zoom, map.getReferer());
if(sync){
return getRequestedImageTile(req);
} else {
asyncLoadingThread.requestToLoadImage(req);
}
}
return cacheOfImages.get(tileId);
}
protected Bitmap getRequestedImageTile(TileLoadDownloadRequest req){
if(req.tileId == null || req.dirWithTiles == null){
return null;
}
Bitmap cacheBmp = cacheOfImages.get(req.tileId);
if (cacheBmp != null) {
return cacheBmp;
}
if (cacheOfImages.size() > maxImgCacheSize) {
clearTiles();
}
if (req.dirWithTiles.canRead() && !asyncLoadingThread.isFileCurrentlyDownloaded(req.fileToSave)
&& !asyncLoadingThread.isFilePendingToDownload(req.fileToSave)) {
long time = System.currentTimeMillis();
if (log.isDebugEnabled()) {
log.debug("Start loaded file : " + req.tileId + " " + Thread.currentThread().getName()); //$NON-NLS-1$ //$NON-NLS-2$
}
Bitmap bmp = null;
if (req.tileSource instanceof SQLiteTileSource) {
try {
long[] tm = new long[1];
bmp = ((SQLiteTileSource) req.tileSource).getImage(req.xTile, req.yTile, req.zoom, tm);
if (tm[0] != 0) {
int ts = req.tileSource.getExpirationTimeMillis();
if (ts != -1 && req.url != null && time - tm[0] > ts) {
asyncLoadingThread.requestToDownload(req);
}
}
} catch (OutOfMemoryError e) {
log.error("Out of memory error", e); //$NON-NLS-1$
clearTiles();
}
} else {
File en = new File(req.dirWithTiles, req.tileId);
if (en.exists()) {
try {
bmp = BitmapFactory.decodeFile(en.getAbsolutePath());
int ts = req.tileSource.getExpirationTimeMillis();
if(ts != -1 && req.url != null && time - en.lastModified() > ts) {
asyncLoadingThread.requestToDownload(req);
}
} catch (OutOfMemoryError e) {
log.error("Out of memory error", e); //$NON-NLS-1$
clearTiles();
}
}
}
if (bmp != null) {
cacheOfImages.put(req.tileId, bmp);
if (log.isDebugEnabled()) {
log.debug("Loaded file : " + req.tileId + " " + -(time - System.currentTimeMillis()) + " ms " + cacheOfImages.size()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
if (cacheOfImages.get(req.tileId) == null && req.url != null) {
asyncLoadingThread.requestToDownload(req);
}
}
return cacheOfImages.get(req.tileId);
}
////////////////////////////////////////////// Working with indexes ////////////////////////////////////////////////
public List<String> reloadIndexesOnStart(AppInitializer progress, List<String> warnings){
close();
// check we have some assets to copy to sdcard
warnings.addAll(checkAssets(progress));
progress.notifyEvent(InitEvents.ASSETS_COPIED);
reloadIndexes(progress, warnings);
progress.notifyEvent(InitEvents.MAPS_INITIALIZED);
return warnings;
}
public List<String> reloadIndexes(IProgress progress, List<String> warnings) {
geoidAltitudeCorrection = new GeoidAltitudeCorrection(context.getAppPath(null));
// do it lazy
// indexingImageTiles(progress);
warnings.addAll(indexingMaps(progress));
warnings.addAll(indexVoiceFiles(progress));
warnings.addAll(indexFontFiles(progress));
warnings.addAll(OsmandPlugin.onIndexingFiles(progress));
warnings.addAll(indexAdditionalMaps(progress));
return warnings;
}
public List<String> indexAdditionalMaps(IProgress progress) {
return context.getAppCustomization().onIndexingFiles(progress, indexFileNames);
}
public List<String> indexVoiceFiles(IProgress progress){
File file = context.getAppPath(IndexConstants.VOICE_INDEX_DIR);
file.mkdirs();
List<String> warnings = new ArrayList<String>();
if (file.exists() && file.canRead()) {
File[] lf = file.listFiles();
if (lf != null) {
for (File f : lf) {
if (f.isDirectory()) {
File conf = new File(f, "_config.p");
if (!conf.exists()) {
conf = new File(f, "_ttsconfig.p");
}
if (conf.exists()) {
indexFileNames.put(f.getName(), dateFormat.format(conf.lastModified())); //$NON-NLS-1$
}
}
}
}
}
return warnings;
}
public List<String> indexFontFiles(IProgress progress){
File file = context.getAppPath(IndexConstants.FONT_INDEX_DIR);
file.mkdirs();
List<String> warnings = new ArrayList<String>();
if (file.exists() && file.canRead()) {
File[] lf = file.listFiles();
if (lf != null) {
for (File f : lf) {
if (!f.isDirectory()) {
indexFileNames.put(f.getName(), dateFormat.format(f.lastModified()));
}
}
}
}
return warnings;
}
private List<String> checkAssets(IProgress progress) {
String fv = Version.getFullVersion(context);
if (!fv.equalsIgnoreCase(context.getSettings().PREVIOUS_INSTALLED_VERSION.get())) {
File applicationDataDir = context.getAppPath(null);
applicationDataDir.mkdirs();
if (applicationDataDir.canWrite()) {
try {
progress.startTask(context.getString(R.string.installing_new_resources), -1);
AssetManager assetManager = context.getAssets();
boolean isFirstInstall = context.getSettings().PREVIOUS_INSTALLED_VERSION.get().equals("");
unpackBundledAssets(assetManager, applicationDataDir, progress, isFirstInstall);
context.getSettings().PREVIOUS_INSTALLED_VERSION.set(fv);
copyRegionsBoundaries();
// see Issue #3381
//copyPoiTypes();
for (String internalStyle : context.getRendererRegistry().getInternalRenderers().keySet()) {
File fl = context.getRendererRegistry().getFileForInternalStyle(internalStyle);
if (fl.exists()) {
context.getRendererRegistry().copyFileForInternalStyle(internalStyle);
}
}
} catch (SQLiteException e) {
log.error(e.getMessage(), e);
} catch (IOException e) {
log.error(e.getMessage(), e);
} catch (XmlPullParserException e) {
log.error(e.getMessage(), e);
}
}
}
return Collections.emptyList();
}
private void copyRegionsBoundaries() {
try {
File file = context.getAppPath("regions.ocbf");
if (file != null) {
FileOutputStream fout = new FileOutputStream(file);
Algorithms.streamCopy(OsmandRegions.class.getResourceAsStream("regions.ocbf"), fout);
fout.close();
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
private void copyPoiTypes() {
try {
File file = context.getAppPath("poi_types.xml");
if (file != null) {
FileOutputStream fout = new FileOutputStream(file);
Algorithms.streamCopy(MapPoiTypes.class.getResourceAsStream("poi_types.xml"), fout);
fout.close();
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
private final static String ASSET_INSTALL_MODE__alwaysCopyOnFirstInstall = "alwaysCopyOnFirstInstall";
private final static String ASSET_COPY_MODE__overwriteOnlyIfExists = "overwriteOnlyIfExists";
private final static String ASSET_COPY_MODE__alwaysOverwriteOrCopy = "alwaysOverwriteOrCopy";
private final static String ASSET_COPY_MODE__copyOnlyIfDoesNotExist = "copyOnlyIfDoesNotExist";
private void unpackBundledAssets(AssetManager assetManager, File appDataDir, IProgress progress, boolean isFirstInstall) throws IOException, XmlPullParserException {
XmlPullParser xmlParser = XmlPullParserFactory.newInstance().newPullParser();
InputStream isBundledAssetsXml = assetManager.open("bundled_assets.xml");
xmlParser.setInput(isBundledAssetsXml, "UTF-8");
int next = 0;
while ((next = xmlParser.next()) != XmlPullParser.END_DOCUMENT) {
if (next == XmlPullParser.START_TAG && xmlParser.getName().equals("asset")) {
final String source = xmlParser.getAttributeValue(null, "source");
final String destination = xmlParser.getAttributeValue(null, "destination");
final String combinedMode = xmlParser.getAttributeValue(null, "mode");
final String[] modes = combinedMode.split("\\|");
if(modes.length == 0) {
log.error("Mode '" + combinedMode + "' is not valid");
continue;
}
String installMode = null;
String copyMode = null;
for(String mode : modes) {
if(ASSET_INSTALL_MODE__alwaysCopyOnFirstInstall.equals(mode))
installMode = mode;
else if(ASSET_COPY_MODE__overwriteOnlyIfExists.equals(mode) ||
ASSET_COPY_MODE__alwaysOverwriteOrCopy.equals(mode) ||
ASSET_COPY_MODE__copyOnlyIfDoesNotExist.equals(mode))
copyMode = mode;
else
log.error("Mode '" + mode + "' is unknown");
}
final File destinationFile = new File(appDataDir, destination);
boolean unconditional = false;
if(installMode != null)
unconditional = unconditional || (ASSET_INSTALL_MODE__alwaysCopyOnFirstInstall.equals(installMode) && isFirstInstall);
if(copyMode == null)
log.error("No copy mode was defined for " + source);
unconditional = unconditional || ASSET_COPY_MODE__alwaysOverwriteOrCopy.equals(copyMode);
boolean shouldCopy = unconditional;
shouldCopy = shouldCopy || (ASSET_COPY_MODE__overwriteOnlyIfExists.equals(copyMode) && destinationFile.exists());
shouldCopy = shouldCopy || (ASSET_COPY_MODE__copyOnlyIfDoesNotExist.equals(copyMode) && !destinationFile.exists());
if(shouldCopy)
copyAssets(assetManager, source, destinationFile);
}
}
isBundledAssetsXml.close();
}
public static void copyAssets(AssetManager assetManager, String assetName, File file) throws IOException {
if(file.exists()){
Algorithms.removeAllFiles(file);
}
file.getParentFile().mkdirs();
InputStream is = assetManager.open(assetName, AssetManager.ACCESS_STREAMING);
FileOutputStream out = new FileOutputStream(file);
Algorithms.streamCopy(is, out);
Algorithms.closeStream(out);
Algorithms.closeStream(is);
}
private List<File> collectFiles(File dir, String ext, List<File> files) {
if(dir.exists() && dir.canRead()) {
File[] lf = dir.listFiles();
if(lf == null || lf.length == 0) {
return files;
}
for (File f : lf) {
if (f.getName().endsWith(ext)) {
files.add(f);
}
}
}
return files;
}
private void renameRoadsFiles(ArrayList<File> files, File roadsPath) {
Iterator<File> it = files.iterator();
while(it.hasNext()) {
File f = it.next();
if (f.getName().endsWith("-roads" + IndexConstants.BINARY_MAP_INDEX_EXT)) {
f.renameTo(new File(roadsPath, f.getName().replace("-roads" + IndexConstants.BINARY_MAP_INDEX_EXT,
IndexConstants.BINARY_ROAD_MAP_INDEX_EXT)));
} else if (f.getName().endsWith(IndexConstants.BINARY_ROAD_MAP_INDEX_EXT)) {
f.renameTo(new File(roadsPath, f.getName()));
}
}
}
public List<String> indexingMaps(final IProgress progress) {
long val = System.currentTimeMillis();
ArrayList<File> files = new ArrayList<File>();
File appPath = context.getAppPath(null);
File roadsPath = context.getAppPath(IndexConstants.ROADS_INDEX_DIR);
roadsPath.mkdirs();
collectFiles(appPath, IndexConstants.BINARY_MAP_INDEX_EXT, files);
renameRoadsFiles(files, roadsPath);
collectFiles(roadsPath, IndexConstants.BINARY_MAP_INDEX_EXT, files);
if (!Version.isFreeVersion(context) || context.getSettings().FULL_VERSION_PURCHASED.get()) {
collectFiles(context.getAppPath(IndexConstants.WIKI_INDEX_DIR), IndexConstants.BINARY_MAP_INDEX_EXT, files);
}
if (OsmandPlugin.getEnabledPlugin(SRTMPlugin.class) != null) {
collectFiles(context.getAppPath(IndexConstants.SRTM_INDEX_DIR), IndexConstants.BINARY_MAP_INDEX_EXT, files);
}
changesManager.collectChangesFiles(context.getAppPath(IndexConstants.LIVE_INDEX_DIR), IndexConstants.BINARY_MAP_INDEX_EXT, files);
Collections.sort(files, Algorithms.getFileVersionComparator());
List<String> warnings = new ArrayList<String>();
renderer.clearAllResources();
CachedOsmandIndexes cachedOsmandIndexes = new CachedOsmandIndexes();
File indCache = context.getAppPath(INDEXES_CACHE);
if (indCache.exists()) {
try {
cachedOsmandIndexes.readFromFile(indCache, CachedOsmandIndexes.VERSION);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
File liveDir = context.getAppPath(IndexConstants.LIVE_INDEX_DIR);
depthContours = false;
for (File f : files) {
progress.startTask(context.getString(R.string.indexing_map) + " " + f.getName(), -1); //$NON-NLS-1$
try {
BinaryMapIndexReader mapReader = null;
try {
mapReader = cachedOsmandIndexes.getReader(f);
if (mapReader.getVersion() != IndexConstants.BINARY_MAP_VERSION) {
mapReader = null;
}
} catch (IOException e) {
log.error(String.format("File %s could not be read", f.getName()), e);
}
boolean wikiMap = (f.getName().contains("_wiki") || f.getName().contains(IndexConstants.BINARY_WIKI_MAP_INDEX_EXT));
boolean srtmMap = f.getName().contains(IndexConstants.BINARY_SRTM_MAP_INDEX_EXT);
if (mapReader == null || (Version.isFreeVersion(context) && wikiMap)) {
warnings.add(MessageFormat.format(context.getString(R.string.version_index_is_not_supported), f.getName())); //$NON-NLS-1$
} else {
if (mapReader.isBasemap()) {
basemapFileNames.put(f.getName(), f.getName());
}
long dateCreated = mapReader.getDateCreated();
if (dateCreated == 0) {
dateCreated = f.lastModified();
}
if(f.getParentFile().getName().equals(liveDir.getName())) {
boolean toUse = changesManager.index(f, dateCreated, mapReader);
if(!toUse) {
try {
mapReader.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
continue;
}
} else if(!wikiMap && !srtmMap) {
changesManager.indexMainMap(f, dateCreated);
}
indexFileNames.put(f.getName(), dateFormat.format(dateCreated)); //$NON-NLS-1$
if (!depthContours && f.getName().toLowerCase().startsWith("depth_")) {
depthContours = true;
}
renderer.initializeNewResource(progress, f, mapReader);
BinaryMapReaderResource resource = new BinaryMapReaderResource(f, mapReader);
fileReaders.put(f.getName(), resource);
if (!mapReader.getRegionNames().isEmpty()) {
RegionAddressRepositoryBinary rarb = new RegionAddressRepositoryBinary(this, resource);
addressMap.put(f.getName(), rarb);
}
if (mapReader.hasTransportData()) {
transportRepositories.put(f.getName(), new TransportIndexRepositoryBinary(resource));
}
// disable osmc for routing temporarily due to some bugs
if (mapReader.containsRouteData() && (!f.getParentFile().equals(liveDir) ||
context.getSettings().USE_OSM_LIVE_FOR_ROUTING.get())) {
resource.setUseForRouting(true);
}
if (mapReader.containsPoiData()) {
try {
RandomAccessFile raf = new RandomAccessFile(f, "r"); //$NON-NLS-1$
amenityRepositories.put(f.getName(), new AmenityIndexRepositoryBinary(new BinaryMapIndexReader(raf, mapReader)));
} catch (IOException e) {
log.error("Exception reading " + f.getAbsolutePath(), e); //$NON-NLS-1$
warnings.add(MessageFormat.format(context.getString(R.string.version_index_is_not_supported), f.getName())); //$NON-NLS-1$
}
}
}
} catch (SQLiteException e) {
log.error("Exception reading " + f.getAbsolutePath(), e); //$NON-NLS-1$
warnings.add(MessageFormat.format(context.getString(R.string.version_index_is_not_supported), f.getName())); //$NON-NLS-1$
} catch (OutOfMemoryError oome) {
log.error("Exception reading " + f.getAbsolutePath(), oome); //$NON-NLS-1$
warnings.add(MessageFormat.format(context.getString(R.string.version_index_is_big_for_memory), f.getName()));
}
}
log.debug("All map files initialized " + (System.currentTimeMillis() - val) + " ms");
if (files.size() > 0 && (!indCache.exists() || indCache.canWrite())) {
try {
cachedOsmandIndexes.writeToFile(indCache);
} catch (Exception e) {
log.error("Index file could not be written", e);
}
}
for (ResourceListener l : resourceListeners) {
l.onMapsIndexed();
}
return warnings;
}
public void initMapBoundariesCacheNative() {
File indCache = context.getAppPath(INDEXES_CACHE);
if (indCache.exists()) {
NativeOsmandLibrary nativeLib = NativeOsmandLibrary.getLoadedLibrary();
if (nativeLib != null) {
nativeLib.initCacheMapFile(indCache.getAbsolutePath());
}
}
}
////////////////////////////////////////////// Working with amenities ////////////////////////////////////////////////
public List<Amenity> searchAmenities(SearchPoiTypeFilter filter,
double topLatitude, double leftLongitude, double bottomLatitude, double rightLongitude, int zoom, final ResultMatcher<Amenity> matcher) {
final List<Amenity> amenities = new ArrayList<Amenity>();
searchAmenitiesInProgress = true;
try {
if (!filter.isEmpty()) {
int top31 = MapUtils.get31TileNumberY(topLatitude);
int left31 = MapUtils.get31TileNumberX(leftLongitude);
int bottom31 = MapUtils.get31TileNumberY(bottomLatitude);
int right31 = MapUtils.get31TileNumberX(rightLongitude);
for (AmenityIndexRepository index : amenityRepositories.values()) {
if (matcher != null && matcher.isCancelled()) {
searchAmenitiesInProgress = false;
break;
}
if (index.checkContainsInt(top31, left31, bottom31, right31)) {
List<Amenity> r = index.searchAmenities(top31,
left31, bottom31, right31, zoom, filter, matcher);
if(r != null) {
amenities.addAll(r);
}
}
}
}
} finally {
searchAmenitiesInProgress = false;
}
return amenities;
}
public List<Amenity> searchAmenitiesOnThePath(List<Location> locations, double radius, SearchPoiTypeFilter filter,
ResultMatcher<Amenity> matcher) {
searchAmenitiesInProgress = true;
final List<Amenity> amenities = new ArrayList<Amenity>();
try {
if (locations != null && locations.size() > 0) {
List<AmenityIndexRepository> repos = new ArrayList<AmenityIndexRepository>();
double topLatitude = locations.get(0).getLatitude();
double bottomLatitude = locations.get(0).getLatitude();
double leftLongitude = locations.get(0).getLongitude();
double rightLongitude = locations.get(0).getLongitude();
for (Location l : locations) {
topLatitude = Math.max(topLatitude, l.getLatitude());
bottomLatitude = Math.min(bottomLatitude, l.getLatitude());
leftLongitude = Math.min(leftLongitude, l.getLongitude());
rightLongitude = Math.max(rightLongitude, l.getLongitude());
}
if (!filter.isEmpty()) {
for (AmenityIndexRepository index : amenityRepositories.values()) {
if (index.checkContainsInt(
MapUtils.get31TileNumberY(topLatitude),
MapUtils.get31TileNumberX(leftLongitude),
MapUtils.get31TileNumberY(bottomLatitude),
MapUtils.get31TileNumberX(rightLongitude))) {
repos.add(index);
}
}
if (!repos.isEmpty()) {
for (AmenityIndexRepository r : repos) {
List<Amenity> res = r.searchAmenitiesOnThePath(locations, radius, filter, matcher);
if(res != null) {
amenities.addAll(res);
}
}
}
}
}
} finally {
searchAmenitiesInProgress = false;
}
return amenities;
}
public boolean containsAmenityRepositoryToSearch(boolean searchByName){
for (AmenityIndexRepository index : amenityRepositories.values()) {
if(searchByName){
if(index instanceof AmenityIndexRepositoryBinary){
return true;
}
} else {
return true;
}
}
return false;
}
public List<Amenity> searchAmenitiesByName(String searchQuery,
double topLatitude, double leftLongitude, double bottomLatitude, double rightLongitude,
double lat, double lon, ResultMatcher<Amenity> matcher) {
List<Amenity> amenities = new ArrayList<Amenity>();
List<AmenityIndexRepositoryBinary> list = new ArrayList<AmenityIndexRepositoryBinary>();
int left = MapUtils.get31TileNumberX(leftLongitude);
int top = MapUtils.get31TileNumberY(topLatitude);
int right = MapUtils.get31TileNumberX(rightLongitude);
int bottom = MapUtils.get31TileNumberY(bottomLatitude);
for (AmenityIndexRepository index : amenityRepositories.values()) {
if (matcher != null && matcher.isCancelled()) {
break;
}
if (index instanceof AmenityIndexRepositoryBinary) {
if (index.checkContainsInt(top, left, bottom, right)) {
if(index.checkContains(lat, lon)){
list.add(0, (AmenityIndexRepositoryBinary) index);
} else {
list.add((AmenityIndexRepositoryBinary) index);
}
}
}
}
// Not using boundares results in very slow initial search if user has many maps installed
// int left = 0;
// int top = 0;
// int right = Integer.MAX_VALUE;
// int bottom = Integer.MAX_VALUE;
for (AmenityIndexRepositoryBinary index : list) {
if (matcher != null && matcher.isCancelled()) {
break;
}
List<Amenity> result = index.searchAmenitiesByName(MapUtils.get31TileNumberX(lon), MapUtils.get31TileNumberY(lat),
left, top, right, bottom,
searchQuery, matcher);
amenities.addAll(result);
}
return amenities;
}
public Map<PoiCategory, List<String>> searchAmenityCategoriesByName(String searchQuery, double lat, double lon) {
Map<PoiCategory, List<String>> map = new LinkedHashMap<PoiCategory, List<String>>();
for (AmenityIndexRepository index : amenityRepositories.values()) {
if (index instanceof AmenityIndexRepositoryBinary) {
if (index.checkContains(lat, lon)) {
((AmenityIndexRepositoryBinary) index).searchAmenityCategoriesByName(searchQuery, map);
}
}
}
return map;
}
////////////////////////////////////////////// Working with address ///////////////////////////////////////////
public RegionAddressRepository getRegionRepository(String name){
return addressMap.get(name);
}
public Collection<RegionAddressRepository> getAddressRepositories(){
return addressMap.values();
}
public Collection<BinaryMapReaderResource> getFileReaders() {
return fileReaders.values();
}
////////////////////////////////////////////// Working with transport ////////////////////////////////////////////////
public List<TransportIndexRepository> searchTransportRepositories(double latitude, double longitude) {
List<TransportIndexRepository> repos = new ArrayList<TransportIndexRepository>();
for (TransportIndexRepository index : transportRepositories.values()) {
if (index.checkContains(latitude,longitude)) {
repos.add(index);
}
}
return repos;
}
public List<TransportStop> searchTransportSync(double topLatitude, double leftLongitude, double bottomLatitude, double rightLongitude, ResultMatcher<TransportStop> matcher){
List<TransportIndexRepository> repos = new ArrayList<TransportIndexRepository>();
List<TransportStop> stops = new ArrayList<>();
for (TransportIndexRepository index : transportRepositories.values()) {
if (index.checkContains(topLatitude, leftLongitude, bottomLatitude, rightLongitude)) {
repos.add(index);
}
}
if(!repos.isEmpty()){
for (TransportIndexRepository repository : repos) {
repository.searchTransportStops(topLatitude, leftLongitude, bottomLatitude, rightLongitude,
-1, stops, matcher);
}
}
return stops;
}
////////////////////////////////////////////// Working with map ////////////////////////////////////////////////
public boolean updateRenderedMapNeeded(RotatedTileBox rotatedTileBox, DrawSettings drawSettings) {
return renderer.updateMapIsNeeded(rotatedTileBox, drawSettings);
}
public void updateRendererMap(RotatedTileBox rotatedTileBox, OnMapLoadedListener mapLoadedListener){
renderer.interruptLoadingMap();
asyncLoadingThread.requestToLoadMap(new MapLoadRequest(rotatedTileBox, mapLoadedListener));
}
public void interruptRendering(){
renderer.interruptLoadingMap();
}
public boolean isSearchAmenitiesInProgress() {
return searchAmenitiesInProgress;
}
public MapRenderRepositories getRenderer() {
return renderer;
}
////////////////////////////////////////////// Closing methods ////////////////////////////////////////////////
public void closeFile(String fileName) {
amenityRepositories.remove(fileName);
addressMap.remove(fileName);
transportRepositories.remove(fileName);
indexFileNames.remove(fileName);
renderer.closeConnection(fileName);
BinaryMapReaderResource resource = fileReaders.remove(fileName);
if(resource != null) {
resource.close();
}
}
public synchronized void close(){
imagesOnFS.clear();
indexFileNames.clear();
basemapFileNames.clear();
renderer.clearAllResources();
transportRepositories.clear();
addressMap.clear();
amenityRepositories.clear();
for(BinaryMapReaderResource res : fileReaders.values()) {
res.close();
}
fileReaders.clear();
}
public BinaryMapIndexReader[] getRoutingMapFiles() {
List<BinaryMapIndexReader> readers = new ArrayList<>(fileReaders.size());
for(BinaryMapReaderResource r : fileReaders.values()) {
if(r.isUseForRouting()) {
readers.add(r.getReader(BinaryMapReaderResourceType.ROUTING));
}
}
return readers.toArray(new BinaryMapIndexReader[readers.size()]);
}
public BinaryMapIndexReader[] getQuickSearchFiles() {
List<BinaryMapIndexReader> readers = new ArrayList<>(fileReaders.size());
for(BinaryMapReaderResource r : fileReaders.values()) {
if(r.getShallowReader().containsPoiData() ||
r.getShallowReader().containsAddressData()) {
readers.add(r.getReader(BinaryMapReaderResourceType.QUICK_SEARCH));
}
}
return readers.toArray(new BinaryMapIndexReader[readers.size()]);
}
public Map<String, String> getIndexFileNames() {
return new LinkedHashMap<String, String>(indexFileNames);
}
public boolean containsBasemap(){
return !basemapFileNames.isEmpty();
}
public boolean isAnyMapIstalled() {
File appPath = context.getAppPath(null);
File[] maps = appPath.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().endsWith(IndexConstants.BINARY_MAP_INDEX_EXT);
}
});
return maps != null && maps.length > 0;
}
public Map<String, String> getBackupIndexes(Map<String, String> map) {
File file = context.getAppPath(IndexConstants.BACKUP_INDEX_DIR);
if (file != null && file.isDirectory()) {
File[] lf = file.listFiles();
if (lf != null) {
for (File f : lf) {
if (f != null && f.getName().endsWith(IndexConstants.BINARY_MAP_INDEX_EXT)) {
map.put(f.getName(), AndroidUtils.formatDate(context, f.lastModified())); //$NON-NLS-1$
}
}
}
}
return map;
}
public synchronized void reloadTilesFromFS(){
imagesOnFS.clear();
}
/// On low memory method ///
public void onLowMemory() {
log.info("On low memory : cleaning tiles - size = " + cacheOfImages.size()); //$NON-NLS-1$
clearTiles();
for(RegionAddressRepository r : addressMap.values()){
r.clearCache();
}
renderer.clearCache();
System.gc();
}
public GeoidAltitudeCorrection getGeoidAltitudeCorrection() {
return geoidAltitudeCorrection;
}
public OsmandRegions getOsmandRegions() {
return context.getRegions();
}
protected synchronized void clearTiles() {
log.info("Cleaning tiles - size = " + cacheOfImages.size()); //$NON-NLS-1$
ArrayList<String> list = new ArrayList<String>(cacheOfImages.keySet());
// remove first images (as we think they are older)
for (int i = 0; i < list.size() / 2; i++) {
cacheOfImages.remove(list.get(i));
}
}
public IncrementalChangesManager getChangesManager() {
return changesManager;
}
}