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

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

see #16073 - use custom http headers when required

  • Property svn:eol-style set to native
File size: 10.3 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 .setHeaders(info.getCustomHttpHeaders())
89 .setConnectTimeout((int) TimeUnit.SECONDS.toMillis(30))
90 .setReadTimeout((int) TimeUnit.SECONDS.toMillis(60))
91 .connect();
92 if (response.getResponseCode() >= 400) {
93 addError(info, url + " -> HTTP " + response.getResponseCode());
94 } else if (response.getResponseCode() >= 300) {
95 Logging.warn(url + " -> HTTP " + response.getResponseCode());
96 } else {
97 workingURLs.add(url);
98 }
99 response.disconnect();
100 } catch (IOException e) {
101 addError(info, url + " -> " + e);
102 }
103 }
104 }
105
106 private void checkTileUrl(ImageryInfo info, AbstractTileSource tileSource, ICoordinate center, int zoom)
107 throws IOException {
108 TileXY xy = tileSource.latLonToTileXY(center, zoom);
109 for (int i = 0; i < 3; i++) {
110 try {
111 checkUrl(info, tileSource.getTileUrl(zoom, xy.getXIndex(), xy.getYIndex()));
112 return;
113 } catch (IOException e) {
114 // Try up to three times max to allow Bing source to initialize itself
115 // and avoid random network errors
116 Logging.trace(e);
117 if (i == 2) {
118 throw e;
119 }
120 try {
121 Thread.sleep(500);
122 } catch (InterruptedException ex) {
123 Logging.warn(ex);
124 }
125 }
126 }
127 }
128
129 private static LatLon getPointInShape(Shape shape) {
130 final Coordinate p1 = shape.getPoints().get(0);
131 final Bounds bounds = new Bounds(p1.getLat(), p1.getLon(), p1.getLat(), p1.getLon());
132 shape.getPoints().forEach(p -> bounds.extend(p.getLat(), p.getLon()));
133
134 final double w = bounds.getWidth();
135 final double h = bounds.getHeight();
136
137 final double x2 = bounds.getMinLon() + (w / 2.0);
138 final double y2 = bounds.getMinLat() + (h / 2.0);
139
140 final LatLon center = new LatLon(y2, x2);
141
142 // check to see if center is inside shape
143 if (shape.contains(center)) {
144 return center;
145 }
146
147 // if center position (C) is not inside shape, try naively some other positions as follows:
148 final double x1 = bounds.getMinLon() + (.25 * w);
149 final double x3 = bounds.getMinLon() + (.75 * w);
150 final double y1 = bounds.getMinLat() + (.25 * h);
151 final double y3 = bounds.getMinLat() + (.75 * h);
152 // +-----------+
153 // | 5 1 6 |
154 // | 4 C 2 |
155 // | 8 3 7 |
156 // +-----------+
157 for (LatLon candidate : new LatLon[] {
158 new LatLon(y1, x2),
159 new LatLon(y2, x3),
160 new LatLon(y3, x2),
161 new LatLon(y2, x1),
162 new LatLon(y1, x1),
163 new LatLon(y1, x3),
164 new LatLon(y3, x3),
165 new LatLon(y3, x1)
166 }) {
167 if (shape.contains(candidate)) {
168 return candidate;
169 }
170 }
171 return center;
172 }
173
174 private static LatLon getCenter(ImageryBounds bounds) {
175 List<Shape> shapes = bounds.getShapes();
176 return shapes != null && !shapes.isEmpty() ? getPointInShape(shapes.get(0)) : bounds.getCenter();
177 }
178
179 private void checkEntry(ImageryInfo info) {
180 Logging.info("Checking "+ info);
181
182 if (info.getAttributionImageRaw() != null && info.getAttributionImage() == null) {
183 addError(info, "Can't fetch attribution image: " + info.getAttributionImageRaw());
184 }
185
186 checkUrl(info, info.getAttributionImageURL());
187 checkUrl(info, info.getAttributionLinkURL());
188 String eula = info.getEulaAcceptanceRequired();
189 if (eula != null) {
190 checkUrl(info, eula.replaceAll("\\{lang\\}", ""));
191 }
192 checkUrl(info, info.getPermissionReferenceURL());
193 checkUrl(info, info.getTermsOfUseURL());
194
195 try {
196 ImageryBounds bounds = info.getBounds();
197 // Some imagery sources do not define tiles at (0,0). So pickup Greenwich Royal Observatory for global sources
198 ICoordinate center = CoordinateConversion.llToCoor(bounds != null ? getCenter(bounds) : new LatLon(51.47810, -0.00170));
199 AbstractTileSource tileSource = getTileSource(info);
200 checkTileUrl(info, tileSource, center, info.getMinZoom());
201 // checking max zoom for real is complex, see https://josm.openstreetmap.de/ticket/16073#comment:27
202 if (info.getMaxZoom() > 0 && info.getImageryType() != ImageryType.SCANEX) {
203 checkTileUrl(info, tileSource, center, Utils.clamp(12, info.getMinZoom() + 1, info.getMaxZoom()));
204 }
205 } catch (IOException | WMTSGetCapabilitiesException | IllegalArgumentException e) {
206 addError(info, info.getUrl() + " -> " + e.toString());
207 }
208
209 for (ImageryInfo mirror : info.getMirrors()) {
210 checkEntry(mirror);
211 }
212 }
213
214 private static Projection getProjection(ImageryInfo info) {
215 if (!info.getServerProjections().isEmpty()) {
216 Projection proj = Projections.getProjectionByCode(info.getServerProjections().get(0));
217 if (proj != null) {
218 return proj;
219 }
220 }
221 return ProjectionRegistry.getProjection();
222 }
223
224 private static AbstractTileSource getTileSource(ImageryInfo info) throws IOException, WMTSGetCapabilitiesException {
225 switch (info.getImageryType()) {
226 case BING:
227 return new BingAerialTileSource(info);
228 case SCANEX:
229 return new ScanexTileSource(info);
230 case TMS:
231 return new TemplatedTMSTileSource(info);
232 case WMS:
233 return new TemplatedWMSTileSource(info, getProjection(info));
234 case WMS_ENDPOINT:
235 return new WMSEndpointTileSource(info, getProjection(info));
236 case WMTS:
237 return new WMTSTileSource(info, getProjection(info));
238 default:
239 throw new UnsupportedOperationException(info.toString());
240 }
241 }
242
243 /**
244 * Test that available imagery entries are valid.
245 * @throws Exception in case of error
246 */
247 @Test
248 public void testValidityOfAvailableImageryEntries() throws Exception {
249 ImageryLayerInfo.instance.load(false);
250 ImageryLayerInfo.instance.getDefaultLayers().parallelStream().forEach(this::checkEntry);
251 assertTrue(errors.toString().replaceAll("\\}, ", "\n\\}, ").replaceAll(", ImageryInfo\\{", "\n ,ImageryInfo\\{"),
252 errors.isEmpty());
253 }
254}
Note: See TracBrowser for help on using the repository browser.