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

Last change on this file since 16039 was 15744, checked in by Don-vip, 4 years ago

see #14921 - fix NPE

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