19 package org.sleuthkit.autopsy.geolocation;
 
   21 import java.awt.image.BufferedImage;
 
   22 import java.io.ByteArrayInputStream;
 
   23 import java.io.IOException;
 
   24 import java.lang.reflect.InvocationTargetException;
 
   26 import java.net.URISyntaxException;
 
   27 import java.util.Comparator;
 
   28 import java.util.HashMap;
 
   30 import java.util.concurrent.BlockingQueue;
 
   31 import java.util.concurrent.ExecutorService;
 
   32 import java.util.concurrent.Executors;
 
   33 import java.util.concurrent.PriorityBlockingQueue;
 
   34 import java.util.concurrent.ThreadFactory;
 
   35 import java.util.logging.Level;
 
   37 import javax.imageio.ImageIO;
 
   38 import javax.swing.SwingUtilities;
 
   40 import org.jxmapviewer.viewer.Tile;
 
   41 import org.jxmapviewer.viewer.TileCache;
 
   42 import org.jxmapviewer.viewer.TileFactory;
 
   43 import org.jxmapviewer.viewer.util.GeoUtil;
 
   54 final class MBTilesTileFactory 
extends TileFactory {
 
   56     private static final Logger logger = Logger.getLogger(MBTilesTileFactory.class.getName());
 
   58     private volatile int pendingTiles = 0;
 
   59     private static final int THREAD_POOL_SIZE = 4;
 
   60     private ExecutorService service;
 
   62     private final Map<String, Tile> tileMap;
 
   64     private final TileCache cache;
 
   66     private MBTilesFileConnector connector;
 
   75     MBTilesTileFactory(String filePath) 
throws GeoLocationDataException {
 
   76         this(
new MBTilesFileConnector(filePath));
 
   84     private MBTilesTileFactory(MBTilesFileConnector connector) {
 
   85         super(connector.getInfo());
 
   86         this.connector = connector;
 
   87         cache = 
new TileCache();
 
   88         tileMap = 
new HashMap<>();
 
  102     public Tile getTile(
int x, 
int y, 
int zoom) {
 
  103         return getTile(x, y, zoom, 
true);
 
  116     private Tile getTile(
int tpx, 
int tpy, 
int zoom, 
boolean eagerLoad) {
 
  120         int numTilesWide = (int) getMapSize(zoom).getWidth();
 
  122             tileX = numTilesWide - (Math.abs(tileX) % numTilesWide);
 
  125         tileX %= numTilesWide;
 
  128         String url = getInfo().getTileUrl(tileX, tileY, zoom);
 
  130         Tile.Priority pri = Tile.Priority.High;
 
  132             pri = Tile.Priority.Low;
 
  135         if (!tileMap.containsKey(url)) {
 
  137             if (!GeoUtil.isValidTile(tileX, tileY, zoom, getInfo())) {
 
  138                 tile = 
new MBTilesTile(tileX, tileY, zoom);
 
  140                 tile = 
new MBTilesTile(tileX, tileY, zoom, url, pri);
 
  143             tileMap.put(url, tile);
 
  145             tile = tileMap.get(url);
 
  148             if (tile.getPriority() == Tile.Priority.Low && eagerLoad && !tile.isLoaded()) {
 
  161     TileCache getTileCache() {
 
  171     private final BlockingQueue<Tile> tileQueue = 
new PriorityBlockingQueue<>(5, 
new Comparator<Tile>() {
 
  173         public int compare(Tile o1, Tile o2) {
 
  174             if (o1.getPriority() == Tile.Priority.Low && o2.getPriority() == Tile.Priority.High) {
 
  177             if (o1.getPriority() == Tile.Priority.High && o2.getPriority() == Tile.Priority.Low) {
 
  192     synchronized ExecutorService getService() {
 
  193         if (service == null) {
 
  195             service = Executors.newFixedThreadPool(THREAD_POOL_SIZE, 
new ThreadFactory() {
 
  196                 private int count = 0;
 
  199                 public Thread newThread(Runnable r) {
 
  200                     Thread t = 
new Thread(r, 
"tile-pool-" + count++);
 
  201                     t.setPriority(Thread.MIN_PRIORITY);
 
  211     public void dispose() {
 
  212         if (service != null) {
 
  219     protected synchronized void startLoading(Tile tile) {
 
  220         if (tile.isLoading()) {
 
  224         tile.setLoading(
true);
 
  227             getService().submit(
new TileRunner());
 
  228         } 
catch (InterruptedException ex) {
 
  238     synchronized void promote(Tile tile) {
 
  239         if (tileQueue.contains(tile)) {
 
  241                 tileQueue.remove(tile);
 
  242                 tile.setPriority(Tile.Priority.High);
 
  244             } 
catch (InterruptedException ex) {
 
  253     int getPendingTiles() {
 
  272         protected URI 
getURI(Tile tile) 
throws URISyntaxException {
 
  273             if (tile.getURL() == null) {
 
  276             return new URI(tile.getURL());
 
  287             final Tile tile = tileQueue.remove();
 
  289             int remainingAttempts = 3;
 
  290             while (!tile.isLoaded() && remainingAttempts > 0) {
 
  294                     BufferedImage img = cache.get(uri);
 
  299                         addImageToTile(tile, img);
 
  301                 } 
catch (OutOfMemoryError memErr) {
 
  302                     cache.needMoreMemory();
 
  303                 } 
catch (IOException | 
GeoLocationDataException | InterruptedException | InvocationTargetException | URISyntaxException ex) {
 
  304                     if (remainingAttempts == 0) {
 
  305                         logger.log(Level.SEVERE, String.format(
"Failed to load a tile at URL: %s, stopping", tile.getURL()), ex);
 
  307                         logger.log(Level.WARNING, 
"Failed to load a tile at URL: " + tile.getURL() + 
", retrying", ex);
 
  311             tile.setLoading(
false);
 
  323         BufferedImage img = null;
 
  324         byte[] bimg = connector.getTileBytes(uri.toString());
 
  325         if (bimg != null && bimg.length > 0) {
 
  326             img = ImageIO.read(
new ByteArrayInputStream(bimg));
 
  327             cache.put(uri, bimg, img);
 
  339     private void addImageToTile(Tile tile, BufferedImage image) 
throws InterruptedException, InvocationTargetException {
 
  340         SwingUtilities.invokeAndWait(
new Runnable() {
 
  343                 if (tile instanceof MBTilesTile) {
 
  344                     ((MBTilesTile) tile).setImage(image);
 
  347                 fireTileLoadedEvent(tile);