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