Autopsy 4.22.1
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 */
19package org.sleuthkit.autopsy.geolocation;
20
21import java.awt.image.BufferedImage;
22import java.io.ByteArrayInputStream;
23import java.io.IOException;
24import java.lang.reflect.InvocationTargetException;
25import java.net.URI;
26import java.net.URISyntaxException;
27import java.util.Comparator;
28import java.util.HashMap;
29import java.util.Map;
30import java.util.concurrent.BlockingQueue;
31import java.util.concurrent.ExecutorService;
32import java.util.concurrent.Executors;
33import java.util.concurrent.PriorityBlockingQueue;
34import java.util.concurrent.ThreadFactory;
35import java.util.logging.Level;
36
37import javax.imageio.ImageIO;
38import javax.swing.SwingUtilities;
39
40import org.jxmapviewer.viewer.Tile;
41import org.jxmapviewer.viewer.TileCache;
42import org.jxmapviewer.viewer.TileFactory;
43import org.jxmapviewer.viewer.util.GeoUtil;
44import org.sleuthkit.autopsy.coreutils.Logger;
45import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
46
54final 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-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.