// License: WTFPL. For details, see LICENSE file.
package iodb;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.ActionEvent;
import java.awt.geom.GeneralPath;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Icon;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.actions.AutoScaleAction;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
import org.openstreetmap.josm.gui.MapView;
import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
import org.openstreetmap.josm.gui.layer.Layer;
import org.openstreetmap.josm.tools.ImageProvider;
/**
* A layer that displays calibration geometry for an offset.
*
* @author Zverik
* @license WTFPL
*/
public class CalibrationLayer extends Layer {
private Color color;
private Icon icon;
private CalibrationObject obj;
private LatLon center;
public CalibrationLayer(CalibrationObject obj) {
super(tr("Calibration Layer"));
color = Color.RED;
this.obj = obj;
}
/**
* Draw the calibration geometry with thin bright lines (or a crosshair
* in case of a point).
*/
@Override
public void paint(Graphics2D g, MapView mv, Bounds box) {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Stroke oldStroke = g.getStroke();
g.setColor(color);
g.setStroke(new BasicStroke(1));
LatLon[] geometry = obj.getGeometry();
if (geometry.length == 1) {
// draw crosshair
Point p = mv.getPoint(geometry[0]);
g.drawLine(p.x, p.y, p.x, p.y);
g.drawLine(p.x - 10, p.y, p.x - 20, p.y);
g.drawLine(p.x + 10, p.y, p.x + 20, p.y);
g.drawLine(p.x, p.y - 10, p.x, p.y - 20);
g.drawLine(p.x, p.y + 10, p.x, p.y + 20);
} else if (geometry.length > 1) {
// draw a line
GeneralPath path = new GeneralPath();
for (int i = 0; i < geometry.length; i++) {
Point p = mv.getPoint(geometry[i]);
if (i == 0)
path.moveTo(p.x, p.y);
else
path.lineTo(p.x, p.y);
}
g.draw(path);
}
g.setStroke(oldStroke);
}
@Override
public Icon getIcon() {
if (icon == null)
icon = ImageProvider.get("calibration_layer");
return icon;
}
@Override
public void mergeFrom(Layer from) {
}
@Override
public boolean isMergable(Layer other) {
return false;
}
/**
* This is for determining a bounding box for the layer.
*/
@Override
public void visitBoundingBox(BoundingXYVisitor v) {
for (LatLon ll : obj.getGeometry()) {
v.visit(ll);
}
}
/**
* A simple tooltip with geometry type, status and author.
*/
@Override
public String getToolTipText() {
if (obj.isDeprecated())
return tr("A deprecated calibration geometry of {0} nodes by {1}", obj.getGeometry().length, obj.getAuthor());
else
return tr("A calibration geometry of {0} nodes by {1}", obj.getGeometry().length, obj.getAuthor());
}
@Override
public Object getInfoComponent() {
return OffsetInfoAction.getInformationObject(obj);
}
/**
* This method returns standard actions plus "zoom to layer" and "change color".
*/
@Override
public Action[] getMenuEntries() {
return new Action[] {
LayerListDialog.getInstance().createShowHideLayerAction(),
LayerListDialog.getInstance().createDeleteLayerAction(),
SeparatorLayerAction.INSTANCE,
new ZoomToLayerAction(),
new SelectColorAction(Color.RED),
new SelectColorAction(Color.CYAN),
new SelectColorAction(Color.YELLOW),
SeparatorLayerAction.INSTANCE,
new LayerListPopup.InfoAction(this)
};
}
/**
* This method pans to the geometry, preserving zoom. It is used
* from {@link GetImageryOffsetAction}, because {@link AutoScaleAction}
* doesn't have a relevant method.
*/
public void panToCenter() {
if (center == null) {
LatLon[] geometry = obj.getGeometry();
double lat = 0.0;
double lon = 0.0;
for (int i = 0; i < geometry.length; i++) {
lon += geometry[i].lon();
lat += geometry[i].lat();
}
center = new LatLon(lat / geometry.length, lon / geometry.length);
}
Main.map.mapView.zoomTo(center);
}
/**
* An action to change a color of a geometry. The color
* is specified in the constuctor. See {@link #getMenuEntries()} for
* the list of enabled colors.
*/
class SelectColorAction extends AbstractAction {
private Color c;
SelectColorAction(Color color) {
super(tr("Change Color"));
putValue(SMALL_ICON, new SingleColorIcon(color));
this.c = color;
}
@Override
public void actionPerformed(ActionEvent e) {
color = c;
Main.map.mapView.repaint();
}
}
/**
* A simple icon with a colored rectangle.
*/
static class SingleColorIcon implements Icon {
private Color color;
SingleColorIcon(Color color) {
this.color = color;
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
g.setColor(color);
g.fillRect(x, y, 24, 24);
}
@Override
public int getIconWidth() {
return 24;
}
@Override
public int getIconHeight() {
return 24;
}
}
/**
* An action that calls {@link AutoScaleAction} which in turn
* uses {@link #visitBoundingBox} to pan and zoom to the calibration geometry.
*/
static class ZoomToLayerAction extends AbstractAction {
ZoomToLayerAction() {
super(tr("Zoom to {0}", tr("layer"))); // to use translation from AutoScaleAction
putValue(SMALL_ICON, ImageProvider.get("dialogs/autoscale/layer"));
}
@Override
public void actionPerformed(ActionEvent e) {
AutoScaleAction.autoScale("layer");
}
}
}