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