[3719] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
[3715] | 2 | package org.openstreetmap.josm.actions;
|
---|
| 3 |
|
---|
[7665] | 4 | import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
|
---|
[3715] | 5 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 6 |
|
---|
[5617] | 7 | import java.awt.Dimension;
|
---|
[9918] | 8 | import java.awt.GraphicsEnvironment;
|
---|
[6000] | 9 | import java.awt.GridBagLayout;
|
---|
[3715] | 10 | import java.awt.event.ActionEvent;
|
---|
[5617] | 11 | import java.io.IOException;
|
---|
| 12 | import java.net.MalformedURLException;
|
---|
[11216] | 13 | import java.util.Collection;
|
---|
[13879] | 14 | import java.util.Collections;
|
---|
[11017] | 15 | import java.util.List;
|
---|
[13733] | 16 | import java.util.stream.Collectors;
|
---|
[6248] | 17 |
|
---|
[6000] | 18 | import javax.swing.JComboBox;
|
---|
[4531] | 19 | import javax.swing.JOptionPane;
|
---|
[6000] | 20 | import javax.swing.JPanel;
|
---|
[5617] | 21 | import javax.swing.JScrollPane;
|
---|
[6248] | 22 |
|
---|
[3715] | 23 | import org.openstreetmap.josm.Main;
|
---|
[11257] | 24 | import org.openstreetmap.josm.data.imagery.DefaultLayer;
|
---|
[3715] | 25 | import org.openstreetmap.josm.data.imagery.ImageryInfo;
|
---|
[3733] | 26 | import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
|
---|
[13824] | 27 | import org.openstreetmap.josm.data.imagery.LayerDetails;
|
---|
[11216] | 28 | import org.openstreetmap.josm.data.imagery.WMTSTileSource;
|
---|
[13897] | 29 | import org.openstreetmap.josm.data.imagery.WMTSTileSource.Layer;
|
---|
[13733] | 30 | import org.openstreetmap.josm.data.imagery.WMTSTileSource.WMTSGetCapabilitiesException;
|
---|
[5617] | 31 | import org.openstreetmap.josm.gui.ExtendedDialog;
|
---|
[8469] | 32 | import org.openstreetmap.josm.gui.layer.AlignImageryPanel;
|
---|
[3715] | 33 | import org.openstreetmap.josm.gui.layer.ImageryLayer;
|
---|
[11657] | 34 | import org.openstreetmap.josm.gui.preferences.ToolbarPreferences;
|
---|
[6248] | 35 | import org.openstreetmap.josm.gui.preferences.imagery.WMSLayerTree;
|
---|
[6707] | 36 | import org.openstreetmap.josm.gui.util.GuiHelper;
|
---|
[5617] | 37 | import org.openstreetmap.josm.io.imagery.WMSImagery;
|
---|
[10463] | 38 | import org.openstreetmap.josm.io.imagery.WMSImagery.WMSGetCapabilitiesException;
|
---|
[11216] | 39 | import org.openstreetmap.josm.tools.CheckParameterUtil;
|
---|
[6000] | 40 | import org.openstreetmap.josm.tools.GBC;
|
---|
[4721] | 41 | import org.openstreetmap.josm.tools.ImageProvider;
|
---|
[12620] | 42 | import org.openstreetmap.josm.tools.Logging;
|
---|
[13388] | 43 | import org.openstreetmap.josm.tools.bugreport.ReportedException;
|
---|
[3715] | 44 |
|
---|
[6707] | 45 | /**
|
---|
[7509] | 46 | * Action displayed in imagery menu to add a new imagery layer.
|
---|
[6707] | 47 | * @since 3715
|
---|
| 48 | */
|
---|
[4032] | 49 | public class AddImageryLayerAction extends JosmAction implements AdaptableAction {
|
---|
[8308] | 50 | private final transient ImageryInfo info;
|
---|
[3715] | 51 |
|
---|
[11343] | 52 | static class SelectWmsLayersDialog extends ExtendedDialog {
|
---|
| 53 | SelectWmsLayersDialog(WMSLayerTree tree, JComboBox<String> formats) {
|
---|
[12279] | 54 | super(Main.parent, tr("Select WMS layers"), tr("Add layers"), tr("Cancel"));
|
---|
[11343] | 55 | final JScrollPane scrollPane = new JScrollPane(tree.getLayerTree());
|
---|
| 56 | scrollPane.setPreferredSize(new Dimension(400, 400));
|
---|
| 57 | final JPanel panel = new JPanel(new GridBagLayout());
|
---|
| 58 | panel.add(scrollPane, GBC.eol().fill());
|
---|
| 59 | panel.add(formats, GBC.eol().fill(GBC.HORIZONTAL));
|
---|
| 60 | setContent(panel);
|
---|
| 61 | }
|
---|
| 62 | }
|
---|
| 63 |
|
---|
[6707] | 64 | /**
|
---|
| 65 | * Constructs a new {@code AddImageryLayerAction} for the given {@code ImageryInfo}.
|
---|
| 66 | * If an http:// icon is specified, it is fetched asynchronously.
|
---|
| 67 | * @param info The imagery info
|
---|
| 68 | */
|
---|
[3715] | 69 | public AddImageryLayerAction(ImageryInfo info) {
|
---|
[11654] | 70 | super(info.getMenuName(), /* ICON */"imagery_menu", tr("Add imagery layer {0}", info.getName()), null,
|
---|
[11657] | 71 | true, ToolbarPreferences.IMAGERY_PREFIX + info.getToolbarName(), false);
|
---|
[7665] | 72 | putValue("help", ht("/Preferences/Imagery"));
|
---|
[11635] | 73 | setTooltip(info.getToolTipText().replaceAll("</?html>", ""));
|
---|
[3715] | 74 | this.info = info;
|
---|
[3733] | 75 | installAdapters();
|
---|
[4713] | 76 |
|
---|
| 77 | // change toolbar icon from if specified
|
---|
[10212] | 78 | String icon = info.getIcon();
|
---|
| 79 | if (icon != null) {
|
---|
[13252] | 80 | new ImageProvider(icon).setOptional(true).getResourceAsync(result -> {
|
---|
[10601] | 81 | if (result != null) {
|
---|
[10634] | 82 | GuiHelper.runInEDT(() -> result.attachImageIcon(this));
|
---|
[10601] | 83 | }
|
---|
| 84 | });
|
---|
[4713] | 85 | }
|
---|
[3715] | 86 | }
|
---|
| 87 |
|
---|
[11216] | 88 | /**
|
---|
| 89 | * Converts general ImageryInfo to specific one, that does not need any user action to initialize
|
---|
| 90 | * see: https://josm.openstreetmap.de/ticket/13868
|
---|
[11217] | 91 | * @param info ImageryInfo that will be converted (or returned when no conversion needed)
|
---|
| 92 | * @return ImageryInfo object that's ready to be used to create TileSource
|
---|
[11216] | 93 | */
|
---|
| 94 | private ImageryInfo convertImagery(ImageryInfo info) {
|
---|
| 95 | try {
|
---|
| 96 | switch(info.getImageryType()) {
|
---|
| 97 | case WMS_ENDPOINT:
|
---|
| 98 | // convert to WMS type
|
---|
[13733] | 99 | if (info.getDefaultLayers() == null || info.getDefaultLayers().isEmpty()) {
|
---|
| 100 | return getWMSLayerInfo(info);
|
---|
| 101 | } else {
|
---|
| 102 | return info;
|
---|
| 103 | }
|
---|
[11216] | 104 | case WMTS:
|
---|
| 105 | // specify which layer to use
|
---|
[13733] | 106 | if (info.getDefaultLayers() == null || info.getDefaultLayers().isEmpty()) {
|
---|
[13879] | 107 | WMTSTileSource tileSource = new WMTSTileSource(info);
|
---|
| 108 | DefaultLayer layerId = tileSource.userSelectLayer();
|
---|
[13733] | 109 | if (layerId != null) {
|
---|
| 110 | ImageryInfo copy = new ImageryInfo(info);
|
---|
[13879] | 111 | copy.setDefaultLayers(Collections.singletonList(layerId));
|
---|
| 112 | String layerName = tileSource.getLayers().stream()
|
---|
| 113 | .filter(x -> x.getIdentifier().equals(layerId.getLayerName()))
|
---|
[13897] | 114 | .map(Layer::getUserTitle)
|
---|
[13879] | 115 | .findFirst()
|
---|
| 116 | .orElse("");
|
---|
| 117 | copy.setName(copy.getName() + ": " + layerName);
|
---|
[13733] | 118 | return copy;
|
---|
| 119 | }
|
---|
| 120 | return null;
|
---|
| 121 | } else {
|
---|
| 122 | return info;
|
---|
[11216] | 123 | }
|
---|
| 124 | default:
|
---|
| 125 | return info;
|
---|
| 126 | }
|
---|
| 127 | } catch (MalformedURLException ex) {
|
---|
[13830] | 128 | handleException(ex, tr("Invalid service URL."), tr("WMS Error"), null);
|
---|
[11216] | 129 | } catch (IOException ex) {
|
---|
[13830] | 130 | handleException(ex, tr("Could not retrieve WMS layer list."), tr("WMS Error"), null);
|
---|
[11216] | 131 | } catch (WMSGetCapabilitiesException ex) {
|
---|
[13830] | 132 | handleException(ex, tr("Could not parse WMS layer list."), tr("WMS Error"),
|
---|
| 133 | "Could not parse WMS layer list. Incoming data:\n" + ex.getIncomingData());
|
---|
| 134 | } catch (WMTSGetCapabilitiesException ex) {
|
---|
| 135 | handleException(ex, tr("Could not parse WMTS layer list."), tr("WMTS Error"),
|
---|
| 136 | "Could not parse WMTS layer list.");
|
---|
[11216] | 137 | }
|
---|
| 138 | return null;
|
---|
| 139 | }
|
---|
| 140 |
|
---|
[3715] | 141 | @Override
|
---|
| 142 | public void actionPerformed(ActionEvent e) {
|
---|
[3733] | 143 | if (!isEnabled()) return;
|
---|
[13388] | 144 | ImageryLayer layer = null;
|
---|
[4531] | 145 | try {
|
---|
[11216] | 146 | final ImageryInfo infoToAdd = convertImagery(info);
|
---|
[5617] | 147 | if (infoToAdd != null) {
|
---|
[13388] | 148 | layer = ImageryLayer.create(infoToAdd);
|
---|
| 149 | getLayerManager().addLayer(layer);
|
---|
[9134] | 150 | AlignImageryPanel.addNagPanelIfNeeded(infoToAdd);
|
---|
[5617] | 151 | }
|
---|
[13388] | 152 | } catch (IllegalArgumentException | ReportedException ex) {
|
---|
[11181] | 153 | if (ex.getMessage() == null || ex.getMessage().isEmpty() || GraphicsEnvironment.isHeadless()) {
|
---|
[4531] | 154 | throw ex;
|
---|
| 155 | } else {
|
---|
[13388] | 156 | Logging.error(ex);
|
---|
| 157 | JOptionPane.showMessageDialog(Main.parent, ex.getMessage(), tr("Error"), JOptionPane.ERROR_MESSAGE);
|
---|
| 158 | if (layer != null) {
|
---|
| 159 | getLayerManager().removeLayer(layer);
|
---|
| 160 | }
|
---|
[4531] | 161 | }
|
---|
| 162 | }
|
---|
[3715] | 163 | }
|
---|
[5617] | 164 |
|
---|
[12469] | 165 | /**
|
---|
| 166 | * Asks user to choose a WMS layer from a WMS endpoint.
|
---|
| 167 | * @param info the WMS endpoint.
|
---|
| 168 | * @return chosen WMS layer, or null
|
---|
| 169 | * @throws IOException if any I/O error occurs while contacting the WMS endpoint
|
---|
| 170 | * @throws WMSGetCapabilitiesException if the WMS getCapabilities request fails
|
---|
| 171 | */
|
---|
| 172 | protected static ImageryInfo getWMSLayerInfo(ImageryInfo info) throws IOException, WMSGetCapabilitiesException {
|
---|
[13733] | 173 | try {
|
---|
| 174 | CheckParameterUtil.ensureThat(ImageryType.WMS_ENDPOINT.equals(info.getImageryType()), "wms_endpoint imagery type expected");
|
---|
[13872] | 175 | final WMSImagery wms = new WMSImagery(info.getUrl(), info.getCustomHttpHeaders());
|
---|
[5617] | 176 |
|
---|
[13733] | 177 | final WMSLayerTree tree = new WMSLayerTree();
|
---|
| 178 | tree.updateTree(wms);
|
---|
[5617] | 179 |
|
---|
[13733] | 180 | Collection<String> wmsFormats = wms.getFormats();
|
---|
[13741] | 181 | final JComboBox<String> formats = new JComboBox<>(wmsFormats.toArray(new String[0]));
|
---|
[13733] | 182 | formats.setSelectedItem(wms.getPreferredFormat());
|
---|
| 183 | formats.setToolTipText(tr("Select image format for WMS layer"));
|
---|
[5617] | 184 |
|
---|
[13839] | 185 | if (!GraphicsEnvironment.isHeadless()) {
|
---|
| 186 | ExtendedDialog dialog = new ExtendedDialog(Main.parent, tr("Select WMS layers"), tr("Add layers"), tr("Cancel"));
|
---|
| 187 | final JScrollPane scrollPane = new JScrollPane(tree.getLayerTree());
|
---|
| 188 | scrollPane.setPreferredSize(new Dimension(400, 400));
|
---|
| 189 | final JPanel panel = new JPanel(new GridBagLayout());
|
---|
| 190 | panel.add(scrollPane, GBC.eol().fill());
|
---|
| 191 | panel.add(formats, GBC.eol().fill(GBC.HORIZONTAL));
|
---|
| 192 | dialog.setContent(panel);
|
---|
| 193 |
|
---|
| 194 | if (dialog.showDialog().getValue() != 1) {
|
---|
| 195 | return null;
|
---|
| 196 | }
|
---|
[13733] | 197 | }
|
---|
[8763] | 198 |
|
---|
[13733] | 199 | final String url = wms.buildGetMapUrl(
|
---|
[13824] | 200 | tree.getSelectedLayers().stream().map(LayerDetails::getName).collect(Collectors.toList()),
|
---|
[13733] | 201 | (List<String>) null,
|
---|
| 202 | (String) formats.getSelectedItem(),
|
---|
[13824] | 203 | true // TODO: ask the user if transparent layer is wanted
|
---|
[13733] | 204 | );
|
---|
| 205 |
|
---|
| 206 | String selectedLayers = tree.getSelectedLayers().stream()
|
---|
[13824] | 207 | .map(LayerDetails::getName)
|
---|
[13733] | 208 | .collect(Collectors.joining(", "));
|
---|
[13872] | 209 | // Use full copy of original Imagery info to copy all attributes. Only overwrite what's different
|
---|
| 210 | ImageryInfo ret = new ImageryInfo(info);
|
---|
| 211 | ret.setUrl(url);
|
---|
| 212 | ret.setImageryType(ImageryType.WMS);
|
---|
| 213 | ret.setName(info.getName() + selectedLayers);
|
---|
[13733] | 214 | ret.setServerProjections(wms.getServerProjections(tree.getSelectedLayers()));
|
---|
| 215 | return ret;
|
---|
| 216 | } catch (MalformedURLException ex) {
|
---|
[13830] | 217 | handleException(ex, tr("Invalid service URL."), tr("WMS Error"), null);
|
---|
[13733] | 218 | } catch (IOException ex) {
|
---|
[13830] | 219 | handleException(ex, tr("Could not retrieve WMS layer list."), tr("WMS Error"), null);
|
---|
[13733] | 220 | } catch (WMSGetCapabilitiesException ex) {
|
---|
[13830] | 221 | handleException(ex, tr("Could not parse WMS layer list."), tr("WMS Error"),
|
---|
| 222 | "Could not parse WMS layer list. Incoming data:\n" + ex.getIncomingData());
|
---|
[5617] | 223 | }
|
---|
[13733] | 224 | return null;
|
---|
[5617] | 225 | }
|
---|
[6069] | 226 |
|
---|
[13830] | 227 | private static void handleException(Exception ex, String uiMessage, String uiTitle, String logMessage) {
|
---|
| 228 | if (!GraphicsEnvironment.isHeadless()) {
|
---|
| 229 | JOptionPane.showMessageDialog(Main.parent, uiMessage, uiTitle, JOptionPane.ERROR_MESSAGE);
|
---|
| 230 | }
|
---|
| 231 | Logging.log(Logging.LEVEL_ERROR, logMessage, ex);
|
---|
| 232 | }
|
---|
| 233 |
|
---|
[3733] | 234 | @Override
|
---|
| 235 | protected void updateEnabledState() {
|
---|
[8853] | 236 | if (info.isBlacklisted()) {
|
---|
[3826] | 237 | setEnabled(false);
|
---|
[11174] | 238 | } else {
|
---|
[3826] | 239 | setEnabled(true);
|
---|
| 240 | }
|
---|
[3733] | 241 | }
|
---|
[11654] | 242 |
|
---|
| 243 | @Override
|
---|
| 244 | public String toString() {
|
---|
| 245 | return "AddImageryLayerAction [info=" + info + ']';
|
---|
| 246 | }
|
---|
[3733] | 247 | }
|
---|