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    }