1 /*******************************************************************************
2 * Copyright (c) 2012 Association for Decentralized Information Management
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.maps.tile;
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;
21 import java.util.ArrayList;
22 import java.util.List;
24 import org.simantics.maps.ProvisionException;
25 import org.simantics.maps.internal.ImageUtil;
28 * @author Tuukka Lehtonen
30 public class CompoundTileProvider implements ITileProvider {
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;
47 List<Provider> providers = new ArrayList<Provider>();
49 public CompoundTileProvider(URI source, int tileSize) {
51 this.tileSize = tileSize;
55 public URI getSource() {
60 public Rectangle2D getExtent() {
61 if (providers.isEmpty())
62 return new Rectangle2D.Double();
64 Rectangle2D result = new Rectangle2D.Double();
65 for (Provider p : providers) {
67 result.setFrame(p.provider.getExtent());
70 Rectangle2D.union(result, p.provider.getExtent(), result);
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));
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.
87 int level = key.getLevel();
91 double xTiles = Math.pow(2, level + 1);
92 double yTiles = Math.pow(2, level);
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 +")");
101 Rectangle2D r = new Rectangle2D.Double();
108 double xdelta = w / xTiles;
109 double ydelta = h / yTiles;
111 double yy = yTiles - y - 1;
120 BufferedImage result = null;
123 for (Provider p : providers) {
124 if (level < p.minTileLevel) {
125 // System.out.println("Provider " + p.provider + " tile level too small: " + level + " < " + p.minTileLevel);
128 Rectangle2D extent = p.provider.getExtent();
129 if (!r.intersects(extent)) {
130 // System.out.println("Provider " + p.provider + ": " + r + " out of bounds: " + extent);
134 if (result == null) {
135 result = ImageUtil.createScreenCompatibleImage(tileSize, tileSize, Transparency.OPAQUE);
136 g = result.createGraphics();
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();
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);
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());
154 g.drawImage(img, 0, 0, tileSize, tileSize, sx1, sy1, sx2, sy2, null);
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);
167 return result != null ? result : getOutOfBoundsImage();
170 BufferedImage outOfBoundsImage;
172 private Image getOutOfBoundsImage() {
173 if (outOfBoundsImage != null)
174 return outOfBoundsImage;
176 final int outOfBoundsImageSize = 1;
177 BufferedImage image = new BufferedImage(outOfBoundsImageSize, outOfBoundsImageSize, BufferedImage.TYPE_3BYTE_BGR);
178 Graphics2D g = image.createGraphics();
180 g.setColor(Color.CYAN);
181 g.fillRect(0, 0, outOfBoundsImageSize, outOfBoundsImageSize);
182 outOfBoundsImage = image;
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);
205 TileKey getParentTile(TileKey k) {
206 if (k.getLevel() == 0)
208 return new TileKey(k.getLevel() - 1, k.getX() / 2, k.getY() / 2);
211 Rectangle2D traverseToQuadrant(Rectangle2D r, int quadrant) {
212 boolean left = quadrant == 1 || quadrant == 2;
213 boolean top = quadrant == 1 || quadrant == 0;
215 left ? r.getMinX() : r.getCenterX(),
216 top ? r.getMinY() : r.getCenterY(),
218 r.getHeight() / 2.0);
222 int findTilePos(int level, double t, double tileSize) {
223 // Change 0 to change the world origin.
225 double p = dt / tileSize;
226 int tt = (int) Math.floor(p);
230 static class TileTraverser {
231 List<TileKey> tile = new ArrayList<TileKey>(4);
232 List<Integer> quadrant = new ArrayList<Integer>(4);
234 static int getTileQuadrant(TileKey k) {
238 return ((y & 1) == 0) ? 1 : 2;
240 return ((y & 1) == 0) ? 0 : 3;
248 void add(TileKey tile) {
250 this.quadrant.add(getTileQuadrant(tile));
257 TileKey getTile(int i) {
261 int getQuadrant(int i) {
262 return quadrant.get(i);
266 private TileKey findFirstAvailableParentTile(int maxLevel, TileKey tile, TileTraverser traverser) {
270 TileKey parent = getParentTile(tile);
271 while (parent != null) {
272 if (parent.getLevel() <= maxLevel)
274 traverser.add(parent);
275 parent = getParentTile(parent);
280 private void traverseRectangle(TileTraverser traverser, Rectangle2D rect) {
281 for (int i = traverser.size() - 1; i >= 0; --i) {
282 traverseToQuadrant(rect, traverser.getQuadrant(i));