source: josm/trunk/src/org/openstreetmap/josm/actions/AddImageryLayerAction.java@ 14549

Last change on this file since 14549 was 14549, checked in by Don-vip, 5 years ago

see #16073 - support WMS_ENDPOINT

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