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<RectifierService>();
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}