package ru.rodsoft.openstreetmap.josm.plugins.customizepublictransportstop;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.util.AbstractMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.command.AddCommand;
import org.openstreetmap.josm.command.ChangeCommand;
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.Way;
import org.openstreetmap.josm.data.osm.WaySegment;
import org.openstreetmap.josm.data.projection.Projections;
import org.openstreetmap.josm.tools.Geometry;
import ru.rodsoft.openstreetmap.josm.plugins.customizepublictransportstop.OSMTags;
/**
*
* @author Rodion Scherbakov
* Operation of creation of new stop point
*/
public class CreateNewStopPointOperation extends StopAreaOperationBase
{
/**
* Constructor of operation object
* @param currentDataSet Current Josm data set
*/
public CreateNewStopPointOperation(DataSet currentDataSet)
{
super(currentDataSet);
}
/**
* The *result* does not depend on the current map selection state,
* neither does the result *order*.
* It solely depends on the distance to point p.
*
* This code is coped from JOSM code
*
* @return a sorted map with the keys representing the distance of
* their associated nodes to point p.
*/
private Map<Double, List<Node>> getNearestNodesImpl(Point p) {
TreeMap<Double, List<Node>> nearestMap = new TreeMap<>();
DataSet ds = getCurrentDataSet();
if (ds != null) {
double dist, snapDistanceSq = 200;
snapDistanceSq *= snapDistanceSq;
for (Node n : ds.searchNodes(getBBox(p, 200))) {
if ((dist = Main.map.mapView.getPoint2D(n).distanceSq(p)) < snapDistanceSq)
{
List<Node> nlist;
if (nearestMap.containsKey(dist)) {
nlist = nearestMap.get(dist);
} else {
nlist = new LinkedList<>();
nearestMap.put(dist, nlist);
}
nlist.add(n);
}
}
}
return nearestMap;
}
/**
* Selection of area for search of roads
* @param p Current point
* @param snapDistance Distance for search
* @return Area
*/
private BBox getBBox(Point p, int snapDistance) {
return new BBox(Main.map.mapView.getLatLon(p.x - snapDistance, p.y - snapDistance),
Main.map.mapView.getLatLon(p.x + snapDistance, p.y + snapDistance));
}
/**
* Search of nearest points on ways
* @param platformCoord Platform coordinates
* @param stopArea Stop area object
* @return Dictionary of founded points and distances from platform
*/
public AbstractMap.SimpleEntry<Double, Node> getNearestNode(LatLon platformCoord, StopArea stopArea)
{
Point p = Main.map.mapView.getPoint(platformCoord);
Map<Double, List<Node>> dist_nodes = getNearestNodesImpl(p);
Double[] distances = dist_nodes.keySet().toArray(new Double[0]);
distances = sort(distances);
Integer distanceIndex = -1;
Node nearestNode = null;
while(++distanceIndex < distances.length && nearestNode == null)
{
List<Node> nodes = dist_nodes.get(distances[distanceIndex]);
for(Node node : nodes)
{
for(Way way : getCurrentDataSet().getWays())
{
if(way.getNodes().contains(node) && testWay(way, stopArea))
{
nearestNode = node;
return new AbstractMap.SimpleEntry<Double, Node> (distances[distanceIndex], nearestNode);
}
}
if(nearestNode != null)
break;
}
}
return null;
}
/**
* Sorting of founded points by distance
* @param distances Array of distances
* @return Sorted array of distances
*/
public Double[] sort(Double[] distances)
{
for(Integer i = 0; i < distances.length - 1; i++)
for(Integer j = i + 1; j < distances.length; j++)
{
if(distances[i]>distances[j])
{
Double d = distances[i];
distances[i] = distances[j];
distances[j] = d;
}
}
return distances;
}
/**
* Selection of ways for stop position by type of way and type of stop
* @param way The way
* @param stopArea Stop area
* @return true, if way can contain stop position
*/
public Boolean testWay(Way way, StopArea stopArea)
{
if(stopArea.isTrainStation || stopArea.isTrainStop)
{
if(OSMTags.RAIL_TAG_VALUE.equals(way.getKeys().get(OSMTags.RAILWAY_TAG)) && OSMTags.MAIN_TAG_VALUE.equals(way.getKeys().get(OSMTags.USAGE_TAG)))
return true;
return false;
}
if(stopArea.isTram)
{
if(OSMTags.TRAM_TAG_VALUE.equals(way.getKeys().get(OSMTags.RAILWAY_TAG)))
return true;
return false;
}
String[] highwayValues = {OSMTags.TRUNK_TAG_VALUE, OSMTags.PRIMARY_TAG_VALUE, OSMTags.SECONDARY_TAG_VALUE, OSMTags.TERTIARY_TAG_VALUE,
OSMTags.UNCLASSIFIED_TAG_VALUE, OSMTags.RESIDENTIAL_TAG_VALUE, OSMTags.SERVICE_TAG_VALUE,
OSMTags.BUS_GUIDEWAY_TAG_VALUE, OSMTags.ROAD_TAG_VALUE, OSMTags.TRUNK_LINK_TAG_VALUE,
OSMTags.PRIMARY_LINK_TAG_VALUE, OSMTags.SECONDARY_LINK_TAG_VALUE, OSMTags.TERTIARY_LINK_TAG_VALUE };
if(stopArea.isBus || stopArea.isTrolleybus || stopArea.isShareTaxi)
{
String highway = way.getKeys().get(OSMTags.HIGHWAY_TAG);
if(highway != null)
for(Integer i = 0; i < highwayValues.length; i++)
{
if(highwayValues[i].equals(highway))
return true;
}
}
return false;
}
/**
* The *result* does not depend on the current map selection state,
* neither does the result *order*.
* It solely depends on the distance to point p.
*
* This code is coped from JOSM code
*
* @return a sorted map with the keys representing the perpendicular
* distance of their associated way segments to point p.
*/
private Map<Double, List<WaySegment>> getNearestWaySegmentsImpl(Point p) {
Map<Double, List<WaySegment>> nearestMap = new TreeMap<>();
DataSet ds = getCurrentDataSet();
if (ds != null) {
double snapDistanceSq = Main.pref.getInteger("mappaint.segment.snap-distance", 200);
snapDistanceSq *= snapDistanceSq;
for (Way w : ds.searchWays(getBBox(p, Main.pref.getInteger("mappaint.segment.snap-distance", 200)))) {
Node lastN = null;
int i = -2;
for (Node n : w.getNodes()) {
i++;
if (n.isDeleted() || n.isIncomplete()) { //FIXME: This shouldn't happen, raise exception?
continue;
}
if (lastN == null) {
lastN = n;
continue;
}
Point2D A = Main.map.mapView.getPoint2D(lastN);
Point2D B = Main.map.mapView.getPoint2D(n);
double c = A.distanceSq(B);
double a = p.distanceSq(B);
double b = p.distanceSq(A);
/* perpendicular distance squared
* loose some precision to account for possible deviations in the calculation above
* e.g. if identical (A and B) come about reversed in another way, values may differ
* -- zero out least significant 32 dual digits of mantissa..
*/
double perDistSq = Double.longBitsToDouble(
Double.doubleToLongBits( a - (a - b + c) * (a - b + c) / 4 / c )
>> 32 << 32); // resolution in numbers with large exponent not needed here..
if (perDistSq < snapDistanceSq && a < c + snapDistanceSq && b < c + snapDistanceSq) {
List<WaySegment> wslist;
if (nearestMap.containsKey(perDistSq)) {
wslist = nearestMap.get(perDistSq);
} else {
wslist = new LinkedList<>();
nearestMap.put(perDistSq, wslist);
}
wslist.add(new WaySegment(w, i));
}
lastN = n;
}
}
}
return nearestMap;
}
/**
* Selection of nearest way for stop position
* @param platformCoord Platform coordinates
* @param stopArea Stop area
* @return Nearest way segment
*/
protected NearestWaySegment getNearestWaySegment(LatLon platformCoord, StopArea stopArea)
{
Point p = Main.map.mapView.getPoint(platformCoord);
Map<Double, List<WaySegment>> dist_waySegments = getNearestWaySegmentsImpl(p);
for(Map.Entry<Double, List<WaySegment>> entry : dist_waySegments.entrySet())
{
for(WaySegment waySegment : entry.getValue())
{
if(testWay(waySegment.way, stopArea))
{
Node n = waySegment.getFirstNode();
Node lastN = waySegment.getSecondNode();
EastNorth newPosition = Geometry.closestPointToSegment(n.getEastNorth(),
lastN.getEastNorth(), Projections.project(platformCoord));
LatLon newNodePosition = Projections.inverseProject(newPosition);
Point2D lastN2D = Main.map.mapView.getPoint2D(lastN);
Point2D n2D = Main.map.mapView.getPoint2D(n);
Point2D newNodePosition2D = Main.map.mapView.getPoint2D(newNodePosition);
Double distCurrenNodes =lastN2D.distance(n2D);
if((newNodePosition2D.distance(lastN2D) < distCurrenNodes) && (newNodePosition2D.distance(n2D) < distCurrenNodes))
{
return new NearestWaySegment(entry.getKey(), waySegment, new Node(newNodePosition));
}
}
}
}
return null;
}
/**
* Creation of stop position node on nearest way
* @param newStopNode New stop position node
* @param waySegment Way segment including stop position node
* @return Stop position node
*/
protected Node createNodeOnWay(Node newStopNode, WaySegment waySegment)
{
Main.main.undoRedo.add(new AddCommand(newStopNode));
List<Node> wayNodes = waySegment.way.getNodes();
wayNodes.add(waySegment.lowerIndex + 1, newStopNode);
Way newWay = new Way(waySegment.way);
newWay.setNodes(wayNodes);
Main.main.undoRedo.add(new ChangeCommand(waySegment.way, newWay));
return newStopNode;
}
/**
* Creation of stop position
* @param stopArea Stop Area
*/
@Override
public StopArea performCustomizing(StopArea stopArea)
{
LatLon platformCoord = null;
if(stopArea.selectedObject instanceof Node)
{
platformCoord = ((Node) stopArea.selectedObject).getCoor();
}
else
platformCoord = getCenterOfWay(stopArea.selectedObject);
if(platformCoord == null)
return stopArea;
AbstractMap.SimpleEntry<Double, Node> nearestNode = getNearestNode(platformCoord, stopArea);
NearestWaySegment nearestWaySegment = getNearestWaySegment(platformCoord, stopArea);
Node newStopPointNode = null;
if(nearestNode != null && nearestWaySegment != null)
{
Double segmentDist = Main.map.mapView.getPoint2D(platformCoord).distanceSq(Main.map.mapView.getPoint2D(nearestWaySegment.newNode));
Double nodeDistSq = nearestNode.getKey();
// nodeDistSq *= nodeDistSq - 2;
if(segmentDist < nodeDistSq - 2)
{
// MessageBox.ok("new stop node v2 " + segmentDist.toString() + " " + nodeDistSq.toString());
newStopPointNode = createNodeOnWay(nearestWaySegment.newNode, nearestWaySegment.waySegment);
}
else
{
// MessageBox.ok("new stop node v3 " + segmentDist.toString() + " " + nodeDistSq.toString());
newStopPointNode = nearestNode.getValue();
}
}
else
if(nearestNode != null && nearestWaySegment == null)
{
newStopPointNode = nearestNode.getValue();
}
else
if(nearestNode == null && nearestWaySegment != null)
{
// MessageBox.ok("new stop node2");
newStopPointNode = createNodeOnWay(nearestWaySegment.newNode, nearestWaySegment.waySegment);
}
if(newStopPointNode != null)
{
stopArea.stopPoints.add(newStopPointNode);
}
return stopArea;
}
}