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