1 package tim.prune.save;
3 import tim.prune.data.DoubleRange;
4 import tim.prune.data.Track;
5 import tim.prune.data.TrackExtents;
6 import tim.prune.gui.map.MapSource;
7 import tim.prune.gui.map.MapTileManager;
8 import tim.prune.gui.map.TileConsumer;
10 import java.awt.Color;
11 import java.awt.Graphics;
12 import java.awt.Image;
13 import java.awt.image.BufferedImage;
17 * Class to handle the sticking together (grouting) of map tiles
18 * to create a single map image for the current track
20 public class MapGrouter implements TileConsumer
22 /** The most recently produced image */
23 private GroutedImage _lastGroutedImage = null;
26 * Clear the last image, it's not needed any more
28 public void clearMapImage() {
29 _lastGroutedImage = null;
33 * Grout the required map tiles together according to the track's extent
34 * @param inTrack track object
35 * @param inMapSource map source to use (may have one or two layers)
36 * @param inZoom selected zoom level
37 * @return grouted image, or null if no image could be created
39 public GroutedImage createMapImage(Track inTrack, MapSource inMapSource, int inZoom)
41 return createMapImage(inTrack, inMapSource, inZoom, false);
45 * Grout the required map tiles together according to the track's extent
46 * @param inTrack track object
47 * @param inMapSource map source to use (may have one or two layers)
48 * @param inZoom selected zoom level
49 * @param inDownload true to download tiles, false (by default) to just pull from disk
50 * @return grouted image, or null if no image could be created
52 public GroutedImage createMapImage(Track inTrack, MapSource inMapSource, int inZoom, boolean inDownload)
54 // Get the extents of the track including a standard (10%) border around the data
55 TrackExtents extents = new TrackExtents(inTrack);
56 extents.applySquareBorder();
57 DoubleRange xRange = extents.getXRange();
58 DoubleRange yRange = extents.getYRange();
60 // Work out which tiles are required
61 final int zoomFactor = 1 << inZoom;
62 final int minTileX = (int) (xRange.getMinimum() * zoomFactor);
63 final int maxTileX = (int) (xRange.getMaximum() * zoomFactor);
64 final int minTileY = (int) (yRange.getMinimum() * zoomFactor);
65 final int maxTileY = (int) (yRange.getMaximum() * zoomFactor);
67 // Work out how big the final image will be, create a BufferedImage
68 final int pixCount = (int) (extents.getXRange().getRange() * zoomFactor * 256);
69 if (pixCount < 2 || inZoom == 0) {return null;}
70 BufferedImage resultImage = new BufferedImage(pixCount, pixCount, BufferedImage.TYPE_INT_RGB);
71 Graphics g = resultImage.getGraphics();
72 g.setColor(Color.WHITE);
73 g.fillRect(0, 0, pixCount, pixCount);
74 // Work out where to start drawing the tiles on the image
75 int xOffset = (int) ((minTileX - xRange.getMinimum() * zoomFactor) * 256);
77 // Make a map tile manager to load (or download) the tiles
78 MapTileManager tileManager = new MapTileManager(this);
79 tileManager.setMapSource(inMapSource);
80 tileManager.enableTileDownloading(inDownload);
81 tileManager.setReturnIncompleteImages();
82 tileManager.setZoom(inZoom);
85 int numTilesMissing = 0;
87 // Loop over the tiles
88 for (int x = minTileX; x <= maxTileX; x++)
90 int yOffset = (int) ((minTileY - yRange.getMinimum() * zoomFactor) * 256);
91 for (int y = minTileY; y <= maxTileY; y++)
93 for (int layer=0; layer < inMapSource.getNumLayers(); layer++)
95 Image tile = tileManager.getTile(layer, x, y, true);
96 // If we're downloading tiles, wait until the tile isn't null
98 while (tile == null && inDownload && waitCount < 3)
100 // System.out.println("wait " + waitCount + " for tile to be not null");
101 try {Thread.sleep(250);} catch (InterruptedException e) {}
102 tile = tileManager.getTile(layer, x, y, false); // don't request another download
105 // See if there's a tile or not
108 // Wait until tile is available (loaded asynchronously)
109 while (tile.getWidth(null) < 0 && !inDownload)
111 // System.out.println("Wait for tile width");
113 Thread.sleep(inDownload ? 500 : 100);
115 catch (InterruptedException ie) {}
117 // work out where to copy it to, paint it
118 // System.out.println("Painting tile " + x + "," + y + " at " + xOffset + "," + yOffset);
120 g.drawImage(tile, xOffset, yOffset, null);
124 // null tile, that means it's either not available or really slow to start downloading
132 // Get rid of the image if it's empty
133 if (numTilesUsed == 0) {
136 // Store the xy limits in the GroutedImage to make it easier to draw on top
137 GroutedImage result = new GroutedImage(resultImage, numTilesUsed, numTilesMissing);
138 result.setXRange(xRange);
139 result.setYRange(yRange);
144 * Get the grouted map image, using the previously-created one if available
145 * @param inTrack track object
146 * @param inMapSource map source to use (may have one or two layers)
147 * @param inZoom selected zoom level
148 * @return grouted image, or null if no image could be created
150 public synchronized GroutedImage getMapImage(Track inTrack, MapSource inMapSource, int inZoom)
152 if (_lastGroutedImage == null) {
153 _lastGroutedImage = createMapImage(inTrack, inMapSource, inZoom);
155 return _lastGroutedImage;
159 * @param inTrack track object
160 * @param inZoom selected zoom level
161 * @return true if the image size is acceptable
163 public static boolean isZoomLevelOk(Track inTrack, int inZoom)
165 // Get the extents of the track including a standard (10%) border around the data
166 TrackExtents extents = new TrackExtents(inTrack);
167 extents.applySquareBorder();
169 // Work out how big the final image will be
170 final int zoomFactor = 1 << inZoom;
171 final int pixCount = (int) (extents.getXRange().getRange() * zoomFactor * 256);
172 return pixCount > 2 && pixCount < 4000;
175 /** React to tiles being updated by the tile manager */
176 public void tilesUpdated(boolean inIsOk)
178 // Doesn't need any action