source: josm/trunk/src/org/openstreetmap/josm/gui/layer/imagery/ReprojectionTile.java@ 12630

Last change on this file since 12630 was 12630, checked in by Don-vip, 7 years ago

see #15182 - deprecate Main.map and Main.isDisplayingMapView(). Replacements: gui.MainApplication.getMap() / gui.MainApplication.isDisplayingMapView()

  • Property svn:eol-style set to native
File size: 8.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.layer.imagery;
3
4import java.awt.Dimension;
5import java.awt.geom.Point2D;
6import java.awt.image.BufferedImage;
7
8import org.openstreetmap.gui.jmapviewer.Tile;
9import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
10import org.openstreetmap.josm.Main;
11import org.openstreetmap.josm.data.ProjectionBounds;
12import org.openstreetmap.josm.data.coor.EastNorth;
13import org.openstreetmap.josm.data.projection.Projection;
14import org.openstreetmap.josm.data.projection.Projections;
15import org.openstreetmap.josm.gui.MainApplication;
16import org.openstreetmap.josm.tools.ImageWarp;
17import org.openstreetmap.josm.tools.Utils;
18
19/**
20 * Tile class that stores a reprojected version of the original tile.
21 * @since 11858
22 */
23public class ReprojectionTile extends Tile {
24
25 protected TileAnchor anchor;
26 private double nativeScale;
27 protected boolean maxZoomReached;
28
29 /**
30 * Constructs a new {@code ReprojectionTile}.
31 * @param source sourec tile
32 * @param xtile X coordinate
33 * @param ytile Y coordinate
34 * @param zoom zoom level
35 */
36 public ReprojectionTile(TileSource source, int xtile, int ytile, int zoom) {
37 super(source, xtile, ytile, zoom);
38 }
39
40 /**
41 * Get the position of the tile inside the image.
42 * @return the position of the tile inside the image
43 * @see #getImage()
44 */
45 public TileAnchor getAnchor() {
46 return anchor;
47 }
48
49 /**
50 * Get the scale that was used for reprojecting the tile.
51 *
52 * This is not necessarily the mapview scale, but may be
53 * adjusted to avoid excessively large cache image.
54 * @return the scale that was used for reprojecting the tile
55 */
56 public double getNativeScale() {
57 return nativeScale;
58 }
59
60 /**
61 * Check if it is necessary to refresh the cache to match the current mapview
62 * scale and get optimized image quality.
63 *
64 * When the maximum zoom is exceeded, this method will generally return false.
65 * @param currentScale the current mapview scale
66 * @return true if the tile should be reprojected again from the source image.
67 */
68 public synchronized boolean needsUpdate(double currentScale) {
69 if (Utils.equalsEpsilon(nativeScale, currentScale))
70 return false;
71 return !maxZoomReached || currentScale >= nativeScale;
72 }
73
74 @Override
75 public void setImage(BufferedImage image) {
76 if (image == null) {
77 reset();
78 } else {
79 transform(image);
80 }
81 }
82
83 /**
84 * Invalidate tile - mark it as not loaded.
85 */
86 public synchronized void invalidate() {
87 this.loaded = false;
88 this.loading = false;
89 this.error = false;
90 this.error_message = null;
91 }
92
93 private synchronized void reset() {
94 this.image = null;
95 this.anchor = null;
96 this.maxZoomReached = false;
97 }
98
99 /**
100 * Transforms the given image.
101 * @param imageIn tile image to reproject
102 */
103 protected void transform(BufferedImage imageIn) {
104 if (!MainApplication.isDisplayingMapView()) {
105 reset();
106 return;
107 }
108 double scaleMapView = MainApplication.getMap().mapView.getScale();
109 ImageWarp.Interpolation interpolation;
110 switch (Main.pref.get("imagery.warp.pixel-interpolation", "bilinear")) {
111 case "nearest_neighbor":
112 interpolation = ImageWarp.Interpolation.NEAREST_NEIGHBOR;
113 break;
114 default:
115 interpolation = ImageWarp.Interpolation.BILINEAR;
116 }
117
118 Projection projCurrent = Main.getProjection();
119 Projection projServer = Projections.getProjectionByCode(source.getServerCRS());
120 EastNorth en00Server = new EastNorth(source.tileXYtoProjected(xtile, ytile, zoom));
121 EastNorth en11Server = new EastNorth(source.tileXYtoProjected(xtile + 1, ytile + 1, zoom));
122 ProjectionBounds pbServer = new ProjectionBounds(en00Server);
123 pbServer.extend(en11Server);
124 // find east-north rectangle in current projection, that will fully contain the tile
125 ProjectionBounds pbTarget = projCurrent.getEastNorthBoundsBox(pbServer, projServer);
126
127 double margin = 2;
128 Dimension dim = getDimension(pbMarginAndAlign(pbTarget, scaleMapView, margin), scaleMapView);
129 Integer scaleFix = limitScale(source.getTileSize(), Math.sqrt(dim.getWidth() * dim.getHeight()));
130 double scale = scaleFix == null ? scaleMapView : (scaleMapView * scaleFix);
131 ProjectionBounds pbTargetAligned = pbMarginAndAlign(pbTarget, scale, margin);
132
133 ImageWarp.PointTransform pointTransform = pt -> {
134 EastNorth target = new EastNorth(pbTargetAligned.minEast + pt.getX() * scale,
135 pbTargetAligned.maxNorth - pt.getY() * scale);
136 EastNorth sourceEN = projServer.latlon2eastNorth(projCurrent.eastNorth2latlon(target));
137 double x = source.getTileSize() *
138 (sourceEN.east() - pbServer.minEast) / (pbServer.maxEast - pbServer.minEast);
139 double y = source.getTileSize() *
140 (pbServer.maxNorth - sourceEN.north()) / (pbServer.maxNorth - pbServer.minNorth);
141 return new Point2D.Double(x, y);
142 };
143
144 // pixel coordinates of tile origin and opposite tile corner inside the target image
145 // (tile may be deformed / rotated by reprojection)
146 EastNorth en00Current = projCurrent.latlon2eastNorth(projServer.eastNorth2latlon(en00Server));
147 EastNorth en11Current = projCurrent.latlon2eastNorth(projServer.eastNorth2latlon(en11Server));
148 Point2D p00Img = new Point2D.Double(
149 (en00Current.east() - pbTargetAligned.minEast) / scale,
150 (pbTargetAligned.maxNorth - en00Current.north()) / scale);
151 Point2D p11Img = new Point2D.Double(
152 (en11Current.east() - pbTargetAligned.minEast) / scale,
153 (pbTargetAligned.maxNorth - en11Current.north()) / scale);
154
155 ImageWarp.PointTransform transform;
156 int stride = Main.pref.getInteger("imagery.warp.projection-interpolation.stride", 7);
157 if (stride > 0) {
158 transform = new ImageWarp.GridTransform(pointTransform, stride);
159 } else {
160 transform = pointTransform;
161 }
162 BufferedImage imageOut = ImageWarp.warp(
163 imageIn, getDimension(pbTargetAligned, scale),
164 transform, interpolation);
165 synchronized (this) {
166 this.image = imageOut;
167 this.anchor = new TileAnchor(p00Img, p11Img);
168 this.nativeScale = scale;
169 this.maxZoomReached = scaleFix != null;
170 }
171 }
172
173 // add margin and align to pixel grid
174 private static ProjectionBounds pbMarginAndAlign(ProjectionBounds box, double scale, double margin) {
175 double minEast = Math.floor(box.minEast / scale - margin) * scale;
176 double minNorth = -Math.floor(-(box.minNorth / scale - margin)) * scale;
177 double maxEast = Math.ceil(box.maxEast / scale + margin) * scale;
178 double maxNorth = -Math.ceil(-(box.maxNorth / scale + margin)) * scale;
179 return new ProjectionBounds(minEast, minNorth, maxEast, maxNorth);
180 }
181
182 // dimension in pixel
183 private static Dimension getDimension(ProjectionBounds bounds, double scale) {
184 return new Dimension(
185 (int) Math.round((bounds.maxEast - bounds.minEast) / scale),
186 (int) Math.round((bounds.maxNorth - bounds.minNorth) / scale));
187 }
188
189 /**
190 * Make sure, the image is not scaled up too much.
191 *
192 * This would not give any significant improvement in image quality and may
193 * exceed the user's memory. The correction factor is a power of 2.
194 * @param lenOrig tile size of original image
195 * @param lenNow (averaged) tile size of warped image
196 * @return factor to shrink if limit is exceeded; 1 if it is already at the
197 * limit, but no change needed; null if it is well below the limit and can
198 * still be scaled up by at least a factor of 2.
199 */
200 protected Integer limitScale(double lenOrig, double lenNow) {
201 final double limit = 3;
202 if (lenNow > limit * lenOrig) {
203 int n = (int) Math.ceil((Math.log(lenNow) - Math.log(limit * lenOrig)) / Math.log(2));
204 int f = 1 << n;
205 double lenNowFixed = lenNow / f;
206 if (lenNowFixed > limit * lenOrig) throw new AssertionError();
207 if (lenNowFixed <= limit * lenOrig / 2) throw new AssertionError();
208 return f;
209 }
210 if (lenNow > limit * lenOrig / 2)
211 return 1;
212 return null;
213 }
214}
Note: See TracBrowser for help on using the repository browser.