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 }