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}