/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: AStarRouter.java
* Written by: Christian Harnisch, Ingo Besenfelder, Michael Neumann (Team 3)
*
* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
*
* Electric(tm) is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* Electric(tm) is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Electric(tm); see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, Mass 02111-1307, USA.
*/
package com.sun.electric.tool.routing.experimentalAStar3;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.routing.RoutingFrame.RoutingSegment;
import com.sun.electric.tool.routing.experimentalAStar3.concurrency.RouteJob;
import com.sun.electric.tool.routing.experimentalAStar3.concurrency.RoutingMain;
import com.sun.electric.tool.routing.experimentalAStar3.datastructures.Point3D;
/**
* @author Christian Harnisch
*
*/
public class AStarRouter extends BenchmarkRouter
{
/* Tuning parameters */
/**
* Number of regions per side. Total number of regions == numRegionsPerSide *
* numRegionsPerSide.
*/
public RoutingParameter numRegionsPerSide = new RoutingParameter("regionsps", "Number of regions per side:", 8);
public AStarRouter()
{
super();
setBenchmarkParameters(16, 60, 2);
}
public void setBenchmarkParameters(int threads, int runtime, int numregionsperside)
{
this.numThreads.setTempIntValue(threads);
this.maxRuntime.setTempIntValue(runtime);
this.numRegionsPerSide.setTempIntValue(numregionsperside);
}
/* Below: Private members, not for tuning. */
RoutingMain router;
/**
* Depicts the size of one node on the actual cell. This value is calculated
* by the private method <code>calculateNodeSize</code> on initialisation of
* the AStarRouter, so don't set it here.
*/
private double nodeSize;
/*
* Used to translate node coordinates.
*/
private int nodeOffsetX = 0;
private int nodeOffsetY = 0;
@Override
public String getAlgorithmName()
{
return "A* - 3"; //$NON-NLS-1$
}
/**
* Returns the next power of 2
*/
private int nextPowerOf2(int value)
{
assert (value > 0);
int x = 1;
while (value > x)
x *= 2;
return x;
}
/**
* Calculates the length of the sides of a node on the cell the router finds
* paths on.
*
* @param allLayers All layers of the cell.
*/
private double calculateNodeSize(List<RoutingLayer> allLayers)
{
double maxMinWidth = 0;
for (RoutingLayer layer : allLayers)
{
if (maxMinWidth < layer.getMinWidth() / 2 + layer.getMinSpacing(layer))
maxMinWidth = layer.getMinSpacing(layer) + layer.getMinWidth() / 2;
}
return maxMinWidth * 2;
}
/* Small helpers for unit conversion */
/**
* This method can be used for converting coordinates from the cell to node
* coordinates, or other similar unit conversions.
*
* @param c
* @return
*/
private int cellUnitToNode(double c)
{
return (int) Math.floor(c / this.getNodeSize());
}
// /**
// * This method can be used for converting node coordinates to cell
// * coordinates, or other similar unit conversions.
// *
// * @param n
// * @return
// */
// private double nodeToCellUnit(int n)
// {
// return (double) (n * this.getNodeSize());
// }
private int cellCoordXToNode(double c)
{
return cellUnitToNode(c) + this.nodeOffsetX;
}
private int cellCoordYToNode(double c)
{
return cellUnitToNode(c) + this.nodeOffsetY;
}
public double nodeCoordXToCellCentered(int n)
{
return this.getNodeSize() * (n - this.nodeOffsetX) + this.getNodeSize() / 2;
}
public double nodeCoordYToCellCentered(int n)
{
return this.getNodeSize() * (n - this.nodeOffsetY) + this.getNodeSize() / 2;
}
public double nodeCoordXToCell(int n)
{
return this.getNodeSize() * (n - this.nodeOffsetX);
}
public double nodeCoordYToCell(int n)
{
return this.getNodeSize() * (n - this.nodeOffsetY);
}
private RoutingLayer getLayerFromZ(int z)
{
return this.allLayers.get(z);
}
private int getZFromLayer(RoutingLayer layer)
{
return layer.getMetalNumber() - 1;
}
private List<RoutingLayer> allLayers;
private List<RoutingContact> allContacts;
// private void checkMetalLayers()
// {
// int lowestMetalLayer = Integer.MAX_VALUE;
// int highestMetalLayer = -1;
// int currentMetalLayer = -1;
// int metalLayerCount = 0;
// boolean hasSeenNonMetal = false;
// for (RoutingLayer layer : this.allLayers)
// {
// if (layer.isMetal())
// {
// metalLayerCount++;
// if (!hasSeenNonMetal)
// {
// if (layer.getMetalNumber() > currentMetalLayer)
// currentMetalLayer = layer.getMetalNumber();
// else
// throw new IllegalStateException("Metal layers unsorted!");
// if (currentMetalLayer < lowestMetalLayer)
// lowestMetalLayer = currentMetalLayer;
// if (currentMetalLayer > highestMetalLayer)
// highestMetalLayer = currentMetalLayer;
// }
// else
// throw new IllegalStateException("Metal layers below non-metal layers!");
// }
// else
// hasSeenNonMetal = true;
// }
// System.out.println("Metal layers: count=" + metalLayerCount + ", lowest=" + lowestMetalLayer + ", highest="
// + highestMetalLayer + ", total layers=" + this.allLayers.size());
// }
@Override
protected void runRouting(Cell cell, List<RoutingSegment> segmentsToRoute, List<RoutingLayer> allLayers,
List<RoutingContact> allContacts, List<RoutingGeometry> blockages)
{
// Store start time for maximum runtime calculation
long startTime = System.currentTimeMillis();
/* Initialisation phase */
this.allLayers = allLayers;
this.allContacts = allContacts;
// User-interface initialization
Job.getUserInterface().startProgressDialog("Routing " + segmentsToRoute.size() + " segments", null);
Job.getUserInterface().setProgressNote("Initialising...");
Job.getUserInterface().setProgressValue(0);
this.setNodeSize(calculateNodeSize(allLayers));
// System.out.println("Node Size: " + this.getNodeSize());
//
// Determine bounds
//
int min_x = cellUnitToNode(cell.getBounds().getMinX());
int min_y = cellUnitToNode(cell.getBounds().getMinY());
int max_x = cellUnitToNode(cell.getBounds().getMaxX());
int max_y = cellUnitToNode(cell.getBounds().getMaxY());
final int border = 10;
int real_width = max_x - min_x + 1;
int real_height = max_y - min_y + 1;
int width = real_width + 2 * border;
int height = real_height + 2 * border;
int quadlen = nextPowerOf2((width > height) ? width : height);
// XXX: we need a minimum width, otherwise we can't divide it well
if (quadlen < 128)
quadlen = 128;
//
// Node coordinate translation happens here.
//
this.nodeOffsetX = -min_x + (quadlen - real_width) / 2; // move into center
this.nodeOffsetY = -min_y + (quadlen - real_height) / 2; // move into center
// System.out.println("" + min_x + "/" + min_y + " -- " + max_x + "/" +
// max_y);
// System.out.println("" + quadlen);
this.router = new RoutingMain(quadlen, quadlen, getMetalLayerCount(allLayers), this.numRegionsPerSide.getTempIntValue(),
this.numThreads.getTempIntValue());
for (RoutingGeometry routingGeometry : blockages)
{
Rectangle2D bounds = routingGeometry.getBounds();
int startX = cellCoordXToNode(bounds.getX());
int startY = cellCoordYToNode(bounds.getY());
int endX = cellCoordXToNode(Math.ceil(bounds.getX() + bounds.getWidth()));
int endY = cellCoordYToNode(Math.ceil(bounds.getY() + bounds.getHeight()));
this.router.setBlockage(startX, startY, endX, endY, getZFromLayer(routingGeometry.getLayer()));
// RoutingSegment routingSegment = segmentsToRoute.get(0);
// RoutingLayer layer = routingSegment.getFinishLayers().get(0);
// RoutePoint rp = new RoutePoint(layer.getPin(), new
// Point2D.Double(nodeCoordXToCell(startX), nodeCoordYToCell(startY)), 0);
// routingSegment.addWireEnd(rp);
// RoutePoint rp2 = new RoutePoint(layer.getPin(), new
// Point2D.Double(nodeCoordXToCell(endX), nodeCoordYToCell(endY)), 0);
// routingSegment.addWireEnd(rp2);
// routingSegment.addWire(new RouteWire(layer, rp, rp2, 2));
}
// Place portals on region borders.
this.router.placePortals();
// Sort segments according to their expected length. This is good because 1)
// with limited time, we can finish more paths successfully and 2) this
// improves path quality since short paths don't make many detours.
Collections.sort(segmentsToRoute, new SegmentComparator());
/* End of init phase */
Job.getUserInterface().setProgressNote("Scheduling segments to route...");
for (RoutingSegment rs : segmentsToRoute)
{
RoutePoint rpSTART = new RoutePoint(RoutingContact.STARTPOINT, rs.getStartEnd().getLocation(), 0);
RoutePoint rpEND = new RoutePoint(RoutingContact.FINISHPOINT, rs.getFinishEnd().getLocation(), 0);
Point3D ptSTART = new Point3D(cellCoordXToNode(rpSTART.getLocation().getX()),
cellCoordYToNode(rpSTART.getLocation().getY()), getZFromLayer(rs.getStartLayers().get(0)));
Point3D ptEND = new Point3D(cellCoordXToNode(rpEND.getLocation().getX()), cellCoordYToNode(rpEND.getLocation().getY()),
getZFromLayer(rs.getFinishLayers().get(0)));
// System.out.print("Submit job: Segment (" +
// (rpSTART.getLocation().getX()) + "/" + (rpSTART.getLocation().getY()) +
// ")->("
// + (rpEND.getLocation().getX()) + "/" + (rpEND.getLocation().getY()) +
// ")");
// System.out.print(" = Node-Route (" + (ptSTART.getX()) + "/" +
// (ptSTART.getY()) + ")");
// System.out.println("->(" + (ptEND.getX()) + "/" + (ptEND.getY()) +
// ")");
MyRouteJob job = new MyRouteJob(ptSTART, ptEND, this);
job.routingSegment = rs;
this.router.submitRouteJob(job);
}
// Now that all jobs are submitted,
// we're ready to let the router do its work.
Job.getUserInterface().setProgressNote("Routing segments...");
// System.out.println("waitForCompletion");
// After initialising and submitting jobs (which took time, too), use the
// rest of the maxRuntime for routing.
// XXX [fschmidt] check timeout here
long restTime = (this.maxRuntime.getIntValue() * 1000) - (System.currentTimeMillis() - startTime);
this.router.setMaxRuntimeMillis(restTime);
this.router.waitForCompletion();
Job.getUserInterface().stopProgressDialog();
}
private int getMetalLayerCount(List<RoutingLayer> allLayers)
{
int highestMetalNumber = 0;
for (RoutingLayer routingLayer : allLayers)
{
if (routingLayer.isMetal())
{
// System.out.println("Layer " + routingLayer.getMetalNumber() + " Via Spacing: " + routingLayer.getPin().getViaSpacing()
// + " Wire Width: " + routingLayer.getMinWidth() + " MinSpacing: " + routingLayer.getMinWidth());
highestMetalNumber = Math.max(highestMetalNumber, routingLayer.getMetalNumber());
}
}
return highestMetalNumber;
}
synchronized void routeJobCompleted(RouteJob job)
{
Job.getUserInterface().setProgressValue(this.router.getProgress());
List<Point3D> path = job.getPath();
if (path != null)
{
RoutingSegment segment = job.routingSegment;
// if (checkOnAngularPointsInPath(path))
// System.err.println("AStarRouter: Path contains angular lines BEFORE converting! ("
// + job.from.getX() + ","
// + job.from.getY() + ") -> (" + job.to.getX() + "," + job.to.getY() +
// ")");
path = optimizePath(path);
PathConversionData data = new PathConversionData();
List<RoutePoint> routePoints = convertPathToRoutePoints(path, segment, data);
// if (checkOnAngularRoutePoints(routePoints))
// System.err.println("AStarRouter: Path contains angular lines AFTER converting! ("
// + job.from.getX() + ","
// + job.from.getY() + ") -> (" + job.to.getX() + "," + job.to.getY() +
// ")");
RoutingLayer layer;
/* Really short path */
if (path.size() == 1)
{
// Use finish layer, because path could switch layers right after the
// first point
layer = segment.getFinishLayers().get(0);
segment.addWireEnd(routePoints.get(0));
segment.addWireEnd(routePoints.get(1));
segment.addWireEnd(routePoints.get(2));
segment.addWire(new RouteWire(segment.getStartLayers().get(0), routePoints.get(0), routePoints.get(1), layer
.getMinWidth()));
segment.addWire(new RouteWire(layer, routePoints.get(1), routePoints.get(2), layer.getMinWidth()));
return;
}
/*
* Connect the start points (including the adapted points). These are
* definitely on the same layer.
*/
layer = segment.getStartLayers().get(0);
// The conversion adds at least one point at the front of the path, so
// this is safe to do.
segment.addWireEnd(routePoints.get(0));
segment.addWireEnd(routePoints.get(1));
segment.addWire(new RouteWire(layer, routePoints.get(0), routePoints.get(1), layer.getMinWidth()));
// When the conversion added another point at the front of the path, add
// the third route point as well.
if (data.startPointsAdded == 1)
{
segment.addWireEnd(routePoints.get(2));
segment.addWire(new RouteWire(layer, routePoints.get(1), routePoints.get(2), layer.getMinWidth()));
}
/*
* Connect the points in between start and end points.
*/
for (int i = 0; i < (path.size() - 1); i++)
{
if (path.get(i).getZ() != path.get(i + 1).getZ())
{
layer = getLayerFromZ(path.get(i + 1).getZ());
/*
* int currentRoutePointIndex = i + 2 + data.startPointsAdded;
* RoutingLayer startLayer = getLayerFromZ(path.get(i).getZ());
* RoutingLayer finishLayer = getLayerFromZ(path.get(i+1).getZ());
* RoutingContact viaContact = getVia(startLayer, finishLayer);
*
* RoutePoint startPoint = routePoints.get(currentRoutePointIndex -
* 1); Point2D.Double viaCoords = new
* Point2D.Double(startPoint.getLocation().getX(),
* startPoint.getLocation().getY()); // XXX: Coordinates of viaPoint
* RoutePoint viaPoint = new RoutePoint(viaContact, viaCoords,
* segment.getNetID()); RoutePoint finishPoint =
* routePoints.get(currentRoutePointIndex);
*
* RouteWire start_via = new RouteWire(startLayer, startPoint,
* viaPoint, startLayer.getMinWidth()); RouteWire via_finish = new
* RouteWire(finishLayer, viaPoint, finishPoint,
* finishLayer.getMinWidth());
*
* // startPoint has already been added as WireEnd in the last
* iteration of the for loop segment.addWireEnd(finishPoint);
* segment.addWireEnd(viaPoint);
*
* segment.addWire(start_via); segment.addWire(via_finish);
*/
}
else
{
// No change in layer
layer = getLayerFromZ(path.get(i).getZ());
/*
* int currentRoutePointIndex = i + 2 + data.startPointsAdded;
* segment.addWireEnd(routePoints.get(currentRoutePointIndex));
* segment.addWire(new RouteWire(layer,
* routePoints.get(currentRoutePointIndex - 1),
* routePoints.get(currentRoutePointIndex), layer.getMinWidth()));
*/
}
int currentRoutePointIndex = i + 2 + data.startPointsAdded;
segment.addWireEnd(routePoints.get(currentRoutePointIndex));
segment.addWire(new RouteWire(layer, routePoints.get(currentRoutePointIndex - 1),
routePoints.get(currentRoutePointIndex), layer.getMinWidth()));
}
/*
* Connect the end points (including the adapted points). The end points
* are on the same layer, too.
*/
// When the conversion added another point at the end of the path, add
// the third last route point as well.
if (data.endPointsAdded == 1)
{
segment.addWireEnd(routePoints.get(routePoints.size() - 2));
segment.addWire(new RouteWire(layer, routePoints.get(routePoints.size() - 3), routePoints.get(routePoints.size() - 2),
layer.getMinWidth()));
}
// The conversion adds at least one point at the end of the path, so
// this is safe to do as well.
layer = segment.getFinishLayers().get(0);
// segment.addWireEnd(routePoints.get(routePoints.size() - 2));
segment.addWireEnd(routePoints.get(routePoints.size() - 1));
segment.addWire(new RouteWire(layer, routePoints.get(routePoints.size() - 2), routePoints.get(routePoints.size() - 1),
layer.getMinWidth()));
}
}
public RoutingContact getVia(RoutingLayer l1, RoutingLayer l2)
{
for (RoutingContact rc : this.allContacts)
{
if ((rc.getFirstLayer() == l1 && rc.getSecondLayer() == l2) || (rc.getFirstLayer() == l2 && rc.getSecondLayer() == l1))
{
return rc;
}
}
throw new IllegalArgumentException("Between the layers " + l1.getName() + " and " + l2.getName()
+ ", no via contact could be found.");
}
// private boolean checkOnAngularRoutePoints(List<RoutePoint> routePoints)
// {
// RoutePoint last = null;
// for (RoutePoint routePoint : routePoints)
// {
// if (last != null)
// if ((routePoint.getLocation().getX() != last.getLocation().getX() && routePoint.getLocation().getY() != last
// .getLocation().getY()))
// // || (segment.getStartLayers().get(0)!=
// // last.getContact().getFirstLayer() &&
// // routePoint.getLocation().getY() != last
// // .getLocation().getY())
// // || (routePoint.getLocation().getX() != last.getLocation().getX() &&
// // routePoint.getContact().getFirstLayer() != last
// // .getContact().getFirstLayer()))
// return true;
// last = routePoint;
// }
// return false;
// }
// private boolean checkOnAngularPointsInPath(List<Point3D> path)
// {
// Point3D last = null;
// for (Point3D point : path)
// {
// // if (point.getZ() != 0)
// // System.err.println("Used layer " + point.getZ());
// if (last != null)
// if ((point.getX() != last.getX() && point.getY() != last.getY())
// || (point.getX() != last.getX() && point.getZ() != last.getZ())
// || (point.getY() != last.getY() && point.getZ() != last.getZ()))
// return true;
// last = point;
// }
// return false;
// }
private List<Point3D> optimizePath(List<Point3D> path)
{
if (path.size() < 3)
return path;
List<Point3D> result = new ArrayList<Point3D>();
Point3D last = null;
Point3D previousToLast = null;
result.add(path.get(0));
int i = 0;
for (Point3D cur : path)
{
if (last != null)
{
if (last.getZ() != cur.getZ() && (i > 1))
{
result.add(last);
}
if (previousToLast != null)
if ((previousToLast.getX() != cur.getX()) && (previousToLast.getY() != cur.getY()))
{
result.add(last);
}
}
// if (checkOnAngularPointsInPath(result))
// System.out.println("AStarRouter: Wrong conversion or true angular path detected! Path-ID:"
// + path.hashCode());
previousToLast = last;
last = cur;
++i;
}
if (path.get(path.size() - 2).getZ() == path.get(path.size() - 1).getZ())
result.add(path.get(path.size() - 1));
// for (Point3D cur : result)
// {
// if (last != null)
// {
// if (last.getZ() == cur.getZ())
// if (last.getY() == cur.getY())
// if (last.getX() == cur.getX())
// {
// System.out.println("Warning: Duplicate points!");
// for (Point3D current : result)
// System.out.print(current.toString() + " ");
// System.out.print("\n");
// }
//
// }
// last = cur;
// }
return result;
}
/**
*
* @param path - the list of nodes in reversed order (the result list of the
* route() method after backtracking)
* @param layer - the layer to operate on.
* @return the list of RoutePoints in correct order from start to destination
* cell
*/
private List<RoutePoint> convertPathToRoutePoints(List<Point3D> path, RoutingSegment segment, PathConversionData data)
{
List<RoutePoint> result = new ArrayList<RoutePoint>();
// Start Point is always needed
RoutePoint startPoint = new RoutePoint(RoutingContact.STARTPOINT, segment.getStartEnd().getLocation(), 0);
result.add(startPoint);
// End Point is always needed
RoutePoint endPoint = new RoutePoint(RoutingContact.FINISHPOINT, segment.getFinishEnd().getLocation(), 0);
RoutePoint smoothedEndPoint = null;
Point3D first = null;
Point3D second = null;
if (path.size() > 1)
{
first = path.get(0);
second = path.get(1);
// Horizontal path starts here: Insert point on same y but with adapted x
if (first.getX() != second.getX())
{
result.add(new RoutePoint(segment.getStartLayers().get(0).getPin(), new Point2D.Double(startPoint.getLocation().getX(),
nodeCoordYToCellCentered(first.getY())), 0));
data.startPointsAdded = 1;
}
// Vertical path starts here: Insert point on same x but with adapted y
else
// if (first.getY() != second.getY())
{
result.add(new RoutePoint(segment.getStartLayers().get(0).getPin(), new Point2D.Double(nodeCoordXToCellCentered(first
.getX()), startPoint.getLocation().getY()), 0));
data.startPointsAdded = 1;
}
Point3D last = null;
for (Point3D cur : path)
{
RoutingContact pin = null;
// If the two points reside on different layers, use a special via
// contact.
if (last != null)
{
if (last.getZ() != cur.getZ())
pin = getVia(getLayerFromZ(last.getZ()), getLayerFromZ(cur.getZ()));
else
pin = getLayerFromZ(last.getZ()).getPin();
result.add(new RoutePoint(pin, new Point2D.Double(nodeCoordXToCellCentered(last.getX()), nodeCoordYToCellCentered(last
.getY())), 0));
// Special case: Last point
if ((cur == path.get(path.size() - 1)) && (getLayerFromZ(cur.getZ()) != segment.getFinishLayers().get(0)))
{
pin = getVia(getLayerFromZ(cur.getZ()), segment.getFinishLayers().get(0));
result.add(new RoutePoint(pin, new Point2D.Double(nodeCoordXToCellCentered(cur.getX()), nodeCoordYToCellCentered(cur
.getY())), 0));
}
}
// if (checkOnAngularRoutePoints(result))
// System.out.println("AStarRouter: Wrong conversion or true angular path detected! Path-ID:"
// + path.hashCode());
last = cur;
}
first = path.get(path.size() - 2);
second = path.get(path.size() - 1);
// Horizontal path ends here: Insert point on same y but with adapted x
if (first.getX() != second.getX())
{
smoothedEndPoint = new RoutePoint(segment.getFinishLayers().get(0).getPin(), new Point2D.Double(endPoint.getLocation()
.getX(), nodeCoordYToCellCentered(first.getY())), 0);
data.endPointsAdded = 1;
}
// Vertical path ends here: Insert point on same x but with adapted y
else
// if (first.getY() != second.getY())
{
smoothedEndPoint = new RoutePoint(segment.getFinishLayers().get(0).getPin(), new Point2D.Double(
nodeCoordXToCellCentered(first.getX()), endPoint.getLocation().getY()), 0);
data.endPointsAdded = 1;
}
}
else
{
smoothedEndPoint = new RoutePoint(segment.getFinishLayers().get(0).getPin(), new Point2D.Double(endPoint.getLocation()
.getX(), startPoint.getLocation().getY()), 0);
data.endPointsAdded = 1;
}
if (smoothedEndPoint != null)
result.add(smoothedEndPoint);
result.add(endPoint);
return result;
}
public void setNodeSize(double nodeSize)
{
this.nodeSize = nodeSize;
}
public double getNodeSize()
{
return nodeSize;
}
}
class MyRouteJob extends RouteJob
{
public MyRouteJob(Point3D from, Point3D to, AStarRouter astarrouter)
{
super(from, to, astarrouter);
}
public void onCompletion()
{
this.astarrouter.routeJobCompleted(this);
}
}
class SegmentComparator implements Comparator<RoutingSegment>
{
/*
* (non-Javadoc)
*
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
public int compare(RoutingSegment arg0, RoutingSegment arg1)
{
double length0 = arg0.getStartEnd().getLocation().distance(arg0.getFinishEnd().getLocation());
double length1 = arg1.getStartEnd().getLocation().distance(arg1.getFinishEnd().getLocation());
if (length0 < length1)
return -1;
else if (length0 == length1)
return 0;
else
return 1;
}
}
class PathConversionData
{
public int startPointsAdded = 0;
public int endPointsAdded = 0;
}