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