001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions;
003
004import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
005import static org.openstreetmap.josm.tools.I18n.tr;
006
007import java.awt.Dimension;
008import java.awt.GraphicsEnvironment;
009import java.awt.GridBagLayout;
010import java.awt.event.ActionEvent;
011import java.io.IOException;
012import java.net.MalformedURLException;
013import java.util.ArrayList;
014import java.util.Collection;
015import java.util.HashSet;
016import java.util.List;
017import java.util.Set;
018
019import javax.swing.JComboBox;
020import javax.swing.JOptionPane;
021import javax.swing.JPanel;
022import javax.swing.JScrollPane;
023
024import org.openstreetmap.josm.Main;
025import org.openstreetmap.josm.data.imagery.ImageryInfo;
026import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
027import org.openstreetmap.josm.data.imagery.WMTSTileSource;
028import org.openstreetmap.josm.gui.ExtendedDialog;
029import org.openstreetmap.josm.gui.layer.AlignImageryPanel;
030import org.openstreetmap.josm.gui.layer.ImageryLayer;
031import org.openstreetmap.josm.gui.preferences.imagery.WMSLayerTree;
032import org.openstreetmap.josm.gui.util.GuiHelper;
033import org.openstreetmap.josm.io.imagery.WMSImagery;
034import org.openstreetmap.josm.io.imagery.WMSImagery.LayerDetails;
035import org.openstreetmap.josm.io.imagery.WMSImagery.WMSGetCapabilitiesException;
036import org.openstreetmap.josm.tools.CheckParameterUtil;
037import org.openstreetmap.josm.tools.GBC;
038import org.openstreetmap.josm.tools.ImageProvider;
039
040/**
041 * Action displayed in imagery menu to add a new imagery layer.
042 * @since 3715
043 */
044public class AddImageryLayerAction extends JosmAction implements AdaptableAction {
045    private final transient ImageryInfo info;
046
047    /**
048     * Constructs a new {@code AddImageryLayerAction} for the given {@code ImageryInfo}.
049     * If an http:// icon is specified, it is fetched asynchronously.
050     * @param info The imagery info
051     */
052    public AddImageryLayerAction(ImageryInfo info) {
053        super(info.getMenuName(), /* ICON */"imagery_menu", tr("Add imagery layer {0}", info.getName()), null, false, false);
054        putValue("toolbar", "imagery_" + info.getToolbarName());
055        putValue("help", ht("/Preferences/Imagery"));
056        this.info = info;
057        installAdapters();
058
059        // change toolbar icon from if specified
060        String icon = info.getIcon();
061        if (icon != null) {
062            new ImageProvider(icon).setOptional(true).getResourceAsync().thenAccept(result -> {
063                if (result != null) {
064                    GuiHelper.runInEDT(() -> result.attachImageIcon(this));
065                }
066            });
067        }
068    }
069
070    /**
071     * Converts general ImageryInfo to specific one, that does not need any user action to initialize
072     * see: https://josm.openstreetmap.de/ticket/13868
073     * @param info ImageryInfo that will be converted (or returned when no conversion needed)
074     * @return ImageryInfo object that's ready to be used to create TileSource
075     */
076    private ImageryInfo convertImagery(ImageryInfo info) {
077        try {
078            switch(info.getImageryType()) {
079            case WMS_ENDPOINT:
080                // convert to WMS type
081                return getWMSLayerInfo();
082            case WMTS:
083                // specify which layer to use
084                String layerId = new WMTSTileSource(info).userSelectLayer();
085                if (layerId != null) {
086                    ImageryInfo copy = new ImageryInfo(info);
087                    Collection<String> defaultLayers = new ArrayList<>(1);
088                    defaultLayers.add(layerId);
089                    copy.setDefaultLayers(defaultLayers);
090                    return copy;
091                }
092                // layer not selected - refuse to add
093                return null;
094            default:
095                return info;
096            }
097        } catch (MalformedURLException ex) {
098            if (!GraphicsEnvironment.isHeadless()) {
099                JOptionPane.showMessageDialog(Main.parent, tr("Invalid service URL."),
100                        tr("WMS Error"), JOptionPane.ERROR_MESSAGE);
101            }
102            Main.error(ex, false);
103        } catch (IOException ex) {
104            if (!GraphicsEnvironment.isHeadless()) {
105                JOptionPane.showMessageDialog(Main.parent, tr("Could not retrieve WMS layer list."),
106                        tr("WMS Error"), JOptionPane.ERROR_MESSAGE);
107            }
108            Main.error(ex, false);
109        } catch (WMSGetCapabilitiesException ex) {
110            if (!GraphicsEnvironment.isHeadless()) {
111                JOptionPane.showMessageDialog(Main.parent, tr("Could not parse WMS layer list."),
112                        tr("WMS Error"), JOptionPane.ERROR_MESSAGE);
113            }
114            Main.error(ex, "Could not parse WMS layer list. Incoming data:\n"+ex.getIncomingData());
115        }
116        return null;
117    }
118
119    @Override
120    public void actionPerformed(ActionEvent e) {
121        if (!isEnabled()) return;
122        try {
123            final ImageryInfo infoToAdd = convertImagery(info);
124            if (infoToAdd != null) {
125                Main.getLayerManager().addLayer(ImageryLayer.create(infoToAdd));
126                AlignImageryPanel.addNagPanelIfNeeded(infoToAdd);
127            }
128        } catch (IllegalArgumentException ex) {
129            if (ex.getMessage() == null || ex.getMessage().isEmpty() || GraphicsEnvironment.isHeadless()) {
130                throw ex;
131            } else {
132                JOptionPane.showMessageDialog(Main.parent,
133                        ex.getMessage(), tr("Error"),
134                        JOptionPane.ERROR_MESSAGE);
135            }
136        }
137    }
138
139    protected ImageryInfo getWMSLayerInfo() throws IOException, WMSGetCapabilitiesException {
140        CheckParameterUtil.ensureThat(ImageryType.WMS_ENDPOINT.equals(info.getImageryType()), "wms_endpoint imagery type expected");
141
142        final WMSImagery wms = new WMSImagery();
143        wms.attemptGetCapabilities(info.getUrl());
144
145        final WMSLayerTree tree = new WMSLayerTree();
146        tree.updateTree(wms);
147        List<String> wmsFormats = wms.getFormats();
148        final JComboBox<String> formats = new JComboBox<>(wmsFormats.toArray(new String[wmsFormats.size()]));
149        formats.setSelectedItem(wms.getPreferredFormats());
150        formats.setToolTipText(tr("Select image format for WMS layer"));
151
152        if (!GraphicsEnvironment.isHeadless()) {
153            if (1 != new ExtendedDialog(Main.parent, tr("Select WMS layers"), new String[]{tr("Add layers"), tr("Cancel")}) { {
154                final JScrollPane scrollPane = new JScrollPane(tree.getLayerTree());
155                scrollPane.setPreferredSize(new Dimension(400, 400));
156                final JPanel panel = new JPanel(new GridBagLayout());
157                panel.add(scrollPane, GBC.eol().fill());
158                panel.add(formats, GBC.eol().fill(GBC.HORIZONTAL));
159                setContent(panel);
160            } }.showDialog().getValue()) {
161                return null;
162            }
163        }
164
165        final String url = wms.buildGetMapUrl(
166                tree.getSelectedLayers(), (String) formats.getSelectedItem());
167        Set<String> supportedCrs = new HashSet<>();
168        boolean first = true;
169        StringBuilder layersString = new StringBuilder();
170        for (LayerDetails layer: tree.getSelectedLayers()) {
171            if (first) {
172                supportedCrs.addAll(layer.getProjections());
173                first = false;
174            }
175            layersString.append(layer.name);
176            layersString.append(", ");
177            supportedCrs.retainAll(layer.getProjections());
178        }
179
180        // copy all information from WMS
181        ImageryInfo ret = new ImageryInfo(info);
182        // and update according to user choice
183        ret.setUrl(url);
184        ret.setImageryType(ImageryType.WMS);
185        if (layersString.length() > 2) {
186            ret.setName(ret.getName() + ' ' + layersString.substring(0, layersString.length() - 2));
187        }
188        ret.setServerProjections(supportedCrs);
189        return ret;
190    }
191
192    @Override
193    protected void updateEnabledState() {
194        if (info.isBlacklisted()) {
195            setEnabled(false);
196        } else {
197            setEnabled(true);
198        }
199    }
200}