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

Last change on this file since 13265 was 12846, checked in by bastiK, 7 years ago

see #15229 - use Config.getPref() wherever possible

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