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.geom.Rectangle2D;
18 import java.awt.image.BufferedImage;
20 import java.util.ArrayList;
21 import java.util.List;
23 import org.simantics.maps.ProvisionException;
26 * @author Tuukka Lehtonen
28 public class CompoundTileProvider implements ITileProvider {
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;
45 List<Provider> providers = new ArrayList<Provider>();
47 public CompoundTileProvider(URI source, int tileSize) {
49 this.tileSize = tileSize;
53 public URI getSource() {
58 public Rectangle2D getExtent() {
59 if (providers.isEmpty())
60 return new Rectangle2D.Double();
62 Rectangle2D result = new Rectangle2D.Double();
63 for (Provider p : providers) {
65 result.setFrame(p.provider.getExtent());
68 Rectangle2D.union(result, p.provider.getExtent(), result);
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));
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.
85 int level = key.getLevel();
89 double xTiles = Math.pow(2, level + 1);
90 double yTiles = Math.pow(2, level);
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 +")");
99 Rectangle2D r = new Rectangle2D.Double();
106 double xdelta = w / xTiles;
107 double ydelta = h / yTiles;
109 double yy = yTiles - y - 1;
118 BufferedImage result = null;
121 for (Provider p : providers) {
122 if (level < p.minTileLevel) {
123 // System.out.println("Provider " + p.provider + " tile level too small: " + level + " < " + p.minTileLevel);
126 Rectangle2D extent = p.provider.getExtent();
127 if (!r.intersects(extent)) {
128 // System.out.println("Provider " + p.provider + ": " + r + " out of bounds: " + extent);
132 if (result == null) {
133 result = new BufferedImage(tileSize, tileSize, BufferedImage.TYPE_3BYTE_BGR);
134 g = result.createGraphics();
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();
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);
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());
152 g.drawImage(img, 0, 0, tileSize, tileSize, sx1, sy1, sx2, sy2, null);
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);
165 return result != null ? result : getOutOfBoundsImage();
168 BufferedImage outOfBoundsImage;
170 private Image getOutOfBoundsImage() {
171 if (outOfBoundsImage != null)
172 return outOfBoundsImage;
174 final int outOfBoundsImageSize = 1;
175 BufferedImage image = new BufferedImage(outOfBoundsImageSize, outOfBoundsImageSize, BufferedImage.TYPE_3BYTE_BGR);
176 Graphics2D g = image.createGraphics();
178 g.setColor(Color.CYAN);
179 g.fillRect(0, 0, outOfBoundsImageSize, outOfBoundsImageSize);
180 outOfBoundsImage = image;
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);
203 TileKey getParentTile(TileKey k) {
204 if (k.getLevel() == 0)
206 return new TileKey(k.getLevel() - 1, k.getX() / 2, k.getY() / 2);
209 Rectangle2D traverseToQuadrant(Rectangle2D r, int quadrant) {
210 boolean left = quadrant == 1 || quadrant == 2;
211 boolean top = quadrant == 1 || quadrant == 0;
213 left ? r.getMinX() : r.getCenterX(),
214 top ? r.getMinY() : r.getCenterY(),
216 r.getHeight() / 2.0);
220 int findTilePos(int level, double t, double tileSize) {
221 // Change 0 to change the world origin.
223 double p = dt / tileSize;
224 int tt = (int) Math.floor(p);
228 static class TileTraverser {
229 List<TileKey> tile = new ArrayList<TileKey>(4);
230 List<Integer> quadrant = new ArrayList<Integer>(4);
232 static int getTileQuadrant(TileKey k) {
236 return ((y & 1) == 0) ? 1 : 2;
238 return ((y & 1) == 0) ? 0 : 3;
246 void add(TileKey tile) {
248 this.quadrant.add(getTileQuadrant(tile));
255 TileKey getTile(int i) {
259 int getQuadrant(int i) {
260 return quadrant.get(i);
264 private TileKey findFirstAvailableParentTile(int maxLevel, TileKey tile, TileTraverser traverser) {
268 TileKey parent = getParentTile(tile);
269 while (parent != null) {
270 if (parent.getLevel() <= maxLevel)
272 traverser.add(parent);
273 parent = getParentTile(parent);
278 private void traverseRectangle(TileTraverser traverser, Rectangle2D rect) {
279 for (int i = traverser.size() - 1; i >= 0; --i) {
280 traverseToQuadrant(rect, traverser.getQuadrant(i));