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