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

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

see #16073 - better error message

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