// License: WTFPL. For details, see LICENSE file.
package iodb;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.event.ActionEvent;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JOptionPane;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.actions.JosmAction;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.gui.JosmUserIdentityManager;
import org.openstreetmap.josm.gui.layer.AbstractTileSourceLayer;
/**
* Upload the current imagery offset or an calibration geometry information.
*
* @author Zverik
* @license WTFPL
*/
public class StoreImageryOffsetAction extends JosmAction {
/**
* Initializes the action.
*/
public StoreImageryOffsetAction() {
super(tr("Store Imagery Offset..."), "storeoffset",
tr("Upload an offset for current imagery (or calibration object geometry) to a server"),
null, true);
}
/**
* Asks user for description and calls the upload task.
* Also calculates a lot of things, checks whether the selected object
* is suitable for calibration geometry, constructs a map of query parameters etc.
* The only thing it doesn't do is check for the real user account name.
* This is because all server queries should be executed in workers,
* and we don't have one when a user name is needed.
*/
@Override
public void actionPerformed(ActionEvent e) {
if (Main.map == null || Main.map.mapView == null)
return;
AbstractTileSourceLayer layer = ImageryOffsetTools.getTopImageryLayer();
if (layer == null)
return;
String userName = JosmUserIdentityManager.getInstance().getUserName();
if (userName == null || userName.length() == 0) {
JOptionPane.showMessageDialog(Main.parent,
tr("To store imagery offsets you must be a registered OSM user."),
ImageryOffsetTools.DIALOG_TITLE, JOptionPane.ERROR_MESSAGE);
return;
}
if (userName.indexOf('@') > 0)
userName = userName.replace('@', ',');
// check if an object suitable for calibration is selected
OsmPrimitive calibration = null;
if (getLayerManager().getEditDataSet() != null) {
Collection<OsmPrimitive> selectedObjects = getLayerManager().getEditDataSet().getSelected();
if (selectedObjects.size() == 1) {
OsmPrimitive selection = selectedObjects.iterator().next();
if ((selection instanceof Node || selection instanceof Way) && !selection.isIncomplete() && !selection.isReferredByWays(1)) {
String[] options = new String[] {tr("Store calibration geometry"), tr("Store imagery offset")};
int result = JOptionPane.showOptionDialog(Main.parent,
tr("The selected object can be used as a calibration geometry. What do you intend to do?"),
ImageryOffsetTools.DIALOG_TITLE, JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE,
null, options, options[0]);
if (result == 2 || result == JOptionPane.CLOSED_OPTION)
return;
if (result == 0)
calibration = selection;
}
}
}
Object message;
LatLon center = ImageryOffsetTools.getMapCenter();
ImageryOffsetBase offsetObj;
if (calibration == null) {
// register imagery offset
if (Math.abs(layer.getDisplaySettings().getDx()) < 1e-8 && Math.abs(layer.getDisplaySettings().getDy()) < 1e-8) {
if (JOptionPane.showConfirmDialog(Main.parent,
tr("The topmost imagery layer has no offset. Are you sure you want to upload this?"),
ImageryOffsetTools.DIALOG_TITLE, JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION)
return;
}
LatLon offset = ImageryOffsetTools.getLayerOffset(layer, center);
offsetObj = new ImageryOffset(ImageryOffsetTools.getImageryID(layer), offset);
message = tr("You are registering an imagery offset. Other users in this area will be able to use it for mapping.\n"
+ "Please make sure it is as precise as possible, and describe a region this offset is applicable to.");
} else {
// register calibration object
offsetObj = new CalibrationObject(calibration);
message = tr("You are registering a calibration geometry. It should be the most precisely positioned object, with\n"
+ "clearly visible boundaries on various satellite imagery. Please describe this object and its whereabouts.");
}
String description = queryDescription(message);
if (description == null)
return;
offsetObj.setBasicInfo(center, userName, null, null);
offsetObj.setDescription(description);
// upload object info to server
try {
Map<String, String> params = new HashMap<>();
offsetObj.putServerParams(params);
StringBuilder query = null;
for (String key : params.keySet()) {
if (query == null) {
query = new StringBuilder("store?");
} else {
query.append('&');
}
query.append(key).append('=').append(URLEncoder.encode(params.get(key), "UTF8"));
}
Main.worker.submit(new SimpleOffsetQueryTask(query.toString(), tr("Uploading a new offset...")));
} catch (UnsupportedEncodingException ex) {
Main.error(ex);
}
}
/**
* Ask a user for a description / reason. This string should be 3 to 200 characters
* long, and the method enforces that.
* @param message A prompt for the input dialog.
* @return Either null or a string 3 to 200 letters long.
*/
public static String queryDescription(Object message) {
String reason = null;
boolean iterated = false;
boolean ok = false;
while (!ok) {
Object result = JOptionPane.showInputDialog(Main.parent, message,
ImageryOffsetTools.DIALOG_TITLE, JOptionPane.PLAIN_MESSAGE, null, null, reason);
if (result == null || result.toString().length() == 0) {
return null;
}
reason = result.toString();
if (reason.length() < 3 || reason.length() > 200) {
if (!iterated) {
message = message + "\n" + tr("This string should be 3 to 200 letters long.");
iterated = true;
}
} else
ok = true;
}
return reason;
}
/**
* This action is enabled when there's a map and a visible imagery layer.
* Note that it doesn't require edit layer.
*/
@Override
protected void updateEnabledState() {
boolean state = true;
if (Main.map == null || Main.map.mapView == null || !Main.map.isVisible())
state = false;
if (ImageryOffsetTools.getTopImageryLayer() == null)
state = false;
setEnabled(state);
}
}