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