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    }