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

Last change on this file since 3715 was 3715, checked in by Upliner, 13 years ago

Added imagery plugin to josm core. Imagery plugin is union of wmsplugin and slippymap plugins. It includes code by Tim Waters, Petr Dlouhý, Frederik Ramm and others. Also enables the remotecontol which was integrated in [3707].

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