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

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

see #16073 - check response contents
see #16854 - stability of created primitive IDs (accidental commit...)

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