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}