]> gerrit.simantics Code Review - simantics/district.git/blob - org.simantics.district.maps/src/org/simantics/maps/tile/CompoundTileProvider.java
Ensure ITileProviders return BufferedImages with compatible ColorModel
[simantics/district.git] / org.simantics.district.maps / src / org / simantics / maps / tile / CompoundTileProvider.java
1 /*******************************************************************************
2  * Copyright (c) 2012 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.maps.tile;
13
14 import java.awt.Color;
15 import java.awt.Graphics2D;
16 import java.awt.Image;
17 import java.awt.Transparency;
18 import java.awt.geom.Rectangle2D;
19 import java.awt.image.BufferedImage;
20 import java.net.URI;
21 import java.util.ArrayList;
22 import java.util.List;
23
24 import org.simantics.maps.ProvisionException;
25 import org.simantics.maps.internal.ImageUtil;
26
27 /**
28  * @author Tuukka Lehtonen
29  */
30 public class CompoundTileProvider implements ITileProvider {
31
32     URI source;
33
34     int tileSize;
35
36     static class Provider {
37         final ITileProvider provider;
38         final int minTileLevel;
39         final int maxTileLevel;
40         public Provider(ITileProvider provider, int minTileLevel, int maxTileLevel) {
41             this.provider = provider;
42             this.minTileLevel = minTileLevel;
43             this.maxTileLevel = maxTileLevel;
44         }
45     }
46
47     List<Provider> providers = new ArrayList<Provider>();
48
49     public CompoundTileProvider(URI source, int tileSize) {
50         this.source = source;
51         this.tileSize = tileSize;
52     }
53
54     @Override
55     public URI getSource() {
56         return source;
57     }
58
59     @Override
60     public Rectangle2D getExtent() {
61         if (providers.isEmpty())
62             return new Rectangle2D.Double();
63         boolean first = true;
64         Rectangle2D result = new Rectangle2D.Double();
65         for (Provider p : providers) {
66             if (first) {
67                 result.setFrame(p.provider.getExtent());
68                 first = false;
69             } else {
70                 Rectangle2D.union(result, p.provider.getExtent(), result);
71             }
72         }
73         return result;
74     }
75
76     public void addProvider(ITileProvider provider, int minTileLevel, int maxTileLevel) {
77         if (minTileLevel > maxTileLevel)
78             throw new IllegalArgumentException("minTileLevel (" + minTileLevel + ") cannot be larger than maxTileLevel" + maxTileLevel + ")");
79         providers.add(new Provider(provider, minTileLevel, maxTileLevel));
80     }
81
82     @Override
83     public Image get(TileKey key) throws ProvisionException {
84         // Prefer the tile of a provider that has larger minZoom value when the requested tile is completely covered by the provider.
85         // If the tile is only partially covered by the service with the larger minZoom, combine it with another provider's tile.
86         
87         int level = key.getLevel();
88         int x = key.getX();
89         int y = key.getY();
90
91         double xTiles = Math.pow(2, level + 1);
92         double yTiles = Math.pow(2, level);
93
94         if (level < 0)
95             throw new IllegalArgumentException("invalid tile level " + level + " (tile=" + key +")");
96         if (x < 0 || x >= (int) xTiles)
97             throw new IllegalArgumentException("tile x out of bounds " + x + " (tile=" + key +")");
98         if (y < 0 || y >= (int) yTiles)
99             throw new IllegalArgumentException("tile y out of bounds " + y + " (tile=" + key +")");
100
101         Rectangle2D r = new Rectangle2D.Double();
102
103         double minx = -180;
104         double miny = -90;
105         double w = 360;
106         double h = 180;
107
108         double xdelta = w / xTiles;
109         double ydelta = h / yTiles;
110         double xx = x;
111         double yy = yTiles - y - 1;
112
113         r.setFrame(
114                 minx + xdelta*xx,
115                 miny + ydelta*yy,
116                 xdelta,
117                 ydelta
118         );
119
120         BufferedImage result = null;
121         Graphics2D g = null;
122         try {
123             for (Provider p : providers) {
124                 if (level < p.minTileLevel) {
125 //                    System.out.println("Provider " + p.provider + " tile level too small: " + level + " < " + p.minTileLevel);
126                     continue;
127                 }
128                 Rectangle2D extent = p.provider.getExtent();
129                 if (!r.intersects(extent)) {
130 //                    System.out.println("Provider " + p.provider + ": " + r + " out of bounds: " + extent);
131                     continue;
132                 }
133
134                 if (result == null) {
135                     result = ImageUtil.createScreenCompatibleImage(tileSize, tileSize, Transparency.OPAQUE);
136                     g = result.createGraphics();
137                 }
138
139                 if (level > p.maxTileLevel) {
140 //                  System.out.println("Provider " + p.provider + " tile level too large: " + level + " > " + p.maxTileLevel);
141                     Rectangle2D helperRect = new Rectangle2D.Double(0, 0, tileSize, tileSize);
142                     TileTraverser tileTraverser = new TileTraverser();
143
144                     TileKey maxTile = findFirstAvailableParentTile(p.maxTileLevel, key, tileTraverser);
145                     Image img = p.provider.get(maxTile);
146                     helperRect.setFrame(0, 0, img.getWidth(null), img.getHeight(null));
147                     traverseRectangle(tileTraverser, helperRect);
148
149                     int sx1 = (int) Math.round(helperRect.getMinX());
150                     int sy1 = (int) Math.round(helperRect.getMinY());
151                     int sx2 = (int) Math.round(helperRect.getMaxX());
152                     int sy2 = (int) Math.round(helperRect.getMaxY());
153
154                     g.drawImage(img, 0, 0, tileSize, tileSize, sx1, sy1, sx2, sy2, null);
155                 } else {
156                     Image src = p.provider.get(key);
157                     int srcW = src.getWidth(null);
158                     int srcH = src.getHeight(null);
159                     g.drawImage(src, 0, 0, tileSize, tileSize, 0, 0, srcW, srcH, null);
160                 }
161             }
162         } finally {
163             if (g != null)
164                 g.dispose();
165         }
166
167         return result != null ? result : getOutOfBoundsImage();
168     }
169
170     BufferedImage outOfBoundsImage;
171
172     private Image getOutOfBoundsImage() {
173         if (outOfBoundsImage != null)
174             return outOfBoundsImage;
175
176         final int outOfBoundsImageSize = 1;
177         BufferedImage image = new BufferedImage(outOfBoundsImageSize, outOfBoundsImageSize, BufferedImage.TYPE_3BYTE_BGR);
178         Graphics2D g = image.createGraphics();
179         try {
180             g.setColor(Color.CYAN);
181             g.fillRect(0, 0, outOfBoundsImageSize, outOfBoundsImageSize);
182             outOfBoundsImage = image;
183             return image;
184         } finally {
185             g.dispose();
186         }
187     }
188
189     /**
190      * Quadrants:
191      * 
192      * 1|0
193      * ---
194      * 2|3
195      * 
196      * @param k
197      * @return
198      */
199     TileKey getChildTile(TileKey k, int quadrant) {
200         int ox = quadrant == 0 || quadrant == 3 ? 1 : 0;
201         int oy = quadrant == 0 || quadrant == 1 ? 1 : 0;
202         return new TileKey(k.getLevel() + 1, k.getX() * 2 + ox, k.getY() * 2 + oy);
203     }
204
205     TileKey getParentTile(TileKey k) {
206         if (k.getLevel() == 0)
207             return null;
208         return new TileKey(k.getLevel() - 1, k.getX() / 2, k.getY() / 2);
209     }
210
211     Rectangle2D traverseToQuadrant(Rectangle2D r, int quadrant) {
212         boolean left = quadrant == 1 || quadrant == 2;
213         boolean top = quadrant == 1 || quadrant == 0;
214         r.setFrame(
215                 left ? r.getMinX() : r.getCenterX(),
216                 top ? r.getMinY() : r.getCenterY(),
217                 r.getWidth() / 2.0,
218                 r.getHeight() / 2.0);
219         return r;
220     }
221
222     int findTilePos(int level, double t, double tileSize) {
223         // Change 0 to change the world origin.
224         double dt = t - 0;
225         double p = dt / tileSize;
226         int tt = (int) Math.floor(p);
227         return tt;
228     }
229
230     static class TileTraverser {
231         List<TileKey> tile = new ArrayList<TileKey>(4);
232         List<Integer> quadrant = new ArrayList<Integer>(4);
233
234         static int getTileQuadrant(TileKey k) {
235             int x = k.getX();
236             int y = k.getY();
237             if ((x & 1) == 0) {
238                 return ((y & 1) == 0) ? 1 : 2;
239             }
240             return ((y & 1) == 0) ? 0 : 3;
241         }
242
243         void clear() {
244             tile.clear();
245             quadrant.clear();
246         }
247
248         void add(TileKey tile) {
249             this.tile.add(tile);
250             this.quadrant.add(getTileQuadrant(tile));
251         }
252
253         int size() {
254             return tile.size();
255         }
256
257         TileKey getTile(int i) {
258             return tile.get(i);
259         }
260
261         int getQuadrant(int i) {
262             return quadrant.get(i);
263         }
264     }
265
266     private TileKey findFirstAvailableParentTile(int maxLevel, TileKey tile, TileTraverser traverser) {
267         traverser.clear();
268         traverser.add(tile);
269
270         TileKey parent = getParentTile(tile);
271         while (parent != null) {
272             if (parent.getLevel() <= maxLevel)
273                 return parent;
274             traverser.add(parent);
275             parent = getParentTile(parent);
276         }
277         return null;
278     }
279
280     private void traverseRectangle(TileTraverser traverser, Rectangle2D rect) {
281         for (int i = traverser.size() - 1; i >= 0; --i) {
282             traverseToQuadrant(rect, traverser.getQuadrant(i));
283         }
284     }
285
286 }