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    }