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.GridBagConstraints; 008import java.awt.GridBagLayout; 009import java.awt.event.ActionEvent; 010import java.awt.event.KeyEvent; 011import java.io.IOException; 012import java.util.ArrayList; 013import java.util.List; 014import java.util.regex.Matcher; 015import java.util.regex.Pattern; 016 017import javax.swing.ButtonGroup; 018import javax.swing.JLabel; 019import javax.swing.JOptionPane; 020import javax.swing.JPanel; 021import javax.swing.JRadioButton; 022 023import org.openstreetmap.josm.Main; 024import org.openstreetmap.josm.data.imagery.ImageryInfo; 025import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType; 026import org.openstreetmap.josm.gui.ExtendedDialog; 027import org.openstreetmap.josm.gui.MainApplication; 028import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils; 029import org.openstreetmap.josm.gui.layer.ImageryLayer; 030import org.openstreetmap.josm.gui.widgets.JosmTextField; 031import org.openstreetmap.josm.gui.widgets.UrlLabel; 032import org.openstreetmap.josm.io.imagery.WMSImagery.WMSGetCapabilitiesException; 033import org.openstreetmap.josm.tools.GBC; 034import org.openstreetmap.josm.tools.Logging; 035import org.openstreetmap.josm.tools.Shortcut; 036 037/** 038 * Download rectified images from various services. 039 * @since 3715 040 */ 041public class MapRectifierWMSmenuAction extends JosmAction { 042 043 /** 044 * Class that bundles all required information of a rectifier service 045 */ 046 public static class RectifierService { 047 private final String name; 048 private final String url; 049 private final String wmsUrl; 050 private final Pattern urlRegEx; 051 private final Pattern idValidator; 052 private JRadioButton btn; 053 054 /** 055 * Constructs a new {@code RectifierService}. 056 * @param name Name of the rectifing service 057 * @param url URL to the service where users can register, upload, etc. 058 * @param wmsUrl URL to the WMS server where JOSM will grab the images. Insert __s__ where the ID should be placed 059 * @param urlRegEx a regular expression that determines if a given URL is one of the service and returns the WMS id if so 060 * @param idValidator regular expression that checks if a given ID is syntactically valid 061 */ 062 public RectifierService(String name, String url, String wmsUrl, String urlRegEx, String idValidator) { 063 this.name = name; 064 this.url = url; 065 this.wmsUrl = wmsUrl; 066 this.urlRegEx = Pattern.compile(urlRegEx); 067 this.idValidator = Pattern.compile(idValidator); 068 } 069 070 private boolean isSelected() { 071 return btn.isSelected(); 072 } 073 } 074 075 /** 076 * List of available rectifier services. 077 */ 078 private final transient List<RectifierService> services = new ArrayList<>(); 079 080 /** 081 * Constructs a new {@code MapRectifierWMSmenuAction}. 082 */ 083 public MapRectifierWMSmenuAction() { 084 super(tr("Rectified Image..."), 085 "OLmarker", 086 tr("Download Rectified Images From Various Services"), 087 Shortcut.registerShortcut("imagery:rectimg", 088 tr("Imagery: {0}", tr("Rectified Image...")), 089 KeyEvent.CHAR_UNDEFINED, Shortcut.NONE), 090 true 091 ); 092 putValue("help", ht("/Menu/Imagery")); 093 094 // Add default services 095 services.add( 096 new RectifierService("Metacarta Map Rectifier", 097 "http://labs.metacarta.com/rectifier/", 098 "http://labs.metacarta.com/rectifier/wms.cgi?id=__s__&srs=EPSG:4326" 099 + "&Service=WMS&Version=1.1.0&Request=GetMap&format=image/png&", 100 // This matches more than the "classic" WMS link, so users can pretty much 101 // copy any link as long as it includes the ID 102 "labs\\.metacarta\\.com/(?:.*?)(?:/|=)([0-9]+)(?:\\?|/|\\.|$)", 103 "^[0-9]+$") 104 ); 105 services.add( 106 new RectifierService("Map Warper", 107 "http://mapwarper.net/", 108 "http://mapwarper.net/maps/wms/__s__?request=GetMap&version=1.1.1" 109 + "&styles=&format=image/png&srs=epsg:4326&exceptions=application/vnd.ogc.se_inimage&", 110 // This matches more than the "classic" WMS link, so users can pretty much 111 // copy any link as long as it includes the ID 112 "(?:mapwarper\\.net|warper\\.geothings\\.net)/(?:.*?)/([0-9]+)(?:\\?|/|\\.|$)", 113 "^[0-9]+$") 114 ); 115 116 // This service serves the purpose of "just this once" without forcing the user 117 // to commit the link to the preferences 118 119 // Clipboard content gets trimmed, so matching whitespace only ensures that this 120 // service will never be selected automatically. 121 services.add(new RectifierService(tr("Custom WMS Link"), "", "", "^\\s+$", "")); 122 } 123 124 @Override 125 public void actionPerformed(ActionEvent e) { 126 if (!isEnabled()) return; 127 JPanel panel = new JPanel(new GridBagLayout()); 128 panel.add(new JLabel(tr("Supported Rectifier Services:")), GBC.eol()); 129 130 JosmTextField tfWmsUrl = new JosmTextField(30); 131 132 String clip = ClipboardUtils.getClipboardStringContent(); 133 clip = clip == null ? "" : clip.trim(); 134 ButtonGroup group = new ButtonGroup(); 135 136 JRadioButton firstBtn = null; 137 for (RectifierService s : services) { 138 JRadioButton serviceBtn = new JRadioButton(s.name); 139 if (firstBtn == null) { 140 firstBtn = serviceBtn; 141 } 142 // Checks clipboard contents against current service if no match has been found yet. 143 // If the contents match, they will be inserted into the text field and the corresponding 144 // service will be pre-selected. 145 if (!clip.isEmpty() && tfWmsUrl.getText().isEmpty() 146 && (s.urlRegEx.matcher(clip).find() || s.idValidator.matcher(clip).matches())) { 147 serviceBtn.setSelected(true); 148 tfWmsUrl.setText(clip); 149 } 150 s.btn = serviceBtn; 151 group.add(serviceBtn); 152 if (!s.url.isEmpty()) { 153 panel.add(serviceBtn, GBC.std()); 154 panel.add(new UrlLabel(s.url, tr("Visit Homepage")), GBC.eol().anchor(GridBagConstraints.EAST)); 155 } else { 156 panel.add(serviceBtn, GBC.eol().anchor(GridBagConstraints.WEST)); 157 } 158 } 159 160 // Fallback in case no match was found 161 if (tfWmsUrl.getText().isEmpty() && firstBtn != null) { 162 firstBtn.setSelected(true); 163 } 164 165 panel.add(new JLabel(tr("WMS URL or Image ID:")), GBC.eol()); 166 panel.add(tfWmsUrl, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); 167 168 ExtendedDialog diag = new ExtendedDialog(Main.parent, 169 tr("Add Rectified Image"), 170 tr("Add Rectified Image"), tr("Cancel")) 171 .setContent(panel) 172 .setButtonIcons("OLmarker", "cancel"); 173 174 // This repeatedly shows the dialog in case there has been an error. 175 // The loop is break;-ed if the users cancels 176 outer: while (true) { 177 diag.showDialog(); 178 int answer = diag.getValue(); 179 // Break loop when the user cancels 180 if (answer != 1) { 181 break; 182 } 183 184 String text = tfWmsUrl.getText().trim(); 185 // Loop all services until we find the selected one 186 for (RectifierService s : services) { 187 if (!s.isSelected()) { 188 continue; 189 } 190 191 // We've reached the custom WMS URL service 192 // Just set the URL and hope everything works out 193 if (s.wmsUrl.isEmpty()) { 194 try { 195 addWMSLayer(s.name + " (" + text + ')', text); 196 break outer; 197 } catch (IllegalStateException ex) { 198 Logging.log(Logging.LEVEL_ERROR, ex); 199 } 200 } 201 202 // First try to match if the entered string as an URL 203 Matcher m = s.urlRegEx.matcher(text); 204 if (m.find()) { 205 String id = m.group(1); 206 String newURL = s.wmsUrl.replaceAll("__s__", id); 207 String title = s.name + " (" + id + ')'; 208 addWMSLayer(title, newURL); 209 break outer; 210 } 211 // If not, look if it's a valid ID for the selected service 212 if (s.idValidator.matcher(text).matches()) { 213 String newURL = s.wmsUrl.replaceAll("__s__", text); 214 String title = s.name + " (" + text + ')'; 215 addWMSLayer(title, newURL); 216 break outer; 217 } 218 219 // We've found the selected service, but the entered string isn't suitable for 220 // it. So quit checking the other radio buttons 221 break; 222 } 223 224 // and display an error message. The while loop ensures that the dialog pops up again 225 JOptionPane.showMessageDialog(Main.parent, 226 tr("Couldn''t match the entered link or id to the selected service. Please try again."), 227 tr("No valid WMS URL or id"), 228 JOptionPane.ERROR_MESSAGE); 229 diag.setVisible(true); 230 } 231 } 232 233 /** 234 * Adds a WMS Layer with given title and URL 235 * @param title Name of the layer as it will shop up in the layer manager 236 * @param url URL to the WMS server 237 * @throws IllegalStateException if imagery time is neither HTML nor WMS 238 */ 239 private static void addWMSLayer(String title, String url) { 240 ImageryInfo info = new ImageryInfo(title, url); 241 if (info.getImageryType() == ImageryType.WMS_ENDPOINT) { 242 try { 243 info = AddImageryLayerAction.getWMSLayerInfo(info); 244 } catch (IOException | WMSGetCapabilitiesException e) { 245 Logging.error(e); 246 JOptionPane.showMessageDialog(Main.parent, e.getMessage(), tr("No valid WMS URL or id"), JOptionPane.ERROR_MESSAGE); 247 return; 248 } 249 } 250 MainApplication.getLayerManager().addLayer(ImageryLayer.create(info)); 251 } 252 253 @Override 254 protected void updateEnabledState() { 255 setEnabled(!getLayerManager().getLayers().isEmpty()); 256 } 257}