source: osm/applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java@ 28553

Last change on this file since 28553 was 28553, checked in by jttt, 12 years ago

better message when bing attribution is not loaded yet

File size: 11.0 KB
Line 
1package org.openstreetmap.gui.jmapviewer.tilesources;
2
3//License: GPL.
4
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.Image;
8import java.io.IOException;
9import java.net.MalformedURLException;
10import java.net.URL;
11import java.util.ArrayList;
12import java.util.List;
13import java.util.Locale;
14import java.util.concurrent.Callable;
15import java.util.concurrent.ExecutionException;
16import java.util.concurrent.Executors;
17import java.util.concurrent.Future;
18import java.util.concurrent.TimeUnit;
19import java.util.concurrent.TimeoutException;
20import java.util.regex.Pattern;
21
22import javax.imageio.ImageIO;
23import javax.xml.parsers.DocumentBuilder;
24import javax.xml.parsers.DocumentBuilderFactory;
25import javax.xml.parsers.ParserConfigurationException;
26import javax.xml.xpath.XPath;
27import javax.xml.xpath.XPathConstants;
28import javax.xml.xpath.XPathExpression;
29import javax.xml.xpath.XPathExpressionException;
30import javax.xml.xpath.XPathFactory;
31
32import org.openstreetmap.gui.jmapviewer.Coordinate;
33import org.w3c.dom.Document;
34import org.w3c.dom.Node;
35import org.w3c.dom.NodeList;
36import org.xml.sax.InputSource;
37import org.xml.sax.SAXException;
38
39public class BingAerialTileSource extends AbstractTMSTileSource {
40
41 private static String API_KEY = "Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU";
42 private static volatile Future<List<Attribution>> attributions; // volatile is required for getAttribution(), see below.
43 private static String imageUrlTemplate;
44 private static Integer imageryZoomMax;
45 private static String[] subdomains;
46
47 private static final Pattern subdomainPattern = Pattern.compile("\\{subdomain\\}");
48 private static final Pattern quadkeyPattern = Pattern.compile("\\{quadkey\\}");
49 private static final Pattern culturePattern = Pattern.compile("\\{culture\\}");
50
51 public BingAerialTileSource() {
52 super("Bing Aerial Maps", "http://example.com/");
53 }
54
55 protected class Attribution {
56 String attribution;
57 int minZoom;
58 int maxZoom;
59 Coordinate min;
60 Coordinate max;
61 }
62
63 @Override
64 public String getTileUrl(int zoom, int tilex, int tiley) throws IOException {
65 // make sure that attribution is loaded. otherwise subdomains is null.
66 if (getAttribution() == null)
67 throw new IOException(tr("Attribution is not loaded yet"));
68
69 int t = (zoom + tilex + tiley) % subdomains.length;
70 String subdomain = subdomains[t];
71
72 String url = imageUrlTemplate;
73 url = subdomainPattern.matcher(url).replaceAll(subdomain);
74 url = quadkeyPattern.matcher(url).replaceAll(computeQuadTree(zoom, tilex, tiley));
75
76 return url;
77 }
78
79 protected URL getAttributionUrl() throws MalformedURLException {
80 return new URL("http://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?include=ImageryProviders&output=xml&key="
81 + API_KEY);
82 }
83
84 protected List<Attribution> parseAttributionText(InputSource xml) throws IOException {
85 try {
86 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
87 DocumentBuilder builder = factory.newDocumentBuilder();
88 Document document = builder.parse(xml);
89
90 XPathFactory xPathFactory = XPathFactory.newInstance();
91 XPath xpath = xPathFactory.newXPath();
92 imageUrlTemplate = xpath.compile("//ImageryMetadata/ImageUrl/text()").evaluate(document);
93 imageUrlTemplate = culturePattern.matcher(imageUrlTemplate).replaceAll(Locale.getDefault().toString());
94 imageryZoomMax = Integer.parseInt(xpath.compile("//ImageryMetadata/ZoomMax/text()").evaluate(document));
95
96 NodeList subdomainTxt = (NodeList) xpath.compile("//ImageryMetadata/ImageUrlSubdomains/string/text()").evaluate(document, XPathConstants.NODESET);
97 subdomains = new String[subdomainTxt.getLength()];
98 for(int i = 0; i < subdomainTxt.getLength(); i++) {
99 subdomains[i] = subdomainTxt.item(i).getNodeValue();
100 }
101
102 XPathExpression attributionXpath = xpath.compile("Attribution/text()");
103 XPathExpression coverageAreaXpath = xpath.compile("CoverageArea");
104 XPathExpression zoomMinXpath = xpath.compile("ZoomMin/text()");
105 XPathExpression zoomMaxXpath = xpath.compile("ZoomMax/text()");
106 XPathExpression southLatXpath = xpath.compile("BoundingBox/SouthLatitude/text()");
107 XPathExpression westLonXpath = xpath.compile("BoundingBox/WestLongitude/text()");
108 XPathExpression northLatXpath = xpath.compile("BoundingBox/NorthLatitude/text()");
109 XPathExpression eastLonXpath = xpath.compile("BoundingBox/EastLongitude/text()");
110
111 NodeList imageryProviderNodes = (NodeList) xpath.compile("//ImageryMetadata/ImageryProvider").evaluate(document, XPathConstants.NODESET);
112 List<Attribution> attributions = new ArrayList<Attribution>(imageryProviderNodes.getLength());
113 for (int i = 0; i < imageryProviderNodes.getLength(); i++) {
114 Node providerNode = imageryProviderNodes.item(i);
115
116 String attribution = attributionXpath.evaluate(providerNode);
117
118 NodeList coverageAreaNodes = (NodeList) coverageAreaXpath.evaluate(providerNode, XPathConstants.NODESET);
119 for(int j = 0; j < coverageAreaNodes.getLength(); j++) {
120 Node areaNode = coverageAreaNodes.item(j);
121 Attribution attr = new Attribution();
122 attr.attribution = attribution;
123
124 attr.maxZoom = Integer.parseInt(zoomMaxXpath.evaluate(areaNode));
125 attr.minZoom = Integer.parseInt(zoomMinXpath.evaluate(areaNode));
126
127 Double southLat = Double.parseDouble(southLatXpath.evaluate(areaNode));
128 Double northLat = Double.parseDouble(northLatXpath.evaluate(areaNode));
129 Double westLon = Double.parseDouble(westLonXpath.evaluate(areaNode));
130 Double eastLon = Double.parseDouble(eastLonXpath.evaluate(areaNode));
131 attr.min = new Coordinate(southLat, westLon);
132 attr.max = new Coordinate(northLat, eastLon);
133
134 attributions.add(attr);
135 }
136 }
137
138 return attributions;
139 } catch (SAXException e) {
140 System.err.println("Could not parse Bing aerials attribution metadata.");
141 e.printStackTrace();
142 } catch (ParserConfigurationException e) {
143 e.printStackTrace();
144 } catch (XPathExpressionException e) {
145 e.printStackTrace();
146 }
147 return null;
148 }
149
150 @Override
151 public int getMaxZoom() {
152 if(imageryZoomMax != null)
153 return imageryZoomMax;
154 else
155 return 22;
156 }
157
158 @Override
159 public TileUpdate getTileUpdate() {
160 return TileUpdate.IfNoneMatch;
161 }
162
163 @Override
164 public boolean requiresAttribution() {
165 return true;
166 }
167
168 @Override
169 public String getAttributionLinkURL() {
170 //return "http://bing.com/maps"
171 // FIXME: I've set attributionLinkURL temporarily to ToU URL to comply with bing ToU
172 // (the requirement is that we have such a link at the bottom of the window)
173 return "http://go.microsoft.com/?linkid=9710837";
174 }
175
176 @Override
177 public Image getAttributionImage() {
178 try {
179 return ImageIO.read(getClass().getResourceAsStream("/org/openstreetmap/gui/jmapviewer/images/bing_maps.png"));
180 } catch (IOException e) {
181 return null;
182 }
183 }
184
185 @Override
186 public String getAttributionImageURL() {
187 return "http://opengeodata.org/microsoft-imagery-details";
188 }
189
190 @Override
191 public String getTermsOfUseText() {
192 return null;
193 }
194
195 @Override
196 public String getTermsOfUseURL() {
197 return "http://opengeodata.org/microsoft-imagery-details";
198 }
199
200 protected Callable<List<Attribution>> getAttributionLoaderCallable() {
201 return new Callable<List<Attribution>>() {
202
203 @Override
204 public List<Attribution> call() throws Exception {
205 int waitTimeSec = 1;
206 while (true) {
207 try {
208 InputSource xml = new InputSource(getAttributionUrl().openStream());
209 List<Attribution> r = parseAttributionText(xml);
210 System.out.println("Successfully loaded Bing attribution data.");
211 return r;
212 } catch (IOException ex) {
213 System.err.println("Could not connect to Bing API. Will retry in " + waitTimeSec + " seconds.");
214 Thread.sleep(waitTimeSec * 1000L);
215 waitTimeSec *= 2;
216 }
217 }
218 }
219 };
220 }
221
222 protected List<Attribution> getAttribution() {
223 if (attributions == null) {
224 // see http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
225 synchronized (BingAerialTileSource.class) {
226 if (attributions == null) {
227 attributions = Executors.newSingleThreadExecutor().submit(getAttributionLoaderCallable());
228 }
229 }
230 }
231 try {
232 return attributions.get(1000, TimeUnit.MILLISECONDS);
233 } catch (TimeoutException ex) {
234 System.err.println("Bing: attribution data is not yet loaded.");
235 } catch (ExecutionException ex) {
236 throw new RuntimeException(ex.getCause());
237 } catch (InterruptedException ign) {
238 }
239 return null;
240 }
241
242 @Override
243 public String getAttributionText(int zoom, Coordinate topLeft, Coordinate botRight) {
244 try {
245 final List<Attribution> data = getAttribution();
246 if (data == null)
247 return "Error loading Bing attribution data";
248 StringBuilder a = new StringBuilder();
249 for (Attribution attr : data) {
250 if (zoom <= attr.maxZoom && zoom >= attr.minZoom) {
251 if (topLeft.getLon() < attr.max.getLon() && botRight.getLon() > attr.min.getLon()
252 && topLeft.getLat() > attr.min.getLat() && botRight.getLat() < attr.max.getLat()) {
253 a.append(attr.attribution);
254 a.append(" ");
255 }
256 }
257 }
258 return a.toString();
259 } catch (Exception e) {
260 e.printStackTrace();
261 }
262 return "Error loading Bing attribution data";
263 }
264
265 static String computeQuadTree(int zoom, int tilex, int tiley) {
266 StringBuilder k = new StringBuilder();
267 for (int i = zoom; i > 0; i--) {
268 char digit = 48;
269 int mask = 1 << (i - 1);
270 if ((tilex & mask) != 0) {
271 digit += 1;
272 }
273 if ((tiley & mask) != 0) {
274 digit += 2;
275 }
276 k.append(digit);
277 }
278 return k.toString();
279 }
280}
Note: See TracBrowser for help on using the repository browser.