source: josm/trunk/test/unit/org/openstreetmap/josm/gui/preferences/imagery/ImageryPreferenceTestIT.java@ 14538

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

see #16073 - avoid multiline error messages

  • Property svn:eol-style set to native
File size: 12.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.preferences.imagery;
3
4import static org.junit.Assert.assertTrue;
5
6import java.io.ByteArrayInputStream;
7import java.io.IOException;
8import java.net.URL;
9import java.nio.charset.StandardCharsets;
10import java.util.ArrayList;
11import java.util.Collections;
12import java.util.List;
13import java.util.Map;
14import java.util.TreeMap;
15import java.util.concurrent.TimeUnit;
16
17import javax.imageio.ImageIO;
18
19import org.apache.commons.jcs.access.CacheAccess;
20import org.junit.Before;
21import org.junit.Rule;
22import org.junit.Test;
23import org.openstreetmap.gui.jmapviewer.Coordinate;
24import org.openstreetmap.gui.jmapviewer.TileXY;
25import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
26import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTileSource;
27import org.openstreetmap.gui.jmapviewer.tilesources.BingAerialTileSource;
28import org.openstreetmap.gui.jmapviewer.tilesources.ScanexTileSource;
29import org.openstreetmap.gui.jmapviewer.tilesources.TemplatedTMSTileSource;
30import org.openstreetmap.josm.TestUtils;
31import org.openstreetmap.josm.data.Bounds;
32import org.openstreetmap.josm.data.coor.LatLon;
33import org.openstreetmap.josm.data.imagery.CoordinateConversion;
34import org.openstreetmap.josm.data.imagery.ImageryInfo;
35import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryBounds;
36import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
37import org.openstreetmap.josm.data.imagery.ImageryLayerInfo;
38import org.openstreetmap.josm.data.imagery.Shape;
39import org.openstreetmap.josm.data.imagery.TMSCachedTileLoaderJob;
40import org.openstreetmap.josm.data.imagery.TemplatedWMSTileSource;
41import org.openstreetmap.josm.data.imagery.TileJobOptions;
42import org.openstreetmap.josm.data.imagery.WMSEndpointTileSource;
43import org.openstreetmap.josm.data.imagery.WMTSTileSource;
44import org.openstreetmap.josm.data.imagery.WMTSTileSource.WMTSGetCapabilitiesException;
45import org.openstreetmap.josm.data.projection.Projection;
46import org.openstreetmap.josm.data.projection.ProjectionRegistry;
47import org.openstreetmap.josm.data.projection.Projections;
48import org.openstreetmap.josm.testutils.JOSMTestRules;
49import org.openstreetmap.josm.tools.HttpClient;
50import org.openstreetmap.josm.tools.HttpClient.Response;
51import org.openstreetmap.josm.tools.Logging;
52import org.openstreetmap.josm.tools.Utils;
53
54import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
55
56/**
57 * Integration tests of {@link ImageryPreference} class.
58 */
59public class ImageryPreferenceTestIT {
60
61 /**
62 * Setup rule
63 */
64 @Rule
65 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
66 public JOSMTestRules test = new JOSMTestRules().https().preferences().projection().projectionNadGrids().timeout(10000*120);
67
68 private final Map<String, Map<ImageryInfo, List<String>>> errors = Collections.synchronizedMap(new TreeMap<>());
69 private final Map<String, byte[]> workingURLs = Collections.synchronizedMap(new TreeMap<>());
70
71 private TMSCachedTileLoaderJob helper;
72 private List<String> ignoredErrors;
73
74 /**
75 * Setup test
76 * @throws IOException in case of I/O error
77 */
78 @Before
79 public void before() throws IOException {
80 helper = new TMSCachedTileLoaderJob(null, null, new CacheAccess<>(null), new TileJobOptions(0, 0, null, 0), null);
81 ignoredErrors = TestUtils.getIgnoredErrorMessages(ImageryPreferenceTestIT.class);
82 }
83
84 private boolean addError(ImageryInfo info, String error) {
85 return !ignoredErrors.contains(error) &&
86 errors.computeIfAbsent(info.getCountryCode(), x -> Collections.synchronizedMap(new TreeMap<>()))
87 .computeIfAbsent(info, x -> Collections.synchronizedList(new ArrayList<>()))
88 .add(error);
89 }
90
91 private byte[] checkUrl(ImageryInfo info, String url) {
92 if (url != null && !url.isEmpty()) {
93 if (workingURLs.containsKey(url)) {
94 return workingURLs.get(url);
95 }
96 try {
97 Response response = HttpClient.create(new URL(url))
98 .setHeaders(info.getCustomHttpHeaders())
99 .setConnectTimeout((int) TimeUnit.SECONDS.toMillis(30))
100 .setReadTimeout((int) TimeUnit.SECONDS.toMillis(60))
101 .connect();
102 if (response.getResponseCode() >= 400) {
103 addError(info, url + " -> HTTP " + response.getResponseCode());
104 } else if (response.getResponseCode() >= 300) {
105 Logging.warn(url + " -> HTTP " + response.getResponseCode());
106 }
107 try {
108 byte[] data = Utils.readBytesFromStream(response.getContent());
109 if (response.getResponseCode() < 300) {
110 workingURLs.put(url, data);
111 }
112 return data;
113 } finally {
114 response.disconnect();
115 }
116 } catch (IOException e) {
117 addError(info, url + " -> " + e);
118 }
119 }
120 return new byte[0];
121 }
122
123 private void checkLinkUrl(ImageryInfo info, String url) {
124 if (url != null && checkUrl(info, url).length == 0) {
125 addError(info, url + " -> returned empty contents");
126 }
127 }
128
129 private void checkTileUrl(ImageryInfo info, AbstractTileSource tileSource, ICoordinate center, int zoom)
130 throws IOException {
131 TileXY xy = tileSource.latLonToTileXY(center, zoom);
132 for (int i = 0; i < 3; i++) {
133 try {
134 String url = tileSource.getTileUrl(zoom, xy.getXIndex(), xy.getYIndex());
135 byte[] data = checkUrl(info, url);
136 try (ByteArrayInputStream bais = new ByteArrayInputStream(data)) {
137 if (ImageIO.read(bais) == null) {
138 addImageError(info, url, data, "did not return an image");
139 }
140 } catch (IOException e) {
141 addImageError(info, url, data, e.toString());
142 Logging.trace(e);
143 }
144 return;
145 } catch (IOException e) {
146 // Try up to three times max to allow Bing source to initialize itself
147 // and avoid random network errors
148 Logging.trace(e);
149 if (i == 2) {
150 throw e;
151 }
152 try {
153 Thread.sleep(500);
154 } catch (InterruptedException ex) {
155 Logging.warn(ex);
156 }
157 }
158 }
159 }
160
161 private void addImageError(ImageryInfo info, String url, byte[] data, String defaultMessage) {
162 // Check if we have received an error message
163 String error = helper.detectErrorMessage(new String(data, StandardCharsets.UTF_8));
164 addError(info, url + " -> " + (error != null ? error.split("\\n")[0] : defaultMessage));
165 }
166
167 private static LatLon getPointInShape(Shape shape) {
168 final Coordinate p1 = shape.getPoints().get(0);
169 final Bounds bounds = new Bounds(p1.getLat(), p1.getLon(), p1.getLat(), p1.getLon());
170 shape.getPoints().forEach(p -> bounds.extend(p.getLat(), p.getLon()));
171
172 final double w = bounds.getWidth();
173 final double h = bounds.getHeight();
174
175 final double x2 = bounds.getMinLon() + (w / 2.0);
176 final double y2 = bounds.getMinLat() + (h / 2.0);
177
178 final LatLon center = new LatLon(y2, x2);
179
180 // check to see if center is inside shape
181 if (shape.contains(center)) {
182 return center;
183 }
184
185 // if center position (C) is not inside shape, try naively some other positions as follows:
186 final double x1 = bounds.getMinLon() + (.25 * w);
187 final double x3 = bounds.getMinLon() + (.75 * w);
188 final double y1 = bounds.getMinLat() + (.25 * h);
189 final double y3 = bounds.getMinLat() + (.75 * h);
190 // +-----------+
191 // | 5 1 6 |
192 // | 4 C 2 |
193 // | 8 3 7 |
194 // +-----------+
195 for (LatLon candidate : new LatLon[] {
196 new LatLon(y1, x2),
197 new LatLon(y2, x3),
198 new LatLon(y3, x2),
199 new LatLon(y2, x1),
200 new LatLon(y1, x1),
201 new LatLon(y1, x3),
202 new LatLon(y3, x3),
203 new LatLon(y3, x1)
204 }) {
205 if (shape.contains(candidate)) {
206 return candidate;
207 }
208 }
209 return center;
210 }
211
212 private static LatLon getCenter(ImageryBounds bounds) {
213 List<Shape> shapes = bounds.getShapes();
214 return shapes != null && !shapes.isEmpty() ? getPointInShape(shapes.get(0)) : bounds.getCenter();
215 }
216
217 private void checkEntry(ImageryInfo info) {
218 Logging.info("Checking "+ info);
219
220 if (info.getAttributionImageRaw() != null && info.getAttributionImage() == null) {
221 addError(info, "Can't fetch attribution image: " + info.getAttributionImageRaw());
222 }
223
224 checkLinkUrl(info, info.getAttributionImageURL());
225 checkLinkUrl(info, info.getAttributionLinkURL());
226 String eula = info.getEulaAcceptanceRequired();
227 if (eula != null) {
228 checkLinkUrl(info, eula.replaceAll("\\{lang\\}", ""));
229 }
230 checkLinkUrl(info, info.getPermissionReferenceURL());
231 checkLinkUrl(info, info.getTermsOfUseURL());
232
233 try {
234 ImageryBounds bounds = info.getBounds();
235 // Some imagery sources do not define tiles at (0,0). So pickup Greenwich Royal Observatory for global sources
236 ICoordinate center = CoordinateConversion.llToCoor(bounds != null ? getCenter(bounds) : new LatLon(51.47810, -0.00170));
237 AbstractTileSource tileSource = getTileSource(info);
238 checkTileUrl(info, tileSource, center, info.getMinZoom());
239 // checking max zoom for real is complex, see https://josm.openstreetmap.de/ticket/16073#comment:27
240 if (info.getMaxZoom() > 0 && info.getImageryType() != ImageryType.SCANEX) {
241 checkTileUrl(info, tileSource, center, Utils.clamp(12, info.getMinZoom() + 1, info.getMaxZoom()));
242 }
243 } catch (IOException | WMTSGetCapabilitiesException | IllegalArgumentException e) {
244 addError(info, info.getUrl() + " -> " + e.toString());
245 }
246
247 for (ImageryInfo mirror : info.getMirrors()) {
248 checkEntry(mirror);
249 }
250 }
251
252 private static Projection getProjection(ImageryInfo info) {
253 if (!info.getServerProjections().isEmpty()) {
254 Projection proj = Projections.getProjectionByCode(info.getServerProjections().get(0));
255 if (proj != null) {
256 return proj;
257 }
258 }
259 return ProjectionRegistry.getProjection();
260 }
261
262 private static AbstractTileSource getTileSource(ImageryInfo info) throws IOException, WMTSGetCapabilitiesException {
263 switch (info.getImageryType()) {
264 case BING:
265 return new BingAerialTileSource(info);
266 case SCANEX:
267 return new ScanexTileSource(info);
268 case TMS:
269 return new TemplatedTMSTileSource(info);
270 case WMS:
271 return new TemplatedWMSTileSource(info, getProjection(info));
272 case WMS_ENDPOINT:
273 return new WMSEndpointTileSource(info, getProjection(info));
274 case WMTS:
275 return new WMTSTileSource(info, getProjection(info));
276 default:
277 throw new UnsupportedOperationException(info.toString());
278 }
279 }
280
281 /**
282 * Test that available imagery entries are valid.
283 * @throws Exception in case of error
284 */
285 @Test
286 public void testValidityOfAvailableImageryEntries() throws Exception {
287 ImageryLayerInfo.instance.load(false);
288 ImageryLayerInfo.instance.getDefaultLayers().parallelStream().forEach(this::checkEntry);
289 assertTrue(errors.toString().replaceAll("\\}, ", "\n\\}, ").replaceAll(", ImageryInfo\\{", "\n ,ImageryInfo\\{"),
290 errors.isEmpty());
291 }
292}
Note: See TracBrowser for help on using the repository browser.