de.fhg.igd.mapviewer.server.ClippingTileProviderDecorator.java Source code

Java tutorial

Introduction

Here is the source code for de.fhg.igd.mapviewer.server.ClippingTileProviderDecorator.java

Source

/*
 * Copyright (c) 2016 Fraunhofer IGD
 * 
 * All rights reserved. This program and the accompanying materials are made
 * available under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution. If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     Fraunhofer IGD <http://www.igd.fraunhofer.de/>
 */
package de.fhg.igd.mapviewer.server;

import gnu.trove.TIntArrayList;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdesktop.swingx.mapviewer.AbstractTileProviderDecorator;
import org.jdesktop.swingx.mapviewer.GeoPosition;
import org.jdesktop.swingx.mapviewer.IllegalGeoPositionException;
import org.jdesktop.swingx.mapviewer.JXMapViewer;
import org.jdesktop.swingx.mapviewer.PixelConverter;
import org.jdesktop.swingx.mapviewer.TileProvider;
import org.jdesktop.swingx.mapviewer.TileProviderUtils;
import org.jdesktop.swingx.painter.AbstractPainter;
import org.jdesktop.swingx.painter.Painter;

/**
 * ClippingTileProviderDecorator
 *
 * @author <a href="mailto:simon.templer@igd.fhg.de">Simon Templer</a>
 */
public class ClippingTileProviderDecorator extends AbstractTileProviderDecorator {

    /**
     * ClippingPainter
     */
    public class ClippingPainter extends AbstractPainter<JXMapViewer> {

        private Color customOverlayColor;

        /**
         * Default constructor
         * 
         * @param customOverlayColor a custom overlay color, may be
         *            <code>null</code>
         */
        public ClippingPainter(Color customOverlayColor) {
            setAntialiasing(true);
            setCacheable(false);
            this.customOverlayColor = customOverlayColor;
        }

        /**
         * @see AbstractPainter#doPaint(Graphics2D, Object, int, int)
         */
        @Override
        protected void doPaint(Graphics2D g, JXMapViewer map, int width, int height) {
            Rectangle viewport = map.getViewportBounds();
            final int zoom = map.getZoom();

            final int mapWidth = getMapWidthInTiles(zoom) * getTileWidth(zoom);
            final int mapHeight = getMapHeightInTiles(zoom) * getTileHeight(zoom);

            Point topLeft = getTopLeft(zoom);
            Point bottomRight = getBottomRight(zoom);

            Rectangle view = new Rectangle(topLeft);
            view.add(bottomRight);

            g.translate(-viewport.x, -viewport.y);
            Color back = map.getBackground();
            Color trans = (customOverlayColor != null) ? (customOverlayColor)
                    : (new Color(back.getRed(), back.getGreen(), back.getBlue(), 90));
            g.setColor(map.getBackground());

            // draw view border
            if (viewport.intersects(view)) {
                g.draw(view);
            }

            // generate other rects
            List<Rectangle> rects = new ArrayList<Rectangle>();

            rects.add(new Rectangle(0, 0, topLeft.x, topLeft.y));
            rects.add(new Rectangle(topLeft.x, 0, bottomRight.x - topLeft.x, topLeft.y));
            rects.add(new Rectangle(bottomRight.x, 0, mapWidth - bottomRight.x, topLeft.y));

            rects.add(new Rectangle(0, topLeft.y, topLeft.x, bottomRight.y - topLeft.y));
            rects.add(new Rectangle(bottomRight.x, topLeft.y, mapWidth - bottomRight.x, bottomRight.y - topLeft.y));

            rects.add(new Rectangle(0, bottomRight.y, topLeft.x, mapHeight - bottomRight.y));
            rects.add(
                    new Rectangle(topLeft.x, bottomRight.y, bottomRight.x - topLeft.x, mapHeight - bottomRight.y));
            rects.add(new Rectangle(bottomRight.x, bottomRight.y, mapWidth - bottomRight.x,
                    mapHeight - bottomRight.y));

            g.setPaint(trans);

            for (Rectangle rect : rects) {
                if (viewport.intersects(rect))
                    g.fill(rect);
            }

            g.translate(viewport.x, viewport.y);
        }

    }

    /**
     * PixelConverter decorator that converts pixels conforming to the
     * {@link ClippingTileProviderDecorator}'s map clipping
     */
    public class PixelConverterDecorator implements PixelConverter {

        private final PixelConverter converter;

        /**
         * Constructor
         * 
         * @param converter the internal pixel converter
         */
        public PixelConverterDecorator(PixelConverter converter) {
            this.converter = converter;
        }

        /**
         * @see PixelConverter#geoToPixel(GeoPosition, int)
         */
        @Override
        public Point2D geoToPixel(final GeoPosition pos, final int zoom) throws IllegalGeoPositionException {
            Point2D result = converter.geoToPixel(pos, zoom);

            // move point (remove offset pixels)
            return new Point2D.Double(result.getX() - getXTileOffset(zoom) * getTileWidth(zoom),
                    result.getY() - getYTileOffset(zoom) * getTileHeight(zoom));
        }

        /**
         * @see PixelConverter#pixelToGeo(Point2D, int)
         */
        @Override
        public GeoPosition pixelToGeo(final Point2D pixelCoordinate, final int zoom) {
            // move point (add offset pixels)
            Point2D moved = new Point2D.Double(pixelCoordinate.getX() + getXTileOffset(zoom) * getTileWidth(zoom),
                    pixelCoordinate.getY() + getYTileOffset(zoom) * getTileHeight(zoom));

            return converter.pixelToGeo(moved, zoom);
        }

        /**
         * @see PixelConverter#getMapEpsg()
         */
        @Override
        public int getMapEpsg() {
            return converter.getMapEpsg();
        }

        /**
         * @see PixelConverter#supportsBoundingBoxes()
         */
        @Override
        public boolean supportsBoundingBoxes() {
            return converter.supportsBoundingBoxes();
        }

    }

    private static final Log log = LogFactory.getLog(ClippingTileProviderDecorator.class);

    private final TIntArrayList xTileOffset = new TIntArrayList();
    private final TIntArrayList xTileRange = new TIntArrayList();
    private final TIntArrayList yTileOffset = new TIntArrayList();
    private final TIntArrayList yTileRange = new TIntArrayList();

    private final ArrayList<Point> topLeft = new ArrayList<Point>();
    private final ArrayList<Point> bottomRight = new ArrayList<Point>();

    private final int maxZoom;

    private final Painter<JXMapViewer> painter;

    private PixelConverter lastConverter;
    private PixelConverterDecorator lastConverterDecorator;

    /**
     * Constructor
     * 
     * @param tileProvider the tile provider
     * @param topLeft the top left constraint
     * @param bottomRight the bottom right constraint
     */
    public ClippingTileProviderDecorator(final TileProvider tileProvider, final GeoPosition topLeft,
            final GeoPosition bottomRight) {
        this(tileProvider, topLeft, bottomRight, 1);
    }

    /**
     * Constructor
     * 
     * @param tileProvider the tile provider
     * @param topLeft the top left constraint
     * @param bottomRight the bottom right constraint
     * @param minRange the minimum visible range
     */
    public ClippingTileProviderDecorator(final TileProvider tileProvider, final GeoPosition topLeft,
            final GeoPosition bottomRight, int minRange) {
        this(tileProvider, topLeft, bottomRight, minRange, null);
    }

    /**
     * Constructor
     * 
     * @param tileProvider the tile provider
     * @param topLeft the top left constraint
     * @param bottomRight the bottom right constraint
     * @param minRange the minimum visible range
     * @param customOverlayColor custom overlay color to use, may be
     *            <code>null</code>
     */
    public ClippingTileProviderDecorator(final TileProvider tileProvider, final GeoPosition topLeft,
            final GeoPosition bottomRight, int minRange, Color customOverlayColor) {
        super(tileProvider);

        if (minRange <= 0)
            minRange = 1;

        int zoom = tileProvider.getMinimumZoom();
        boolean tryNextZoom = true;

        // determine valid zoom levels and their tile offsets/ranges
        while (tryNextZoom && zoom <= tileProvider.getMaximumZoom()) {
            try {
                Point2D topLeftPixel = tileProvider.getConverter().geoToPixel(topLeft, zoom);
                Point2D bottomRightPixel = tileProvider.getConverter().geoToPixel(bottomRight, zoom);

                int xMin = ((int) topLeftPixel.getX()) / tileProvider.getTileWidth(zoom);
                int yMin = ((int) topLeftPixel.getY()) / tileProvider.getTileHeight(zoom);
                int xMax = ((int) bottomRightPixel.getX()) / tileProvider.getTileWidth(zoom);
                int yMax = ((int) bottomRightPixel.getY()) / tileProvider.getTileHeight(zoom);

                // check for validity
                if (xMin <= xMax && yMin <= yMax && TileProviderUtils.isValidTile(tileProvider, xMin, yMin, zoom)
                        && TileProviderUtils.isValidTile(tileProvider, xMax, yMax, zoom)) {
                    // valid tiles, enter offset and ranges
                    xTileOffset.add(xMin);
                    xTileRange.add(xMax - xMin + 1);

                    yTileOffset.add(yMin);
                    yTileRange.add(yMax - yMin + 1);

                    this.topLeft.add(new Point((int) topLeftPixel.getX() - xMin * tileProvider.getTileWidth(zoom),
                            (int) topLeftPixel.getY() - yMin * tileProvider.getTileHeight(zoom)));
                    this.bottomRight
                            .add(new Point((int) bottomRightPixel.getX() - xMin * tileProvider.getTileWidth(zoom),
                                    (int) bottomRightPixel.getY() - yMin * tileProvider.getTileHeight(zoom)));

                    if (xMax - xMin + 1 <= minRange || yMax - yMin + 1 <= minRange)
                        tryNextZoom = false; // we reached the max zoom
                    else
                        zoom++; // prepare next zoom
                } else {
                    // invalid tiles
                    tryNextZoom = false;
                    zoom--; // previous zoom
                }
            } catch (IllegalGeoPositionException e) {
                // invalid positions or conversion failed
                tryNextZoom = false;
                zoom--; // previous zoom
            }
        }

        if (zoom < getMinimumZoom()) {
            throw new IllegalArgumentException("No zoom levels are valid for clipping"); //$NON-NLS-1$
        } else {
            maxZoom = zoom;

            painter = new ClippingPainter(customOverlayColor);

            log.info("Initialized ClippingTileProviderDecorator with minZoom = " //$NON-NLS-1$
                    + tileProvider.getMinimumZoom() + ", maxZoom = " + maxZoom); //$NON-NLS-1$
        }
    }

    private int getXTileOffset(final int zoom) {
        int index = zoom - tileProvider.getMinimumZoom();
        if (index < 0 || index >= xTileOffset.size())
            throw new IllegalArgumentException("Illegal zoom value: " + zoom); //$NON-NLS-1$
        else
            return xTileOffset.get(index);
    }

    private int getXTileRange(final int zoom) {
        int index = zoom - tileProvider.getMinimumZoom();
        if (index < 0 || index >= xTileRange.size())
            throw new IllegalArgumentException("Illegal zoom value: " + zoom); //$NON-NLS-1$
        else
            return xTileRange.get(index);
    }

    private int getYTileOffset(final int zoom) {
        int index = zoom - tileProvider.getMinimumZoom();
        if (index < 0 || index >= yTileOffset.size())
            throw new IllegalArgumentException("Illegal zoom value: " + zoom); //$NON-NLS-1$
        else
            return yTileOffset.get(index);
    }

    private int getYTileRange(final int zoom) {
        int index = zoom - tileProvider.getMinimumZoom();
        if (index < 0 || index >= yTileRange.size())
            throw new IllegalArgumentException("Illegal zoom value: " + zoom); //$NON-NLS-1$
        else
            return yTileRange.get(index);
    }

    private Point getTopLeft(final int zoom) {
        int index = zoom - tileProvider.getMinimumZoom();
        if (index < 0 || index >= topLeft.size())
            throw new IllegalArgumentException("Illegal zoom value: " + zoom); //$NON-NLS-1$
        else
            return topLeft.get(index);
    }

    private Point getBottomRight(final int zoom) {
        int index = zoom - tileProvider.getMinimumZoom();
        if (index < 0 || index >= bottomRight.size())
            throw new IllegalArgumentException("Illegal zoom value: " + zoom); //$NON-NLS-1$
        else
            return bottomRight.get(index);
    }

    /**
     * @see AbstractTileProviderDecorator#getConverter()
     */
    @Override
    public PixelConverter getConverter() {
        PixelConverter converter = super.getConverter();

        if (converter == null) {
            return converter;
        } else if (lastConverterDecorator != null && converter == lastConverter) {
            return lastConverterDecorator;
        } else {
            lastConverter = converter;
            lastConverterDecorator = new PixelConverterDecorator(converter);
            return lastConverterDecorator;
        }
    }

    /**
     * @see AbstractTileProviderDecorator#getDefaultZoom()
     */
    @Override
    public int getDefaultZoom() {
        int zoom = super.getDefaultZoom();
        if (zoom > maxZoom)
            return maxZoom;
        else
            return zoom;
    }

    /**
     * @see AbstractTileProviderDecorator#getMapHeightInTiles(int)
     */
    @Override
    public int getMapHeightInTiles(int zoom) {
        return getYTileRange(zoom);
    }

    /**
     * @see AbstractTileProviderDecorator#getMapWidthInTiles(int)
     */
    @Override
    public int getMapWidthInTiles(int zoom) {
        return getXTileRange(zoom);
    }

    /**
     * @see AbstractTileProviderDecorator#getMaximumZoom()
     */
    @Override
    public int getMaximumZoom() {
        return maxZoom;
    }

    /**
     * @see AbstractTileProviderDecorator#getTileUris(int, int, int)
     */
    @Override
    public URI[] getTileUris(int x, int y, int zoom) {
        try {
            return super.getTileUris(x + getXTileOffset(zoom), y + getYTileOffset(zoom), zoom);
        } catch (Exception e) {
            log.error("Error getting tile uris", e); //$NON-NLS-1$
            return null;
        }
    }

    /**
     * @see AbstractTileProviderDecorator#getTotalMapZoom()
     */
    @Override
    public int getTotalMapZoom() {
        return maxZoom;
    }

    /**
     * @see TileProvider#getMapOverlayPainter()
     */
    @Override
    public Painter<JXMapViewer> getMapOverlayPainter() {
        return painter;
    }

}