// License: WTFPL. For details, see LICENSE file. package iodb; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.List; import javax.swing.Action; import javax.swing.Icon; import javax.swing.JOptionPane; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.actions.JosmAction; import org.openstreetmap.josm.data.coor.CoordinateFormat; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.projection.Projection; import org.openstreetmap.josm.gui.layer.AbstractTileSourceLayer; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.Shortcut; import org.xml.sax.SAXException; /** * Download a list of imagery offsets for the current position, let user choose which one to use. * * @author Zverik * @license WTFPL */ public class GetImageryOffsetAction extends JosmAction implements ImageryOffsetWatcher.OffsetStateListener { private Icon iconOffsetOk; private Icon iconOffsetBad; /** * Initialize the action. Sets "Ctrl+Alt+I" shortcut: the only shortcut in this plugin. * Also registers itself with {@link ImageryOffsetWatcher}. */ public GetImageryOffsetAction() { super(tr("Get Imagery Offset..."), "getoffset", tr("Download offsets for current imagery from a server"), Shortcut.registerShortcut("imageryoffset:get", tr("Imagery: {0}", tr("Get Imagery Offset...")), KeyEvent.VK_I, Shortcut.ALT_CTRL), true); iconOffsetOk = new ImageProvider("getoffset").setSize(ImageProvider.ImageSizes.MENU).get(); iconOffsetBad = new ImageProvider("getoffsetnow").setSize(ImageProvider.ImageSizes.MENU).get(); ImageryOffsetWatcher.getInstance().register(this); } /** * The action just executes {@link DownloadOffsetsTask}. */ @Override public void actionPerformed(ActionEvent e) { if (Main.map == null || Main.map.mapView == null || !Main.map.isVisible()) return; Projection proj = Main.map.mapView.getProjection(); LatLon center = proj.eastNorth2latlon(Main.map.mapView.getCenter()); AbstractTileSourceLayer layer = ImageryOffsetTools.getTopImageryLayer(); String imagery = ImageryOffsetTools.getImageryID(layer); if (imagery == null) return; DownloadOffsetsTask download = new DownloadOffsetsTask(center, layer, imagery); Main.worker.submit(download); } /** * This action is enabled when there's a map, mapView and one of the layers * is an imagery layer. */ @Override protected void updateEnabledState() { boolean state = true; if (Main.map == null || Main.map.mapView == null || !Main.map.isVisible()) state = false; AbstractTileSourceLayer layer = ImageryOffsetTools.getTopImageryLayer(); if (ImageryOffsetTools.getImageryID(layer) == null) state = false; setEnabled(state); } /** * Display a dialog for choosing between offsets. If there are no offsets in * the list, displays the relevant message instead. * @param offsets List of offset objects to choose from. */ private void showOffsetDialog(List<ImageryOffsetBase> offsets) { if (offsets.isEmpty()) { JOptionPane.showMessageDialog(Main.parent, tr("No data for this region. Please adjust imagery layer and upload an offset."), ImageryOffsetTools.DIALOG_TITLE, JOptionPane.INFORMATION_MESSAGE); return; } OffsetDialog offsetDialog = new OffsetDialog(offsets); if (offsetDialog.showDialog() != null) offsetDialog.applyOffset(); } /** * Update action icon based on an offset state. */ @Override public void offsetStateChanged(boolean isOffsetGood) { putValue(Action.SMALL_ICON, isOffsetGood ? iconOffsetOk : iconOffsetBad); } /** * Remove offset listener. */ @Override public void destroy() { ImageryOffsetWatcher.getInstance().unregister(this); super.destroy(); } /** * A task that downloads offsets for a given position and imagery layer, * then parses resulting XML and calls * {@link #showOffsetDialog(java.util.List)} on success. */ private class DownloadOffsetsTask extends SimpleOffsetQueryTask { private List<ImageryOffsetBase> offsets; /** * Initializes query object from the parameters. * @param center A center point of a map view. * @param layer The topmost imagery layer. * @param imagery Imagery ID for the layer. */ DownloadOffsetsTask(LatLon center, AbstractTileSourceLayer layer, String imagery) { super(null, tr("Loading imagery offsets...")); try { String query = "get?lat=" + center.latToString(CoordinateFormat.DECIMAL_DEGREES) + "&lon=" + center.lonToString(CoordinateFormat.DECIMAL_DEGREES) + "&imagery=" + URLEncoder.encode(imagery, "UTF8"); int radius = Main.pref.getInteger("iodb.radius", -1); if (radius > 0) query = query + "&radius=" + radius; setQuery(query); } catch (UnsupportedEncodingException e) { throw new IllegalArgumentException(e); } } /** * Displays offset dialog on success. */ @Override protected void afterFinish() { if (!cancelled && offsets != null) showOffsetDialog(offsets); } /** * Parses the response with {@link IODBReader}. * @param inp Response input stream. * @throws iodb.SimpleOffsetQueryTask.UploadException Thrown on XML parsing error. */ @Override protected void processResponse(InputStream inp) throws UploadException { offsets = null; try { offsets = new IODBReader(inp).parse(); } catch (IOException | SAXException e) { throw new UploadException(tr("Error processing XML response: {0}", e.getMessage())); } } } }