package net.osmand.plus; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.text.Collator; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Stack; import java.util.TreeMap; import net.osmand.IProgress; import net.osmand.LogUtil; import net.osmand.binary.BinaryMapIndexReader; import net.osmand.data.Amenity; import net.osmand.data.TransportStop; import net.osmand.data.index.IndexConstants; import net.osmand.data.preparation.MapTileDownloader; import net.osmand.data.preparation.MapTileDownloader.DownloadRequest; import net.osmand.data.preparation.MapTileDownloader.IMapDownloaderCallback; import net.osmand.map.ITileSource; import net.osmand.osm.LatLon; import net.osmand.osm.MapUtils; import net.osmand.plus.activities.OsmandApplication; import net.osmand.plus.render.BaseOsmandRender; import net.osmand.plus.render.MapRenderRepositories; import net.osmand.plus.render.RendererRegistry; import net.osmand.plus.views.POIMapLayer; import org.apache.commons.logging.Log; import android.database.sqlite.SQLiteException; import android.graphics.Bitmap; import android.graphics.BitmapFactory; /** * 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 APP_DIR = "osmand/"; //$NON-NLS-1$ public static final String POI_PATH = APP_DIR + IndexConstants.POI_INDEX_DIR; public static final String VOICE_PATH = APP_DIR + IndexConstants.VOICE_INDEX_DIR; public static final String MAPS_PATH = APP_DIR; public static final String ADDRESS_PATH = APP_DIR + IndexConstants.ADDRESS_INDEX_DIR; public static final String TRANSPORT_PATH = APP_DIR + IndexConstants.TRANSPORT_INDEX_DIR; public static final String TILES_PATH = APP_DIR+"tiles/"; //$NON-NLS-1$ public static final String TEMP_SOURCE_TO_LOAD = "temp"; //$NON-NLS-1$ public static final String VECTOR_MAP = "#vector_map"; //$NON-NLS-1$ public static final int LIMIT_TRANSPORT = 200; private static final Log log = LogUtil.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 protected int maxImgCacheSize = 32; 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 BusyIndicator busyIndicator; private final MapTileDownloader downloader = MapTileDownloader.getInstance(); // Indexes private final Map<String, RegionAddressRepository> addressMap = new TreeMap<String, RegionAddressRepository>(Collator.getInstance()); protected final List<AmenityIndexRepository> amenityRepositories = new ArrayList<AmenityIndexRepository>(); protected final List<TransportIndexRepository> transportRepositories = new ArrayList<TransportIndexRepository>(); protected final Map<String, String> indexFileNames = new LinkedHashMap<String, String>(); protected final MapRenderRepositories renderer; public final AsyncLoadingThread asyncLoadingTiles = new AsyncLoadingThread(); protected boolean internetIsNotAccessible = false; public ResourceManager(OsmandApplication context) { this.context = context; this.renderer = new MapRenderRepositories(context); asyncLoadingTiles.start(); resetStoreDirectory(); } public void resetStoreDirectory() { dirWithTiles = OsmandSettings.extendOsmandPath(context, TILES_PATH); dirWithTiles.mkdirs(); } public OsmandApplication getContext() { return context; } ////////////////////////////////////////////// Working with tiles //////////////////////////////////////////////// public void indexingImageTiles(IProgress progress){ progress.startTask(context.getString(R.string.reading_cached_tiles), -1); //$NON-NLS-1$ imagesOnFS.clear(); for(File c : dirWithTiles.listFiles()){ indexImageTilesFS("", c); //$NON-NLS-1$ } } private void indexImageTilesFS(String prefix, File f){ if(f.isDirectory()){ for(File c : f.listFiles()){ indexImageTilesFS(prefix +f.getName() +"/" , c); //$NON-NLS-1$ } } else if(f.getName().endsWith(".tile")){ //$NON-NLS-1$ imagesOnFS.put(prefix + f.getName(), Boolean.TRUE); } else if(f.getName().endsWith(".sqlitedb")){ //$NON-NLS-1$ // nothing to do here } } 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 Bitmap getTileImageFromCache(String file){ return cacheOfImages.get(file); } 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 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){ 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; } 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 private int insertString(char[] ar, int offset, String s) { for (int j = 0; j < s.length(); j++) { ar[offset++] = s.charAt(j); } return offset; } protected StringBuilder builder = new StringBuilder(40); protected char[] tileId = new char[120]; public synchronized String calculateTileId(ITileSource map, int x, int y, int zoom){ if(false){ // performance improve ? int ind = 0; String mapName = map == null ? TEMP_SOURCE_TO_LOAD : map.getName(); ind = insertString(tileId, ind, mapName); if (map instanceof SQLiteTileSource) { tileId[ind++] = '@'; } else { tileId[ind++] = '/'; } ind = insertString(tileId, ind, Integer.toString(zoom)); tileId[ind++] = '/'; ind = insertString(tileId, ind, Integer.toString(x)); tileId[ind++] = '/'; ind = insertString(tileId, ind, Integer.toString(y)); ind = insertString(tileId, ind, map == null ? ".jpg" : map.getTileFormat()); //$NON-NLS-1$ ind = insertString(tileId, ind, ".tile"); //$NON-NLS-1$ return new String(tileId, 0, ind); } else { builder.setLength(0); if (map == null) { builder.append(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) { if(!loadFromInternetIfNeeded && !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); if(sync){ return getRequestedImageTile(req); } else { asyncLoadingTiles.requestToLoadImage(req); } } return cacheOfImages.get(tileId); } private Bitmap getRequestedImageTile(TileLoadDownloadRequest req){ if(req.tileId == null || req.dirWithTiles == null){ return null; } if (cacheOfImages.size() > maxImgCacheSize) { clearTiles(); } if (req.dirWithTiles.canRead() && !downloader.isFileCurrentlyDownloaded(req.fileToSave)) { long time = System.currentTimeMillis(); Bitmap bmp = null; if (req.tileSource instanceof SQLiteTileSource) { bmp = ((SQLiteTileSource) req.tileSource).getImage(req.xTile, req.yTile, req.zoom); } else { File en = new File(req.dirWithTiles, req.tileId); if (en.exists()) { try { bmp = BitmapFactory.decodeFile(en.getAbsolutePath()); } 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) { downloader.requestToDownload(req); } } return cacheOfImages.get(req.tileId); } ////////////////////////////////////////////// Working with indexes //////////////////////////////////////////////// public List<String> reloadIndexes(IProgress progress){ close(); initRenderers(progress); // do it lazy // indexingImageTiles(progress); List<String> warnings = new ArrayList<String>(); warnings.addAll(indexingPoi(progress)); warnings.addAll(indexingAddresses(progress)); warnings.addAll(indexingTransport(progress)); warnings.addAll(indexingMaps(progress)); return warnings; } private void initRenderers(IProgress progress) { File file = OsmandSettings.extendOsmandPath(context, APP_DIR + IndexConstants.RENDERERS_DIR); file.mkdirs(); Map<String, File> externalRenderers = new LinkedHashMap<String, File>(); if (file.exists() && file.canRead()) { for (File f : file.listFiles()) { if (f.getName().endsWith(IndexConstants.RENDERER_INDEX_EXT)) { String name = f.getName().substring(0, f.getName().length() - IndexConstants.RENDERER_INDEX_EXT.length()); externalRenderers.put(name, f); } } } RendererRegistry.getRegistry().setExternalRenderers(externalRenderers); String r = OsmandSettings.getVectorRenderer(OsmandSettings.getPrefs(context)); if(r != null){ BaseOsmandRender obj = RendererRegistry.getRegistry().getRenderer(r); if(obj != null){ RendererRegistry.getRegistry().setCurrentSelectedRender(obj); } } } public List<String> indexingMaps(final IProgress progress) { File file = OsmandSettings.extendOsmandPath(context, MAPS_PATH); file.mkdirs(); List<String> warnings = new ArrayList<String>(); renderer.clearAllResources(); if (file.exists() && file.canRead()) { for (File f : file.listFiles()) { if (f.getName().endsWith(IndexConstants.BINARY_MAP_INDEX_EXT)) { progress.startTask(context.getString(R.string.indexing_map) + " " + f.getName(), -1); //$NON-NLS-1$ try { BinaryMapIndexReader index = renderer.initializeNewResource(progress, f); if (index == null) { warnings.add(MessageFormat.format(context.getString(R.string.version_index_is_not_supported), f.getName())); //$NON-NLS-1$ } else { indexFileNames.put(f.getName(), MessageFormat.format("{0,date,dd.MM.yyyy}", new Date(f.lastModified()))); //$NON-NLS-1$ for(String rName : index.getRegionNames()) { // skip duplicate names (don't make collision between getName() and name in the map) RegionAddressRepositoryBinary rarb = new RegionAddressRepositoryBinary(index, rName); addressMap.put(rName, rarb); } if (index.hasTransportData()) { try { RandomAccessFile raf = new RandomAccessFile(f, "r"); //$NON-NLS-1$ transportRepositories.add(new TransportIndexRepositoryBinary(new BinaryMapIndexReader(raf))); } 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$ } } if(index.containsMapData()){ // that's not fully acceptable // TODO // try { // RandomAccessFile raf = new RandomAccessFile(f, "r"); //$NON-NLS-1$ // amenityRepositories.add(new AmenityIndexRepositoryBinary(new BinaryMapIndexReader(raf))); // } catch (IOException e) { // log.error("Exception reading " + f.getAbsolutePath(), e); //$NON-NLS-1$ // warnings.add(MessageFormat.format(Messages.getMessage("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())); } } else if(f.getName().endsWith(".map.odb")){ //$NON-NLS-1$ warnings.add(MessageFormat.format(context.getString(R.string.old_map_index_is_not_supported), f.getName())); //$NON-NLS-1$ } } } return warnings; } // POI INDEX // public List<String> indexingPoi(final IProgress progress) { File file = OsmandSettings.extendOsmandPath(context, POI_PATH); file.mkdirs(); List<String> warnings = new ArrayList<String>(); closeAmenities(); if (file.exists() && file.canRead()) { for (File f : file.listFiles()) { indexingPoi(progress, warnings, f); } } return warnings; } public void indexingPoi(final IProgress progress, List<String> warnings, File f) { if (f.getName().endsWith(IndexConstants.POI_INDEX_EXT)) { AmenityIndexRepositoryOdb repository = new AmenityIndexRepositoryOdb(); progress.startTask(context.getString(R.string.indexing_poi) + " " + f.getName(), -1); //$NON-NLS-1$ try { boolean initialized = repository.initialize(progress, f); if (initialized) { amenityRepositories.add(repository); indexFileNames.put(f.getName(), MessageFormat.format("{0,date,dd.MM.yyyy}", new Date(f.lastModified()))); //$NON-NLS-1$ } else { 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$ } } } public void updateIndexLastDateModified(File f){ if(f != null && f.exists()){ indexFileNames.put(f.getName(), MessageFormat.format("{0,date,dd.MM.yyyy}", new Date(f.lastModified()))); //$NON-NLS-1$ } } public List<String> indexingAddresses(final IProgress progress){ File file = OsmandSettings.extendOsmandPath(context, ADDRESS_PATH); List<String> warnings = new ArrayList<String>(); closeAddresses(); if (file.exists() && file.canRead()) { for (File f : file.listFiles()) { indexingAddress(progress, warnings, f); } } return warnings; } public void indexingAddress(final IProgress progress, List<String> warnings, File f) { if (f.getName().endsWith(IndexConstants.ADDRESS_INDEX_EXT)) { RegionAddressRepositoryOdb repository = new RegionAddressRepositoryOdb(); progress.startTask(context.getString(R.string.indexing_address) + " " + f.getName(), -1); //$NON-NLS-1$ try { boolean initialized = repository.initialize(progress, f); if (initialized) { addressMap.put(repository.getName(), repository); indexFileNames.put(f.getName(), MessageFormat.format("{0,date,dd.MM.yyyy}", new Date(f.lastModified()))); //$NON-NLS-1$ } else { 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$ } } } public List<String> indexingTransport(final IProgress progress){ File file = OsmandSettings.extendOsmandPath(context, TRANSPORT_PATH); List<String> warnings = new ArrayList<String>(); closeTransport(); if (file.exists() && file.canRead()) { for (File f : file.listFiles()) { indexingTransport(progress, warnings, f); } } return warnings; } public void indexingTransport(final IProgress progress, List<String> warnings, File f) { if (f.getName().endsWith(IndexConstants.TRANSPORT_INDEX_EXT)) { TransportIndexRepositoryOdb repository = new TransportIndexRepositoryOdb(); progress.startTask(context.getString(R.string.indexing_transport) + " " + f.getName(), -1); //$NON-NLS-1$ try { boolean initialized = repository.initialize(progress, f); if (initialized) { transportRepositories.add(repository); indexFileNames.put(f.getName(), MessageFormat.format("{0,date,dd.MM.yyyy}", new Date(f.lastModified()))); //$NON-NLS-1$ } else { 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$ } } } ////////////////////////////////////////////// Working with amenities //////////////////////////////////////////////// public List<AmenityIndexRepository> searchAmenityRepositories(double latitude, double longitude) { List<AmenityIndexRepository> repos = new ArrayList<AmenityIndexRepository>(); for (AmenityIndexRepository index : amenityRepositories) { if (index.checkContains(latitude,longitude)) { repos.add(index); } } return repos; } public List<Amenity> searchAmenities(PoiFilter filter, double latitude, double longitude, int zoom, int limit) { double tileNumberX = MapUtils.getTileNumberX(zoom, longitude); double tileNumberY = MapUtils.getTileNumberY(zoom, latitude); double topLatitude = MapUtils.getLatitudeFromTile(zoom, tileNumberY - 0.5); double bottomLatitude = MapUtils.getLatitudeFromTile(zoom, tileNumberY + 0.5); double leftLongitude = MapUtils.getLongitudeFromTile(zoom, tileNumberX - 0.5); double rightLongitude = MapUtils.getLongitudeFromTile(zoom, tileNumberX + 0.5); List<Amenity> amenities = new ArrayList<Amenity>(); for (AmenityIndexRepository index : amenityRepositories) { if (index.checkContains(topLatitude, leftLongitude, bottomLatitude, rightLongitude)) { if (!index.checkCachedAmenities(topLatitude, leftLongitude, bottomLatitude, rightLongitude, zoom, filter.getFilterId(), amenities, false)) { index.searchAmenities(topLatitude, leftLongitude, bottomLatitude, rightLongitude, limit, filter, amenities); } } } return amenities; } public void searchAmenitiesAsync(double topLatitude, double leftLongitude, double bottomLatitude, double rightLongitude, int zoom, PoiFilter filter, List<Amenity> toFill){ if(filter instanceof NameFinderPoiFilter){ List<Amenity> amenities = ((NameFinderPoiFilter) filter).getSearchedAmenities(); for(Amenity a : amenities){ LatLon l = a.getLocation(); if(l != null && l.getLatitude() <= topLatitude && l.getLatitude() >= bottomLatitude && l.getLongitude() >= leftLongitude && l.getLongitude() <= rightLongitude){ toFill.add(a); } } } else { String filterId = filter == null ? null : filter.getFilterId(); for (AmenityIndexRepository index : amenityRepositories) { if (index.checkContains(topLatitude, leftLongitude, bottomLatitude, rightLongitude)) { if (!index.checkCachedAmenities(topLatitude, leftLongitude, bottomLatitude, rightLongitude, zoom, filterId, toFill, true)) { asyncLoadingTiles.requestToLoadAmenities(new AmenityLoadRequest(index, topLatitude, leftLongitude, bottomLatitude, rightLongitude, zoom, filter)); } } } } } ////////////////////////////////////////////// Working with address /////////////////////////////////////////// public RegionAddressRepository getRegionRepository(String name){ return addressMap.get(name); } public Collection<RegionAddressRepository> getAddressRepositories(){ return addressMap.values(); } ////////////////////////////////////////////// Working with transport //////////////////////////////////////////////// public List<TransportIndexRepository> searchTransportRepositories(double latitude, double longitude) { List<TransportIndexRepository> repos = new ArrayList<TransportIndexRepository>(); for (TransportIndexRepository index : transportRepositories) { if (index.checkContains(latitude,longitude)) { repos.add(index); } } return repos; } public void searchTransportAsync(double topLatitude, double leftLongitude, double bottomLatitude, double rightLongitude, int zoom, List<TransportStop> toFill){ for(TransportIndexRepository index : transportRepositories){ if(index.checkContains(topLatitude, leftLongitude, bottomLatitude, rightLongitude)){ if(!index.checkCachedObjects(topLatitude, leftLongitude, bottomLatitude, rightLongitude, zoom, toFill, true)){ asyncLoadingTiles.requestToLoadTransport( new TransportLoadRequest(index, topLatitude, leftLongitude, bottomLatitude, rightLongitude, zoom)); } } } } ////////////////////////////////////////////// Working with map //////////////////////////////////////////////// public boolean updateRenderedMapNeeded(RotatedTileBox rotatedTileBox){ return renderer.updateMapIsNeeded(rotatedTileBox); } public void updateRendererMap(RotatedTileBox rotatedTileBox){ renderer.interruptLoadingMap(); asyncLoadingTiles.requestToLoadMap( new MapLoadRequest(new RotatedTileBox(rotatedTileBox))); } public void interruptRendering(){ renderer.interruptLoadingMap(); } public MapRenderRepositories getRenderer() { return renderer; } ////////////////////////////////////////////// Closing methods //////////////////////////////////////////////// public void closeAmenities(){ for(AmenityIndexRepository r : amenityRepositories){ r.close(); } amenityRepositories.clear(); } public void closeAddresses(){ for(RegionAddressRepository r : addressMap.values()){ r.close(); } addressMap.clear(); } public void closeTransport(){ for(TransportIndexRepository r : transportRepositories){ r.close(); } transportRepositories.clear(); } public BusyIndicator getBusyIndicator() { return busyIndicator; } public synchronized void setBusyIndicator(BusyIndicator busyIndicator) { this.busyIndicator = busyIndicator; } public synchronized void close(){ imagesOnFS.clear(); indexFileNames.clear(); renderer.clearAllResources(); closeAmenities(); closeAddresses(); closeTransport(); } public Map<String, String> getIndexFileNames() { return indexFileNames; } 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(AmenityIndexRepository r : amenityRepositories){ r.clearCache(); } for(RegionAddressRepository r : addressMap.values()){ r.clearCache(); } renderer.clearCache(); System.gc(); } public synchronized void updateMapSource(boolean useVectorMap, ITileSource source){ log.info("Clear cache with new source " + cacheOfImages.size()); //$NON-NLS-1$ cacheOfImages.clear(); renderer.clearCache(); if(source == null || source.getBitDensity() == 0){ maxImgCacheSize = 32; } else { maxImgCacheSize = Math.max(384 / source.getBitDensity() , 32); } } 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)); } } private static class TileLoadDownloadRequest extends DownloadRequest { public final String tileId; public final File dirWithTiles; public final ITileSource tileSource; public TileLoadDownloadRequest(File dirWithTiles, String url, File fileToSave, String tileId, ITileSource source, int tileX, int tileY, int zoom) { super(url, fileToSave, tileX, tileY, zoom); this.dirWithTiles = dirWithTiles; this.tileSource = source; this.tileId = tileId; } } private static class AmenityLoadRequest { public final AmenityIndexRepository repository; public final double topLatitude; public final double bottomLatitude; public final double leftLongitude; public final double rightLongitude; public final PoiFilter filter; public final int zoom; public AmenityLoadRequest(AmenityIndexRepository repository, double topLatitude, double leftLongitude, double bottomLatitude, double rightLongitude, int zoom, PoiFilter filter) { super(); this.bottomLatitude = bottomLatitude; this.leftLongitude = leftLongitude; this.repository = repository; this.rightLongitude = rightLongitude; this.topLatitude = topLatitude; this.zoom = zoom; this.filter = filter; } } private static class TransportLoadRequest { public final TransportIndexRepository repository; public final double topLatitude; public final double bottomLatitude; public final double leftLongitude; public final double rightLongitude; public final int zoom; public TransportLoadRequest(TransportIndexRepository repository, double topLatitude, double leftLongitude, double bottomLatitude, double rightLongitude, int zoom) { super(); this.bottomLatitude = bottomLatitude; this.leftLongitude = leftLongitude; this.repository = repository; this.rightLongitude = rightLongitude; this.topLatitude = topLatitude; this.zoom = zoom; } } private static class MapLoadRequest { public final RotatedTileBox tileBox; public MapLoadRequest(RotatedTileBox tileBox) { super(); this.tileBox = tileBox; } } public class AsyncLoadingThread extends Thread { Stack<Object> requests = new Stack<Object>(); public AsyncLoadingThread(){ super("Loader map objects (tiles, poi)"); //$NON-NLS-1$ } @Override public void run() { while(true){ try { boolean update = false; boolean amenityLoaded = false; boolean transportLoaded = false; boolean mapLoaded = false; int progress = 0; if(downloader.isSomethingBeingDownloaded()){ progress = BusyIndicator.STATUS_GREEN; } synchronized(ResourceManager.this){ if(busyIndicator != null){ if(context.getRoutingHelper().isRouteBeingCalculated()){ progress = BusyIndicator.STATUS_BLUE; } else if(!requests.isEmpty()){ progress = BusyIndicator.STATUS_BLACK;; } busyIndicator.updateStatus(progress); } } while(!requests.isEmpty()){ Object req = requests.pop(); if (req instanceof TileLoadDownloadRequest) { TileLoadDownloadRequest r = (TileLoadDownloadRequest) req; if (cacheOfImages.get(r.tileId) == null) { update |= getRequestedImageTile(r) != null; } } else if(req instanceof AmenityLoadRequest){ if(!amenityLoaded){ AmenityLoadRequest r = (AmenityLoadRequest) req; r.repository.evaluateCachedAmenities(r.topLatitude, r.leftLongitude, r.bottomLatitude, r.rightLongitude, r.zoom, POIMapLayer.LIMIT_POI, r.filter, null); amenityLoaded = true; } } else if(req instanceof TransportLoadRequest){ if(!transportLoaded){ TransportLoadRequest r = (TransportLoadRequest) req; r.repository.evaluateCachedTransportStops(r.topLatitude, r.leftLongitude, r.bottomLatitude, r.rightLongitude, r.zoom, LIMIT_TRANSPORT, null); transportLoaded = true; } } else if(req instanceof MapLoadRequest){ if(!mapLoaded){ MapLoadRequest r = (MapLoadRequest) req; renderer.loadMap(r.tileBox, downloader.getDownloaderCallbacks()); mapLoaded = true; } } } if(update || amenityLoaded || transportLoaded || mapLoaded){ // use downloader callback for(IMapDownloaderCallback c : downloader.getDownloaderCallbacks()){ c.tileDownloaded(null); } } boolean routeBeingCalculated = context.getRoutingHelper().isRouteBeingCalculated(); if (progress != 0 || routeBeingCalculated || downloader.isSomethingBeingDownloaded()) { synchronized (ResourceManager.this) { if (busyIndicator != null) { if(routeBeingCalculated){ progress = BusyIndicator.STATUS_BLUE; } else if(downloader.isSomethingBeingDownloaded()){ progress = BusyIndicator.STATUS_GREEN; } else { progress = 0; } busyIndicator.updateStatus(progress); } } } sleep(750); } catch (InterruptedException e) { log.error(e, e); } catch (RuntimeException e){ log.error(e, e); } } } public void requestToLoadImage(TileLoadDownloadRequest req){ requests.push(req); } public void requestToLoadAmenities(AmenityLoadRequest req){ requests.push(req); } public void requestToLoadMap(MapLoadRequest req){ requests.push(req); } public void requestToLoadTransport(TransportLoadRequest req){ requests.push(req); } }; }