diff --git a/src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java b/src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java
index 47447f9..dabc204 100644
|
a
|
b
|
|
| 9 | 9 | import java.util.ArrayList; |
| 10 | 10 | import java.util.List; |
| 11 | 11 | import java.util.Locale; |
| 12 | | import java.util.concurrent.Callable; |
| 13 | | import java.util.concurrent.ExecutionException; |
| 14 | | import java.util.concurrent.Future; |
| 15 | | import java.util.concurrent.FutureTask; |
| 16 | | import java.util.concurrent.TimeUnit; |
| | 12 | import java.util.concurrent.ExecutorService; |
| | 13 | import java.util.concurrent.CompletableFuture; |
| | 14 | import java.util.concurrent.Executors; |
| | 15 | import java.util.function.Supplier; |
| 17 | 16 | import java.util.regex.Pattern; |
| 18 | 17 | |
| 19 | 18 | import javax.imageio.ImageIO; |
| … |
… |
|
| 43 | 42 | public class BingAerialTileSource extends TMSTileSource { |
| 44 | 43 | |
| 45 | 44 | private static final String API_KEY = "Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU"; |
| 46 | | private static volatile Future<List<Attribution>> attributions; // volatile is required for getAttribution(), see below. |
| | 45 | private static volatile List<Attribution> attributions; // volatile is required for getAttribution(), see below. |
| 47 | 46 | private static String imageUrlTemplate; |
| 48 | 47 | private static Integer imageryZoomMax; |
| 49 | 48 | private static String[] subdomains; |
| … |
… |
|
| 52 | 51 | private static final Pattern quadkeyPattern = Pattern.compile("\\{quadkey\\}"); |
| 53 | 52 | private static final Pattern culturePattern = Pattern.compile("\\{culture\\}"); |
| 54 | 53 | private String brandLogoUri; |
| | 54 | private final ExecutorService executor; |
| 55 | 55 | |
| 56 | 56 | /** |
| 57 | 57 | * Constructs a new {@code BingAerialTileSource}. |
| 58 | 58 | */ |
| 59 | 59 | public BingAerialTileSource() { |
| 60 | | super(new TileSourceInfo("Bing Aerial Maps", null, null)); |
| | 60 | this(new TileSourceInfo("Bing Aerial Maps", null, null)); |
| 61 | 61 | } |
| 62 | 62 | |
| 63 | 63 | /** |
| … |
… |
public BingAerialTileSource() {
|
| 66 | 66 | */ |
| 67 | 67 | public BingAerialTileSource(TileSourceInfo info) { |
| 68 | 68 | super(info); |
| | 69 | attributions = new ArrayList<>(); |
| | 70 | executor = Executors.newCachedThreadPool(); |
| 69 | 71 | } |
| 70 | 72 | |
| 71 | 73 | protected static class Attribution { |
| … |
… |
public BingAerialTileSource(TileSourceInfo info) {
|
| 79 | 81 | @Override |
| 80 | 82 | public String getTileUrl(int zoom, int tilex, int tiley) throws IOException { |
| 81 | 83 | // make sure that attribution is loaded. otherwise subdomains is null. |
| 82 | | if (getAttribution() == null) |
| | 84 | if (attributions.isEmpty()) |
| 83 | 85 | throw new IOException("Attribution is not loaded yet"); |
| 84 | 86 | |
| 85 | 87 | int t = (zoom + tilex + tiley) % subdomains.length; |
| … |
… |
public String getTermsOfUseURL() {
|
| 234 | 236 | return "http://opengeodata.org/microsoft-imagery-details"; |
| 235 | 237 | } |
| 236 | 238 | |
| 237 | | protected Callable<List<Attribution>> getAttributionLoaderCallable() { |
| 238 | | return new Callable<List<Attribution>>() { |
| | 239 | protected List<Attribution> getAttribution() { |
| | 240 | final Supplier<List<Attribution>> loader = new Supplier<List<Attribution>>() { |
| 239 | 241 | |
| 240 | 242 | @Override |
| 241 | | public List<Attribution> call() throws Exception { |
| 242 | | int waitTimeSec = 1; |
| 243 | | while (true) { |
| 244 | | try { |
| 245 | | InputSource xml = new InputSource(getAttributionUrl().openStream()); |
| 246 | | List<Attribution> r = parseAttributionText(xml); |
| 247 | | return r; |
| 248 | | } catch (IOException ex) { |
| 249 | | System.err.println("Could not connect to Bing API. Will retry in " + waitTimeSec + " seconds."); |
| 250 | | Thread.sleep(TimeUnit.SECONDS.toMillis(waitTimeSec)); |
| 251 | | waitTimeSec *= 2; |
| 252 | | } |
| | 243 | public List<Attribution> get() { |
| | 244 | try { |
| | 245 | InputSource xml = new InputSource(getAttributionUrl().openStream()); |
| | 246 | List<Attribution> r = parseAttributionText(xml); |
| | 247 | return r; |
| | 248 | } catch (IOException ex) { |
| | 249 | System.err.println("Could not connect to Bing API."); |
| 253 | 250 | } |
| | 251 | return null; |
| 254 | 252 | } |
| 255 | 253 | }; |
| 256 | | } |
| 257 | | |
| 258 | | protected List<Attribution> getAttribution() { |
| 259 | | if (attributions == null) { |
| 260 | | // see http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html |
| 261 | | synchronized (BingAerialTileSource.class) { |
| 262 | | if (attributions == null) { |
| 263 | | final FutureTask<List<Attribution>> loader = new FutureTask<>(getAttributionLoaderCallable()); |
| 264 | | new Thread(loader, "bing-attribution-loader").start(); |
| 265 | | attributions = loader; |
| 266 | | } |
| 267 | | } |
| 268 | | } |
| 269 | | try { |
| 270 | | return attributions.get(); |
| 271 | | } catch (ExecutionException ex) { |
| 272 | | throw new RuntimeException(ex.getCause()); |
| 273 | | } catch (InterruptedException ign) { |
| 274 | | System.err.println("InterruptedException: " + ign.getMessage()); |
| 275 | | } |
| 276 | | return null; |
| | 254 | CompletableFuture.supplyAsync(loader, executor).thenAccept(attr -> { |
| | 255 | attributions.clear(); |
| | 256 | attributions.addAll(attr); |
| | 257 | }); |
| | 258 | return attributions; |
| 277 | 259 | } |
| 278 | 260 | |
| 279 | 261 | @Override |