1 package fr.ifremer.dali.ui.swing.util.map.layer.tile;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 import fr.ifremer.dali.ui.swing.util.map.DataMapPane;
25 import fr.ifremer.dali.ui.swing.util.map.layer.MapLayer;
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.geotools.geometry.jts.ReferencedEnvelope;
29 import org.geotools.map.DirectLayer;
30 import org.geotools.map.MapContent;
31 import org.geotools.map.MapViewport;
32 import org.geotools.renderer.lite.RendererUtilities;
33 import org.geotools.tile.Tile;
34 import org.geotools.tile.TileService;
35 import org.opengis.referencing.FactoryException;
36 import org.opengis.referencing.operation.TransformException;
37
38 import java.awt.Graphics2D;
39 import java.awt.Rectangle;
40 import java.awt.RenderingHints;
41 import java.awt.geom.AffineTransform;
42 import java.awt.image.BufferedImage;
43 import java.util.Collection;
44 import java.util.concurrent.*;
45
46
47
48
49
50
51 public class MapTileLayer extends DirectLayer implements MapLayer {
52
53 private static final Log LOG = LogFactory.getLog(MapTileLayer.class);
54 private static boolean trace = LOG.isTraceEnabled();
55
56 private TileService service;
57 private DataMapPane mapPane;
58
59 public MapTileLayer(TileService service, String title) {
60 super();
61 this.service = service;
62 setTitle(title);
63 }
64
65 public void setMapPane(DataMapPane mapPane) {
66 this.mapPane = mapPane;
67 }
68
69 @Override
70 public ReferencedEnvelope getBounds() {
71 return service.getBounds();
72 }
73
74 @Override
75 public void draw(Graphics2D graphics, MapContent map, MapViewport theViewport) {
76
77 final MapViewport viewport = new MapViewport(theViewport);
78
79 final ReferencedEnvelope viewportExtent = viewport.getBounds();
80 int scale = calculateScale(viewportExtent, viewport.getScreenArea());
81
82 Collection<Tile> tiles = service.findTilesInExtent(viewportExtent, scale, false, 256);
83
84 if (LOG.isDebugEnabled()) {
85 LOG.debug(String.format("rendering %s tiles for envelope %s to screen %s (scale = %,d)",
86 tiles.size(), viewportExtent, viewport.getScreenArea(), scale));
87 }
88
89 try {
90 renderTiles(tiles, viewport.getScreenArea(), viewportExtent, viewport.getWorldToScreen(), graphics);
91 } catch (InterruptedException ignored) {
92 }
93
94 }
95
96 private void renderTiles(Collection<Tile> tiles, Rectangle rectangle,
97 ReferencedEnvelope viewportExtent, AffineTransform worldToImageTransform, Graphics2D targetGraphics) throws InterruptedException {
98
99 BufferedImage mosaicImage = new BufferedImage(rectangle.width, rectangle.height, BufferedImage.TYPE_INT_ARGB);
100 Graphics2D g2d = mosaicImage.createGraphics();
101
102 g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
103
104 ExecutorService pool = Executors.newFixedThreadPool(8);
105
106 for (Tile tile : tiles) {
107 ReferencedEnvelope nativeTileEnvelope = tile.getExtent();
108
109 ReferencedEnvelope tileEnvViewport;
110 try {
111 tileEnvViewport = nativeTileEnvelope.transform(viewportExtent.getCoordinateReferenceSystem(), true);
112 } catch (TransformException | FactoryException e) {
113 throw new RuntimeException(e);
114 }
115 double[] points = new double[4];
116 points[0] = tileEnvViewport.getMinX();
117 points[3] = tileEnvViewport.getMinY();
118 points[2] = tileEnvViewport.getMaxX();
119 points[1] = tileEnvViewport.getMaxY();
120
121 worldToImageTransform.transform(points, 0, points, 0, 2);
122 Rectangle tileRect = new Rectangle((int) points[0], (int) points[1], (int) Math.ceil(points[2] - points[0]), (int) Math.ceil(points[3] - points[1]));
123
124
125 if (trace) {
126 LOG.trace(String.format("ask for tile '%s' , bounds:%s", tile.getId(), tileRect));
127 }
128
129 Runnable callback = () -> {
130 if (mapPane != null && mapPane.getMapBuilder().getCurrentMapMode().isProgressiveRender()) {
131
132
133 targetGraphics.drawImage(mosaicImage, 0, 0, null);
134
135 mapPane.repaint();
136
137 }
138 };
139
140 pool.execute(new ImageLoader(tile, tileRect, g2d, callback));
141
142 }
143
144 pool.shutdown();
145 pool.awaitTermination(10, TimeUnit.SECONDS);
146
147
148 targetGraphics.drawImage(mosaicImage, 0, 0, null);
149
150 }
151
152 private class ImageLoader implements Runnable {
153
154 private final FutureTask<BufferedImage> task;
155
156 ImageLoader(Tile tile, Rectangle rectangle, Graphics2D g2d, Runnable callback) {
157
158 Callable<BufferedImage> callable = () -> {
159 if (trace) LOG.trace("load tile " + tile.getId());
160 return tile.getBufferedImage();
161 };
162
163 task = new FutureTask<BufferedImage>(callable) {
164 @Override
165 protected void done() {
166
167 if (isCancelled()) {
168 if (trace) LOG.trace("cancel tile " + tile.getId());
169 return;
170 }
171 if (trace) LOG.trace("draw tile " + tile.getId());
172 try {
173
174 g2d.drawImage(get(), rectangle.x, rectangle.y, rectangle.width, rectangle.height, null);
175
176
177
178
179
180 callback.run();
181
182 } catch (InterruptedException | ExecutionException e) {
183 LOG.error("can get the image", e);
184 }
185
186 }
187 };
188 }
189
190 @Override
191 public void run() {
192 task.run();
193 }
194 }
195
196 private int calculateScale(ReferencedEnvelope extent, Rectangle screenArea) {
197
198 int scale;
199 try {
200 scale = (int) Math.round(RendererUtilities.calculateScale(extent, screenArea.width, screenArea.height, 90));
201 } catch (FactoryException | TransformException ex) {
202 throw new RuntimeException("Failed to calculate scale", ex);
203 }
204 return scale;
205 }
206 }