Changeset 36174 in osm for applications


Ignore:
Timestamp:
2023-10-17T15:05:06+02:00 (9 months ago)
Author:
taylor.smock
Message:

See #23227: Add layer option for Bing Imagery

iD/Rapid use the layer AerialOSM instead of Aerial, and that layer is higher
resolution (but older) in some locations. This adds a method to set the Bing Maps
layer (see https://learn.microsoft.com/en-us/bingmaps/rest-services/imagery/get-imagery-metadata#template-parameters )
so that downstream users can decide whether their agreement with Bing Maps allows
the use of AerialOSM. We have not received communication as to which layer Bing
wants us to use for editing OpenStreetMap.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java

    r35456 r36174  
    1919import java.util.stream.Collectors;
    2020
     21import javax.xml.XMLConstants;
    2122import javax.xml.parsers.DocumentBuilder;
    2223import javax.xml.parsers.DocumentBuilderFactory;
     
    5152    /** Setting key for Bing API key */
    5253    public static final String API_KEY_SETTING = "jmapviewer.bing.api-key";
    53     /** Placeholder to specify Bing API key in metadata API URL*/
     54    /** Placeholder to specify Bing API key in metadata API URL */
    5455    public static final String API_KEY_PLACEHOLDER = "{apikey}";
     56    /** Placeholder to specify Bing API layer in metadata API URL */
     57    private static final String API_KEY_LAYER = "{layer}";
    5558
    5659    /** Bing Metadata API URL */
    5760    private static final String METADATA_API_URL =
    58             "https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?include=ImageryProviders&output=xml&key=" + API_KEY_PLACEHOLDER;
     61            "https://dev.virtualearth.net/REST/v1/Imagery/Metadata/{layer}?include=ImageryProviders&output=xml&key=" + API_KEY_PLACEHOLDER;
    5962    /** Original Bing API key created by Potlatch2 developers in 2010 */
    6063    private static final String API_KEY = "Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU";
    6164   
    62     private static volatile Future<List<Attribution>> attributions; // volatile is required for getAttribution(), see below.
    63     private static String imageUrlTemplate;
    64     private static Integer imageryZoomMax;
    65     private static String[] subdomains;
    66 
    67     private static final Pattern subdomainPattern = Pattern.compile("\\{subdomain\\}");
    68     private static final Pattern quadkeyPattern = Pattern.compile("\\{quadkey\\}");
    69     private static final Pattern culturePattern = Pattern.compile("\\{culture\\}");
     65    private volatile Future<List<Attribution>> attributions; // volatile is required for getAttribution(), see below.
     66    private String imageUrlTemplate;
     67    private int imageryZoomMax = Integer.MIN_VALUE;
     68    private String[] subdomains;
     69
     70    private static final Pattern subdomainPattern = Pattern.compile("\\{subdomain}");
     71    private static final Pattern quadkeyPattern = Pattern.compile("\\{quadkey}");
     72    private static final Pattern culturePattern = Pattern.compile("\\{culture}");
    7073    private String brandLogoUri;
     74    private String layer = "Aerial";
    7175
    7276    /**
     
    110114    }
    111115
     116    /**
     117     * Set the layer for this Bing tile source
     118     * @param layer The layer to use. See
     119     *              <a href="https://learn.microsoft.com/en-us/bingmaps/rest-services/imagery/get-imagery-metadata#template-parameters">
     120     *                  get-imagery-metadata
     121     *              </a> for valid layers.
     122     * @since JMapViewer 2.18
     123     */
     124    protected void setLayer(String layer) {
     125        this.layer = layer;
     126    }
     127
    112128    protected URL getAttributionUrl() throws MalformedURLException {
    113129        return new URL(FeatureAdapter.getSetting(METADATA_API_SETTING, METADATA_API_URL)
    114                 .replace(API_KEY_PLACEHOLDER, FeatureAdapter.getSetting(API_KEY_SETTING, API_KEY)));
     130                .replace(API_KEY_PLACEHOLDER, FeatureAdapter.getSetting(API_KEY_SETTING, API_KEY))
     131                .replace(API_KEY_LAYER, this.layer));
    115132    }
    116133
     
    118135        try {
    119136            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
     137            factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
     138            factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
     139            factory.setXIncludeAware(false);
    120140            DocumentBuilder builder = factory.newDocumentBuilder();
    121141            Document document = builder.parse(xml);
     
    123143            XPathFactory xPathFactory = XPathFactory.newInstance();
    124144            XPath xpath = xPathFactory.newXPath();
    125             imageUrlTemplate = xpath.compile("//ImageryMetadata/ImageUrl/text()").evaluate(document).replace(
    126                     "http://ecn.{subdomain}.tiles.virtualearth.net/",
    127                     "https://ecn.{subdomain}.tiles.virtualearth.net/");
    128             imageUrlTemplate = culturePattern.matcher(imageUrlTemplate).replaceAll(Locale.getDefault().toString());
    129             imageryZoomMax = Integer.valueOf(xpath.compile("//ImageryMetadata/ZoomMax/text()").evaluate(document));
     145            setImageUrlTemplate(xpath.compile("//ImageryMetadata/ImageUrl/text()").evaluate(document));
     146            setImageryZoomMax(Integer.parseInt(xpath.compile("//ImageryMetadata/ZoomMax/text()").evaluate(document)));
    130147
    131148            NodeList subdomainTxt = (NodeList) xpath.compile("//ImageryMetadata/ImageUrlSubdomains/string/text()")
    132149                    .evaluate(document, XPathConstants.NODESET);
    133             subdomains = new String[subdomainTxt.getLength()];
    134             for (int i = 0; i < subdomainTxt.getLength(); i++) {
    135                 subdomains[i] = subdomainTxt.item(i).getNodeValue();
    136             }
     150            setSubdomains(subdomainTxt);
    137151
    138152            brandLogoUri = xpath.compile("/Response/BrandLogoUri/text()").evaluate(document);
     
    164178                    attr.minZoom = Integer.parseInt(zoomMinXpath.evaluate(areaNode));
    165179
    166                     Double southLat = Double.valueOf(southLatXpath.evaluate(areaNode));
    167                     Double northLat = Double.valueOf(northLatXpath.evaluate(areaNode));
    168                     Double westLon = Double.valueOf(westLonXpath.evaluate(areaNode));
    169                     Double eastLon = Double.valueOf(eastLonXpath.evaluate(areaNode));
     180                    double southLat = Double.parseDouble(southLatXpath.evaluate(areaNode));
     181                    double northLat = Double.parseDouble(northLatXpath.evaluate(areaNode));
     182                    double westLon = Double.parseDouble(westLonXpath.evaluate(areaNode));
     183                    double eastLon = Double.parseDouble(eastLonXpath.evaluate(areaNode));
    170184                    attr.min = new Coordinate(southLat, westLon);
    171185                    attr.max = new Coordinate(northLat, eastLon);
     
    184198    @Override
    185199    public int getMaxZoom() {
    186         if (imageryZoomMax != null)
     200        if (imageryZoomMax != Integer.MIN_VALUE)
    187201            return imageryZoomMax;
    188202        else
     
    210224            } else {
    211225                // Some Linux distributions (like Debian) will remove Bing logo from sources, so get it at runtime
    212                 for (int i = 0; i < 5 && getAttribution() == null; i++) {
     226                for (int i = 0; i < 5 && (getAttribution() == null || getAttribution().isEmpty()); i++) {
    213227                    // Makes sure attribution is loaded
    214228                    if (JMapViewer.debug) {
    215                         System.out.println("Bing attribution attempt " + (i+1));
     229                        LOG.log(Level.FINE, "Bing attribution attempt {0}", (i + 1));
    216230                    }
    217231                }
    218232                if (brandLogoUri != null && !brandLogoUri.isEmpty()) {
    219                     System.out.println("Reading Bing logo from "+brandLogoUri);
     233                    LOG.log(Level.FINE, "Reading Bing logo from {0}", brandLogoUri);
    220234                    return FeatureAdapter.readImage(new URL(brandLogoUri));
    221235                }
     
    243257
    244258    protected Callable<List<Attribution>> getAttributionLoaderCallable() {
    245         return new Callable<List<Attribution>>() {
    246 
    247             @Override
    248             public List<Attribution> call() throws Exception {
    249                 int waitTimeSec = 1;
    250                 while (true) {
    251                     try {
    252                         InputSource xml = new InputSource(getAttributionUrl().openStream());
    253                         List<Attribution> r = parseAttributionText(xml);
    254                         System.out.println("Successfully loaded Bing attribution data.");
    255                         return r;
    256                     } catch (IOException ex) {
    257                         LOG.log(Level.SEVERE, "Could not connect to Bing API. Will retry in " + waitTimeSec + " seconds.");
    258                         Thread.sleep(TimeUnit.SECONDS.toMillis(waitTimeSec));
    259                         waitTimeSec *= 2;
    260                     }
     259        return () -> {
     260            int waitTimeSec = 1;
     261            while (true) {
     262                try {
     263                    InputSource xml = new InputSource(getAttributionUrl().openStream());
     264                    List<Attribution> r = parseAttributionText(xml);
     265                    LOG.log(Level.FINE, "Successfully loaded Bing attribution data.");
     266                    return r;
     267                } catch (IOException ex) {
     268                    LOG.log(Level.SEVERE, "Could not connect to Bing API. Will retry in " + waitTimeSec + " seconds.");
     269                    LOG.log(Level.FINE, ex.getMessage(), ex);
     270                    Thread.sleep(TimeUnit.SECONDS.toMillis(waitTimeSec));
     271                    waitTimeSec *= 2;
    261272                }
    262273            }
     
    278289            return attributions.get();
    279290        } catch (ExecutionException ex) {
    280             throw new RuntimeException(ex.getCause());
     291            throw new RuntimeException(ex);
    281292        } catch (InterruptedException ign) {
    282             LOG.log(Level.SEVERE, "InterruptedException: " + ign.getMessage());
     293            LOG.log(Level.SEVERE, "InterruptedException: {0}", ign.getMessage());
     294            LOG.log(Level.FINE, ign.getMessage(), ign);
     295            Thread.currentThread().interrupt();
    283296        }
    284297        return null;
     
    298311                    .collect(Collectors.joining(" "));
    299312        } catch (RuntimeException e) {
    300             e.printStackTrace();
     313            LOG.log(Level.SEVERE, e.getMessage(), e);
    301314        }
    302315        return "Error loading Bing attribution data";
     316    }
     317
     318    private void setImageUrlTemplate(String template) {
     319        String noHttpTemplate = template.replace("http://ecn.{subdomain}.tiles.virtualearth.net/",
     320                "https://ecn.{subdomain}.tiles.virtualearth.net/");
     321        this.imageUrlTemplate = culturePattern.matcher(noHttpTemplate).replaceAll(Locale.getDefault().toString());
     322    }
     323
     324    private void setImageryZoomMax(int maxZoom) {
     325        imageryZoomMax = maxZoom;
     326    }
     327
     328    private void setSubdomains(NodeList subdomainTxt) {
     329        subdomains = new String[subdomainTxt.getLength()];
     330        for (int i = 0; i < subdomainTxt.getLength(); i++) {
     331            subdomains[i] = subdomainTxt.item(i).getNodeValue();
     332        }
    303333    }
    304334
Note: See TracChangeset for help on using the changeset viewer.