// License: GPL. For details, see LICENSE file.
package buildings_tools;
import static buildings_tools.BuildingsToolsPlugin.eastNorth2latlon;
import static buildings_tools.BuildingsToolsPlugin.latlon2eastNorth;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.geom.GeneralPath;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import javax.swing.JOptionPane;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.command.AddCommand;
import org.openstreetmap.josm.command.ChangeCommand;
import org.openstreetmap.josm.command.Command;
import org.openstreetmap.josm.command.DeleteCommand;
import org.openstreetmap.josm.command.SequenceCommand;
import org.openstreetmap.josm.data.coor.EastNorth;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.osm.BBox;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.gui.MapView;
class Building {
private final EastNorth[] en = new EastNorth[4];
double meter = 0;
private double len = 0;
private double width;
private double heading;
private AngleSnap angleSnap = new AngleSnap();
private Double drawingAngle;
public void clearAngleSnap() {
angleSnap.clear();
drawingAngle = null;
}
public void addAngleSnap(Node[] nodes) {
drawingAngle = angleSnap.addSnap(nodes);
}
public void addAngleSnap(Way way) {
angleSnap.addSnap(way);
if (drawingAngle == null) {
drawingAngle = angleSnap.getAngle();
}
}
public double getLength() {
return len;
}
public double getWidth() {
return width;
}
public boolean isRectDrawing() {
return drawingAngle != null && ToolSettings.getWidth() == 0 && ToolSettings.getLenStep() == 0;
}
public Double getDrawingAngle() {
return drawingAngle;
}
public void reset() {
len = 0;
for (int i = 0; i < 4; i++) {
en[i] = null;
}
}
public EastNorth getPoint(int num) {
return en[num];
}
private void updMetrics() {
meter = 1 / Math.cos(Math.toRadians(eastNorth2latlon(en[0]).lat()));
len = 0;
}
public void setBase(EastNorth base) {
en[0] = base;
updMetrics();
}
public void setBase(Node base) {
en[0] = latlon2eastNorth(base.getCoor());
updMetrics();
}
/**
* @param p
* The point to project
* @return Projection of the point to the heading vector in metres
*/
private double projection1(EastNorth p) {
final EastNorth vec = p.subtract(en[0]);
return (Math.sin(heading) * vec.east() + Math.cos(heading) * vec.north()) / meter;
}
/**
* @param p
* The point to project
* @return Projection of the point to the perpendicular of the heading
* vector in metres
*/
private double projection2(EastNorth p) {
final EastNorth vec = p.subtract(en[0]);
return (Math.cos(heading) * vec.east() - Math.sin(heading) * vec.north()) / meter;
}
private void updatePos() {
if (len == 0)
return;
final EastNorth p1 = en[0];
en[1] = new EastNorth(p1.east() + Math.sin(heading) * len * meter, p1.north() + Math.cos(heading) * len * meter);
en[2] = new EastNorth(p1.east() + Math.sin(heading) * len * meter + Math.cos(heading) * width * meter,
p1.north() + Math.cos(heading) * len * meter - Math.sin(heading) * width * meter);
en[3] = new EastNorth(p1.east() + Math.cos(heading) * width * meter,
p1.north() - Math.sin(heading) * width * meter);
}
public void setLengthWidth(double length, double width) {
this.len = length;
this.width = width;
updatePos();
}
public void setWidth(EastNorth p3) {
this.width = projection2(p3);
updatePos();
}
public void setPlace(EastNorth p2, double width, double lenstep, boolean ignoreConstraints) {
if (en[0] == null)
throw new IllegalStateException("setPlace() called without the base point");
this.heading = en[0].heading(p2);
if (!ignoreConstraints)
this.heading = angleSnap.snapAngle(this.heading);
this.width = width;
this.len = projection1(p2);
if (lenstep > 0 && !ignoreConstraints)
this.len = Math.round(this.len / lenstep) * lenstep;
updatePos();
Main.map.statusLine.setHeading(Math.toDegrees(heading));
if (this.drawingAngle != null && !ignoreConstraints) {
double ang = Math.toDegrees(heading - this.drawingAngle);
if (ang < 0)
ang += 360;
if (ang > 360)
ang -= 360;
Main.map.statusLine.setAngle(ang);
}
}
public void setPlaceRect(EastNorth p2) {
if (en[0] == null)
throw new IllegalStateException("SetPlaceRect() called without the base point");
if (!isRectDrawing())
throw new IllegalStateException("Invalid drawing mode");
heading = drawingAngle;
setLengthWidth(projection1(p2), projection2(p2));
Main.map.statusLine.setHeading(Math.toDegrees(heading));
}
public void angFix(EastNorth point) {
EastNorth en3 = en[2];
EastNorth mid = en[0].getCenter(en3);
double radius = en3.distance(mid);
heading = mid.heading(point);
heading = en[0].heading(mid.add(Math.sin(heading) * radius, Math.cos(heading) * radius));
setLengthWidth(projection1(en3), projection2(en3));
en[2] = en3;
}
public void paint(Graphics2D g, MapView mv) {
if (len == 0)
return;
GeneralPath b = new GeneralPath();
Point pp1 = mv.getPoint(eastNorth2latlon(en[0]));
Point pp2 = mv.getPoint(eastNorth2latlon(en[1]));
Point pp3 = mv.getPoint(eastNorth2latlon(en[2]));
Point pp4 = mv.getPoint(eastNorth2latlon(en[3]));
b.moveTo(pp1.x, pp1.y);
b.lineTo(pp2.x, pp2.y);
b.lineTo(pp3.x, pp3.y);
b.lineTo(pp4.x, pp4.y);
b.lineTo(pp1.x, pp1.y);
g.draw(b);
}
private Node findNode(EastNorth pos) {
DataSet ds = Main.getLayerManager().getEditDataSet();
LatLon l = eastNorth2latlon(pos);
List<Node> nodes = ds.searchNodes(new BBox(l.lon() - 0.0000001, l.lat() - 0.0000001,
l.lon() + 0.0000001, l.lat() + 0.0000001));
Node bestnode = null;
double mindist = 0.0003;
for (Node n : nodes) {
double dist = n.getCoor().distanceSq(l);
if (dist < mindist && n.isUsable()) {
bestnode = n;
mindist = dist;
}
}
return bestnode;
}
/**
* Returns a node with address tags under the building.
*
* @return A node with address tags under the building.
*/
private Node getAddressNode() {
BBox bbox = new BBox(eastNorth2latlon(en[0]), eastNorth2latlon(en[1]));
bbox.add(eastNorth2latlon(en[2]));
bbox.add(eastNorth2latlon(en[3]));
List<Node> nodes = new LinkedList<>();
nodesloop:
for (Node n : Main.getLayerManager().getEditDataSet().searchNodes(bbox)) {
if (!n.isUsable())
continue;
tagcheck: do {
for (String key : n.getKeys().keySet()) {
if (key.equals("building") || key.startsWith("addr:"))
break tagcheck;
}
continue nodesloop;
} while (false);
double x = projection1(latlon2eastNorth(n.getCoor()));
double y = projection2(latlon2eastNorth(n.getCoor()));
if (Math.signum(x) != Math.signum(len) || Math.signum(y) != Math.signum(width))
continue;
if (Math.abs(x) > Math.abs(len) || Math.abs(y) > Math.abs(width))
continue;
for (OsmPrimitive p : n.getReferrers()) {
// Don't use nodes if they're referenced by ways
if (p.getType() == OsmPrimitiveType.WAY)
continue nodesloop;
}
nodes.add(n);
}
if (nodes.size() != 1)
return null;
return nodes.get(0);
}
public Way create() {
if (len == 0)
return null;
final boolean[] created = new boolean[4];
final Node[] nodes = new Node[4];
for (int i = 0; i < 4; i++) {
Node n = findNode(en[i]);
if (n == null) {
nodes[i] = new Node(eastNorth2latlon(en[i]));
created[i] = true;
} else {
nodes[i] = n;
created[i] = false;
}
if (nodes[i].getCoor().isOutSideWorld()) {
JOptionPane.showMessageDialog(Main.parent,
tr("Cannot place building outside of the world."));
return null;
}
}
Way w = new Way();
w.addNode(nodes[0]);
if (projection2(en[2]) > 0 ^ len < 0) {
w.addNode(nodes[1]);
w.addNode(nodes[2]);
w.addNode(nodes[3]);
} else {
w.addNode(nodes[3]);
w.addNode(nodes[2]);
w.addNode(nodes[1]);
}
w.addNode(nodes[0]);
Collection<Command> cmds = new LinkedList<>();
for (int i = 0; i < 4; i++) {
if (created[i])
cmds.add(new AddCommand(nodes[i]));
}
cmds.add(new AddCommand(w));
if (ToolSettings.PROP_USE_ADDR_NODE.get()) {
Node addrNode = getAddressNode();
if (addrNode != null) {
for (Entry<String, String> entry : addrNode.getKeys().entrySet()) {
w.put(entry.getKey(), entry.getValue());
}
for (OsmPrimitive p : addrNode.getReferrers()) {
Relation r = (Relation) p;
Relation rnew = new Relation(r);
for (int i = 0; i < r.getMembersCount(); i++) {
RelationMember member = r.getMember(i);
if (addrNode.equals(member.getMember())) {
rnew.removeMember(i);
rnew.addMember(i, new RelationMember(member.getRole(), w));
}
}
cmds.add(new ChangeCommand(r, rnew));
}
cmds.add(new DeleteCommand(addrNode));
}
}
Command c = new SequenceCommand(tr("Create building"), cmds);
Main.main.undoRedo.add(c);
return w;
}
}