X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=org.simantics.district.maps%2Fsrc%2Forg%2Fsimantics%2Fmaps%2Ftile%2FCompoundTileProvider.java;fp=org.simantics.district.maps%2Fsrc%2Forg%2Fsimantics%2Fmaps%2Ftile%2FCompoundTileProvider.java;h=10abef0369231473bdb77b8d9e99536ccd33057d;hb=e9f74f09e0cedb603c0b4de9e542de8dd64a5ce3;hp=0000000000000000000000000000000000000000;hpb=16ee01dc5a40981c58fd5b478b89552e5814e8bb;p=simantics%2Fdistrict.git diff --git a/org.simantics.district.maps/src/org/simantics/maps/tile/CompoundTileProvider.java b/org.simantics.district.maps/src/org/simantics/maps/tile/CompoundTileProvider.java new file mode 100644 index 00000000..10abef03 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/tile/CompoundTileProvider.java @@ -0,0 +1,284 @@ +/******************************************************************************* + * Copyright (c) 2012 Association for Decentralized Information Management + * in Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.maps.tile; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import org.simantics.maps.ProvisionException; + +/** + * @author Tuukka Lehtonen + */ +public class CompoundTileProvider implements ITileProvider { + + URI source; + + int tileSize; + + static class Provider { + final ITileProvider provider; + final int minTileLevel; + final int maxTileLevel; + public Provider(ITileProvider provider, int minTileLevel, int maxTileLevel) { + this.provider = provider; + this.minTileLevel = minTileLevel; + this.maxTileLevel = maxTileLevel; + } + } + + List providers = new ArrayList(); + + public CompoundTileProvider(URI source, int tileSize) { + this.source = source; + this.tileSize = tileSize; + } + + @Override + public URI getSource() { + return source; + } + + @Override + public Rectangle2D getExtent() { + if (providers.isEmpty()) + return new Rectangle2D.Double(); + boolean first = true; + Rectangle2D result = new Rectangle2D.Double(); + for (Provider p : providers) { + if (first) { + result.setFrame(p.provider.getExtent()); + first = false; + } else { + Rectangle2D.union(result, p.provider.getExtent(), result); + } + } + return result; + } + + public void addProvider(ITileProvider provider, int minTileLevel, int maxTileLevel) { + if (minTileLevel > maxTileLevel) + throw new IllegalArgumentException("minTileLevel (" + minTileLevel + ") cannot be larger than maxTileLevel" + maxTileLevel + ")"); + providers.add(new Provider(provider, minTileLevel, maxTileLevel)); + } + + @Override + public Image get(TileKey key) throws ProvisionException { + // Prefer the tile of a provider that has larger minZoom value when the requested tile is completely covered by the provider. + // If the tile is only partially covered by the service with the larger minZoom, combine it with another provider's tile. + + int level = key.getLevel(); + int x = key.getX(); + int y = key.getY(); + + double xTiles = Math.pow(2, level + 1); + double yTiles = Math.pow(2, level); + + if (level < 0) + throw new IllegalArgumentException("invalid tile level " + level + " (tile=" + key +")"); + if (x < 0 || x >= (int) xTiles) + throw new IllegalArgumentException("tile x out of bounds " + x + " (tile=" + key +")"); + if (y < 0 || y >= (int) yTiles) + throw new IllegalArgumentException("tile y out of bounds " + y + " (tile=" + key +")"); + + Rectangle2D r = new Rectangle2D.Double(); + + double minx = -180; + double miny = -90; + double w = 360; + double h = 180; + + double xdelta = w / xTiles; + double ydelta = h / yTiles; + double xx = x; + double yy = yTiles - y - 1; + + r.setFrame( + minx + xdelta*xx, + miny + ydelta*yy, + xdelta, + ydelta + ); + + BufferedImage result = null; + Graphics2D g = null; + try { + for (Provider p : providers) { + if (level < p.minTileLevel) { +// System.out.println("Provider " + p.provider + " tile level too small: " + level + " < " + p.minTileLevel); + continue; + } + Rectangle2D extent = p.provider.getExtent(); + if (!r.intersects(extent)) { +// System.out.println("Provider " + p.provider + ": " + r + " out of bounds: " + extent); + continue; + } + + if (result == null) { + result = new BufferedImage(tileSize, tileSize, BufferedImage.TYPE_3BYTE_BGR); + g = result.createGraphics(); + } + + if (level > p.maxTileLevel) { +// System.out.println("Provider " + p.provider + " tile level too large: " + level + " > " + p.maxTileLevel); + Rectangle2D helperRect = new Rectangle2D.Double(0, 0, tileSize, tileSize); + TileTraverser tileTraverser = new TileTraverser(); + + TileKey maxTile = findFirstAvailableParentTile(p.maxTileLevel, key, tileTraverser); + Image img = p.provider.get(maxTile); + helperRect.setFrame(0, 0, img.getWidth(null), img.getHeight(null)); + traverseRectangle(tileTraverser, helperRect); + + int sx1 = (int) Math.round(helperRect.getMinX()); + int sy1 = (int) Math.round(helperRect.getMinY()); + int sx2 = (int) Math.round(helperRect.getMaxX()); + int sy2 = (int) Math.round(helperRect.getMaxY()); + + g.drawImage(img, 0, 0, tileSize, tileSize, sx1, sy1, sx2, sy2, null); + } else { + Image src = p.provider.get(key); + int srcW = src.getWidth(null); + int srcH = src.getHeight(null); + g.drawImage(src, 0, 0, tileSize, tileSize, 0, 0, srcW, srcH, null); + } + } + } finally { + if (g != null) + g.dispose(); + } + + return result != null ? result : getOutOfBoundsImage(); + } + + BufferedImage outOfBoundsImage; + + private Image getOutOfBoundsImage() { + if (outOfBoundsImage != null) + return outOfBoundsImage; + + final int outOfBoundsImageSize = 1; + BufferedImage image = new BufferedImage(outOfBoundsImageSize, outOfBoundsImageSize, BufferedImage.TYPE_3BYTE_BGR); + Graphics2D g = image.createGraphics(); + try { + g.setColor(Color.CYAN); + g.fillRect(0, 0, outOfBoundsImageSize, outOfBoundsImageSize); + outOfBoundsImage = image; + return image; + } finally { + g.dispose(); + } + } + + /** + * Quadrants: + * + * 1|0 + * --- + * 2|3 + * + * @param k + * @return + */ + TileKey getChildTile(TileKey k, int quadrant) { + int ox = quadrant == 0 || quadrant == 3 ? 1 : 0; + int oy = quadrant == 0 || quadrant == 1 ? 1 : 0; + return new TileKey(k.getLevel() + 1, k.getX() * 2 + ox, k.getY() * 2 + oy); + } + + TileKey getParentTile(TileKey k) { + if (k.getLevel() == 0) + return null; + return new TileKey(k.getLevel() - 1, k.getX() / 2, k.getY() / 2); + } + + Rectangle2D traverseToQuadrant(Rectangle2D r, int quadrant) { + boolean left = quadrant == 1 || quadrant == 2; + boolean top = quadrant == 1 || quadrant == 0; + r.setFrame( + left ? r.getMinX() : r.getCenterX(), + top ? r.getMinY() : r.getCenterY(), + r.getWidth() / 2.0, + r.getHeight() / 2.0); + return r; + } + + int findTilePos(int level, double t, double tileSize) { + // Change 0 to change the world origin. + double dt = t - 0; + double p = dt / tileSize; + int tt = (int) Math.floor(p); + return tt; + } + + static class TileTraverser { + List tile = new ArrayList(4); + List quadrant = new ArrayList(4); + + static int getTileQuadrant(TileKey k) { + int x = k.getX(); + int y = k.getY(); + if ((x & 1) == 0) { + return ((y & 1) == 0) ? 1 : 2; + } + return ((y & 1) == 0) ? 0 : 3; + } + + void clear() { + tile.clear(); + quadrant.clear(); + } + + void add(TileKey tile) { + this.tile.add(tile); + this.quadrant.add(getTileQuadrant(tile)); + } + + int size() { + return tile.size(); + } + + TileKey getTile(int i) { + return tile.get(i); + } + + int getQuadrant(int i) { + return quadrant.get(i); + } + } + + private TileKey findFirstAvailableParentTile(int maxLevel, TileKey tile, TileTraverser traverser) { + traverser.clear(); + traverser.add(tile); + + TileKey parent = getParentTile(tile); + while (parent != null) { + if (parent.getLevel() <= maxLevel) + return parent; + traverser.add(parent); + parent = getParentTile(parent); + } + return null; + } + + private void traverseRectangle(TileTraverser traverser, Rectangle2D rect) { + for (int i = traverser.size() - 1; i >= 0; --i) { + traverseToQuadrant(rect, traverser.getQuadrant(i)); + } + } + +}