001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui.preferences.imagery; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 import static org.openstreetmap.josm.tools.I18n.trc; 006 007 import java.awt.Component; 008 import java.awt.Cursor; 009 import java.awt.Dimension; 010 import java.awt.GridBagConstraints; 011 import java.awt.GridBagLayout; 012 import java.awt.HeadlessException; 013 import java.awt.event.ActionEvent; 014 import java.awt.event.ActionListener; 015 import java.awt.event.KeyAdapter; 016 import java.awt.event.KeyEvent; 017 import java.io.BufferedReader; 018 import java.io.IOException; 019 import java.io.InputStream; 020 import java.io.StringReader; 021 import java.net.MalformedURLException; 022 import java.net.URL; 023 import java.net.URLConnection; 024 import java.util.HashSet; 025 import java.util.Iterator; 026 import java.util.LinkedList; 027 import java.util.List; 028 import java.util.Set; 029 import java.util.regex.Pattern; 030 031 import javax.swing.JButton; 032 import javax.swing.JLabel; 033 import javax.swing.JOptionPane; 034 import javax.swing.JPanel; 035 import javax.swing.JScrollPane; 036 import javax.swing.JTabbedPane; 037 import javax.swing.JTextArea; 038 import javax.swing.JTextField; 039 import javax.swing.JTree; 040 import javax.swing.event.ChangeEvent; 041 import javax.swing.event.ChangeListener; 042 import javax.swing.event.TreeSelectionEvent; 043 import javax.swing.event.TreeSelectionListener; 044 import javax.swing.tree.DefaultMutableTreeNode; 045 import javax.swing.tree.DefaultTreeCellRenderer; 046 import javax.swing.tree.DefaultTreeModel; 047 import javax.swing.tree.MutableTreeNode; 048 import javax.swing.tree.TreePath; 049 import javax.xml.parsers.DocumentBuilder; 050 import javax.xml.parsers.DocumentBuilderFactory; 051 import javax.xml.parsers.ParserConfigurationException; 052 053 import org.openstreetmap.josm.data.Bounds; 054 import org.openstreetmap.josm.data.imagery.ImageryInfo; 055 import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType; 056 import org.openstreetmap.josm.gui.bbox.SlippyMapBBoxChooser; 057 import org.openstreetmap.josm.gui.layer.TMSLayer; 058 import org.openstreetmap.josm.gui.preferences.projection.ProjectionChoice; 059 import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference; 060 import org.openstreetmap.josm.io.UTFInputStreamReader; 061 import org.openstreetmap.josm.tools.GBC; 062 import org.w3c.dom.Document; 063 import org.w3c.dom.Element; 064 import org.w3c.dom.Node; 065 import org.w3c.dom.NodeList; 066 import org.xml.sax.EntityResolver; 067 import org.xml.sax.InputSource; 068 import org.xml.sax.SAXException; 069 070 071 public class AddWMSLayerPanel extends JPanel { 072 private List<LayerDetails> selectedLayers; 073 private URL serviceUrl; 074 private LayerDetails selectedLayer; 075 076 private JTextField menuName; 077 private JTextArea resultingLayerField; 078 private MutableTreeNode treeRootNode; 079 private DefaultTreeModel treeData; 080 private JTree layerTree; 081 private JButton showBoundsButton; 082 083 private boolean previouslyShownUnsupportedCrsError = false; 084 private JTextArea tmsURL; 085 private JTextField tmsZoom; 086 087 public AddWMSLayerPanel() { 088 super(new GridBagLayout()); 089 add(new JLabel(tr("Menu Name")), GBC.std().insets(0,0,5,0)); 090 menuName = new JTextField(40); 091 menuName.setText(tr("Unnamed Imagery Layer")); 092 add(menuName, GBC.eop().insets(5,0,0,0).fill(GridBagConstraints.HORIZONTAL)); 093 094 final JTabbedPane tabbedPane = new JTabbedPane(); 095 096 final JPanel wmsFetchPanel = new JPanel(new GridBagLayout()); 097 tabbedPane.addTab(tr("WMS"), wmsFetchPanel); 098 add(tabbedPane, GBC.eop().insets(5,0,0,0).weight(1.0, 1.0).fill(GridBagConstraints.BOTH)); 099 100 final JTextArea serviceUrlText = new JTextArea(3, 40); 101 serviceUrlText.setLineWrap(true); 102 serviceUrlText.setText("http://sample.com/wms?"); 103 wmsFetchPanel.add(new JLabel(tr("Service URL")), GBC.std().insets(0,0,5,0)); 104 JScrollPane scrollPane = new JScrollPane(serviceUrlText, 105 JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, 106 JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); 107 scrollPane.setMinimumSize(new Dimension(60, 60)); 108 wmsFetchPanel.add(scrollPane, GBC.eol().weight(1.0, 0.0).insets(5,0,0,0).fill(GridBagConstraints.HORIZONTAL)); 109 JButton getLayersButton = new JButton(tr("Get Layers")); 110 getLayersButton.addActionListener(new ActionListener() { 111 @Override 112 public void actionPerformed(ActionEvent e) { 113 Cursor beforeCursor = getCursor(); 114 try { 115 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); 116 attemptGetCapabilities(sanitize(serviceUrlText.getText())); 117 } finally { 118 setCursor(beforeCursor); 119 } 120 } 121 }); 122 wmsFetchPanel.add(getLayersButton, GBC.eop().anchor(GridBagConstraints.EAST)); 123 124 treeRootNode = new DefaultMutableTreeNode(); 125 treeData = new DefaultTreeModel(treeRootNode); 126 layerTree = new JTree(treeData); 127 layerTree.setCellRenderer(new LayerTreeCellRenderer()); 128 layerTree.addTreeSelectionListener(new TreeSelectionListener() { 129 130 @Override 131 public void valueChanged(TreeSelectionEvent e) { 132 TreePath[] selectionRows = layerTree.getSelectionPaths(); 133 if(selectionRows == null) { 134 showBoundsButton.setEnabled(false); 135 selectedLayer = null; 136 return; 137 } 138 139 selectedLayers = new LinkedList<LayerDetails>(); 140 for (TreePath i : selectionRows) { 141 Object userObject = ((DefaultMutableTreeNode) i.getLastPathComponent()).getUserObject(); 142 if(userObject instanceof LayerDetails) { 143 LayerDetails detail = (LayerDetails) userObject; 144 if(!detail.isSupported()) { 145 layerTree.removeSelectionPath(i); 146 if(!previouslyShownUnsupportedCrsError) { 147 JOptionPane.showMessageDialog(null, tr("That layer does not support any of JOSM''s projections,\n" + 148 "so you can not use it. This message will not show again."), 149 tr("WMS Error"), JOptionPane.ERROR_MESSAGE); 150 previouslyShownUnsupportedCrsError = true; 151 } 152 } else if(detail.ident != null) { 153 selectedLayers.add(detail); 154 } 155 } 156 } 157 158 if (!selectedLayers.isEmpty()) { 159 resultingLayerField.setText(buildGetMapUrl()); 160 161 if(selectedLayers.size() == 1) { 162 showBoundsButton.setEnabled(true); 163 selectedLayer = selectedLayers.get(0); 164 } 165 } else { 166 showBoundsButton.setEnabled(false); 167 selectedLayer = null; 168 } 169 } 170 }); 171 wmsFetchPanel.add(new JScrollPane(layerTree), GBC.eol().weight(1.0, 1.0).insets(5,0,0,0).fill(GridBagConstraints.BOTH)); 172 173 JPanel layerManipulationButtons = new JPanel(); 174 showBoundsButton = new JButton(tr("Show Bounds")); 175 showBoundsButton.setEnabled(false); 176 showBoundsButton.addActionListener(new ActionListener() { 177 @Override 178 public void actionPerformed(ActionEvent e) { 179 if(selectedLayer.bounds != null) { 180 SlippyMapBBoxChooser mapPanel = new SlippyMapBBoxChooser(); 181 mapPanel.setBoundingBox(selectedLayer.bounds); 182 JOptionPane.showMessageDialog(null, mapPanel, tr("Show Bounds"), JOptionPane.PLAIN_MESSAGE); 183 } else { 184 JOptionPane.showMessageDialog(null, tr("No bounding box was found for this layer."), 185 tr("WMS Error"), JOptionPane.ERROR_MESSAGE); 186 } 187 } 188 }); 189 layerManipulationButtons.add(showBoundsButton); 190 191 wmsFetchPanel.add(layerManipulationButtons, GBC.eol().insets(0,0,5,0)); 192 193 final JPanel tmsView = new JPanel(new GridBagLayout()); 194 tmsView.add(new JLabel(tr("TMS URL")), GBC.std().insets(0,0,5,0)); 195 tmsURL = new JTextArea(3, 40); 196 tmsURL.setLineWrap(true); 197 tmsURL.setText("http://sample.com/tms/{zoom}/{x}/{y}.jpg"); 198 tmsURL.addKeyListener(new KeyAdapter() { 199 @Override 200 public void keyReleased(KeyEvent e) { 201 resultingLayerField.setText(buildTMSUrl()); 202 } 203 }); 204 JScrollPane tmsUrlScrollPane = new JScrollPane(tmsURL, 205 JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, 206 JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); 207 tmsUrlScrollPane.setMinimumSize(new Dimension(60, 60)); 208 tmsView.add(tmsUrlScrollPane, GBC.eol().insets(5,0,0,0).fill(GridBagConstraints.HORIZONTAL)); 209 tmsView.add(new JLabel(trc("layer", "Zoom")), GBC.std().insets(0,0,5,0)); 210 tmsZoom = new JTextField(3); 211 tmsZoom.addKeyListener(new KeyAdapter() { 212 @Override 213 public void keyReleased(KeyEvent e) { 214 resultingLayerField.setText(buildTMSUrl()); 215 } 216 }); 217 tmsView.add(tmsZoom, GBC.eol().insets(5,0,0,0).fill(GridBagConstraints.HORIZONTAL)); 218 tmsView.add(new JLabel(), GBC.eop().weight(1.0, 1.0).fill(GridBagConstraints.BOTH)); 219 tabbedPane.addTab(tr("TMS"), tmsView); 220 221 add(new JLabel(tr("Imagery URL")), GBC.std().insets(0,0,5,0)); 222 resultingLayerField = new JTextArea(3, 40); 223 resultingLayerField.setLineWrap(true); 224 JScrollPane bottomScrollPane = new JScrollPane(resultingLayerField, 225 JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); 226 bottomScrollPane.setMinimumSize(new Dimension(60, 60)); 227 add(bottomScrollPane, GBC.eol().weight(1.0, 0.0).insets(5,0,0,0).fill(GridBagConstraints.HORIZONTAL)); 228 229 tabbedPane.addChangeListener(new ChangeListener() { 230 @Override 231 public void stateChanged(ChangeEvent e) { 232 Component sel = tabbedPane.getSelectedComponent(); 233 if(tmsView == sel) { 234 resultingLayerField.setText(buildTMSUrl()); 235 } else if(wmsFetchPanel == sel) { 236 if(serviceUrl != null) { 237 resultingLayerField.setText(buildGetMapUrl()); 238 } 239 } 240 } 241 }); 242 } 243 244 private String sanitize(String s) { 245 return s.replaceAll("[\r\n]+","").trim(); 246 } 247 248 private String buildTMSUrl() { 249 StringBuilder a = new StringBuilder("tms"); 250 String z = sanitize(tmsZoom.getText()); 251 if(!z.isEmpty()) { 252 a.append("["+z+"]"); 253 } 254 a.append(":"); 255 a.append(sanitize(tmsURL.getText())); 256 return a.toString(); 257 } 258 259 private String buildRootUrl() { 260 StringBuilder a = new StringBuilder(serviceUrl.getProtocol()); 261 a.append("://"); 262 a.append(serviceUrl.getHost()); 263 if(serviceUrl.getPort() != -1) { 264 a.append(":"); 265 a.append(serviceUrl.getPort()); 266 } 267 a.append(serviceUrl.getPath()); 268 a.append("?"); 269 if(serviceUrl.getQuery() != null) { 270 a.append(serviceUrl.getQuery()); 271 if (!serviceUrl.getQuery().isEmpty() && !serviceUrl.getQuery().endsWith("&")) { 272 a.append("&"); 273 } 274 } 275 return a.toString(); 276 } 277 278 private String buildGetMapUrl() { 279 StringBuilder a = new StringBuilder(); 280 a.append(buildRootUrl()); 281 a.append("FORMAT=image/jpeg&VERSION=1.1.1&SERVICE=WMS&REQUEST=GetMap&LAYERS="); 282 a.append(commaSepLayerList()); 283 a.append("&STYLES=&SRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}"); 284 285 return a.toString(); 286 } 287 288 private String commaSepLayerList() { 289 StringBuilder b = new StringBuilder(); 290 291 if (selectedLayers != null) { 292 Iterator<LayerDetails> iterator = selectedLayers.iterator(); 293 while (iterator.hasNext()) { 294 LayerDetails layerDetails = iterator.next(); 295 b.append(layerDetails.ident); 296 if(iterator.hasNext()) { 297 b.append(","); 298 } 299 } 300 } 301 302 return b.toString(); 303 } 304 305 private void showError(String incomingData, Exception e) { 306 JOptionPane.showMessageDialog(this, tr("Could not parse WMS layer list."), 307 tr("WMS Error"), JOptionPane.ERROR_MESSAGE); 308 System.err.println("Could not parse WMS layer list. Incoming data:"); 309 System.err.println(incomingData); 310 e.printStackTrace(); 311 } 312 313 private void attemptGetCapabilities(String serviceUrlStr) { 314 URL getCapabilitiesUrl = null; 315 try { 316 if (!Pattern.compile(".*GetCapabilities.*", Pattern.CASE_INSENSITIVE).matcher(serviceUrlStr).matches()) { 317 // If the url doesn't already have GetCapabilities, add it in 318 getCapabilitiesUrl = new URL(serviceUrlStr); 319 final String getCapabilitiesQuery = "VERSION=1.1.1&SERVICE=WMS&REQUEST=GetCapabilities"; 320 if (getCapabilitiesUrl.getQuery() == null) { 321 getCapabilitiesUrl = new URL(serviceUrlStr + "?" + getCapabilitiesQuery); 322 } else if (!getCapabilitiesUrl.getQuery().isEmpty() && !getCapabilitiesUrl.getQuery().endsWith("&")) { 323 getCapabilitiesUrl = new URL(serviceUrlStr + "&" + getCapabilitiesQuery); 324 } else { 325 getCapabilitiesUrl = new URL(serviceUrlStr + getCapabilitiesQuery); 326 } 327 } else { 328 // Otherwise assume it's a good URL and let the subsequent error 329 // handling systems deal with problems 330 getCapabilitiesUrl = new URL(serviceUrlStr); 331 } 332 serviceUrl = new URL(serviceUrlStr); 333 } catch (HeadlessException e) { 334 return; 335 } catch (MalformedURLException e) { 336 JOptionPane.showMessageDialog(this, tr("Invalid service URL."), 337 tr("WMS Error"), JOptionPane.ERROR_MESSAGE); 338 return; 339 } 340 341 String incomingData; 342 try { 343 System.out.println("GET "+getCapabilitiesUrl.toString()); 344 URLConnection openConnection = getCapabilitiesUrl.openConnection(); 345 InputStream inputStream = openConnection.getInputStream(); 346 BufferedReader br = new BufferedReader(UTFInputStreamReader.create(inputStream, "UTF-8")); 347 String line; 348 StringBuilder ba = new StringBuilder(); 349 while ((line = br.readLine()) != null) { 350 ba.append(line); 351 ba.append("\n"); 352 } 353 incomingData = ba.toString(); 354 } catch (IOException e) { 355 JOptionPane.showMessageDialog(this, tr("Could not retrieve WMS layer list."), 356 tr("WMS Error"), JOptionPane.ERROR_MESSAGE); 357 return; 358 } 359 360 Document document; 361 try { 362 //System.out.println("WMS capabilities:\n"+incomingData+"\n"); 363 DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); 364 builderFactory.setValidating(false); 365 builderFactory.setNamespaceAware(true); 366 DocumentBuilder builder = builderFactory.newDocumentBuilder(); 367 builder.setEntityResolver(new EntityResolver() { 368 @Override 369 public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { 370 System.out.println("Ignoring DTD " + publicId + ", " + systemId); 371 return new InputSource(new StringReader("")); 372 } 373 }); 374 document = builder.parse(new InputSource(new StringReader(incomingData))); 375 } catch (ParserConfigurationException e) { 376 showError(incomingData, e); 377 return; 378 } catch (SAXException e) { 379 showError(incomingData, e); 380 return; 381 } catch (IOException e) { 382 showError(incomingData, e); 383 return; 384 } 385 386 // Some WMS service URLs specify a different base URL for their GetMap service 387 Element child = getChild(document.getDocumentElement(), "Capability"); 388 child = getChild(child, "Request"); 389 child = getChild(child, "GetMap"); 390 child = getChild(child, "DCPType"); 391 child = getChild(child, "HTTP"); 392 child = getChild(child, "Get"); 393 child = getChild(child, "OnlineResource"); 394 if (child != null) { 395 String baseURL = child.getAttribute("xlink:href"); 396 if (baseURL != null && !baseURL.equals(serviceUrlStr)) { 397 try { 398 System.out.println("GetCapabilities specifies a different service URL: " + baseURL); 399 serviceUrl = new URL(baseURL); 400 } catch (MalformedURLException e1) { 401 } 402 } 403 } 404 405 try { 406 treeRootNode.setUserObject(getCapabilitiesUrl.getHost()); 407 Element capabilityElem = getChild(document.getDocumentElement(), "Capability"); 408 List<Element> children = getChildren(capabilityElem, "Layer"); 409 List<LayerDetails> layers = parseLayers(children, new HashSet<String>()); 410 updateTreeList(layers); 411 } catch(Exception e) { 412 showError(incomingData, e); 413 return; 414 } 415 } 416 417 private void updateTreeList(List<LayerDetails> layers) { 418 addLayersToTreeData(treeRootNode, layers); 419 layerTree.expandRow(0); 420 } 421 422 private void addLayersToTreeData(MutableTreeNode parent, List<LayerDetails> layers) { 423 for (LayerDetails layerDetails : layers) { 424 DefaultMutableTreeNode treeNode = new DefaultMutableTreeNode(layerDetails); 425 addLayersToTreeData(treeNode, layerDetails.children); 426 treeData.insertNodeInto(treeNode, parent, 0); 427 } 428 } 429 430 private List<LayerDetails> parseLayers(List<Element> children, Set<String> parentCrs) { 431 List<LayerDetails> details = new LinkedList<LayerDetails>(); 432 for (Element element : children) { 433 details.add(parseLayer(element, parentCrs)); 434 } 435 return details; 436 } 437 438 private LayerDetails parseLayer(Element element, Set<String> parentCrs) { 439 String name = getChildContent(element, "Title", null, null); 440 String ident = getChildContent(element, "Name", null, null); 441 442 // The set of supported CRS/SRS for this layer 443 Set<String> crsList = new HashSet<String>(); 444 // ...including this layer's already-parsed parent projections 445 crsList.addAll(parentCrs); 446 447 // Parse the CRS/SRS pulled out of this layer's XML element 448 // I think CRS and SRS are the same at this point 449 List<Element> crsChildren = getChildren(element, "CRS"); 450 crsChildren.addAll(getChildren(element, "SRS")); 451 for (Element child : crsChildren) { 452 String crs = (String) getContent(child); 453 if(crs != null) { 454 String upperCase = crs.trim().toUpperCase(); 455 crsList.add(upperCase); 456 } 457 } 458 459 // Check to see if any of the specified projections are supported by JOSM 460 boolean josmSupportsThisLayer = false; 461 for (String crs : crsList) { 462 josmSupportsThisLayer |= isProjSupported(crs); 463 } 464 465 Bounds bounds = null; 466 Element bboxElem = getChild(element, "EX_GeographicBoundingBox"); 467 if(bboxElem != null) { 468 // Attempt to use EX_GeographicBoundingBox for bounding box 469 double left = Double.parseDouble(getChildContent(bboxElem, "westBoundLongitude", null, null)); 470 double top = Double.parseDouble(getChildContent(bboxElem, "northBoundLatitude", null, null)); 471 double right = Double.parseDouble(getChildContent(bboxElem, "eastBoundLongitude", null, null)); 472 double bot = Double.parseDouble(getChildContent(bboxElem, "southBoundLatitude", null, null)); 473 bounds = new Bounds(bot, left, top, right); 474 } else { 475 // If that's not available, try LatLonBoundingBox 476 bboxElem = getChild(element, "LatLonBoundingBox"); 477 if(bboxElem != null) { 478 double left = Double.parseDouble(bboxElem.getAttribute("minx")); 479 double top = Double.parseDouble(bboxElem.getAttribute("maxy")); 480 double right = Double.parseDouble(bboxElem.getAttribute("maxx")); 481 double bot = Double.parseDouble(bboxElem.getAttribute("miny")); 482 bounds = new Bounds(bot, left, top, right); 483 } 484 } 485 486 List<Element> layerChildren = getChildren(element, "Layer"); 487 List<LayerDetails> childLayers = parseLayers(layerChildren, crsList); 488 489 return new LayerDetails(name, ident, crsList, josmSupportsThisLayer, bounds, childLayers); 490 } 491 492 private boolean isProjSupported(String crs) { 493 for (ProjectionChoice pc : ProjectionPreference.getProjectionChoices()) { 494 if (pc.getPreferencesFromCode(crs) != null) return true; 495 } 496 return false; 497 } 498 499 public ImageryInfo getImageryInfo() { 500 ImageryInfo info = new ImageryInfo(menuName.getText(), resultingLayerField.getText()); 501 if (ImageryType.TMS.equals(info.getImageryType())) { 502 TMSLayer.checkUrl(info.getUrl()); 503 } else if (selectedLayers != null) { 504 HashSet<String> proj = new HashSet<String>(); 505 for(LayerDetails l : selectedLayers) { 506 proj.addAll(l.getProjections()); 507 } 508 info.setServerProjections(proj); 509 } 510 return info; 511 } 512 513 private static String getChildContent(Element parent, String name, String missing, String empty) { 514 Element child = getChild(parent, name); 515 if (child == null) 516 return missing; 517 else { 518 String content = (String) getContent(child); 519 return (content != null) ? content : empty; 520 } 521 } 522 523 private static Object getContent(Element element) { 524 NodeList nl = element.getChildNodes(); 525 StringBuffer content = new StringBuffer(); 526 for (int i = 0; i < nl.getLength(); i++) { 527 Node node = nl.item(i); 528 switch (node.getNodeType()) { 529 case Node.ELEMENT_NODE: 530 return node; 531 case Node.CDATA_SECTION_NODE: 532 case Node.TEXT_NODE: 533 content.append(node.getNodeValue()); 534 break; 535 } 536 } 537 return content.toString().trim(); 538 } 539 540 private static List<Element> getChildren(Element parent, String name) { 541 List<Element> retVal = new LinkedList<Element>(); 542 for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) { 543 if (child instanceof Element && name.equals(child.getNodeName())) { 544 retVal.add((Element) child); 545 } 546 } 547 return retVal; 548 } 549 550 private static Element getChild(Element parent, String name) { 551 if (parent == null) 552 return null; 553 for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) { 554 if (child instanceof Element && name.equals(child.getNodeName())) 555 return (Element) child; 556 } 557 return null; 558 } 559 560 static class LayerDetails { 561 562 private String name; 563 private String ident; 564 private List<LayerDetails> children; 565 private Bounds bounds; 566 private Set<String> crsList; 567 private boolean supported; 568 569 public LayerDetails(String name, String ident, Set<String> crsList, 570 boolean supportedLayer, Bounds bounds, 571 List<LayerDetails> childLayers) { 572 this.name = name; 573 this.ident = ident; 574 this.supported = supportedLayer; 575 this.children = childLayers; 576 this.bounds = bounds; 577 this.crsList = crsList; 578 } 579 580 public boolean isSupported() { 581 return this.supported; 582 } 583 584 public Set<String> getProjections() { 585 return crsList; 586 } 587 588 @Override 589 public String toString() { 590 if(this.name == null || this.name.isEmpty()) 591 return this.ident; 592 else 593 return this.name; 594 } 595 596 } 597 598 static class LayerTreeCellRenderer extends DefaultTreeCellRenderer { 599 @Override 600 public Component getTreeCellRendererComponent(JTree tree, Object value, 601 boolean sel, boolean expanded, boolean leaf, int row, 602 boolean hasFocus) { 603 super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, 604 row, hasFocus); 605 DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) value; 606 Object userObject = treeNode.getUserObject(); 607 if (userObject instanceof LayerDetails) { 608 LayerDetails layer = (LayerDetails) userObject; 609 setEnabled(layer.isSupported()); 610 } 611 return this; 612 } 613 } 614 615 }