Ticket #6866: BingAerialTileSource.java

File BingAerialTileSource.java, 14.4 KB (added by jhuntley, 14 years ago)
Line 
1package org.openstreetmap.gui.jmapviewer.tilesources;
2
3import java.awt.Image;
4import java.io.IOException;
5import java.io.InputStream;
6import java.net.URL;
7import java.net.URLConnection;
8import java.util.ArrayList;
9import java.util.List;
10import java.util.Locale;
11import java.util.concurrent.Callable;
12import java.util.concurrent.Executors;
13import java.util.concurrent.Future;
14import java.util.regex.Pattern;
15
16import javax.imageio.ImageIO;
17import javax.xml.parsers.DocumentBuilder;
18import javax.xml.parsers.DocumentBuilderFactory;
19import javax.xml.parsers.ParserConfigurationException;
20
21import org.openstreetmap.gui.jmapviewer.Coordinate;
22import org.w3c.dom.Document;
23import org.w3c.dom.Element;
24import org.w3c.dom.Node;
25import org.w3c.dom.NodeList;
26import org.xml.sax.SAXException;
27
28public class BingAerialTileSource extends AbstractTSMTileSource {
29 private static String API_KEY = "Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU";
30 private static Future<List<Attribution>> attributions;
31 private static String imageUrlTemplate;
32 private static Integer imageryZoomMax;
33 private static String[] subdomains;
34
35 private static final Pattern subdomainPattern = Pattern.compile("\\{subdomain\\}");
36 private static final Pattern quadkeyPattern = Pattern.compile("\\{quadkey\\}");
37 private static final Pattern culturePattern = Pattern.compile("\\{culture\\}");
38
39 public BingAerialTileSource() {
40 super("Bing Aerial Maps", "http://example.com/");
41
42 if (attributions == null) {
43 attributions = Executors.newSingleThreadExecutor().submit(new Callable<List<Attribution>>() {
44 public List<Attribution> call() throws Exception {
45 List<Attribution> attrs = new ArrayList<Attribution>();
46 int waitTime = 1;
47 do {
48
49 try {
50 attrs = loadAttributionText();
51 System.out.println("Successfully loaded Bing attribution data.");
52 return attrs;
53 } catch (TileLoadException e) {
54 System.err.println(e.getMessage());
55 return attrs;
56 } catch(IOException e) {
57 System.err.println("Could not connect to Bing API. Will retry in " + waitTime + " seconds.");
58 Thread.sleep(waitTime * 1000L);
59 waitTime *= 2;
60 }
61
62 } while(true);
63 }
64 });
65 }
66 }
67
68 class Attribution {
69 String attribution;
70 int minZoom;
71 int maxZoom;
72 Coordinate min;
73 Coordinate max;
74 }
75
76 @Override
77 public String getTileUrl(int zoom, int tilex, int tiley) throws IOException {
78 int t = (zoom + tilex + tiley) % subdomains.length;
79 String subdomain = subdomains[t];
80
81 String url = new String(imageUrlTemplate);
82 url = subdomainPattern.matcher(url).replaceAll(subdomain);
83 url = quadkeyPattern.matcher(url).replaceAll(computeQuadTree(zoom, tilex, tiley));
84
85 return url;
86 }
87
88 /*private List<Attribution> loadAttributionText() throws IOException {
89 try {
90 URL u = new URL("http://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?include=ImageryProviders&output=xml&key="
91 + API_KEY);
92 URLConnection conn = u.openConnection();
93
94 InputStream stream = conn.getInputStream();
95
96 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
97 DocumentBuilder builder = factory.newDocumentBuilder();
98 Document document = builder.parse(stream);
99
100 XPathFactory xPathFactory = XPathFactory.newInstance();
101 XPath xpath = xPathFactory.newXPath();
102 imageUrlTemplate = xpath.compile("//ImageryMetadata/ImageUrl/text()").evaluate(document);
103 imageUrlTemplate = culturePattern.matcher(imageUrlTemplate).replaceAll(Locale.getDefault().toString());
104 imageryZoomMax = Integer.parseInt(xpath.compile("//ImageryMetadata/ZoomMax/text()").evaluate(document));
105
106 NodeList subdomainTxt = (NodeList) xpath.compile("//ImageryMetadata/ImageUrlSubdomains/string/text()").evaluate(document, XPathConstants.NODESET);
107 subdomains = new String[subdomainTxt.getLength()];
108 for(int i = 0; i < subdomainTxt.getLength(); i++) {
109 subdomains[i] = subdomainTxt.item(i).getNodeValue();
110 }
111
112 XPathExpression attributionXpath = xpath.compile("Attribution/text()");
113 XPathExpression coverageAreaXpath = xpath.compile("CoverageArea");
114 XPathExpression zoomMinXpath = xpath.compile("ZoomMin/text()");
115 XPathExpression zoomMaxXpath = xpath.compile("ZoomMax/text()");
116 XPathExpression southLatXpath = xpath.compile("BoundingBox/SouthLatitude/text()");
117 XPathExpression westLonXpath = xpath.compile("BoundingBox/WestLongitude/text()");
118 XPathExpression northLatXpath = xpath.compile("BoundingBox/NorthLatitude/text()");
119 XPathExpression eastLonXpath = xpath.compile("BoundingBox/EastLongitude/text()");
120
121 NodeList imageryProviderNodes = (NodeList) xpath.compile("//ImageryMetadata/ImageryProvider").evaluate(document, XPathConstants.NODESET);
122 List<Attribution> attributions = new ArrayList<Attribution>(imageryProviderNodes.getLength());
123 for (int i = 0; i < imageryProviderNodes.getLength(); i++) {
124 Node providerNode = imageryProviderNodes.item(i);
125
126 String attribution = attributionXpath.evaluate(providerNode);
127
128 NodeList coverageAreaNodes = (NodeList) coverageAreaXpath.evaluate(providerNode, XPathConstants.NODESET);
129 for(int j = 0; j < coverageAreaNodes.getLength(); j++) {
130 Node areaNode = coverageAreaNodes.item(j);
131 Attribution attr = new Attribution();
132 attr.attribution = attribution;
133
134 attr.maxZoom = Integer.parseInt(zoomMaxXpath.evaluate(areaNode));
135 attr.minZoom = Integer.parseInt(zoomMinXpath.evaluate(areaNode));
136
137 Double southLat = Double.parseDouble(southLatXpath.evaluate(areaNode));
138 Double northLat = Double.parseDouble(northLatXpath.evaluate(areaNode));
139 Double westLon = Double.parseDouble(westLonXpath.evaluate(areaNode));
140 Double eastLon = Double.parseDouble(eastLonXpath.evaluate(areaNode));
141 attr.min = new Coordinate(southLat, westLon);
142 attr.max = new Coordinate(northLat, eastLon);
143
144 attributions.add(attr);
145 }
146 }
147
148 return attributions;
149 } catch (SAXException e) {
150 System.err.println("Could not parse Bing aerials attribution metadata.");
151 e.printStackTrace();
152 } catch (ParserConfigurationException e) {
153 e.printStackTrace();
154 } catch (XPathExpressionException e) {
155 e.printStackTrace();
156 } catch (Exception e) {
157 e.printStackTrace();
158 }
159
160 return null;
161 }*/
162
163 private List<Attribution> loadAttributionText() throws TileLoadException, IOException {
164 List<Attribution> foundAttribs = new ArrayList<Attribution>();
165
166 try {
167 URL u = new URL("http://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?include=ImageryProviders&output=xml&key="
168 + API_KEY);
169 URLConnection conn = u.openConnection();
170
171 InputStream stream = conn.getInputStream();
172
173 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
174 DocumentBuilder builder = factory.newDocumentBuilder();
175 Document document = builder.parse(stream);
176
177 Element eImagery = XMLTileParseUtil.getElement(document, "ImageryMetadata");
178
179 Node nImageUrl=XMLTileParseUtil.getElement(document,eImagery, "ImageUrl");
180 if (nImageUrl!=null) {
181 imageUrlTemplate=XMLTileParseUtil.getNodeStrValue(nImageUrl);
182 imageUrlTemplate = culturePattern.matcher(imageUrlTemplate).
183 replaceAll(Locale.getDefault().toString());
184 }
185
186 Node nZoom=XMLTileParseUtil.getElement(document,eImagery,"ZoomMax");
187 if (nZoom!=null)
188 imageryZoomMax=XMLTileParseUtil.getNodeIntValue(nZoom);
189
190 Element eSub=XMLTileParseUtil.getElement(document,eImagery,"ImageUrlSubdomains");
191 if (eSub !=null){
192 NodeList subdomainTxt = eSub.getElementsByTagName("string");
193 subdomains = new String[subdomainTxt.getLength()];
194
195 for(int i = 0; i < subdomainTxt.getLength(); i++)
196 subdomains[i] = XMLTileParseUtil.getNodeStrValue(subdomainTxt.item(i));
197 }
198
199 NodeList imageryProviderNodes = eImagery.getElementsByTagName("ImageryProvider");
200 foundAttribs = new ArrayList<Attribution>(imageryProviderNodes.getLength());
201
202 for (int i = 0; i < imageryProviderNodes.getLength(); i++) {
203 if (imageryProviderNodes.item(i).getNodeType() == Node.ELEMENT_NODE){
204 String attribution = "";
205 Element eProviderNode=(Element)imageryProviderNodes.item(i);
206
207 Element eAttrib=XMLTileParseUtil.getElement(document,eProviderNode,"Attribution");
208 if (eAttrib!=null)
209 attribution=XMLTileParseUtil.getNodeStrValue(eAttrib);
210
211 NodeList coverageAreaNodes = eProviderNode.getElementsByTagName("CoverageArea");
212
213 for(int j = 0; j < coverageAreaNodes.getLength(); j++) {
214 if (coverageAreaNodes.item(j).getNodeType() == Node.ELEMENT_NODE){
215 Element eAreaNode = (Element)coverageAreaNodes.item(j);
216 Attribution attr = new Attribution();
217 attr.attribution = attribution;
218
219 Node maxZoom=XMLTileParseUtil.getElement(document,eAreaNode,"ZoomMax");
220 if (maxZoom!=null)
221 attr.maxZoom = XMLTileParseUtil.getNodeIntValue(maxZoom);
222
223 Node minZoom=XMLTileParseUtil.getElement(document,eAreaNode,"ZoomMin");
224 if (minZoom!=null)
225 attr.minZoom = XMLTileParseUtil.getNodeIntValue(minZoom);
226
227 Node sLat=XMLTileParseUtil.getElement(document,eAreaNode,"SouthLatitude");
228 Double dSouthLat = XMLTileParseUtil.getNodeDblValue(sLat);
229
230 Node wLong=XMLTileParseUtil.getElement(document,eAreaNode,"WestLongitude");
231 Double dWLong = XMLTileParseUtil.getNodeDblValue(wLong);
232
233 Node northLat=XMLTileParseUtil.getElement(document,eAreaNode,"NorthLatitude");
234 Double dNorthLat = XMLTileParseUtil.getNodeDblValue(northLat);
235
236 Node eastLon=XMLTileParseUtil.getElement(document,eAreaNode,"EastLongitude");
237 Double dEastLon = XMLTileParseUtil.getNodeDblValue(eastLon);
238
239 if ((dSouthLat==null)||(dWLong==null) || (dNorthLat==null) || (dEastLon==null)) {
240 TileLoadException e=new TileLoadException(
241 "Unable to find proper tile " +
242 "coordinates from source. Source " +
243 "meta-data may have been modified.");
244
245 throw e;
246 }
247
248 attr.min = new Coordinate(dSouthLat, dWLong);
249 attr.max = new Coordinate(dNorthLat, dEastLon);
250
251 foundAttribs.add(attr);
252 }
253 }
254 }
255 }
256 } catch (SAXException e) {
257 throw new TileLoadException("Could not parse Bing aerials attribution metadata.");
258 } catch (ParserConfigurationException e) {
259 throw new TileLoadException("Could not parse Bing aerials attribution metadata.");
260 }
261
262 return foundAttribs;
263 }
264
265 @Override
266 public int getMaxZoom() {
267 if(imageryZoomMax != null)
268 return imageryZoomMax;
269 else
270 return 22;
271 }
272
273 public TileUpdate getTileUpdate() {
274 return TileUpdate.IfNoneMatch;
275 }
276
277 @Override
278 public boolean requiresAttribution() {
279 return true;
280 }
281
282 @Override
283 public Image getAttributionImage() {
284 try {
285 return ImageIO.read(getClass().getResourceAsStream("/org/openstreetmap/gui/jmapviewer/images/bing_maps.png"));
286 } catch (IOException e) {
287 return null;
288 }
289 }
290
291 @Override
292 public String getAttributionLinkURL() {
293 //return "http://bing.com/maps"
294 // FIXME: I've set attributionLinkURL temporarily to ToU URL to comply with bing ToU
295 // (the requirement is that we have such a link at the bottom of the window)
296 return "http://go.microsoft.com/?linkid=9710837";
297 }
298
299 @Override
300 public String getTermsOfUseURL() {
301 return "http://opengeodata.org/microsoft-imagery-details";
302 }
303
304 @Override
305 public String getAttributionText(int zoom, Coordinate topLeft, Coordinate botRight) {
306 try {
307 if (!attributions.isDone())
308 return "Loading Bing attribution data...";
309 if (attributions.get() == null)
310 return "Error loading Bing attribution data";
311 StringBuilder a = new StringBuilder();
312 for (Attribution attr : attributions.get()) {
313 if (zoom <= attr.maxZoom && zoom >= attr.minZoom) {
314 if (topLeft.getLon() < attr.max.getLon() && botRight.getLon() > attr.min.getLon()
315 && topLeft.getLat() > attr.min.getLat() && botRight.getLat() < attr.max.getLat()) {
316 a.append(attr.attribution);
317 a.append(" ");
318 }
319 }
320 }
321 return a.toString();
322 } catch (Exception e) {
323 e.printStackTrace();
324 }
325 return "Error loading Bing attribution data";
326 }
327
328 static String computeQuadTree(int zoom, int tilex, int tiley) {
329 StringBuilder k = new StringBuilder();
330 for (int i = zoom; i > 0; i--) {
331 char digit = 48;
332 int mask = 1 << (i - 1);
333 if ((tilex & mask) != 0) {
334 digit += 1;
335 }
336 if ((tiley & mask) != 0) {
337 digit += 2;
338 }
339 k.append(digit);
340 }
341 return k.toString();
342 }
343}