Autopsy  4.20.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
MBTilesTileFactory.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2019 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License")
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.geolocation;
20 
21 import java.awt.image.BufferedImage;
22 import java.io.ByteArrayInputStream;
23 import java.io.IOException;
24 import java.lang.reflect.InvocationTargetException;
25 import java.net.URI;
26 import java.net.URISyntaxException;
27 import java.util.Comparator;
28 import java.util.HashMap;
29 import java.util.Map;
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;
36 
37 import javax.imageio.ImageIO;
38 import javax.swing.SwingUtilities;
39 
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;
46 
54 final class MBTilesTileFactory extends TileFactory {
55 
56  private static final Logger logger = Logger.getLogger(MBTilesTileFactory.class.getName());
57 
58  private volatile int pendingTiles = 0;
59  private static final int THREAD_POOL_SIZE = 4;
60  private ExecutorService service;
61 
62  private final Map<String, Tile> tileMap;
63 
64  private final TileCache cache;
65 
66  private MBTilesFileConnector connector;
67 
75  MBTilesTileFactory(String filePath) throws GeoLocationDataException {
76  this(new MBTilesFileConnector(filePath));
77  }
78 
84  private MBTilesTileFactory(MBTilesFileConnector connector) {
85  super(connector.getInfo());
86  this.connector = connector;
87  cache = new TileCache();
88  tileMap = new HashMap<>();
89  }
90 
101  @Override
102  public Tile getTile(int x, int y, int zoom) {
103  return getTile(x, y, zoom, true);
104  }
105 
116  private Tile getTile(int tpx, int tpy, int zoom, boolean eagerLoad) {
117  // wrap the tiles horizontally --> mod the X with the max width
118  // and use that
119  int tileX = tpx;// tilePoint.getX();
120  int numTilesWide = (int) getMapSize(zoom).getWidth();
121  if (tileX < 0) {
122  tileX = numTilesWide - (Math.abs(tileX) % numTilesWide);
123  }
124 
125  tileX %= numTilesWide;
126  int tileY = tpy;
127 
128  String url = getInfo().getTileUrl(tileX, tileY, zoom);
129 
130  Tile.Priority pri = Tile.Priority.High;
131  if (!eagerLoad) {
132  pri = Tile.Priority.Low;
133  }
134  Tile tile;
135  if (!tileMap.containsKey(url)) {
136  // If its not a valid tile location return an empty tile.
137  if (!GeoUtil.isValidTile(tileX, tileY, zoom, getInfo())) {
138  tile = new MBTilesTile(tileX, tileY, zoom);
139  } else {
140  tile = new MBTilesTile(tileX, tileY, zoom, url, pri);
141  startLoading(tile);
142  }
143  tileMap.put(url, tile);
144  } else {
145  tile = tileMap.get(url);
146  // If the tile is in the tileMap, but the image is not loaded yet,
147  // bump the priority.
148  if (tile.getPriority() == Tile.Priority.Low && eagerLoad && !tile.isLoaded()) {
149  promote(tile);
150  }
151  }
152 
153  return tile;
154  }
155 
161  TileCache getTileCache() {
162  return cache;
163  }
164 
171  private final BlockingQueue<Tile> tileQueue = new PriorityBlockingQueue<>(5, new Comparator<Tile>() {
172  @Override
173  public int compare(Tile o1, Tile o2) {
174  if (o1.getPriority() == Tile.Priority.Low && o2.getPriority() == Tile.Priority.High) {
175  return 1;
176  }
177  if (o1.getPriority() == Tile.Priority.High && o2.getPriority() == Tile.Priority.Low) {
178  return -1;
179  }
180  return 0;
181 
182  }
183  });
184 
192  synchronized ExecutorService getService() {
193  if (service == null) {
194  // System.out.println("creating an executor service with a threadpool of size " + threadPoolSize);
195  service = Executors.newFixedThreadPool(THREAD_POOL_SIZE, new ThreadFactory() {
196  private int count = 0;
197 
198  @Override
199  public Thread newThread(Runnable r) {
200  Thread t = new Thread(r, "tile-pool-" + count++);
201  t.setPriority(Thread.MIN_PRIORITY);
202  t.setDaemon(true);
203  return t;
204  }
205  });
206  }
207  return service;
208  }
209 
210  @Override
211  public void dispose() {
212  if (service != null) {
213  service.shutdown();
214  service = null;
215  }
216  }
217 
218  @Override
219  protected synchronized void startLoading(Tile tile) {
220  if (tile.isLoading()) {
221  return;
222  }
223  pendingTiles++;
224  tile.setLoading(true);
225  try {
226  tileQueue.put(tile);
227  getService().submit(new TileRunner());
228  } catch (InterruptedException ex) {
229 
230  }
231  }
232 
238  synchronized void promote(Tile tile) {
239  if (tileQueue.contains(tile)) {
240  try {
241  tileQueue.remove(tile);
242  tile.setPriority(Tile.Priority.High);
243  tileQueue.put(tile);
244  } catch (InterruptedException ex) {
245 
246  }
247  }
248  }
249 
253  int getPendingTiles() {
254  return pendingTiles;
255  }
256 
262  private class TileRunner implements Runnable {
263 
272  protected URI getURI(Tile tile) throws URISyntaxException {
273  if (tile.getURL() == null) {
274  return null;
275  }
276  return new URI(tile.getURL());
277  }
278 
279  @Override
280  public void run() {
281  /*
282  * Attempt to load the tile from . If loading fails, retry two more
283  * times. If all attempts fail, nothing else is done. This way, if
284  * there is some kind of failure, the pooled thread can try to load
285  * other tiles.
286  */
287  final Tile tile = tileQueue.remove();
288 
289  int remainingAttempts = 3;
290  while (!tile.isLoaded() && remainingAttempts > 0) {
291  remainingAttempts--;
292  try {
293  URI uri = getURI(tile);
294  BufferedImage img = cache.get(uri);
295  if (img == null) {
296  img = getImage(uri);
297  }
298  if (img != null) {
299  addImageToTile(tile, img);
300  }
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);
306  } else {
307  logger.log(Level.WARNING, "Failed to load a tile at URL: " + tile.getURL() + ", retrying", ex);
308  }
309  }
310  }
311  tile.setLoading(false);
312  }
313  }
314 
322  private BufferedImage getImage(URI uri ) throws IOException, GeoLocationDataException{
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);
328  }
329  return img;
330  }
331 
339  private void addImageToTile(Tile tile, BufferedImage image) throws InterruptedException, InvocationTargetException {
340  SwingUtilities.invokeAndWait(new Runnable() {
341  @Override
342  public void run() {
343  if (tile instanceof MBTilesTile) {
344  ((MBTilesTile) tile).setImage(image);
345  }
346  pendingTiles--;
347  fireTileLoadedEvent(tile);
348  }
349  });
350  }
351 }

Copyright © 2012-2022 Basis Technology. Generated on: Tue Aug 1 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.