Changeset 11841 in josm


Ignore:
Timestamp:
2017-04-05T11:33:25+02:00 (7 years ago)
Author:
bastiK
Message:

see #7427 - rework the way screen pixel coordinates for tiles are calculated

Location:
trunk/src/org/openstreetmap/josm/gui/layer
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java

    r11835 r11841  
    1313import java.awt.Image;
    1414import java.awt.Point;
     15import java.awt.Shape;
    1516import java.awt.Toolkit;
    1617import java.awt.event.ActionEvent;
    1718import java.awt.event.MouseAdapter;
    1819import java.awt.event.MouseEvent;
     20import java.awt.geom.AffineTransform;
    1921import java.awt.geom.Point2D;
    2022import java.awt.geom.Rectangle2D;
     
    2527import java.net.MalformedURLException;
    2628import java.net.URL;
     29import java.text.MessageFormat;
    2730import java.text.SimpleDateFormat;
    2831import java.util.ArrayList;
     
    6265import org.openstreetmap.gui.jmapviewer.OsmTileLoader;
    6366import org.openstreetmap.gui.jmapviewer.Tile;
     67import org.openstreetmap.gui.jmapviewer.TileAnchor;
    6468import org.openstreetmap.gui.jmapviewer.TileRange;
    6569import org.openstreetmap.gui.jmapviewer.TileXY;
     
    10441048     * @return  the image of the tile or null.
    10451049     */
    1046     private Image getLoadedTileImage(Tile tile) {
    1047         Image img = tile.getImage();
     1050    private BufferedImage getLoadedTileImage(Tile tile) {
     1051        BufferedImage img = tile.getImage();
    10481052        if (!imageLoaded(img))
    10491053            return null;
     
    10511055    }
    10521056
    1053     // 'source' is the pixel coordinates for the area that the img is capable of filling in.
    1054     // However, we probably only want a portion of it.
    1055     //
    1056     // 'border' is the screen cordinates that need to be drawn. We must not draw outside of it.
    1057     private void drawImageInside(Graphics g, Image sourceImg, Rectangle2D source, Rectangle2D border) {
    1058         Rectangle2D target = source;
    1059 
    1060         // If a border is specified, only draw the intersection if what we have combined with what we are supposed to draw.
    1061         if (border != null) {
    1062             target = source.createIntersection(border);
    1063             if (Main.isDebugEnabled()) {
    1064                 Main.debug("source: " + source + "\nborder: " + border + "\nintersection: " + target);
    1065             }
    1066         }
    1067 
    1068         // All of the rectangles are in screen coordinates. We need to how these correlate to the sourceImg pixels.
    1069         // We could avoid doing this by scaling the image up to the 'source' size, but this should be cheaper.
    1070         //
    1071         // In some projections, x any y are scaled differently enough to
    1072         // cause a pixel or two of fudge.  Calculate them separately.
    1073         double imageYScaling = sourceImg.getHeight(this) / source.getHeight();
    1074         double imageXScaling = sourceImg.getWidth(this) / source.getWidth();
    1075 
    1076         // How many pixels into the 'source' rectangle are we drawing?
    1077         double screenXoffset = target.getX() - source.getX();
    1078         double screenYoffset = target.getY() - source.getY();
    1079         // And how many pixels into the image itself does that correlate to?
    1080         int imgXoffset = (int) (screenXoffset * imageXScaling + 0.5);
    1081         int imgYoffset = (int) (screenYoffset * imageYScaling + 0.5);
    1082         // Now calculate the other corner of the image that we need
    1083         // by scaling the 'target' rectangle's dimensions.
    1084         int imgXend = imgXoffset + (int) (target.getWidth() * imageXScaling + 0.5);
    1085         int imgYend = imgYoffset + (int) (target.getHeight() * imageYScaling + 0.5);
    1086 
    1087         if (Main.isDebugEnabled()) {
    1088             Main.debug("drawing image into target rect: " + target);
    1089         }
    1090         g.drawImage(sourceImg,
    1091                 (int) target.getX(), (int) target.getY(),
    1092                 (int) target.getMaxX(), (int) target.getMaxY(),
    1093                 imgXoffset, imgYoffset,
    1094                 imgXend, imgYend,
    1095                 this);
    1096     }
    1097 
    1098     private List<Tile> paintTileImages(Graphics g, TileSet ts) {
     1057    /**
     1058     * Draw a tile image on screen.
     1059     * @param g the Graphics2D
     1060     * @param toDrawImg tile image
     1061     * @param anchorImage tile anchor in image coordinates
     1062     * @param anchorScreen tile anchor in screen coordinates
     1063     * @param clip clipping region in screen coordinates (can be null)
     1064     */
     1065    private void drawImageInside(Graphics2D g, BufferedImage toDrawImg, TileAnchor anchorImage, TileAnchor anchorScreen, Shape clip) {
     1066        AffineTransform imageToScreen = anchorImage.convert(anchorScreen);
     1067        Point2D screen0 = imageToScreen.transform(new Point.Double(0, 0), null);
     1068        Point2D screen1 = imageToScreen.transform(new Point.Double(toDrawImg.getWidth(), toDrawImg.getHeight()), null);
     1069        Shape oldClip = null;
     1070        if (clip != null) {
     1071            oldClip = g.getClip();
     1072            g.clip(clip);
     1073        }
     1074        g.drawImage(toDrawImg, (int) Math.round(screen0.getX()), (int) Math.round(screen0.getY()),
     1075                (int) Math.round(screen1.getX() - screen0.getX()), (int) Math.round(screen1.getY() - screen0.getY()), this);
     1076        if (clip != null) {
     1077            g.setClip(oldClip);
     1078        }
     1079    }
     1080
     1081    private List<Tile> paintTileImages(Graphics2D g, TileSet ts) {
    10991082        Object paintMutex = new Object();
    11001083        List<TilePosition> missed = Collections.synchronizedList(new ArrayList<>());
    11011084        ts.visitTiles(tile -> {
    11021085            boolean miss = false;
    1103             Image img = null;
     1086            BufferedImage img = null;
     1087            TileAnchor anchorImage = null;
    11041088            if (!tile.isLoaded() || tile.hasError()) {
    11051089                miss = true;
    11061090            } else {
    1107                 img = getLoadedTileImage(tile);
     1091                synchronized (tile) {
     1092                    img = getLoadedTileImage(tile);
     1093                    anchorImage = tile.getAnchor();
     1094                }
    11081095                if (img == null) {
    11091096                    miss = true;
     
    11151102            }
    11161103            img = applyImageProcessors((BufferedImage) img);
    1117             Rectangle2D sourceRect = coordinateConverter.getRectangleForTile(tile);
     1104            TileAnchor anchorScreen = coordinateConverter.getScreenAnchorForTile(tile);
    11181105            synchronized (paintMutex) {
    11191106                //cannot paint in parallel
    1120                 drawImageInside(g, img, sourceRect, null);
     1107                drawImageInside(g, img, anchorImage, anchorScreen, null);
    11211108            }
    11221109        }, missed::add);
     
    11321119    // It will not be from the zoom level that is being drawn currently.
    11331120    // If drawing the displayZoomLevel, border is null and we draw the entire tile set.
    1134     private List<Tile> paintTileImages(Graphics g, TileSet ts, int zoom, Tile border) {
     1121    private List<Tile> paintTileImages(Graphics2D g, TileSet ts, int zoom, Tile border) {
    11351122        if (zoom <= 0) return Collections.emptyList();
    1136         Rectangle2D borderRect = coordinateConverter.getRectangleForTile(border);
     1123        Shape borderClip = coordinateConverter.getScreenQuadrilateralForTile(border);
    11371124        List<Tile> missedTiles = new LinkedList<>();
    11381125        // The callers of this code *require* that we return any tiles that we do not draw in missedTiles.
     
    11411128        for (Tile tile : ts.allTilesCreate()) {
    11421129            boolean miss = false;
    1143             Image img = null;
     1130            BufferedImage img = null;
     1131            TileAnchor anchorImage = null;
    11441132            if (!tile.isLoaded() || tile.hasError()) {
    11451133                miss = true;
    11461134            } else {
    1147                 img = getLoadedTileImage(tile);
     1135               synchronized (tile) {
     1136                    img = getLoadedTileImage(tile);
     1137                    anchorImage = tile.getAnchor();
     1138                }
    11481139                if (img == null) {
    11491140                    miss = true;
     
    11581149            img = applyImageProcessors((BufferedImage) img);
    11591150
    1160             Rectangle2D sourceRect = coordinateConverter.getRectangleForTile(tile);
    1161             Rectangle2D clipRect;
     1151            Shape clip;
    11621152            if (tileSource.isInside(tile, border)) {
    1163                 clipRect = null;
     1153                clip = null;
    11641154            } else if (tileSource.isInside(border, tile)) {
    1165                 clipRect = borderRect;
     1155                clip = borderClip;
    11661156            } else {
    11671157                continue;
    11681158            }
    1169             drawImageInside(g, img, sourceRect, clipRect);
     1159            TileAnchor anchorScreen = coordinateConverter.getScreenAnchorForTile(tile);
     1160            drawImageInside(g, img, anchorImage, anchorScreen, clip);
    11701161        }
    11711162        return missedTiles;
  • trunk/src/org/openstreetmap/josm/gui/layer/imagery/TileCoordinateConverter.java

    r11015 r11841  
    22package org.openstreetmap.josm.gui.layer.imagery;
    33
     4import java.awt.Shape;
     5import java.awt.geom.Path2D;
    46import java.awt.geom.Point2D;
    57import java.awt.geom.Rectangle2D;
    68
    79import org.openstreetmap.gui.jmapviewer.Tile;
     10import org.openstreetmap.gui.jmapviewer.TileAnchor;
    811import org.openstreetmap.gui.jmapviewer.TileXY;
    912import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
     13import org.openstreetmap.gui.jmapviewer.interfaces.IProjected;
    1014import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
     15import org.openstreetmap.josm.data.coor.EastNorth;
    1116import org.openstreetmap.josm.data.coor.LatLon;
    1217import org.openstreetmap.josm.data.projection.Projecting;
     
    4146    }
    4247
     48    private MapViewPoint pos(IProjected p) {
     49        return mapView.getState().getPointFor(new EastNorth(p)).add(settings.getDisplacement());
     50    }
     51
    4352    /**
    4453     * Gets the projecting instance to use to convert between latlon and eastnorth coordinates.
     
    5160    /**
    5261     * Gets the top left position of the tile inside the map view.
     62     * @param x x tile index
     63     * @param y y tile index
     64     * @param zoom zoom level
     65     * @return the position
     66     */
     67    public Point2D getPixelForTile(int x, int y, int zoom) {
     68        ICoordinate coord = tileSource.tileXYToLatLon(x, y, zoom);
     69        return pos(coord).getInView();
     70    }
     71
     72    /**
     73     * Gets the top left position of the tile inside the map view.
    5374     * @param tile The tile
    54      * @return The positon.
     75     * @return The position.
    5576     */
    5677    public Point2D getPixelForTile(Tile tile) {
    57         ICoordinate coord = tile.getTileSource().tileXYToLatLon(tile);
    58         return pos(coord).getInView();
     78        return this.getPixelForTile(tile.getXtile(), tile.getYtile(), tile.getZoom());
    5979    }
    6080
     
    6989
    7090        return pos(c1).rectTo(pos(c2)).getInView();
     91    }
     92
     93    /**
     94     * Returns a quadrilateral formed by the 4 corners of the tile in screen coordinates.
     95     *
     96     * If the tile is rectangular, this will be the exact border of the tile.
     97     * The tile may be more oddly shaped due to reprojection, then it is an approximation
     98     * of the tile outline.
     99     * @param tile the tile
     100     * @return quadrilateral tile outline in screen coordinates
     101     */
     102    public Shape getScreenQuadrilateralForTile(Tile tile) {
     103        Point2D p00 = this.getPixelForTile(tile.getXtile(), tile.getYtile(), tile.getZoom());
     104        Point2D p10 = this.getPixelForTile(tile.getXtile() + 1, tile.getYtile(), tile.getZoom());
     105        Point2D p11 = this.getPixelForTile(tile.getXtile() + 1, tile.getYtile() + 1, tile.getZoom());
     106        Point2D p01 = this.getPixelForTile(tile.getXtile(), tile.getYtile() + 1, tile.getZoom());
     107
     108        Path2D pth = new Path2D.Double();
     109        pth.moveTo(p00.getX(), p00.getY());
     110        pth.lineTo(p01.getX(), p01.getY());
     111        pth.lineTo(p11.getX(), p11.getY());
     112        pth.lineTo(p10.getX(), p10.getY());
     113        pth.closePath();
     114        return pth;
    71115    }
    72116
     
    87131        return screenPixels/tilePixels;
    88132    }
     133
     134    /**
     135     * Get {@link TileAnchor} for a tile in screen pixel coordinates.
     136     * @param tile the tile
     137     * @return position of the tile in screen coordinates
     138     */
     139    public TileAnchor getScreenAnchorForTile(Tile tile) {
     140        IProjected p1 = tileSource.tileXYtoProjected(tile.getXtile(), tile.getYtile(), tile.getZoom());
     141        IProjected p2 = tileSource.tileXYtoProjected(tile.getXtile() + 1, tile.getYtile() + 1, tile.getZoom());
     142        return new TileAnchor(pos(p1).getInView(), pos(p2).getInView());
     143    }
    89144}
Note: See TracChangeset for help on using the changeset viewer.