source: josm/trunk/src/org/openstreetmap/josm/gui/preferences/AddWMSLayerPanel.java@ 3720

Last change on this file since 3720 was 3720, checked in by bastiK, 13 years ago

added missing svn:eol-style

  • Property svn:eol-style set to native
File size: 20.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.preferences;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Component;
7import java.awt.Cursor;
8import java.awt.GridBagConstraints;
9import java.awt.GridBagLayout;
10import java.awt.HeadlessException;
11import java.awt.event.ActionEvent;
12import java.awt.event.ActionListener;
13import java.io.BufferedReader;
14import java.io.IOException;
15import java.io.InputStream;
16import java.io.InputStreamReader;
17import java.io.StringReader;
18import java.net.MalformedURLException;
19import java.net.URL;
20import java.net.URLConnection;
21import java.util.HashSet;
22import java.util.Iterator;
23import java.util.LinkedList;
24import java.util.List;
25import java.util.Set;
26
27import javax.swing.JButton;
28import javax.swing.JFrame;
29import javax.swing.JLabel;
30import javax.swing.JOptionPane;
31import javax.swing.JPanel;
32import javax.swing.JScrollPane;
33import javax.swing.JTextArea;
34import javax.swing.JTextField;
35import javax.swing.JTree;
36import javax.swing.event.TreeSelectionEvent;
37import javax.swing.event.TreeSelectionListener;
38import javax.swing.tree.DefaultMutableTreeNode;
39import javax.swing.tree.DefaultTreeCellRenderer;
40import javax.swing.tree.DefaultTreeModel;
41import javax.swing.tree.MutableTreeNode;
42import javax.swing.tree.TreePath;
43import javax.xml.parsers.DocumentBuilder;
44import javax.xml.parsers.DocumentBuilderFactory;
45import javax.xml.parsers.ParserConfigurationException;
46
47import org.openstreetmap.josm.data.Bounds;
48import org.openstreetmap.josm.data.projection.Projection;
49import org.openstreetmap.josm.data.projection.ProjectionSubPrefs;
50import org.openstreetmap.josm.gui.bbox.SlippyMapBBoxChooser;
51import org.openstreetmap.josm.tools.GBC;
52import org.w3c.dom.Document;
53import org.w3c.dom.Element;
54import org.w3c.dom.Node;
55import org.w3c.dom.NodeList;
56import org.xml.sax.EntityResolver;
57import org.xml.sax.InputSource;
58import org.xml.sax.SAXException;
59
60
61public class AddWMSLayerPanel extends JPanel {
62 private List<LayerDetails> selectedLayers;
63 private URL serviceUrl;
64 private LayerDetails selectedLayer;
65
66 private JTextField menuName;
67 private JTextArea resultingLayerField;
68 private MutableTreeNode treeRootNode;
69 private DefaultTreeModel treeData;
70 private JTree layerTree;
71 private JButton showBoundsButton;
72
73 private boolean previouslyShownUnsupportedCrsError = false;
74
75 public AddWMSLayerPanel() {
76 JPanel wmsFetchPanel = new JPanel(new GridBagLayout());
77 menuName = new JTextField(40);
78 menuName.setText(tr("Unnamed WMS Layer"));
79 final JTextArea serviceUrl = new JTextArea(3, 40);
80 serviceUrl.setLineWrap(true);
81 serviceUrl.setText("http://sample.com/wms?");
82 wmsFetchPanel.add(new JLabel(tr("Menu Name")), GBC.std().insets(0,0,5,0));
83 wmsFetchPanel.add(menuName, GBC.eop().insets(5,0,0,0).fill(GridBagConstraints.HORIZONTAL));
84 wmsFetchPanel.add(new JLabel(tr("Service URL")), GBC.std().insets(0,0,5,0));
85 JScrollPane scrollPane = new JScrollPane(serviceUrl,
86 JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
87 JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
88 wmsFetchPanel.add(scrollPane, GBC.eop().insets(5,0,0,0));
89 JButton getLayersButton = new JButton(tr("Get Layers"));
90 getLayersButton.addActionListener(new ActionListener() {
91 @Override
92 public void actionPerformed(ActionEvent e) {
93 Cursor beforeCursor = getCursor();
94 try {
95 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
96 attemptGetCapabilities(serviceUrl.getText());
97 } finally {
98 setCursor(beforeCursor);
99 }
100 }
101 });
102 wmsFetchPanel.add(getLayersButton, GBC.eop().anchor(GridBagConstraints.EAST));
103
104 treeRootNode = new DefaultMutableTreeNode();
105 treeData = new DefaultTreeModel(treeRootNode);
106 layerTree = new JTree(treeData);
107 layerTree.setCellRenderer(new LayerTreeCellRenderer());
108 layerTree.addTreeSelectionListener(new TreeSelectionListener() {
109
110 @Override
111 public void valueChanged(TreeSelectionEvent e) {
112 TreePath[] selectionRows = layerTree.getSelectionPaths();
113 if(selectionRows == null) {
114 showBoundsButton.setEnabled(false);
115 selectedLayer = null;
116 return;
117 }
118
119 selectedLayers = new LinkedList<LayerDetails>();
120 for (TreePath i : selectionRows) {
121 Object userObject = ((DefaultMutableTreeNode) i.getLastPathComponent()).getUserObject();
122 if(userObject instanceof LayerDetails) {
123 LayerDetails detail = (LayerDetails) userObject;
124 if(!detail.isSupported()) {
125 layerTree.removeSelectionPath(i);
126 if(!previouslyShownUnsupportedCrsError) {
127 JOptionPane.showMessageDialog(null, tr("That layer does not support any of JOSM's projections,\n" +
128 "so you can not use it. This message will not show again."),
129 tr("WMS Error"), JOptionPane.ERROR_MESSAGE);
130 previouslyShownUnsupportedCrsError = true;
131 }
132 } else if(detail.ident != null) {
133 selectedLayers.add(detail);
134 }
135 }
136 }
137
138 if (!selectedLayers.isEmpty()) {
139 resultingLayerField.setText(buildGetMapUrl());
140
141 if(selectedLayers.size() == 1) {
142 showBoundsButton.setEnabled(true);
143 selectedLayer = selectedLayers.get(0);
144 }
145 } else {
146 showBoundsButton.setEnabled(false);
147 selectedLayer = null;
148 }
149 }
150 });
151 wmsFetchPanel.add(new JScrollPane(layerTree), GBC.eop().insets(5,0,0,0).fill(GridBagConstraints.HORIZONTAL));
152
153 JPanel layerManipulationButtons = new JPanel();
154 showBoundsButton = new JButton(tr("Show Bounds"));
155 showBoundsButton.setEnabled(false);
156 showBoundsButton.addActionListener(new ActionListener() {
157 @Override
158 public void actionPerformed(ActionEvent e) {
159 if(selectedLayer.bounds != null) {
160 SlippyMapBBoxChooser mapPanel = new SlippyMapBBoxChooser();
161 mapPanel.setBoundingBox(selectedLayer.bounds);
162 JOptionPane.showMessageDialog(null, mapPanel, tr("Show Bounds"), JOptionPane.PLAIN_MESSAGE);
163 } else {
164 JOptionPane.showMessageDialog(null, tr("No bounding box was found for this layer."),
165 tr("WMS Error"), JOptionPane.ERROR_MESSAGE);
166 }
167 }
168 });
169 layerManipulationButtons.add(showBoundsButton);
170
171 wmsFetchPanel.add(layerManipulationButtons, GBC.eol().insets(0,0,5,0));
172 wmsFetchPanel.add(new JLabel(tr("WMS URL")), GBC.std().insets(0,0,5,0));
173 resultingLayerField = new JTextArea(3, 40);
174 resultingLayerField.setLineWrap(true);
175 wmsFetchPanel.add(new JScrollPane(resultingLayerField, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER), GBC.eop().insets(5,0,0,0).fill(GridBagConstraints.HORIZONTAL));
176
177 add(wmsFetchPanel);
178 }
179
180 private String buildRootUrl() {
181 StringBuilder a = new StringBuilder(serviceUrl.getProtocol());
182 a.append("://");
183 a.append(serviceUrl.getHost());
184 if(serviceUrl.getPort() != -1) {
185 a.append(":");
186 a.append(serviceUrl.getPort());
187 }
188 a.append(serviceUrl.getPath());
189 a.append("?");
190 if(serviceUrl.getQuery() != null) {
191 a.append(serviceUrl.getQuery());
192 if (!serviceUrl.getQuery().isEmpty() && !serviceUrl.getQuery().endsWith("&")) {
193 a.append("&");
194 }
195 }
196 return a.toString();
197 }
198
199 private String buildGetMapUrl() {
200 StringBuilder a = new StringBuilder();
201 a.append(buildRootUrl());
202 a.append("FORMAT=image/jpeg&VERSION=1.1.1&SERVICE=WMS&REQUEST=GetMap&Layers=");
203 a.append(commaSepLayerList());
204 a.append("&");
205
206 return a.toString();
207 }
208
209 private String commaSepLayerList() {
210 StringBuilder b = new StringBuilder();
211
212 Iterator<LayerDetails> iterator = selectedLayers.iterator();
213 while (iterator.hasNext()) {
214 LayerDetails layerDetails = iterator.next();
215 b.append(layerDetails.ident);
216 if(iterator.hasNext()) {
217 b.append(",");
218 }
219 }
220
221 return b.toString();
222 }
223
224 private void showError(String incomingData, Exception e) {
225 JOptionPane.showMessageDialog(this, tr("Could not parse WMS layer list."),
226 tr("WMS Error"), JOptionPane.ERROR_MESSAGE);
227 System.err.println("Could not parse WMS layer list. Incoming data:");
228 System.err.println(incomingData);
229 e.printStackTrace();
230 }
231
232 private void attemptGetCapabilities(String serviceUrlStr) {
233 URL getCapabilitiesUrl = null;
234 try {
235 if (!serviceUrlStr.trim().contains("capabilities")) {
236 // If the url doesn't already have GetCapabilities, add it in
237 getCapabilitiesUrl = new URL(serviceUrlStr + "VERSION=1.1.1&SERVICE=WMS&REQUEST=GetCapabilities");
238 } else {
239 // Otherwise assume it's a good URL and let the subsequent error
240 // handling systems deal with problems
241 getCapabilitiesUrl = new URL(serviceUrlStr);
242 }
243 serviceUrl = new URL(serviceUrlStr);
244 } catch (HeadlessException e) {
245 return;
246 } catch (MalformedURLException e) {
247 JOptionPane.showMessageDialog(this, tr("Invalid service URL."),
248 tr("WMS Error"), JOptionPane.ERROR_MESSAGE);
249 return;
250 }
251
252 String incomingData;
253 try {
254 URLConnection openConnection = getCapabilitiesUrl.openConnection();
255 InputStream inputStream = openConnection.getInputStream();
256 BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
257 String line;
258 StringBuilder ba = new StringBuilder();
259 while((line = br.readLine()) != null) {
260 ba.append(line);
261 ba.append("\n");
262 }
263 incomingData = ba.toString();
264 } catch (IOException e) {
265 JOptionPane.showMessageDialog(this, tr("Could not retrieve WMS layer list."),
266 tr("WMS Error"), JOptionPane.ERROR_MESSAGE);
267 return;
268 }
269
270 Document document;
271 try {
272 DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
273 builderFactory.setValidating(false);
274 builderFactory.setNamespaceAware(true);
275 DocumentBuilder builder = builderFactory.newDocumentBuilder();
276 builder.setEntityResolver(new EntityResolver() {
277 @Override
278 public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
279 System.out.println("Ignoring DTD " + publicId + ", " + systemId);
280 return new InputSource(new StringReader(""));
281 }
282 });
283 document = builder.parse(new InputSource(new StringReader(incomingData)));
284 } catch (ParserConfigurationException e) {
285 showError(incomingData, e);
286 return;
287 } catch (SAXException e) {
288 showError(incomingData, e);
289 return;
290 } catch (IOException e) {
291 showError(incomingData, e);
292 return;
293 }
294
295 // Some WMS service URLs specify a different base URL for their GetMap service
296 Element child = getChild(document.getDocumentElement(), "Capability");
297 child = getChild(child, "Request");
298 child = getChild(child, "GetMap");
299 child = getChild(child, "DCPType");
300 child = getChild(child, "HTTP");
301 child = getChild(child, "Get");
302 child = getChild(child, "OnlineResource");
303 if (child != null) {
304 String baseURL = child.getAttribute("xlink:href");
305 if(baseURL != null) {
306 try {
307 System.out.println("GetCapabilities specifies a different service URL: " + baseURL);
308 serviceUrl = new URL(baseURL);
309 } catch (MalformedURLException e1) {
310 }
311 }
312 }
313
314 try {
315 treeRootNode.setUserObject(getCapabilitiesUrl.getHost());
316 Element capabilityElem = getChild(document.getDocumentElement(), "Capability");
317 List<Element> children = getChildren(capabilityElem, "Layer");
318 List<LayerDetails> layers = parseLayers(children, new HashSet<String>());
319 updateTreeList(layers);
320 } catch(Exception e) {
321 showError(incomingData, e);
322 return;
323 }
324 }
325
326 private void updateTreeList(List<LayerDetails> layers) {
327 addLayersToTreeData(treeRootNode, layers);
328 layerTree.expandRow(0);
329 }
330
331 private void addLayersToTreeData(MutableTreeNode parent, List<LayerDetails> layers) {
332 for (LayerDetails layerDetails : layers) {
333 DefaultMutableTreeNode treeNode = new DefaultMutableTreeNode(layerDetails);
334 addLayersToTreeData(treeNode, layerDetails.children);
335 treeData.insertNodeInto(treeNode, parent, 0);
336 }
337 }
338
339 private List<LayerDetails> parseLayers(List<Element> children, Set<String> parentCrs) {
340 List<LayerDetails> details = new LinkedList<LayerDetails>();
341 for (Element element : children) {
342 details.add(parseLayer(element, parentCrs));
343 }
344 return details;
345 }
346
347 private LayerDetails parseLayer(Element element, Set<String> parentCrs) {
348 String name = getChildContent(element, "Title", null, null);
349 String ident = getChildContent(element, "Name", null, null);
350
351 // The set of supported CRS/SRS for this layer
352 Set<String> crsList = new HashSet<String>();
353 // ...including this layer's already-parsed parent projections
354 crsList.addAll(parentCrs);
355
356 // Parse the CRS/SRS pulled out of this layer's XML element
357 // I think CRS and SRS are the same at this point
358 List<Element> crsChildren = getChildren(element, "CRS");
359 crsChildren.addAll(getChildren(element, "SRS"));
360 for (Element child : crsChildren) {
361 String crs = (String) getContent(child);
362 if(crs != null) {
363 String upperCase = crs.trim().toUpperCase();
364 crsList.add(upperCase);
365 }
366 }
367
368 // Check to see if any of the specified projections are supported by JOSM
369 boolean josmSupportsThisLayer = false;
370 for (String crs : crsList) {
371 josmSupportsThisLayer |= isProjSupported(crs);
372 }
373
374 Bounds bounds = null;
375 Element bboxElem = getChild(element, "EX_GeographicBoundingBox");
376 if(bboxElem != null) {
377 // Attempt to use EX_GeographicBoundingBox for bounding box
378 double left = Double.parseDouble(getChildContent(bboxElem, "westBoundLongitude", null, null));
379 double top = Double.parseDouble(getChildContent(bboxElem, "northBoundLatitude", null, null));
380 double right = Double.parseDouble(getChildContent(bboxElem, "eastBoundLongitude", null, null));
381 double bot = Double.parseDouble(getChildContent(bboxElem, "southBoundLatitude", null, null));
382 bounds = new Bounds(bot, left, top, right);
383 } else {
384 // If that's not available, try LatLonBoundingBox
385 bboxElem = getChild(element, "LatLonBoundingBox");
386 if(bboxElem != null) {
387 double left = Double.parseDouble(bboxElem.getAttribute("minx"));
388 double top = Double.parseDouble(bboxElem.getAttribute("maxy"));
389 double right = Double.parseDouble(bboxElem.getAttribute("maxx"));
390 double bot = Double.parseDouble(bboxElem.getAttribute("miny"));
391 bounds = new Bounds(bot, left, top, right);
392 }
393 }
394
395 List<Element> layerChildren = getChildren(element, "Layer");
396 List<LayerDetails> childLayers = parseLayers(layerChildren, crsList);
397
398 return new LayerDetails(name, ident, crsList, josmSupportsThisLayer, bounds, childLayers);
399 }
400
401 private boolean isProjSupported(String crs) {
402 for (Projection proj : Projection.allProjections) {
403 if (proj instanceof ProjectionSubPrefs) {
404 if (((ProjectionSubPrefs) proj).getPreferencesFromCode(crs) == null) {
405 return true;
406 }
407 } else {
408 if (proj.toCode().equals(crs)) {
409 return true;
410 }
411 }
412 }
413 return false;
414 }
415
416 public String getUrlName() {
417 return menuName.getText();
418 }
419
420 public String getUrl() {
421 return resultingLayerField.getText();
422 }
423
424 public static void main(String[] args) {
425 JFrame f = new JFrame("Test");
426 f.setContentPane(new AddWMSLayerPanel());
427 f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
428 f.pack();
429 f.setVisible(true);
430 }
431
432 private static String getChildContent(Element parent, String name, String missing, String empty) {
433 Element child = getChild(parent, name);
434 if (child == null) {
435 return missing;
436 } else {
437 String content = (String) getContent(child);
438 return (content != null) ? content : empty;
439 }
440 }
441
442 private static Object getContent(Element element) {
443 NodeList nl = element.getChildNodes();
444 StringBuffer content = new StringBuffer();
445 for (int i = 0; i < nl.getLength(); i++) {
446 Node node = nl.item(i);
447 switch (node.getNodeType()) {
448 case Node.ELEMENT_NODE:
449 return node;
450 case Node.CDATA_SECTION_NODE:
451 case Node.TEXT_NODE:
452 content.append(node.getNodeValue());
453 break;
454 }
455 }
456 return content.toString().trim();
457 }
458
459 private static List<Element> getChildren(Element parent, String name) {
460 List<Element> retVal = new LinkedList<Element>();
461 for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) {
462 if (child instanceof Element && name.equals(child.getNodeName())) {
463 retVal.add((Element) child);
464 }
465 }
466 return retVal;
467 }
468
469 private static Element getChild(Element parent, String name) {
470 if (parent == null) {
471 return null;
472 }
473 for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) {
474 if (child instanceof Element && name.equals(child.getNodeName())) {
475 return (Element) child;
476 }
477 }
478 return null;
479 }
480
481 class LayerDetails {
482
483 private String name;
484 private String ident;
485 private List<LayerDetails> children;
486 private Bounds bounds;
487 private boolean supported;
488
489 public LayerDetails(String name, String ident, Set<String> crsList,
490 boolean supportedLayer, Bounds bounds,
491 List<LayerDetails> childLayers) {
492 this.name = name;
493 this.ident = ident;
494 this.supported = supportedLayer;
495 this.children = childLayers;
496 this.bounds = bounds;
497 }
498
499 public boolean isSupported() {
500 return this.supported;
501 }
502
503 @Override
504 public String toString() {
505 if(this.name == null || this.name.isEmpty()) {
506 return this.ident;
507 } else {
508 return this.name;
509 }
510 }
511
512 }
513
514 class LayerTreeCellRenderer extends DefaultTreeCellRenderer {
515 @Override
516 public Component getTreeCellRendererComponent(JTree tree, Object value,
517 boolean sel, boolean expanded, boolean leaf, int row,
518 boolean hasFocus) {
519 super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf,
520 row, hasFocus);
521 DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) value;
522 Object userObject = treeNode.getUserObject();
523 if (userObject instanceof LayerDetails) {
524 LayerDetails layer = (LayerDetails) userObject;
525 setEnabled(layer.isSupported());
526 }
527 return this;
528 }
529 }
530
531}
Note: See TracBrowser for help on using the repository browser.