de.fhg.igd.mapviewer.waypoints.CustomWaypointPainter.java Source code

Java tutorial

Introduction

Here is the source code for de.fhg.igd.mapviewer.waypoints.CustomWaypointPainter.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.waypoints;

import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdesktop.swingx.mapviewer.GeoPosition;
import org.jdesktop.swingx.mapviewer.GeotoolsConverter;
import org.jdesktop.swingx.mapviewer.IllegalGeoPositionException;
import org.jdesktop.swingx.mapviewer.PixelConverter;
import org.jdesktop.swingx.mapviewer.TileOverlayPainter;
import org.jdesktop.swingx.mapviewer.TileProviderUtils;

import de.fhg.igd.geom.BoundingBox;
import de.fhg.igd.geom.Verifier;
import de.fhg.igd.geom.indices.RTree;
import de.fhg.igd.mapviewer.AbstractTileOverlayPainter;
import de.fhg.igd.mapviewer.MapKitTileOverlayPainter;
import de.fhg.igd.mapviewer.Refresher;
import de.fhg.igd.mapviewer.marker.area.Area;

/**
 * CustomWaypointPainter
 * 
 * Based on the implementation of the WaypointPainter class
 *
 * @param <W> the way-point type
 * @author <a href="mailto:simon.templer@igd.fhg.de">Simon Templer</a>
 * @version $Id$
 */
public abstract class CustomWaypointPainter<W extends SelectableWaypoint<W>> extends MapKitTileOverlayPainter {

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

    private WaypointRenderer<W> renderer;

    private static final int PAGE_SIZE = 32;

    private final RTree<W> waypoints = new RTree<W>(PAGE_SIZE);

    private final Verifier<? super W, BoundingBox> matchTileVerifier = new Verifier<SelectableWaypoint<W>, BoundingBox>() {

        @Override
        public boolean verify(SelectableWaypoint<W> first, BoundingBox second) {
            return second.intersectsOrCovers(first.getBoundingBox());
        }
    };

    /**
     * Way-points with a big area (bounding box) are painted first, selected
     * way-points are painted last
     */
    private final Comparator<? super W> paintFirstComparator = new Comparator<W>() {

        @Override
        public int compare(W o1, W o2) {
            // selected come last
            if (o1.isSelected() && !o2.isSelected()) {
                return 1;
            } else if (o2.isSelected() && !o1.isSelected()) {
                return -1;
            } else {
                double a1 = (o1.isPoint()) ? (0)
                        : (o1.getBoundingBox().getWidth() * o1.getBoundingBox().getHeight());
                double a2 = (o2.isPoint()) ? (0)
                        : (o2.getBoundingBox().getWidth() * o2.getBoundingBox().getHeight());

                int areaHint = 0;
                // compare size
                if (a1 > a2) {
                    areaHint = -1;
                } else if (a2 > a1) {
                    areaHint = 1;
                }

                return areaHint;
            }
        }

    };

    /**
     * Creates a custom way-point painter that uses markers for painting
     */
    public CustomWaypointPainter() {
        this(new MarkerWaypointRenderer<W>());
    }

    /**
     * Creates a new instance of CustomWaypointPainter with one worker thread
     * for painting tiles.
     * 
     * @param renderer the way-point renderer
     */
    public CustomWaypointPainter(WaypointRenderer<W> renderer) {
        this(renderer, 1);
    }

    /**
     * Creates a new instance of CustomWaypointPainter.
     * 
     * @param renderer the way-point renderer
     * @param numberOfThreads the number of worker threads to use for painting
     *            tiles
     */
    public CustomWaypointPainter(WaypointRenderer<W> renderer, int numberOfThreads) {
        super(numberOfThreads);

        setRenderer(renderer);
    }

    /**
     * Sets the way-point renderer to use when painting way-points
     * 
     * @param renderer the new CustomWaypointRenderer to use
     */
    public void setRenderer(WaypointRenderer<W> renderer) {
        this.renderer = renderer;
    }

    /**
     * Add a way-point
     * 
     * @param wp the way-point
     * @param refresh the refresher
     */
    public void addWaypoint(W wp, Refresher refresh) {
        BoundingBox bb = wp.getBoundingBox();

        if (bb != null) {
            synchronized (waypoints) {
                waypoints.insert(wp);
            }

            if (refresh != null) {
                wp.addToRefresher(refresh);
            }
        }
    }

    /**
     * Remove a way-point
     * 
     * @param wp the way-point
     * @param refresh the refresher
     */
    public void removeWaypoint(W wp, Refresher refresh) {
        synchronized (waypoints) {
            waypoints.delete(wp);
        }

        if (refresh != null) {
            wp.addToRefresher(refresh);
        }
    }

    /**
     * @see AbstractTileOverlayPainter#repaintTile(int, int, int, int,
     *      PixelConverter, int)
     */
    @Override
    public BufferedImage repaintTile(int posX, int posY, int width, int height, PixelConverter converter,
            int zoom) {
        if (renderer == null) {
            return null;
        }

        int overlap = getMaxOverlap();

        // overlap pixel coordinates
        Point topLeftPixel = new Point(Math.max(posX - overlap, 0), Math.max(posY - overlap, 0));
        Point bottomRightPixel = new Point(posX + width + overlap, posY + height + overlap); // TODO
        // check
        // against
        // map
        // size

        // overlap geo positions
        GeoPosition topLeft = converter.pixelToGeo(topLeftPixel, zoom);
        GeoPosition bottomRight = converter.pixelToGeo(bottomRightPixel, zoom);

        // overlap geo positions in RTree CRS
        try {
            BoundingBox tileBounds = createSearchBB(topLeft, bottomRight);

            synchronized (waypoints) {
                Set<W> candidates = waypoints.query(tileBounds, matchTileVerifier);

                if (candidates != null) {
                    // sort way-points
                    List<W> sorted = new ArrayList<W>(candidates);
                    Collections.sort(sorted, paintFirstComparator);

                    BufferedImage image = createImage(width, height);
                    Graphics2D gfx = image.createGraphics();
                    configureGraphics(gfx);

                    try {
                        // for each way-point within these bounds
                        for (W w : sorted) {
                            processWaypoint(w, posX, posY, width, height, converter, zoom, gfx);
                        }

                        /*
                         * DEBUG String test = getClass().getSimpleName() +
                         * " - x=" + posX + ", y=" + posY + ": " +
                         * candidates.size() + " WPs"; gfx.setColor(Color.BLUE);
                         * gfx.drawString(test, 4, height - 4);
                         * 
                         * gfx.drawString("minX: " + tileBounds.getMinX(), 4,
                         * height - 84); gfx.drawString("maxX: " +
                         * tileBounds.getMaxX(), 4, height - 64);
                         * gfx.drawString("minY: " + tileBounds.getMinY(), 4,
                         * height - 44); gfx.drawString("maxY: " +
                         * tileBounds.getMaxY(), 4, height - 24);
                         * 
                         * gfx.drawRect(0, 0, width - 1, height - 1);
                         */
                    } finally {
                        gfx.dispose();
                    }

                    return image;
                } else {
                    return null;
                }
            }
        } catch (IllegalGeoPositionException e) {
            log.warn("Error painting waypoint tile: " + e.getMessage()); //$NON-NLS-1$
            return null;
        }
    }

    /**
     * Create a search bounding box
     * 
     * @param topLeft the first geo-position
     * @param bottomRight the second geo-position
     * @return the bounding box
     * 
     * @throws IllegalGeoPositionException if a conversion fails
     */
    private BoundingBox createSearchBB(GeoPosition topLeft, GeoPosition bottomRight)
            throws IllegalGeoPositionException {
        topLeft = GeotoolsConverter.getInstance().convert(topLeft, SelectableWaypoint.COMMON_EPSG);
        bottomRight = GeotoolsConverter.getInstance().convert(bottomRight, SelectableWaypoint.COMMON_EPSG);

        return new BoundingBox(Math.min(bottomRight.getX(), topLeft.getX()),
                Math.min(bottomRight.getY(), topLeft.getY()), -2.0, Math.max(bottomRight.getX(), topLeft.getX()),
                Math.max(bottomRight.getY(), topLeft.getY()), 2.0);
    }

    private void processWaypoint(W w, int minX, int minY, int width, int height, PixelConverter converter, int zoom,
            Graphics2D g) {
        try {
            Point2D point = converter.geoToPixel(w.getPosition(), zoom);
            int x = (int) (point.getX() - minX);
            int y = (int) (point.getY() - minY);
            PixelConverter converterWrapper = new TranslationPixelConverterDecorator(converter, (int) point.getX(),
                    (int) point.getY());
            g.translate(x, y);
            Rectangle gBounds = new Rectangle(minX - (int) point.getX(), minY - (int) point.getY(), width, height);
            renderer.paintWaypoint(g, converterWrapper, zoom, w, gBounds);
            g.translate(-x, -y);
        } catch (IllegalGeoPositionException e) {
            // waypoint not in map bounds or position invalid
            // log.warn("Error painting waypoint", e);
        }
    }

    /**
     * Find a way-point at a given position
     * 
     * @param point the position
     * @return the way-point
     */
    public W findWaypoint(Point point) {
        Rectangle viewPort = getMapKit().getMainMap().getViewportBounds();

        final int overlap = getMaxOverlap(); // the overlap is the reason why
        // the point is used instead of
        // a GeoPosition

        final int x = viewPort.x + point.x;
        final int y = viewPort.y + point.y;
        final int zoom = getMapKit().getMainMap().getZoom();
        final PixelConverter converter = getMapKit().getMainMap().getTileFactory().getTileProvider().getConverter();

        final Dimension mapSize = TileProviderUtils
                .getMapSize(getMapKit().getMainMap().getTileFactory().getTileProvider(), zoom);
        final int width = mapSize.width
                * getMapKit().getMainMap().getTileFactory().getTileProvider().getTileWidth(zoom);
        final int height = mapSize.height
                * getMapKit().getMainMap().getTileFactory().getTileProvider().getTileHeight(zoom);

        final GeoPosition topLeft = converter
                .pixelToGeo(new Point(Math.max(x - overlap, 0), Math.max(y - overlap, 0)), zoom);
        final GeoPosition bottomRight = converter
                .pixelToGeo(new Point(Math.min(x + overlap, width), Math.min(y + overlap, height)), zoom);

        BoundingBox searchBox;
        try {
            searchBox = createSearchBB(topLeft, bottomRight);

            Set<W> wps = waypoints.query(searchBox, new Verifier<W, BoundingBox>() {

                @Override
                public boolean verify(W wp, BoundingBox box) {
                    try {
                        Point2D wpPixel = converter.geoToPixel(wp.getPosition(), zoom);

                        int relX = x - (int) wpPixel.getX();
                        int relY = y - (int) wpPixel.getY();

                        Area area = wp.getMarker().getArea(zoom);
                        if (area != null && area.contains(relX, relY)) {
                            // match
                            return true;
                        }
                    } catch (IllegalGeoPositionException e) {
                        log.debug("Error converting waypoint position", e); //$NON-NLS-1$
                    }

                    return false;
                }
            });

            if (wps == null || wps.isEmpty()) {
                return null;
            } else {
                if (wps.size() == 1) {
                    return wps.iterator().next();
                } else {
                    List<W> sorted = new ArrayList<W>(wps);
                    Collections.sort(sorted, new Comparator<W>() {

                        @Override
                        public int compare(W o1, W o2) {
                            double a1 = o1.getMarker().getArea(zoom).getArea();
                            double a2 = o2.getMarker().getArea(zoom).getArea();

                            // compare size
                            if (a1 < a2) {
                                return -1;
                            } else if (a2 < a1) {
                                return 1;
                            } else {
                                return 0;
                            }
                        }

                    });
                    return sorted.get(0);
                }
            }
        } catch (IllegalGeoPositionException e) {
            return null;
        }
    }

    /**
     * Find the way-points in a given rectangular area
     * 
     * @param rect the area
     * @return the way-points in the area
     */
    public Set<W> findWaypoints(Rectangle rect) {
        Rectangle viewPort = getMapKit().getMainMap().getViewportBounds();

        final Rectangle worldRect = new Rectangle(viewPort.x + rect.x, viewPort.y + rect.y, rect.width,
                rect.height);

        final int zoom = getMapKit().getMainMap().getZoom();
        final PixelConverter converter = getMapKit().getMainMap().getTileFactory().getTileProvider().getConverter();

        final GeoPosition topLeft = converter.pixelToGeo(new Point(worldRect.x, worldRect.y), zoom);
        final GeoPosition bottomRight = converter
                .pixelToGeo((new Point(worldRect.x + worldRect.width, worldRect.y + worldRect.height)), zoom);

        return findWaypoints(topLeft, bottomRight, worldRect, converter, zoom);
    }

    /**
     * Find way-points in a rectangular area defined by the given
     * {@link GeoPosition}s
     * 
     * @param topLeft the top left position
     * @param bottomRight the bottom right position
     * @param worldRect the bounding box in world pixel coordinates
     * @param converter the pixel converter
     * @param zoom the zoom level
     * 
     * @return the way-points in the area
     */
    public Set<W> findWaypoints(GeoPosition topLeft, GeoPosition bottomRight, final Rectangle worldRect,
            final PixelConverter converter, final int zoom) {
        BoundingBox searchBox;
        try {
            searchBox = createSearchBB(topLeft, bottomRight);
            final BoundingBox verifyBox = searchBox;

            Set<W> wps = waypoints.query(searchBox, new Verifier<W, BoundingBox>() {

                @Override
                public boolean verify(W wp, BoundingBox second) {
                    try {
                        Point2D wpPixel = converter.geoToPixel(wp.getPosition(), zoom);
                        int dx = (int) wpPixel.getX();
                        int dy = (int) wpPixel.getY();

                        worldRect.translate(-dx, -dy);
                        try {
                            Area area = wp.getMarker().getArea(zoom);
                            if (area != null) {
                                return area.containedIn(worldRect);
                            } else {
                                // something that has not been painted yet may
                                // not be selected
                                return false;
                            }
                        } finally {
                            worldRect.translate(dx, dy);
                        }
                    } catch (IllegalGeoPositionException e) {
                        log.warn("Could not convert waypoint position to pixel", e);
                        // fall back to simple method
                        return verifyBox.covers(wp.getBoundingBox());
                    }
                }

            });

            if (wps == null) {
                return new HashSet<W>();
            } else {
                return wps;
            }
        } catch (IllegalGeoPositionException e) {
            return new HashSet<W>();
        }
    }

    /**
     * Find the way-points in a given polygon
     * 
     * @param poly the polygon
     * @return the way-points in the polygon area
     */
    public Set<W> findWaypoints(final Polygon poly) {
        Rectangle viewPort = getMapKit().getMainMap().getViewportBounds();

        final int zoom = getMapKit().getMainMap().getZoom();
        final PixelConverter converter = getMapKit().getMainMap().getTileFactory().getTileProvider().getConverter();

        de.fhg.igd.geom.Point2D[] points = new de.fhg.igd.geom.Point2D[poly.npoints];

        // create a metamodel polygon
        for (int i = 0; i < poly.npoints; i++) {
            int worldX = viewPort.x + poly.xpoints[i];
            int worldY = viewPort.y + poly.ypoints[i];

            // convert to geo position
            GeoPosition pos = converter.pixelToGeo(new Point(worldX, worldY), zoom);

            // convert to common CRS
            try {
                pos = GeotoolsConverter.getInstance().convert(pos, SelectableWaypoint.COMMON_EPSG);
            } catch (IllegalGeoPositionException e) {
                log.warn("Error converting polygon point for query"); //$NON-NLS-1$
                return new HashSet<W>();
            }

            points[i] = new de.fhg.igd.geom.Point2D(pos.getX(), pos.getY());
        }

        final de.fhg.igd.geom.shape.Polygon verifyPolygon = new de.fhg.igd.geom.shape.Polygon(points);

        // we need a 3D search bounding box for the R-Tree
        BoundingBox searchBox = verifyPolygon.getBoundingBox();
        searchBox.setMinZ(-2.0);
        searchBox.setMaxZ(2.0);

        poly.translate(viewPort.x, viewPort.y);
        try {
            Set<W> wps = waypoints.query(searchBox, new Verifier<W, BoundingBox>() {

                @Override
                public boolean verify(W wp, BoundingBox second) {
                    try {
                        Point2D wpPixel = converter.geoToPixel(wp.getPosition(), zoom);
                        int dx = (int) wpPixel.getX();
                        int dy = (int) wpPixel.getY();

                        poly.translate(-dx, -dy);
                        try {
                            Area area = wp.getMarker().getArea(zoom);
                            if (area != null) {
                                return area.containedIn(poly);
                            } else {
                                // something that has not been painted yet may
                                // not be selected
                                return false;
                            }
                        } finally {
                            poly.translate(dx, dy);
                        }
                    } catch (IllegalGeoPositionException e) {
                        log.warn("Could not convert waypoint position to pixel", e);
                        // fall back to simple method
                        return verifyPolygon.contains(wp.getBoundingBox().toExtent());
                    }
                }

            });

            if (wps == null) {
                return new HashSet<W>();
            } else {
                return wps;
            }
        } finally {
            poly.translate(-viewPort.x, -viewPort.y);
        }
    }

    /**
     * Clear the way-points
     */
    public void clearWaypoints() {
        synchronized (waypoints) {
            waypoints.flush();
        }

        refreshAll();
    }

    /**
     * @see TileOverlayPainter#dispose()
     */
    @Override
    public void dispose() {
        clearWaypoints();

        super.dispose();
    }

    /**
     * Get the way-points bounding box.
     * 
     * @return the bounding box
     */
    public BoundingBox getBoundingBox() {
        return waypoints.getRoot().getBoundingBox();
    }

}