]> gerrit.simantics Code Review - simantics/district.git/blobdiff - 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
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 (file)
index 0000000..10abef0
--- /dev/null
@@ -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<Provider> providers = new ArrayList<Provider>();
+
+    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<TileKey> tile = new ArrayList<TileKey>(4);
+        List<Integer> quadrant = new ArrayList<Integer>(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));
+        }
+    }
+
+}