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

Last change on this file since 3872 was 3872, checked in by stoecker, 13 years ago

see #5532 - add missing addProjectsions() function nobody seems able to add when asked to do so

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