Ticket #7427: reprojection.patch
File reprojection.patch, 59.4 KB (added by , 8 years ago) |
---|
-
src/org/openstreetmap/josm/data/imagery/AbstractWMSTileSource.java
10 10 import org.openstreetmap.gui.jmapviewer.interfaces.IProjected; 11 11 import org.openstreetmap.gui.jmapviewer.tilesources.TMSTileSource; 12 12 import org.openstreetmap.gui.jmapviewer.tilesources.TileSourceInfo; 13 import org.openstreetmap.josm.Main;14 13 import org.openstreetmap.josm.data.Bounds; 15 14 import org.openstreetmap.josm.data.ProjectionBounds; 16 15 import org.openstreetmap.josm.data.coor.EastNorth; … … 31 30 private int[] tileYMax; 32 31 private double[] degreesPerTile; 33 32 private static final float SCALE_DENOMINATOR_ZOOM_LEVEL_1 = 559082264.0287178f; 33 private Projection tileProjection; 34 34 35 35 /** 36 36 * Constructs a new {@code AbstractWMSTileSource}. 37 37 * @param info tile source info 38 38 */ 39 public AbstractWMSTileSource(TileSourceInfo info ) {39 public AbstractWMSTileSource(TileSourceInfo info, Projection tileProjection) { 40 40 super(info); 41 this.tileProjection = tileProjection; 41 42 } 42 43 43 44 private void initAnchorPosition(Projection proj) { … … 47 48 this.anchorPosition = new EastNorth(min.east(), max.north()); 48 49 } 49 50 51 public void setTileProjection(Projection tileProjection) { 52 this.tileProjection = tileProjection; 53 } 54 55 public Projection getTileProjection() { 56 return this.tileProjection; 57 } 58 50 59 /** 51 60 * Initializes class with current projection in JOSM. This call is needed every time projection changes. 52 61 */ 53 62 public void initProjection() { 54 initProjection( Main.getProjection());63 initProjection(this.tileProjection); 55 64 } 56 65 57 66 /** … … 98 107 99 108 @Override 100 109 public ICoordinate tileXYToLatLon(int x, int y, int zoom) { 101 return Main.getProjection().eastNorth2latlon(getTileEastNorth(x, y, zoom)).toCoordinate();110 return tileProjection.eastNorth2latlon(getTileEastNorth(x, y, zoom)).toCoordinate(); 102 111 } 103 112 104 113 private TileXY eastNorthToTileXY(EastNorth enPoint, int zoom) { … … 111 120 112 121 @Override 113 122 public TileXY latLonToTileXY(double lat, double lon, int zoom) { 114 EastNorth enPoint = Main.getProjection().latlon2eastNorth(new LatLon(lat, lon));123 EastNorth enPoint = tileProjection.latlon2eastNorth(new LatLon(lat, lon)); 115 124 return eastNorthToTileXY(enPoint, zoom); 116 125 } 117 126 … … 143 152 @Override 144 153 public Point latLonToXY(double lat, double lon, int zoom) { 145 154 double scale = getDegreesPerTile(zoom) / getTileSize(); 146 EastNorth point = Main.getProjection().latlon2eastNorth(new LatLon(lat, lon));155 EastNorth point = tileProjection.latlon2eastNorth(new LatLon(lat, lon)); 147 156 return new Point( 148 157 (int) Math.round((point.east() - anchorPosition.east()) / scale), 149 158 (int) Math.round((anchorPosition.north() - point.north()) / scale) … … 163 172 @Override 164 173 public ICoordinate xyToLatLon(int x, int y, int zoom) { 165 174 double scale = getDegreesPerTile(zoom) / getTileSize(); 166 Projection proj = Main.getProjection();167 175 EastNorth ret = new EastNorth( 168 176 anchorPosition.east() + x * scale, 169 177 anchorPosition.north() - y * scale 170 178 ); 171 return proj.eastNorth2latlon(ret).toCoordinate();179 return tileProjection.eastNorth2latlon(ret).toCoordinate(); 172 180 } 173 181 174 182 protected EastNorth getTileEastNorth(int x, int y, int z) { … … 196 204 197 205 @Override 198 206 public String getServerCRS() { 199 return Main.getProjection().toCode();207 return this.tileProjection.toCode(); 200 208 } 201 209 } -
src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java
45 45 private static final Logger LOG = FeatureAdapter.getLogger(TMSCachedTileLoaderJob.class.getCanonicalName()); 46 46 private static final LongProperty MAXIMUM_EXPIRES = new LongProperty("imagery.generic.maximum_expires", TimeUnit.DAYS.toMillis(30)); 47 47 private static final LongProperty MINIMUM_EXPIRES = new LongProperty("imagery.generic.minimum_expires", TimeUnit.HOURS.toMillis(1)); 48 pr ivatefinal Tile tile;48 protected final Tile tile; 49 49 private volatile URL url; 50 50 51 51 // we need another deduplication of Tile Loader listeners, as for each submit, new TMSCachedTileLoaderJob was created -
src/org/openstreetmap/josm/data/imagery/TemplatedWMSTileSource.java
18 18 import org.openstreetmap.josm.Main; 19 19 import org.openstreetmap.josm.data.coor.EastNorth; 20 20 import org.openstreetmap.josm.data.coor.LatLon; 21 import org.openstreetmap.josm.data.projection.Projection; 21 22 import org.openstreetmap.josm.gui.layer.WMSLayer; 22 23 import org.openstreetmap.josm.tools.CheckParameterUtil; 23 24 … … 54 55 * Creates a tile source based on imagery info 55 56 * @param info imagery info 56 57 */ 57 public TemplatedWMSTileSource(ImageryInfo info ) {58 super(info );58 public TemplatedWMSTileSource(ImageryInfo info, Projection tileProjection) { 59 super(info, tileProjection); 59 60 this.serverProjections = new TreeSet<>(info.getServerProjections()); 60 61 handleTemplate(); 61 62 initProjection(); … … 68 69 69 70 @Override 70 71 public String getTileUrl(int zoom, int tilex, int tiley) { 71 String myProjCode = Main.getProjection().toCode();72 String myProjCode = getServerCRS(); 72 73 73 74 EastNorth nw = getTileEastNorth(tilex, tiley, zoom); 74 75 EastNorth se = getTileEastNorth(tilex + 1, tiley + 1, zoom); … … 79 80 double s = se.getY(); 80 81 double e = se.getX(); 81 82 83 // FIXME 82 84 if (!serverProjections.contains(myProjCode) && serverProjections.contains("EPSG:4326") && "EPSG:3857".equals(myProjCode)) { 83 85 LatLon swll = Main.getProjection().eastNorth2latlon(new EastNorth(w, s)); 84 86 LatLon nell = Main.getProjection().eastNorth2latlon(new EastNorth(e, n)); -
src/org/openstreetmap/josm/data/imagery/WMSCachedTileLoaderJob.java
7 7 import org.apache.commons.jcs.access.behavior.ICacheAccess; 8 8 import org.openstreetmap.gui.jmapviewer.Tile; 9 9 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener; 10 import org.openstreetmap.josm.Main;11 10 import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry; 12 11 13 12 /** … … 39 38 // include projection in cache key, as with different projections different response will be returned from server 40 39 String key = super.getCacheKey(); 41 40 if (key != null) { 42 return key + Main.getProjection().toCode();41 return key + tile.getSource().getServerCRS(); 43 42 } 44 43 return null; 45 44 } -
src/org/openstreetmap/josm/data/imagery/WMTSTileSource.java
61 61 import org.openstreetmap.josm.tools.Utils; 62 62 63 63 /** 64 * Tile Source handling WM S providers64 * Tile Source handling WMTS providers 65 65 * 66 66 * @author Wiktor Niesiobędzki 67 67 * @since 8526 … … 268 268 269 269 private final WMTSDefaultLayer defaultLayer; 270 270 271 private Projection tileProjection; 272 271 273 /** 272 274 * Creates a tile source based on imagery info 273 275 * @param info imagery info … … 597 599 * @param proj projection to be used by this TileSource 598 600 */ 599 601 public void initProjection(Projection proj) { 600 // getLayers will return only layers matching the name, if the user already choose the layer 601 // so we will not ask the user again to chose the layer, if he just changes projection 602 Collection<Layer> candidates = getLayers( 603 currentLayer != null ? new WMTSDefaultLayer(currentLayer.identifier, currentLayer.tileMatrixSet.identifier) : defaultLayer, 604 proj.toCode()); 605 606 if (candidates.size() > 1 && defaultLayer != null) { 607 candidates = candidates.stream() 608 .filter(t -> t.tileMatrixSet.identifier.equals(defaultLayer.getTileMatrixSet())) 609 .collect(Collectors.toList()); 610 } 611 if (candidates.size() == 1) { 612 Layer newLayer = candidates.iterator().next(); 613 if (newLayer != null) { 614 this.currentTileMatrixSet = newLayer.tileMatrixSet; 615 this.currentLayer = newLayer; 616 Collection<Double> scales = new ArrayList<>(currentTileMatrixSet.tileMatrix.size()); 617 for (TileMatrix tileMatrix : currentTileMatrixSet.tileMatrix) { 618 scales.add(tileMatrix.scaleDenominator * 0.28e-03); 602 if (proj.equals(tileProjection)) 603 return; 604 List<Layer> matchingLayers = layers.stream().filter( 605 l -> l.identifier.equals(defaultLayer.layerName) && l.tileMatrixSet.crs.equals(proj.toCode())) 606 .collect(Collectors.toList()); 607 if (matchingLayers.size() > 1) { 608 this.currentLayer = matchingLayers.stream().filter( 609 l -> l.tileMatrixSet.identifier.equals(defaultLayer.getTileMatrixSet())) 610 .findFirst().orElse(null); 611 this.tileProjection = proj; 612 } else if (matchingLayers.size() == 1) { 613 this.currentLayer = matchingLayers.get(0); 614 this.tileProjection = proj; 615 } else { 616 // no tile matrix sets with current projection 617 if (this.currentLayer == null) { 618 this.tileProjection = null; 619 for (Layer layer : layers) { 620 if (!layer.identifier.equals(defaultLayer.layerName)) { 621 continue; 622 } 623 Projection pr = Projections.getProjectionByCode(layer.tileMatrixSet.crs); 624 if (pr != null) { 625 this.currentLayer = layer; 626 this.tileProjection = pr; 627 break; 628 } 619 629 } 620 this.nativeScaleList = new ScaleList(scales); 621 } 622 } else if (candidates.size() > 1) { 623 Main.warn("More than one layer WMTS available: {0} for projection {1} and name {2}. Do not know which to process", 624 candidates.stream().map(x -> x.getUserTitle() + ": " + x.tileMatrixSet.identifier).collect(Collectors.joining(", ")), 625 proj.toCode(), 626 currentLayer != null ? currentLayer.getUserTitle() : defaultLayer 627 ); 630 if (this.currentLayer == null) 631 return; 632 } // else: keep currentLayer and tileProjection as is 628 633 } 629 this.crsScale = getTileSize() * 0.28e-03 / proj.getMetersPerUnit(); 634 this.currentTileMatrixSet = this.currentLayer.tileMatrixSet; 635 Collection<Double> scales = new ArrayList<>(currentTileMatrixSet.tileMatrix.size()); 636 for (TileMatrix tileMatrix : currentTileMatrixSet.tileMatrix) { 637 scales.add(tileMatrix.scaleDenominator * 0.28e-03); 638 } 639 this.nativeScaleList = new ScaleList(scales); 640 this.crsScale = getTileSize() * 0.28e-03 / this.tileProjection.getMetersPerUnit(); 630 641 } 631 642 632 643 /** … … 654 665 public int getTileSize() { 655 666 // no support for non-square tiles (tileHeight != tileWidth) 656 667 // and for different tile sizes at different zoom levels 657 Collection<Layer> projLayers = getLayers(null, Main.getProjection().toCode());668 Collection<Layer> projLayers = getLayers(null, tileProjection.toCode()); 658 669 if (!projLayers.isEmpty()) { 659 670 return projLayers.iterator().next().tileMatrixSet.tileMatrix.get(0).tileHeight; 660 671 } … … 735 746 public ICoordinate tileXYToLatLon(int x, int y, int zoom) { 736 747 TileMatrix matrix = getTileMatrix(zoom); 737 748 if (matrix == null) { 738 return Main.getProjection().getWorldBoundsLatLon().getCenter().toCoordinate();749 return tileProjection.getWorldBoundsLatLon().getCenter().toCoordinate(); 739 750 } 740 751 double scale = matrix.scaleDenominator * this.crsScale; 741 752 EastNorth ret = new EastNorth(matrix.topLeftCorner.east() + x * scale, matrix.topLeftCorner.north() - y * scale); 742 return Main.getProjection().eastNorth2latlon(ret).toCoordinate();753 return tileProjection.eastNorth2latlon(ret).toCoordinate(); 743 754 } 744 755 745 756 @Override … … 749 760 return new TileXY(0, 0); 750 761 } 751 762 752 Projection proj = Main.getProjection(); 753 EastNorth enPoint = proj.latlon2eastNorth(new LatLon(lat, lon)); 763 EastNorth enPoint = tileProjection.latlon2eastNorth(new LatLon(lat, lon)); 754 764 double scale = matrix.scaleDenominator * this.crsScale; 755 765 return new TileXY( 756 766 (enPoint.east() - matrix.topLeftCorner.east()) / scale, … … 765 775 766 776 @Override 767 777 public int getTileXMax(int zoom) { 768 return getTileXMax(zoom, Main.getProjection());778 return getTileXMax(zoom, tileProjection); 769 779 } 770 780 771 781 @Override 772 782 public int getTileYMax(int zoom) { 773 return getTileYMax(zoom, Main.getProjection());783 return getTileYMax(zoom, tileProjection); 774 784 } 775 785 776 786 @Override … … 780 790 return new Point(0, 0); 781 791 } 782 792 double scale = matrix.scaleDenominator * this.crsScale; 783 EastNorth point = Main.getProjection().latlon2eastNorth(new LatLon(lat, lon));793 EastNorth point = tileProjection.latlon2eastNorth(new LatLon(lat, lon)); 784 794 return new Point( 785 795 (int) Math.round((point.east() - matrix.topLeftCorner.east()) / scale), 786 796 (int) Math.round((matrix.topLeftCorner.north() - point.north()) / scale) … … 804 814 return new Coordinate(0, 0); 805 815 } 806 816 double scale = matrix.scaleDenominator * this.crsScale; 807 Projection proj = Main.getProjection();808 817 EastNorth ret = new EastNorth( 809 818 matrix.topLeftCorner.east() + x * scale, 810 819 matrix.topLeftCorner.north() - y * scale 811 820 ); 812 LatLon ll = proj.eastNorth2latlon(ret);821 LatLon ll = tileProjection.eastNorth2latlon(ret); 813 822 return new Coordinate(ll.lat(), ll.lon()); 814 823 } 815 824 … … 911 920 return nativeScaleList; 912 921 } 913 922 923 public Projection getTileProjection() { 924 return tileProjection; 925 } 926 914 927 @Override 915 928 public IProjected tileXYtoProjected(int x, int y, int zoom) { 916 929 TileMatrix matrix = getTileMatrix(zoom); … … 977 990 978 991 @Override 979 992 public String getServerCRS() { 980 return Main.getProjection().toCode();993 return tileProjection.toCode(); 981 994 } 982 995 } -
src/org/openstreetmap/josm/data/projection/CustomProjection.java
874 874 } 875 875 return result; 876 876 } 877 878 @Override 879 public ProjectionBounds getEastNorthBoundsBox(ProjectionBounds box, Projection boxProjection) { 880 final int n = 8; 881 ProjectionBounds result = null; 882 for (int i = 0; i < 4*n; i++) { 883 EastNorth en = latlon2eastNorth(boxProjection.eastNorth2latlon(getPointAlong(i, n, box))); 884 if (result == null) { 885 result = new ProjectionBounds(en); 886 } else { 887 result.extend(en); 888 } 889 } 890 return result; 891 } 877 892 } -
src/org/openstreetmap/josm/data/projection/Projection.java
86 86 Bounds getLatLonBoundsBox(ProjectionBounds pb); 87 87 88 88 /** 89 * Get a box in east/north space of this projection, that fully contains an 90 * east/north box of another projection. 91 * 92 * Reprojecting a rectangular box from one projection to another may distort/rotate 93 * the shape of the box, so in general one needs to walk along the boundary 94 * in small steps to get a reliable result. 95 * 96 * This is an approximate method. 97 * 98 * @param box the east/north box given in projection <code>boxProjection</code> 99 * @param boxProjection the projection of <code>box</code> 100 * @return an east/north box in this projection, containing the given box 101 */ 102 ProjectionBounds getEastNorthBoundsBox(ProjectionBounds box, Projection boxProjection); 103 104 /** 89 105 * Get the number of meters per unit of this projection. This more 90 106 * defines the scale of the map, than real conversion of unit to meters 91 107 * as this value is more less correct only along certain lines of true scale. -
src/org/openstreetmap/josm/gui/NavigatableComponent.java
637 637 MapViewState mvs = getState().usingScale(newScale); 638 638 mvs = mvs.movedTo(mvs.getCenter(), newCenter); 639 639 Point2D enOrigin = mvs.getPointFor(new EastNorth(0, 0)).getInView(); 640 Point2D enOriginAligned = new Point2D.Double(Math.round(enOrigin.getX()), Math.round(enOrigin.getY())); 640 // as a result of the alignment, it is common to round "half integer" values 641 // like 1.49999, which is numerically unstable; add small epsilon to resolve this 642 double EPSILON = 1e-3; 643 Point2D enOriginAligned = new Point2D.Double( 644 Math.round(enOrigin.getX()) + EPSILON, 645 Math.round(enOrigin.getY()) + EPSILON); 641 646 EastNorth enShift = mvs.getForView(enOriginAligned.getX(), enOriginAligned.getY()).getEastNorth(); 642 647 newCenter = newCenter.subtract(enShift); 643 648 -
src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java
62 62 import org.openstreetmap.gui.jmapviewer.AttributionSupport; 63 63 import org.openstreetmap.gui.jmapviewer.MemoryTileCache; 64 64 import org.openstreetmap.gui.jmapviewer.OsmTileLoader; 65 import org.openstreetmap.gui.jmapviewer.Projected; 65 66 import org.openstreetmap.gui.jmapviewer.Tile; 66 67 import org.openstreetmap.gui.jmapviewer.TileRange; 67 68 import org.openstreetmap.gui.jmapviewer.TileXY; … … 87 88 import org.openstreetmap.josm.data.imagery.TileLoaderFactory; 88 89 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 89 90 import org.openstreetmap.josm.data.preferences.IntegerProperty; 91 import org.openstreetmap.josm.data.projection.Projection; 92 import org.openstreetmap.josm.data.projection.Projections; 90 93 import org.openstreetmap.josm.gui.ExtendedDialog; 91 94 import org.openstreetmap.josm.gui.MapFrame; 92 95 import org.openstreetmap.josm.gui.MapView; … … 95 98 import org.openstreetmap.josm.gui.dialogs.LayerListDialog; 96 99 import org.openstreetmap.josm.gui.dialogs.LayerListPopup; 97 100 import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings.FilterChangeListener; 101 import org.openstreetmap.josm.gui.layer.imagery.ReprojectionTile; 98 102 import org.openstreetmap.josm.gui.layer.imagery.TileAnchor; 99 103 import org.openstreetmap.josm.gui.layer.imagery.TileCoordinateConverter; 100 104 import org.openstreetmap.josm.gui.layer.imagery.TilePosition; … … 108 112 import org.openstreetmap.josm.tools.MemoryManager; 109 113 import org.openstreetmap.josm.tools.MemoryManager.MemoryHandle; 110 114 import org.openstreetmap.josm.tools.MemoryManager.NotEnoughMemoryException; 115 import org.openstreetmap.josm.tools.Utils; 111 116 112 117 /** 113 118 * Base abstract class that supports displaying images provided by TileSource. It might be TMS source, WMS or WMTS … … 184 189 185 190 private final ImageryAdjustAction adjustAction = new ImageryAdjustAction(this); 186 191 // prepared to be moved to the painter 187 pr ivateTileCoordinateConverter coordinateConverter;192 protected TileCoordinateConverter coordinateConverter; 188 193 189 194 /** 190 195 * Creates Tile Source based Imagery Layer based on Imagery Info … … 362 367 * @param zoom zoom level 363 368 * @return average number of screen pixels per tile pixel 364 369 */ 365 pr ivatedouble getScaleFactor(int zoom) {370 protected double getScaleFactor(int zoom) { 366 371 if (coordinateConverter != null) { 367 372 return coordinateConverter.getScaleFactor(zoom); 368 373 } else { … … 383 388 * maps as a imagery layer 384 389 */ 385 390 int intResult = (int) Math.round(result + 1 + ZOOM_OFFSET.get() / 1.9); 386 387 intResult = Math.min(intResult, getMaxZoomLvl()); 388 intResult = Math.max(intResult, getMinZoomLvl()); 391 intResult = Utils.clamp(intResult, getMinZoomLvl(), getMaxZoomLvl()); 389 392 return intResult; 390 393 } 391 394 … … 425 428 Main.trace(e); 426 429 } 427 430 428 String[][] content = { 429 {"Tile name", clickedTile.getKey()}, 430 {"Tile url", url}, 431 {"Tile size", getSizeString(clickedTile.getTileSource().getTileSize()) }, 432 {"Tile display size", new StringBuilder().append(displaySize.getWidth()) 433 .append('x') 434 .append(displaySize.getHeight()).toString()}, 435 }; 431 List<List<String>> content = new ArrayList<>(); 432 content.add(Arrays.asList(tr("Tile name"), clickedTile.getKey())); 433 content.add(Arrays.asList(tr("Tile URL"), url)); 434 content.add(Arrays.asList(tr("Tile size"), 435 getSizeString(clickedTile.getTileSource().getTileSize()))); 436 content.add(Arrays.asList(tr("Tile display size"), 437 new StringBuilder().append(displaySize.getWidth()) 438 .append('x') 439 .append(displaySize.getHeight()).toString())); 440 if (coordinateConverter.requiresReprojection()) { 441 content.add(Arrays.asList(tr("Reprojection"), 442 clickedTile.getTileSource().getServerCRS() + 443 " -> " + Main.getProjection().toCode())); 444 BufferedImage img = clickedTile.getImage(); 445 if (img != null) { 446 content.add(Arrays.asList(tr("Reprojected tile size"), 447 img.getWidth() + "x" + img.getHeight())); 436 448 437 for (String[] entry: content) { 438 panel.add(new JLabel(tr(entry[0]) + ':'), GBC.std()); 449 } 450 } 451 for (List<String> entry: content) { 452 panel.add(new JLabel(tr(entry.get(0)) + ':'), GBC.std()); 439 453 panel.add(GBC.glue(5, 0), GBC.std()); 440 panel.add(createTextField(entry [1]), GBC.eol().fill(GBC.HORIZONTAL));454 panel.add(createTextField(entry.get(1)), GBC.eol().fill(GBC.HORIZONTAL)); 441 455 } 442 456 443 457 for (Entry<String, String> e: clickedTile.getMetadata().entrySet()) { … … 945 959 private Tile getOrCreateTile(int x, int y, int zoom) { 946 960 Tile tile = getTile(x, y, zoom); 947 961 if (tile == null) { 948 tile = new Tile(tileSource, x, y, zoom); 962 if (coordinateConverter.requiresReprojection()) { 963 tile = new ReprojectionTile(tileSource, x, y, zoom); 964 } else { 965 tile = new Tile(tileSource, x, y, zoom); 966 } 949 967 tileCache.addTile(tile); 950 968 } 951 969 return tile; … … 1020 1038 } 1021 1039 1022 1040 /** 1023 * Invalidate the layer at a time in the future so t aht the user still sees the interface responsive.1041 * Invalidate the layer at a time in the future so that the user still sees the interface responsive. 1024 1042 */ 1025 1043 private void invalidateLater() { 1026 1044 GuiHelper.runInEDT(() -> { … … 1054 1072 return img; 1055 1073 } 1056 1074 1057 /**1075 /** 1058 1076 * Draw a tile image on screen. 1059 1077 * @param g the Graphics2D 1060 1078 * @param toDrawImg tile image … … 1065 1083 private void drawImageInside(Graphics2D g, BufferedImage toDrawImg, TileAnchor anchorImage, TileAnchor anchorScreen, Shape clip) { 1066 1084 AffineTransform imageToScreen = anchorImage.convert(anchorScreen); 1067 1085 Point2D screen0 = imageToScreen.transform(new Point.Double(0, 0), null); 1068 Point2D screen1 = imageToScreen.transform(new Point.Double(toDrawImg.getWidth(), toDrawImg.getHeight()), null); 1086 Point2D screen1 = imageToScreen.transform(new Point.Double( 1087 toDrawImg.getWidth(), toDrawImg.getHeight()), null); 1088 1069 1089 Shape oldClip = null; 1070 1090 if (clip != null) { 1071 1091 oldClip = g.getClip(); … … 1084 1104 ts.visitTiles(tile -> { 1085 1105 boolean miss = false; 1086 1106 BufferedImage img = null; 1107 TileAnchor anchorImage = null; 1087 1108 if (!tile.isLoaded() || tile.hasError()) { 1088 1109 miss = true; 1089 1110 } else { 1090 img = getLoadedTileImage(tile); 1091 if (img == null) { 1111 synchronized (tile) { 1112 img = getLoadedTileImage(tile); 1113 anchorImage = getAnchor(tile, img); 1114 } 1115 if (img == null || anchorImage == null) { 1092 1116 miss = true; 1093 1117 } 1094 1118 } … … 1096 1120 missed.add(new TilePosition(tile)); 1097 1121 return; 1098 1122 } 1099 TileAnchor anchorImage = new TileAnchor( 1100 new Point.Double(0, 0), 1101 new Point.Double(img.getWidth(), img.getHeight())); 1102 img = applyImageProcessors((BufferedImage) img); 1123 1124 img = applyImageProcessors(img); 1125 1103 1126 TileAnchor anchorScreen = coordinateConverter.getScreenAnchorForTile(tile); 1104 1127 synchronized (paintMutex) { 1105 1128 //cannot paint in parallel 1106 1129 drawImageInside(g, img, anchorImage, anchorScreen, null); 1107 1130 } 1131 if (tile instanceof ReprojectionTile) { 1132 // This means we have a reprojected tile in memory cache, but not at 1133 // current scale. Generally, the positioning of the tile will still 1134 // be correct, but for best image quality, the tile should be 1135 // reprojected to the target scale. The original tile image should 1136 // still be in disk cache, so this is fairly cheap. 1137 if (((ReprojectionTile) tile).needsUpdate(Main.map.mapView.getScale())) { 1138 loadTile(tile, true); 1139 } 1140 } 1141 1108 1142 }, missed::add); 1109 1143 1110 1144 return missed.stream().map(this::getOrCreateTile).collect(Collectors.toList()); … … 1127 1161 for (Tile tile : ts.allTilesCreate()) { 1128 1162 boolean miss = false; 1129 1163 BufferedImage img = null; 1164 TileAnchor anchorImage = null; 1130 1165 if (!tile.isLoaded() || tile.hasError()) { 1131 1166 miss = true; 1132 1167 } else { 1133 img = getLoadedTileImage(tile); 1134 if (img == null) { 1168 synchronized (tile) { 1169 img = getLoadedTileImage(tile); 1170 anchorImage = getAnchor(tile, img); 1171 } 1172 1173 if (img == null || anchorImage == null) { 1135 1174 miss = true; 1136 1175 } 1137 1176 } … … 1139 1178 missedTiles.add(tile); 1140 1179 continue; 1141 1180 } 1142 TileAnchor anchorImage = new TileAnchor(1143 new Point.Double(0, 0),1144 new Point.Double(img.getWidth(), img.getHeight()));1145 1181 1146 1182 // applying all filters to this layer 1147 1183 img = applyImageProcessors((BufferedImage) img); … … 1160 1196 return missedTiles; 1161 1197 } 1162 1198 1199 private TileAnchor getAnchor(Tile tile, BufferedImage image) { 1200 if (tile instanceof ReprojectionTile) { 1201 return ((ReprojectionTile) tile).getAnchor(); 1202 } else { 1203 return new TileAnchor(new Point.Double(0, 0), new Point.Double(image.getWidth(), image.getHeight())); 1204 } 1205 } 1206 1163 1207 private void myDrawString(Graphics g, String text, int x, int y) { 1164 1208 Color oldColor = g.getColor(); 1165 1209 String textToDraw = text; … … 1187 1231 } 1188 1232 } 1189 1233 1190 private void paintTileText(Tile tile, Graphics g, MapView mv) {1234 private void paintTileText(Tile tile, Graphics2D g) { 1191 1235 if (tile == null) { 1192 1236 return; 1193 1237 } … … 1217 1261 //texty += 1 + fontHeight; 1218 1262 } 1219 1263 1220 int xCursor = -1;1221 int yCursor = -1;1222 1264 if (Main.isDebugEnabled()) { 1223 if (yCursor < tile.getYtile()) { 1224 if (Math.abs(tile.getYtile() % 32) == 31) { 1225 g.fillRect(0, y - 1, mv.getWidth(), 3); 1226 } else { 1227 g.drawLine(0, y, mv.getWidth(), y); 1228 } 1229 //yCursor = t.getYtile(); 1230 } 1231 // This draws the vertical lines for the entire column. Only draw them for the top tile in the column. 1232 if (xCursor < tile.getXtile()) { 1233 if (tile.getXtile() % 32 == 0) { 1234 // level 7 tile boundary 1235 g.fillRect(x - 1, 0, 3, mv.getHeight()); 1236 } else { 1237 g.drawLine(x, 0, x, mv.getHeight()); 1238 } 1239 //xCursor = t.getXtile(); 1240 } 1265 // draw tile outline in semi-transparent red 1266 g.setColor(new Color(255, 0, 0, 50)); 1267 g.draw(coordinateConverter.getScreenQuadrilateralForTile(tile)); 1241 1268 } 1242 1269 } 1243 1270 … … 1396 1423 * @return the tile set 1397 1424 */ 1398 1425 protected TileSet getTileSet(ProjectionBounds bounds, int zoom) { 1399 IProjected topLeftUnshifted = coordinateConverter.shiftDisplayToServer(bounds.getMin()); 1400 IProjected botRightUnshifted = coordinateConverter.shiftDisplayToServer(bounds.getMax()); 1401 TileXY t1 = tileSource.projectedToTileXY(topLeftUnshifted, zoom); 1402 TileXY t2 = tileSource.projectedToTileXY(botRightUnshifted, zoom); 1426 if (zoom == 0) 1427 return new TileSet(); 1428 TileXY t1, t2; 1429 if (coordinateConverter.requiresReprojection()) { 1430 Projection projCurrent = Main.getProjection(); 1431 Projection projServer = Projections.getProjectionByCode(tileSource.getServerCRS()); 1432 bounds = projServer.getEastNorthBoundsBox(bounds, projCurrent); 1433 t1 = tileSource.projectedToTileXY(bounds.getMin().toProjected(), zoom); 1434 t2 = tileSource.projectedToTileXY(bounds.getMax().toProjected(), zoom); 1435 } else { 1436 IProjected topLeftUnshifted = coordinateConverter.shiftDisplayToServer(bounds.getMin()); 1437 IProjected botRightUnshifted = coordinateConverter.shiftDisplayToServer(bounds.getMax()); 1438 t1 = tileSource.projectedToTileXY(topLeftUnshifted, zoom); 1439 t2 = tileSource.projectedToTileXY(botRightUnshifted, zoom); 1440 } 1403 1441 return new TileSet(t1, t2, zoom); 1404 1442 } 1405 1443 … … 1592 1630 1593 1631 // The current zoom tileset should have all of its tiles due to the loadAllTiles(), unless it to tooLarge() 1594 1632 for (Tile t : ts.allExistingTiles()) { 1595 this.paintTileText(t, g , mv);1633 this.paintTileText(t, g); 1596 1634 } 1597 1635 1598 1636 EastNorth min = pb.getMin(); … … 1639 1677 if (Main.isDebugEnabled()) { 1640 1678 Main.debug("getTileForPixelpos("+px+", "+py+')'); 1641 1679 } 1642 Point clicked = new Point(px, py); 1643 TileSet ts = getVisibleTileSet(); 1644 1645 if (!ts.tooLarge()) { 1646 ts.loadAllTiles(false); // make sure there are tile objects for all tiles 1647 } 1648 Stream<Tile> clickedTiles = ts.allExistingTiles().stream() 1649 .filter(t -> coordinateConverter.getRectangleForTile(t).contains(clicked)); 1650 if (Main.isTraceEnabled()) { 1651 clickedTiles = clickedTiles.peek(t -> Main.trace("Clicked on tile: " + t.getXtile() + ' ' + t.getYtile() + 1652 " currentZoomLevel: " + currentZoomLevel)); 1653 } 1654 return clickedTiles.findAny().orElse(null); 1680 TileXY xy = coordinateConverter.getTileforPixel(px, py, currentZoomLevel); 1681 return getTile(xy.getXIndex(), xy.getYIndex(), currentZoomLevel); 1655 1682 } 1656 1683 1657 1684 /** -
src/org/openstreetmap/josm/gui/layer/TMSLayer.java
160 160 } 161 161 return new ScaleList(scales); 162 162 } 163 163 } -
src/org/openstreetmap/josm/gui/layer/WMSLayer.java
7 7 import java.util.ArrayList; 8 8 import java.util.Arrays; 9 9 import java.util.List; 10 import java.util.Set; 11 import java.util.TreeSet; 10 import java.util.Objects; 12 11 13 12 import javax.swing.AbstractAction; 14 13 import javax.swing.Action; … … 27 26 import org.openstreetmap.josm.data.preferences.BooleanProperty; 28 27 import org.openstreetmap.josm.data.preferences.IntegerProperty; 29 28 import org.openstreetmap.josm.data.projection.Projection; 29 import org.openstreetmap.josm.data.projection.Projections; 30 30 import org.openstreetmap.josm.gui.ExtendedDialog; 31 31 import org.openstreetmap.josm.gui.layer.imagery.TileSourceDisplaySettings; 32 32 import org.openstreetmap.josm.tools.CheckParameterUtil; … … 54 54 55 55 private static final String CACHE_REGION_NAME = "WMS"; 56 56 57 private final Set<String> supportedProjections;57 private final List<String> supportedProjections; 58 58 59 59 /** 60 60 * Constructs a new {@code WMSLayer}. … … 65 65 CheckParameterUtil.ensureThat(info.getImageryType() == ImageryType.WMS, "ImageryType is WMS"); 66 66 CheckParameterUtil.ensureParameterNotNull(info.getUrl(), "info.url"); 67 67 TemplatedWMSTileSource.checkUrl(info.getUrl()); 68 this.supportedProjections = new TreeSet<>(info.getServerProjections());68 this.supportedProjections = new ArrayList<>(info.getServerProjections()); 69 69 } 70 70 71 71 @Override … … 86 86 87 87 @Override 88 88 protected AbstractWMSTileSource getTileSource() { 89 AbstractWMSTileSource tileSource = new TemplatedWMSTileSource(info); 89 AbstractWMSTileSource tileSource = new TemplatedWMSTileSource( 90 info, chooseProjection(Main.getProjection())); 90 91 info.setAttribution(tileSource); 91 92 return tileSource; 92 93 } … … 135 136 @Override 136 137 public void projectionChanged(Projection oldValue, Projection newValue) { 137 138 // do not call super - we need custom warning dialog 138 139 Projection tileProjection = null; 139 140 if (!isProjectionSupported(newValue)) { 140 141 String message = 141 142 "<html><body><p>" + tr("The layer {0} does not support the new projection {1}.", … … 152 153 warningDialog.toggleEnable("imagery.wms.projectionSupportWarnings." + tileSource.getBaseUrl()); 153 154 } 154 155 warningDialog.showDialog(); 156 tileProjection = chooseProjection(newValue); 157 } else { 158 tileProjection = newValue; 155 159 } 156 160 157 if (! newValue.equals(oldValue)) {158 tileSource. initProjection(newValue);161 if (!Objects.equals(tileSource.getTileProjection(), tileProjection)) { 162 tileSource.setTileProjection(tileProjection); 159 163 } 160 164 } 161 165 166 private Projection chooseProjection(Projection requested) { 167 if (isProjectionSupported(requested)) { 168 return requested; 169 } else { 170 Projection result = null; 171 for (String code : supportedProjections) { 172 result = Projections.getProjectionByCode(code); 173 if (result != null) 174 break; 175 } 176 if (result == null) { 177 result = Projections.getProjectionByCode("EPSG:4326"); 178 } 179 return result; 180 } 181 } 182 162 183 @Override 163 184 protected Class<? extends TileLoader> getTileLoaderClass() { 164 185 return WMSCachedTileLoader.class; -
src/org/openstreetmap/josm/gui/layer/WMTSLayer.java
14 14 import org.openstreetmap.josm.data.imagery.WMTSTileSource; 15 15 import org.openstreetmap.josm.data.projection.Projection; 16 16 import org.openstreetmap.josm.gui.layer.imagery.TileSourceDisplaySettings; 17 import org.openstreetmap.josm.tools.Utils; 17 18 18 19 /** 19 20 * WMTS layer based on AbstractTileSourceLayer. Overrides few methods to align WMTS to Tile based computations … … 74 75 if (scaleList == null) { 75 76 return getMaxZoomLvl(); 76 77 } 77 double displayScale = Main.map.mapView.getScale() * Main.getProjection().getMetersPerUnit(); // meter per pixel 78 double displayScale = Main.map.mapView.getScale(); 79 if (coordinateConverter.requiresReprojection()) { 80 displayScale *= Main.getProjection().getMetersPerUnit(); 81 } 78 82 Scale snap = scaleList.getSnapScale(displayScale, false); 79 return Math.max( 80 getMinZoomLvl(), 81 Math.min( 82 snap != null ? snap.getIndex() : getMaxZoomLvl(), 83 getMaxZoomLvl() 84 ) 85 ); 83 return Utils.clamp(snap != null ? snap.getIndex() : getMaxZoomLvl(), 84 getMinZoomLvl(), getMaxZoomLvl()); 86 85 } 87 86 88 87 @Override -
src/org/openstreetmap/josm/gui/layer/imagery/ReprojectionTile.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.layer.imagery; 3 4 import java.awt.Dimension; 5 import java.awt.geom.Point2D; 6 import java.awt.image.BufferedImage; 7 8 import org.openstreetmap.gui.jmapviewer.Tile; 9 import org.openstreetmap.gui.jmapviewer.interfaces.TileSource; 10 import org.openstreetmap.josm.Main; 11 import org.openstreetmap.josm.data.ProjectionBounds; 12 import org.openstreetmap.josm.data.coor.EastNorth; 13 import org.openstreetmap.josm.data.projection.Projection; 14 import org.openstreetmap.josm.data.projection.Projections; 15 import org.openstreetmap.josm.tools.ImageWarp; 16 import org.openstreetmap.josm.tools.Utils; 17 18 /** 19 * Tile class that stores a reprojected version of the original tile. 20 */ 21 public class ReprojectionTile extends Tile { 22 23 protected TileAnchor anchor; 24 private double nativeScale; 25 protected boolean maxZoomReached; 26 27 public ReprojectionTile(TileSource source, int xtile, int ytile, int zoom) { 28 super(source, xtile, ytile, zoom); 29 } 30 31 /** 32 * Get the position of the tile inside the image. 33 * @return the position of the tile inside the image 34 * @see #getImage() 35 */ 36 public TileAnchor getAnchor() { 37 return anchor; 38 } 39 40 public double getNativeScale() { 41 return nativeScale; 42 } 43 44 public boolean needsUpdate(double currentScale) { 45 if (Utils.equalsEpsilon(nativeScale, currentScale)) 46 return false; 47 if (maxZoomReached && currentScale < nativeScale) 48 // zoomed in even more - max zoom already reached, so no update 49 return false; 50 return true; 51 } 52 53 @Override 54 public void setImage(BufferedImage image) { 55 if (image == null) { 56 reset(); 57 } else { 58 transform(image); 59 } 60 } 61 62 private synchronized void reset() { 63 this.image = null; 64 this.anchor = null; 65 this.maxZoomReached = false; 66 } 67 68 public void transform(BufferedImage imageIn) { 69 if (!Main.isDisplayingMapView()) { 70 reset(); 71 return; 72 } 73 double scaleMapView = Main.map.mapView.getScale(); 74 ImageWarp.Interpolation interpolation; 75 switch (Main.pref.get("imagery.warp.interpolation", "bilinear")) { 76 case "nearest_neighbor": 77 interpolation = ImageWarp.Interpolation.NEAREST_NEIGHBOR; 78 break; 79 default: 80 interpolation = ImageWarp.Interpolation.BILINEAR; 81 } 82 double margin = interpolation.getMargin(); 83 84 Projection projCurrent = Main.getProjection(); 85 Projection projServer = Projections.getProjectionByCode(source.getServerCRS()); 86 EastNorth en00Server = new EastNorth(source.tileXYtoProjected(xtile, ytile, zoom)); 87 EastNorth en11Server = new EastNorth(source.tileXYtoProjected(xtile + 1, ytile + 1, zoom)); 88 ProjectionBounds pbServer = new ProjectionBounds(en00Server); 89 pbServer.extend(en11Server); 90 // find east-north rectangle in current projection, that will fully contain the tile 91 ProjectionBounds pbTarget = projCurrent.getEastNorthBoundsBox(pbServer, projServer); 92 93 // add margin and align to pixel grid 94 double minEast = Math.floor(pbTarget.minEast / scaleMapView - margin) * scaleMapView; 95 double minNorth = -Math.floor(-(pbTarget.minNorth / scaleMapView - margin)) * scaleMapView; 96 double maxEast = Math.ceil(pbTarget.maxEast / scaleMapView + margin) * scaleMapView; 97 double maxNorth = -Math.ceil(-(pbTarget.maxNorth / scaleMapView + margin)) * scaleMapView; 98 ProjectionBounds pbTargetAligned = new ProjectionBounds(minEast, minNorth, maxEast, maxNorth); 99 100 Dimension dim = getDimension(pbTargetAligned, scaleMapView); 101 Integer scaleFix = limitScale(source.getTileSize(), Math.sqrt(dim.getWidth() * dim.getHeight())); 102 double scale = scaleFix == null ? scaleMapView : scaleMapView * scaleFix; 103 104 ImageWarp.PointTransform pointTransform = pt -> { 105 EastNorth target = new EastNorth(pbTargetAligned.minEast + (pt.getX()) * scale, 106 pbTargetAligned.maxNorth - (pt.getY()) * scale); 107 EastNorth sourceEN = projServer.latlon2eastNorth(projCurrent.eastNorth2latlon(target)); 108 double x2 = source.getTileSize() * 109 (sourceEN.east() - pbServer.minEast) / (pbServer.maxEast - pbServer.minEast); 110 double y2 = source.getTileSize() * 111 (pbServer.maxNorth - sourceEN.north()) / (pbServer.maxNorth - pbServer.minNorth); 112 return new Point2D.Double(x2, y2); 113 }; 114 115 // pixel coordinates of tile origin and opposite tile corner inside the target image 116 // (tile may be deformed / rotated by reprojection) 117 EastNorth en00Current = projCurrent.latlon2eastNorth(projServer.eastNorth2latlon(new EastNorth(en00Server.getX(), en00Server.getY()))); 118 EastNorth en11Current = projCurrent.latlon2eastNorth(projServer.eastNorth2latlon(new EastNorth(en11Server.getX(), en11Server.getY()))); 119 Point2D p00Img = new Point2D.Double( 120 (en00Current.east() - pbTargetAligned.minEast) / scale, 121 (pbTargetAligned.maxNorth - en00Current.north()) / scale); 122 Point2D p11Img = new Point2D.Double( 123 (en11Current.east() - pbTargetAligned.minEast) / scale, 124 (pbTargetAligned.maxNorth - en11Current.north()) / scale); 125 126 BufferedImage imageOut = ImageWarp.warp( 127 imageIn, getDimension(pbTargetAligned, scale), pointTransform, 128 interpolation); 129 synchronized (this) { 130 this.image = imageOut; 131 this.anchor = new TileAnchor(p00Img, p11Img); 132 this.nativeScale = scale; 133 this.maxZoomReached = scaleFix != null; 134 } 135 } 136 137 private Dimension getDimension(ProjectionBounds bounds, double scale) { 138 return new Dimension( 139 (int) Math.round((bounds.maxEast - bounds.minEast) / scale), 140 (int) Math.round((bounds.maxNorth - bounds.minNorth) / scale)); 141 } 142 143 /** 144 * Make sure, the image is not scaled up too much. 145 * 146 * This would not give any significant improvement in image quality and may 147 * exceed the user's memory. The correction factor is a power of 2. 148 * @param lenOrig tile size of original image 149 * @param lenNow (averaged) tile size of warped image 150 * @return factor to shrink if limit is exceeded; 1 if it is already at the 151 * limit, but no change needed; null if it is well below the limit and can 152 * still be scaled up by at least a factor of 2. 153 */ 154 protected Integer limitScale(double lenOrig, double lenNow) { 155 double LIMIT = 3; 156 if (lenNow > LIMIT * lenOrig) { 157 int n = (int) Math.ceil((Math.log(lenNow) - Math.log(LIMIT * lenOrig)) / Math.log(2)); 158 int f = 1 << n; 159 double lenNowFixed = lenNow / f; 160 if (!(lenNowFixed <= LIMIT * lenOrig)) throw new AssertionError(); 161 if (!(lenNowFixed > LIMIT * lenOrig / 2)) throw new AssertionError(); 162 return f; 163 } 164 if (lenNow > LIMIT * lenOrig / 2) 165 return 1; 166 return null; 167 } 168 } -
src/org/openstreetmap/josm/gui/layer/imagery/TileCoordinateConverter.java
Property changes on: src/org/openstreetmap/josm/gui/layer/imagery/ReprojectionTile.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property
11 11 import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate; 12 12 import org.openstreetmap.gui.jmapviewer.interfaces.IProjected; 13 13 import org.openstreetmap.gui.jmapviewer.interfaces.TileSource; 14 import org.openstreetmap.josm.Main; 14 15 import org.openstreetmap.josm.data.coor.EastNorth; 15 16 import org.openstreetmap.josm.data.coor.LatLon; 16 17 import org.openstreetmap.josm.data.projection.Projecting; … … 84 85 * @return The position. 85 86 */ 86 87 public Point2D getPixelForTile(Tile tile) { 87 return this.getPixelForTile(tile.getXtile(), tile.getYtile(), tile.getZoom());88 return getPixelForTile(tile.getXtile(), tile.getYtile(), tile.getZoom()); 88 89 } 89 90 90 91 /** 92 * Convert screen pixel coordinate to tile position at certain zoom level. 93 * @param sx x coordinate (screen pixel) 94 * @param sy y coordinate (screen pixel) 95 * @param zoom zoom level 96 * @return the tile 97 */ 98 public TileXY getTileforPixel(int sx, int sy, int zoom) { 99 if (requiresReprojection()) { 100 LatLon ll = getProjecting().eastNorth2latlonClamped(mapView.getEastNorth(sx, sy)); 101 return tileSource.latLonToTileXY(ll.toCoordinate(), zoom); 102 } else { 103 IProjected p = shiftDisplayToServer(mapView.getEastNorth(sx, sy)); 104 return tileSource.projectedToTileXY(p, zoom); 105 } 106 } 107 108 /** 91 109 * Gets the position of the tile inside the map view. 92 110 * @param tile The tile 93 * @return The positon .111 * @return The positon as a rectangle in screen coordinates 94 112 */ 95 113 public Rectangle2D getRectangleForTile(Tile tile) { 96 114 ICoordinate c1 = tile.getTileSource().tileXYToLatLon(tile); … … 129 147 * @return average number of screen pixels per tile pixel 130 148 */ 131 149 public double getScaleFactor(int zoom) { 132 LatLon topLeft = mapView.getLatLon(0, 0); 133 LatLon botRight = mapView.getLatLon(mapView.getWidth(), mapView.getHeight()); 134 TileXY t1 = tileSource.latLonToTileXY(topLeft.toCoordinate(), zoom); 135 TileXY t2 = tileSource.latLonToTileXY(botRight.toCoordinate(), zoom); 136 150 TileXY t1, t2; 151 if (requiresReprojection()) { 152 LatLon topLeft = mapView.getLatLon(0, 0); 153 LatLon botRight = mapView.getLatLon(mapView.getWidth(), mapView.getHeight()); 154 t1 = tileSource.latLonToTileXY(topLeft.toCoordinate(), zoom); 155 t2 = tileSource.latLonToTileXY(botRight.toCoordinate(), zoom); 156 } else { 157 EastNorth topLeftEN = mapView.getEastNorth(0, 0); 158 EastNorth botRightEN = mapView.getEastNorth(mapView.getWidth(), mapView.getHeight()); 159 t1 = tileSource.projectedToTileXY(topLeftEN.toProjected(), zoom); 160 t2 = tileSource.projectedToTileXY(botRightEN.toProjected(), zoom); 161 } 137 162 int screenPixels = mapView.getWidth()*mapView.getHeight(); 138 163 double tilePixels = Math.abs((t2.getY()-t1.getY())*(t2.getX()-t1.getX())*tileSource.getTileSize()*tileSource.getTileSize()); 139 164 if (screenPixels == 0 || tilePixels == 0) return 1; … … 146 171 * @return position of the tile in screen coordinates 147 172 */ 148 173 public TileAnchor getScreenAnchorForTile(Tile tile) { 149 IProjected p1 = tileSource.tileXYtoProjected(tile.getXtile(), tile.getYtile(), tile.getZoom()); 150 IProjected p2 = tileSource.tileXYtoProjected(tile.getXtile() + 1, tile.getYtile() + 1, tile.getZoom()); 151 return new TileAnchor(pos(p1).getInView(), pos(p2).getInView()); 174 if (requiresReprojection()) { 175 ICoordinate c1 = tile.getTileSource().tileXYToLatLon(tile); 176 ICoordinate c2 = tile.getTileSource().tileXYToLatLon(tile.getXtile() + 1, tile.getYtile() + 1, tile.getZoom()); 177 return new TileAnchor(pos(c1).getInView(), pos(c2).getInView()); 178 } else { 179 IProjected p1 = tileSource.tileXYtoProjected(tile.getXtile(), tile.getYtile(), tile.getZoom()); 180 IProjected p2 = tileSource.tileXYtoProjected(tile.getXtile() + 1, tile.getYtile() + 1, tile.getZoom()); 181 return new TileAnchor(pos(p1).getInView(), pos(p2).getInView()); 182 } 152 183 } 184 185 public boolean requiresReprojection() { 186 return !tileSource.getServerCRS().equals(Main.getProjection().toCode()); 187 } 153 188 } -
src/org/openstreetmap/josm/tools/ImageWarp.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.tools; 3 4 import java.awt.Color; 5 import java.awt.Dimension; 6 import java.awt.geom.Point2D; 7 import java.awt.geom.Rectangle2D; 8 import java.awt.image.BufferedImage; 9 import java.awt.image.WritableRaster; 10 11 /** 12 * Image warping algorithm. 13 * 14 * Deforms an image geometrically according to a given transformation formula. 15 */ 16 public class ImageWarp { 17 18 /** 19 * Transformation that translates the pixel coordinates. 20 */ 21 public static interface PointTransform { 22 Point2D transform(Point2D pt); 23 } 24 25 /** 26 * Interpolation method. 27 */ 28 public enum Interpolation { 29 /** 30 * Nearest neighbor. 31 * 32 * Simplest possible method. Faster, but not very good quality. 33 */ 34 NEAREST_NEIGHBOR(1), 35 /** 36 * Bilinear. 37 * 38 * Decent quality. 39 */ 40 BILINEAR(2); 41 42 private final int margin; 43 44 private Interpolation(int margin) { 45 this.margin = margin; 46 } 47 48 /** 49 * Number of pixels to scan outside the source image. 50 * Used to get smoother borders. 51 * @return the margin 52 */ 53 public int getMargin() { 54 return margin; 55 } 56 } 57 58 /** 59 * Warp an image. 60 * @param srcImg the original image 61 * @param targetDim dimension of the target image 62 * @param invTransform inverse transformation (translates pixel coordinates 63 * of the target image to pixel coordinates of the original image) 64 * @param interpolation the interpolation method 65 * @return the warped image 66 */ 67 public static BufferedImage warp(BufferedImage srcImg, Dimension targetDim, PointTransform invTransform, Interpolation interpolation) { 68 BufferedImage imgTarget = new BufferedImage(targetDim.width, targetDim.height, BufferedImage.TYPE_INT_ARGB); 69 Rectangle2D srcRect = new Rectangle2D.Double(0, 0, srcImg.getWidth(), srcImg.getHeight()); 70 for (int j = 0; j < imgTarget.getHeight(); j++) { 71 for (int i = 0; i < imgTarget.getWidth(); i++) { 72 Point2D srcCoord = invTransform.transform(new Point2D.Double(i, j)); 73 if (isInside(srcCoord, srcRect, interpolation.getMargin())) { 74 int rgb; 75 switch (interpolation) { 76 case NEAREST_NEIGHBOR: 77 rgb = getColor((int) Math.round(srcCoord.getX()), (int) Math.round(srcCoord.getY()), srcImg).getRGB(); 78 break; 79 case BILINEAR: 80 int x0 = (int) Math.floor(srcCoord.getX()); 81 double dx = srcCoord.getX() - x0; 82 int y0 = (int) Math.floor(srcCoord.getY()); 83 double dy = srcCoord.getY() - y0; 84 Color c00 = getColor(x0, y0, srcImg); 85 Color c01 = getColor(x0, y0 + 1, srcImg); 86 Color c10 = getColor(x0 + 1, y0, srcImg); 87 Color c11 = getColor(x0 + 1, y0 + 1, srcImg); 88 int red = (int) Math.round( 89 (c00.getRed() * (1-dx) + c10.getRed() * dx) * (1-dy) + 90 (c01.getRed() * (1-dx) + c11.getRed() * dx) * dy); 91 int green = (int) Math.round( 92 (c00.getGreen()* (1-dx) + c10.getGreen() * dx) * (1-dy) + 93 (c01.getGreen() * (1-dx) + c11.getGreen() * dx) * dy); 94 int blue = (int) Math.round( 95 (c00.getBlue()* (1-dx) + c10.getBlue() * dx) * (1-dy) + 96 (c01.getBlue() * (1-dx) + c11.getBlue() * dx) * dy); 97 int alpha = (int) Math.round( 98 (c00.getAlpha()* (1-dx) + c10.getAlpha() * dx) * (1-dy) + 99 (c01.getAlpha() * (1-dx) + c11.getAlpha() * dx) * dy); 100 rgb = new Color(red, green, blue, alpha).getRGB(); 101 break; 102 default: 103 throw new AssertionError(); 104 } 105 imgTarget.setRGB(i, j, rgb); 106 } 107 } 108 } 109 return imgTarget; 110 } 111 112 private static boolean isInside(Point2D p, Rectangle2D rect, double margin) { 113 return isInside(p.getX(), rect.getMinX(), rect.getMaxX(), margin) && 114 isInside(p.getY(), rect.getMinY(), rect.getMaxY(), margin); 115 } 116 117 private static boolean isInside(double x, double xMin, double xMax, double margin) { 118 return x + margin >= xMin && x - margin <= xMax; 119 } 120 121 private static Color getColor(int x, int y, BufferedImage img) { 122 // border strategy: continue with the color of the outermost pixel, 123 // but change alpha component to fully translucent 124 boolean transparent = false; 125 if (x < 0) { 126 x = 0; 127 transparent = true; 128 } else if (x >= img.getWidth()) { 129 x = img.getWidth() - 1; 130 transparent = true; 131 } 132 if (y < 0) { 133 y = 0; 134 transparent = true; 135 } else if (y >= img.getHeight()) { 136 y = img.getHeight() - 1; 137 transparent = true; 138 } 139 Color clr = new Color(img.getRGB(x, y)); 140 if (!transparent) 141 return clr; 142 // keep color components, but set transparency to 0 143 // (the idea is that border fades out and mixes with next tile) 144 return new Color(clr.getRed(), clr.getGreen(), clr.getBlue(), 0); 145 } 146 } -
src/org/openstreetmap/gui/jmapviewer/Tile.java
Property changes on: src/org/openstreetmap/josm/tools/ImageWarp.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property
75 75 this.xtile = xtile; 76 76 this.ytile = ytile; 77 77 this.zoom = zoom; 78 this. setImage(image);78 this.image = image; 79 79 this.key = getTileKey(source, xtile, ytile, zoom); 80 80 } 81 81 … … 239 239 return image; 240 240 } 241 241 242 public finalvoid setImage(BufferedImage image) {242 public void setImage(BufferedImage image) { 243 243 this.image = image; 244 244 } 245 245 … … 302 302 303 303 @Override 304 304 public String toString() { 305 return "Tile " + key; 305 StringBuilder sb = new StringBuilder("Tile ").append(key); 306 if (loading) { 307 sb.append(" [LOADING...]"); 308 } 309 if (loaded) { 310 sb.append(" [loaded]"); 311 } 312 if (error) { 313 sb.append(" [ERROR]"); 314 } 315 return sb.toString(); 306 316 } 307 317 308 318 /** -
src/org/openstreetmap/gui/jmapviewer/TileXY.java
54 54 public double getY() { 55 55 return y; 56 56 } 57 58 @Override 59 public String toString() { 60 return "TileXY{" + x + ", " + y + "}"; 61 } 57 62 }