使用Dijkstra Search的地图导航
使用交互式用户界面实现A*/Dijkstra搜索
寻找确切的最短路径
这个项目探讨了一种实现精确最短路径查找方法的方式,称为迪杰斯特拉算法。 这是一种非常适合指导更复杂搜索算法(如A*搜索)的算法。
示例输入:
//
我 GILBERT-LONG 43.130480 -77.631559
我 GILBERT-COMMON 43.130584 -77.631216
...
我1 GILBERT-LONG I2
我2 I1 I2
...
//Beginning monroe.txt
...
//Beginning nys.txt
...
示例输出:
给定代表特定地理区域中道路和交叉路口的数据集,程序应能够绘制数据地图,并使用迪杰斯特拉算法为任意两个交叉路口提供最短路径指示方向
开发过程
存储:
- HashMap<String, 节点> 用于存储交叉点
- Hashmap<String, 边> && URLinkedList<边> 用于存储边
边缘与节点
public class Edge {
//Attributes
String name;
Node from;
Node to;
//Constructors
public Edge(){}
public Edge(String name, Node from, Node to){
this.name = name;
this.from = from;
this.to = to;
}
//Setters and getters
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public Node getFrom(){
return from;
}
public Node getTo(){
return to;
}
public void setFrom(Node from){
this.from = from;
}
public void setTo(Node to){
this.to = to;
}
public class Node {
//Attributes
String name;
Double latitude;
Double longitude;
Double minDistance;
Node prececessor;
//LinkedList of string names of this node's adjacent node
URLinkedList<Node> adjacent = new URLinkedList<>();
//Constructors
public Node(){}
public Node(String name,Double la, Double lo){
this.name = name;
this.latitude = la;
this.longitude = lo;
}
public Node(String name, Double la, Double lo, Double minDistance, URLinkedList<Node> list){
this.name = name;
this.latitude = la;
this.longitude = lo;
this.minDistance = minDistance;
this.adjacent = list;
}
//Setters and getters
public void setName(String name){this.name = name;}
public void setLatitude(Double latitude){this.latitude = latitude;}
public void setLongitude(Double longitude){this.longitude = longitude;}
public String getName(){return name;}
public Double getLatitude(){return latitude;}
public Double getLongitude(){return longitude;}
public URLinkedList<Node> getAdjacent(){return adjacent;}
public void setMinDistance(Double distance){minDistance = distance;}
public Double getMinDistance(){return minDistance;}
public URLinkedList<Node> getList(){return adjacent;}
public Node getPredecessor(){return prececessor;}
public void setPredecessor(Node node){this.prececessor = node;}
//Methods
public void addAdjacent(Node vertex){
adjacent.add(vertex);
}
}
}
迪科斯特拉和哈弗辛
/**
* Find shortest path using Dijkstra algorithm
* @param vertice1 Node of start point
* @param vertice2 Node of end point
* @throws IllegalArgumentException
*/
public static void Dijkstra(String vertice1, String vertice2){
//Get two vertices: start and end
Node start = intersections.get(vertice1);//Lookup vertice using its string name
Node end = intersections.get(vertice2);
if(start == null || end == null){
throw new IllegalArgumentException("Vertices entered not valid, check again");
}
//Create a priority queue to store all the unvisited node.
MyPriorityQueue<MyKeyValueEntry<Node, Node>, Double> queue = new MyPriorityQueue<>();
//Set distance from start to itself to be 0.0...
start.setMinDistance(0.0);
//Get starting node's adjacency list
while(!(start.getAdjacent().isEmpty())){//Add adjacent list from started vertex.
Node next = start.getAdjacent().removeFirst();
Double distance = harversine(start.getLatitude(), start.getLongitude(), next.getLatitude(), next.getLongitude());
next.setMinDistance(distance);
next.setPredecessor(start);
//Add node-pair into priority queue
queue.add(new MyKeyValueEntry<>(start, next), distance);
}
while(!queue.isEmpty()){
//Get second node in the pair
MyKeyValueEntry<MyKeyValueEntry<Node, Node>, Double> pop = queue.poll();
//get cur node and its predecessor
Node previous = pop.getKey().getKey();
Node cur = pop.getKey().getValue();
while(!(cur.getAdjacent().isEmpty())){ //get cur's adjacent list
//Get first node in cur's adjacent list
Node next = cur.getAdjacent().removeFirst();
if(next != previous){//Proceed if adjacent node isn't previously visited node
//Set distance to be previous distance + distance between two nodes
Double distance = cur.getMinDistance() + harversine(cur.getLatitude(), cur.getLongitude(), next.getLatitude(), next.getLongitude());
//if calculated distance smaller than previously calculated distance, replace it
if(distance < next.getMinDistance()){
next.setMinDistance(distance);
//Set adjacent's predecessor to cur node
next.setPredecessor(cur);
//Push new shorter distance into the PriorityQueue
queue.add(new MyKeyValueEntry<>(cur,next), distance);
}
}
}
}
//If end's distance smaller than double max and predecessor not null, then path found
if(end.getMinDistance() == Double.MAX_VALUE ){
System.out.println("Shortest path not found, intersections not connected");
return;
}
//Create shortest path in String type
StringBuilder rstr = new StringBuilder();
Double len = end.getMinDistance() / 1.6;
while(end.getPredecessor() != null){//loop backup until no precedessor found (start point)
rstr.append(end.getName());
rstr.append(">");
end = end.getPredecessor();
}
rstr.append(start.getName());
String str = rstr.toString();
String[] tokens = str.split(">"); //split reverse ordered string into parts
//Build shortest path in names of intersections
String intersection = "";
for(int i = tokens.length-1; i > 0; i--){
intersection += tokens[i];
intersection += " > ";
}
intersection += intersections.get(vertice2).getName();
shortestPath = intersection;
//Build shortest path in names of edges
String[] tokens2 = intersection.split(" > ");
String paths = "";
for(int i = 0; i < tokens2.length - 1; i++){
for(int j = 0; j < road.size(); j++){
if((tokens2[i].equals(road.get(j).getFrom().getName())&&tokens2[i + 1].equals(road.get(j).getTo().getName()))||
(tokens2[i].equals(road.get(j).getTo().getName())&&tokens2[i + 1].equals(road.get(j).getFrom().getName()))){
paths += road.get(j).getName();
if(i+2<tokens2.length){//if the last one, no need to add arrow pointer
paths += " > ";
}
break;
}
}
}
System.out.println("Shortest path detected: " + paths);
System.out.println("Shortest path detected: " + intersection + "\nTotal distance: " + len + "miles");
}
创建地图图像,计算后端
- 使用哈尔文斯距离来计算距离
- 让迪杰斯特拉返回最短路径字符串
- 转换 路径为多个节点并用于绘制高亮线条
障碍
URLinkedList链表
使用内部节点类使用URLinkedList来存储相邻列表,以实现双向链表
//Author: Tianyi Zhou
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class URLinkedList<T> implements URList<T>{
/**
* create attributions
*
*/
int size = 0;
URNode<T> first;
URNode<T> last;
/**
* Constructors for URLinkedList
*
*/
public URLinkedList(){}
/**
*
* @param c collection added to the linkedlist at the beginning of initialization
* @see URLinkedList.addAll()
*/
@SuppressWarnings("unchecked")
public URLinkedList(Collection<?> c){
Collection<? extends T> d = (Collection<? extends T>)c;
addAll(d);
}
//METHODS
URNode<T> node(int index){
URNode<T> x = first;
for(int i = 0;i < index;i++)
x = x.next();
return x;
}
/**
* add item e to the first node in the linked list
* @param e
*/
public void addFirst(T e){
URNode<T> original = first;
URNode<T> newNode = new URNode<T>(e, null, original);
first = newNode;
if(original == null)
last = newNode;//if there's only e in the linkedlist, then the first is also the last
else
original.setPrev(newNode);
size++;
}
public void addLast(T e){
URNode<T> original = last;
URNode<T> newNode = new URNode<T>(e, original, null);
last = newNode;
if(original == null)
first = newNode; // if only e exist, then the last is also the first
else
original.setNext(newNode);
size++;
}
/**
* peekFirst/peekLast reuturn the First/Last element in the list, pollFirst/pollLast remove the First/Last element in the list
* after element returned
* @see URLinkedList.removeFirst()]
* @see URLinkedList.removeLast()
*/
public T peekFirst(){
if(first == null) return null;
return first.element();
}
public T peekLast(){
if(last == null) return null;
return last.element();
}
public T pollFirst(){
if(first == null) return null;
T element = first.element();
removeFirst();
return element;
}
public T pollLast(){
if(last == null) return null;
T element = last.element();
removeLast();
return element;
}
/**
* add element to the end of the list
* @param e element added
* @see URLinkedList.addLast()
*/
public boolean add(T e){
addLast(e);
return true;
}
/**
* add element to the specific location
* @param index index for insertion
* @param element element for insertion
* @throws indexOutOfBoundsException if index > 0 && index <= list's size
*/
public void add(int index, T element){
if(!(index >= 0 && index <= size)){
throw new IndexOutOfBoundsException("index has to be larger than 0 and smaller than list size");
}
if(index == size)
addLast(element);
else
linkBefore(element, node(index));
}
public void linkBefore(T element, URNode<T> succNode){
URNode<T> prev = succNode.prev();
URNode<T> newNode = new URNode<>(element, prev, succNode);
if(prev == null)
first = newNode;
else
prev.setNext(newNode);
size++;
}
/**
* Appends all of the elements in the specified collection to the end of this list,
* in the order that they are returned by the specified collection's iterator
* @param c collection of elements waiting to add
* @throws IndexOutOfBoundsException if requested index illegal
* @throws NullPointerException if collection is null
* */
public boolean addAll(Collection<? extends T> c){
return addAll(size, c);
}
@SuppressWarnings("unchecked")
public boolean addAll(int index, Collection<? extends T> c){
if(!(size >= 0 && size <= size)) throw new IndexOutOfBoundsException();
if(c == null) throw new NullPointerException("added collection can't be null");
Object[] other = c.toArray();
int len = other.length;
if(len == 0) return false;
URNode<T> prev;
URNode<T> succ;
if(index == size){
succ = null;
prev = last;
}else{
succ = node(index);
prev = succ.prev();
}
for(Object i: other){
T e = (T) i;
URNode<T> newNode = new URNode<>(e, prev, null);
if(prev == null) first = newNode;
else prev.setNext(newNode);
prev = newNode;
}
if(succ == null) last = prev;
else{
prev.setNext(succ);
succ.setPrev(prev);
}
size++;
return true;
}
// Removes all of the elements from this list
public void clear(){
for(URNode<T> x = first; x != null;){
x.setPrev(null);
x.setElement(null);
x.setNext(null);
URNode<T> next = x.next();
x = next;
}
first = null;
last = null;
size = 0;
}
// Returns true if this list contains the specified element.
public boolean contains(Object o){
return indexOf(o) >= 0;
}
/**
* Check if all elements in specific colleciton contained within the list
* @see URLinkedList.contains(
* @see URLinkedlist.toArray()
*/
public boolean containsAll(Collection<?> c){
Object[] d = c.toArray();
for(Object i: d){
if(!(contains(i))) return false;
}
if(c.size() > size) return false;
else return true;
}
/**
* Compares if Object equals to the list
* @param o Object
* @see URLinkedList.toArray()
* @throws NullPointerExcpetion
* @throws ClassCastException
*/
public boolean equals(Object o){
if(o == null) throw new NullPointerException("comparing object cannot be null");
if(!(o instanceof URLinkedList<?>)) throw new ClassCastException("not the same instance");
Collection<?> c = (Collection<?>)o;
Object[] d = c.toArray();
if(size != d.length) return false;
int index = 0;
for(URNode<T> x = first; x != null; x = x.next()){
if(!x.element().equals(d[index])) return false;
}
return true;
}
/**
* Returns the element at the specified position in this list.
* @param index index returned
* @throws IndexOutOfBound if index illegal
* @see URNode.element()
*/
public T get(int index){
if(!(index >= 0 && index <= size)) throw new IndexOutOfBoundsException("cannot get outOfBound index");
return node(index).element();
}
/**
* return -1 if not found, otherwise return index where first element found
* @param o object searching for
* @see URNode.element()
*/
public int indexOf(Object o){
int index = 0;
if(o == null){
for(URNode<T> x = first;x != null;){
if(x.element()==null) return index;
index++;
x = x.next();
}
}else{
for(URNode<T> x = first;x != null;){
if(o.equals(x.element())) return index;
index++;
x = x.next();
}
}
return -1;
}
// Returns true if this list contains no elements.
public boolean isEmpty(){
return size == 0;
}
/**
* All type of remove methods based on different parameters (Multiple overrides)
* @param index element index
*/
public T remove(int index){
if(!(index >= 0 && index <= size)) throw new IndexOutOfBoundsException("Index out of bound");
URNode<T> x = node(index);
T element = x.element();
URNode<T> prev = x.prev();//ROY,IS,MY,SUPERHERO
URNode<T> next = x.next();
prev.setNext(next);
next.setPrev(prev);
x.setNext(null);
x.setPrev(null);
x.setElement(null);
size--;
return element;
}
public T removeFirst(){
URNode<T> f = first;
if(f == null) throw new NoSuchElementException("this list is empty");
else{
T element = f.element();
URNode<T> next = f.next();
f.setElement(null);
first = next;
if(next == null) last = null;//if next is null, then after first removed, list be empty
else next.setPrev(null);//if next not null, then next(now first)'s previous points to null(head)
size--;
return element;
}
}
public T removeLast(){
URNode<T> l = last;
if(l==null) throw new NoSuchElementException("this list is empty");
else{
T element = l.element();
URNode<T> prev = l.prev();
l.setElement(null);//clean up element value
last = prev;
if(prev == null) first = null;
else prev.setNext(null);//clean up node value
size--;
return element;
}
}
/**
* Remove specific Object within the list
* @param o object
* @see URLinkedList.remove()
*/
public boolean remove(Object o){
if(o == null) throw new IllegalArgumentException("Don't need to remove null");
if(indexOf(o) >= 0){
int index = indexOf(o);
if(index == 0) removeFirst();
else if(index == size-1) removeLast();
else remove(index);
return true;
}
else return false;
}
/**
* Removes all elements within the specific collection and return true
* if failed to remove any or partial elements in the list return false
* @param c specific collection
* @see URLinkedList.toArray()
* @see URLinkedList.contains()
* @see URLinkedList.remove()
* @throws NullPointerException
*/
public boolean removeAll(Collection<?> c){
if(c == null) throw new NullPointerException("given collection is null");
URNode<T> x = first;
Object[] d = c.toArray();
int len = d.length;
int count = 0;
for(;x != null;x = x.next()){
T element = x.element();
Object e = (Object) element;
if(contains(e)){
remove(e);
count++;
}
}
if(count == len) return true;
else return false;
}
/**
* Set index element to element, return the replaced (original) element
* @param index index of element
* @param element element want to replace
* @throws IndexOutOfBound
*/
public T set(int index, T element){
if(!(index >= 0 && index <=size)) throw new IndexOutOfBoundsException();
URNode<T> x = node(index);
T e = x.element();
x.setElement(element);
return e;
}
// Returns the number of elements in this list.
public int size(){
return size;
}
/**
* return a object array type containing all elements within the list
*/
public Object[] toArray(){
Object[] arr = new Object[size];
int index = 0;
for(URNode<T> x = first; x != null; x = x.next()){
arr[index] = x.element();
index++;
}
return arr;
}
// Returns an iterator over the elements in this list in proper sequence.
public Iterator<T> iterator(){
return new Iterator<T>(){
URNode<T> current = first;
@Override
public boolean hasNext(){
return current != null;
}
@Override
public T next(){
if(hasNext()){
T element = current.element();
current = current.next();
return element;
}
return null;
}
};
}
/**
* Return a subList between given indexes.
* @param fromIndex where the subList began
* @param toIndex where the subList ended
* @throws IndexOutOfBoundException
* @throws IllegalArgumentExcpetion
* @see URLinkedList.add()
*/
public URList<T> subList(int fromIndex, int toIndex){
if(fromIndex < 0) throw new IndexOutOfBoundsException("index less than 0");
if(toIndex > size) throw new IndexOutOfBoundsException("index provided larger than ArrayList's size");
if(fromIndex > toIndex) throw new IllegalArgumentException("fromIndex cannot be larger than toIndex");
URLinkedList<T> newList = new URLinkedList<>();
for(int i = fromIndex; i < toIndex; i++){
URNode<T> x = node(i);
newList.add(x.element());
}
return newList;
}
public class URNode<E> { // Doubly linked list node
private E e; // Value for this node
private URNode<E> n; // Reference to next node in list
private URNode<E> p; // Reference to previous node
// Constructors
URNode(E it, URNode<E> inp, URNode<E> inn) { e = it; p = inp; n = inn; }
URNode(URNode<E> inp, URNode<E> inn) { p = inp; n = inn; }
// Get and set methods for the data members
public E element() { return e; } // Return the value
public E setElement(E it) { return e = it; } // Set element value
public URNode<E> next() { return n; } // Return next link
public URNode<E> setNext(URNode<E> nextval) { return n = nextval; } // Set next link
public URNode<E> prev() { return p; } // Return prev link
public URNode<E> setPrev(URNode<E> prevval) { return p = prevval; } // Set prev link
}
}
节点和回溯
在每个节点类中存储最短路径中的上一个节点 —> 循环直到遇到起始节点
//在字符串类型中创建最短路径
StringBuilder rstr = new StringBuilder();
Double len = end.getMinDistance() / 1.6;
while(end.getPredecessor() != null){//循环备份直到找不到前辈(起点)
rstr.append(end.getName());
rstr.append(">");
end = end.getPredecessor();
}
rstr.append(start.getName());
String str = rstr.toString();
String[] tokens = str.split(">"); //将反向排序的字符串拆分成部分
//以交叉点名称构建最短路径
String intersection = "";
for(int i = tokens.length-1; i > 0; i--){
intersection += tokens[i];
intersection += " > ";
}
intersection += intersections.get(vertice2).getName();
shortestPath = intersection;
//以边缘名称构建最短路径
String[] tokens2 = intersection.split(" > ");
String paths = "";
for(int i = 0; i < tokens2.length - 1; i++){
for(int j = 0; j < road.size(); j++){
if((tokens2[i].equals(road.get(j).getFrom().getName())&&tokens2[i + 1].equals(road.get(j).getTo().getName()))||
(tokens2[i].equals(road.get(j).getTo().getName())&&tokens2[i + 1].equals(road.get(j).getFrom().getName()))){
paths += road.get(j).getName();
if(i+2<tokens2.length){//如果是最后一个,就不需要添加箭头指针
paths += " > ";
}
break;
}
}
}
扩展和UI设计
- 获取相对比率:[最大纬度] - [最小纬度];[最大经度] - [最小经度]。
- 将结果反转以适应正常的xy坐标
- 另一种方法:实现UI:拖放和缩放功能。
public class Graph{
//Attributes
public HashMap<String, Edge> edges;
public HashMap<String, Node> intersections;
Double minLatitude; Double minLongitude; Double maxLatitude; Double maxLongitude;
String shortestPath;
//Constructors
public Graph(String shortestPath, Double minLatitude, Double minLongitude, Double maxLatitude, Double maxLongitude, HashMap<String, Node> intersections, HashMap<String, Edge> edge){
this.shortestPath = shortestPath;
this.edges = edge;
this.intersections = intersections;
this.minLatitude = minLatitude; this.maxLatitude = maxLatitude;
this.minLongitude = minLongitude; this.maxLongitude = maxLongitude;
}
//Methods
public void drawGUI(){
JFrame frame = new JFrame();
frame.setTitle("地图");
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
URPanel panel = new URPanel();
frame.add(panel);
frame.setVisible(true);
}
public class URPanel extends JPanel implements MouseWheelListener, MouseListener, MouseMotionListener{
private double zoomFactor = 1;
private double prevZoomFactor = 1;
private boolean zoomer;
private boolean dragger;
private boolean released;
private double ox = 0;
private double oy = 0;
private int xDiff;
private int yDiff;
private Point startPoint;
//Constructor
public URPanel(){
initComponent();
}
private void initComponent(){
addMouseWheelListener(this);
addMouseListener(this);
addMouseMotionListener(this);
}
@Override
public void paintComponent(Graphics g){
//Make super finished all background constructing
super.paintComponent(g);
//Painting own graph && if --show only
Graphics2D graph = (Graphics2D) g;
//Zoom
if(zoomer){
AffineTransform at = new AffineTransform();
double realX = MouseInfo.getPointerInfo().getLocation().getX() - getLocationOnScreen().getX();
double realY = MouseInfo.getPointerInfo().getLocation().getY() - getLocationOnScreen().getY();
double ratio = zoomFactor / prevZoomFactor;
ox = (ratio) * (ox) + (1 - ratio) * realX;
oy = (ratio) * (oy) + (1 - ratio) * realY;
at.translate(ox, oy);
at.scale(zoomFactor, zoomFactor);
prevZoomFactor = zoomFactor;
graph.transform(at);
zoomer = false;
}
//Drag
if(dragger){
AffineTransform at = new AffineTransform();
at.translate(ox + xDiff, oy + yDiff);
at.scale(zoomFactor, zoomFactor);
graph.transform(at);
if(released){
ox += xDiff;
oy += yDiff;
dragger = false;
}
}
graph.setColor(Color.black);
graph.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int width = this.getWidth();
int height = this.getHeight();
double ratio = Math.min(width / Math.abs(maxLatitude - minLatitude), height / Math.abs(maxLongitude - minLongitude));
edges.forEach((String, Edge)->{
Edge edge = edges.get(String);
Node node1 = edge.getFrom();
Node node2 = edge.getTo();
double bounderX = maxLongitude - minLongitude;
int startX = (int) ((node1.getLongitude() - minLongitude) * ratio);
int startY = (int) (this.getHeight() - ((node1.getLatitude() - minLatitude) * ratio));
int endX = (int) ((node2.getLongitude() - minLongitude) * ratio);
int endY = (int) (this.getHeight() - ((node2.getLatitude() - minLatitude) * ratio));
graph.drawLine(startX, startY, endX, endY);
//Zoom in and zoom out
});
//If --show --direction/--direction --show is true
if(shortestPath.length()!=0){
String[] tokens = shortestPath.split(" > ");
for(int i = 0; i < tokens.length - 1; i++){
Node node1= intersections.get(tokens[i]);
Node node2 = intersections.get(tokens[i+1]);
int startX = (int) ((node1.getLongitude() - minLongitude) * ratio);
int startY = (int) (this.getHeight() - ((node1.getLatitude() - minLatitude) * ratio));
int endX = (int) ((node2.getLongitude() - minLongitude) * ratio);
int endY = (int) (this.getHeight() - ((node2.getLatitude() - minLatitude) * ratio));
graph.setColor(Color.red);
graph.drawLine(startX, startY, endX, endY);
}
}
}
@Override
public void mouseWheelMoved(MouseWheelEvent e){
zoomer = true;
//Zoom in
if (e.getWheelRotation() < 0) {
zoomFactor *= 1.1;
repaint();
}
//Zoom out
if (e.getWheelRotation() > 0) {
zoomFactor /= 1.1;
repaint();
}
}
@Override
public void mouseDragged(MouseEvent e){
Point curPoint = e.getLocationOnScreen();
xDiff = curPoint.x - startPoint.x;
yDiff = curPoint.y - startPoint.y;
dragger = true;
repaint();
}
@Override
public void mousePressed(MouseEvent e){
released = false;
startPoint = MouseInfo.getPointerInfo().getLocation();
}
@Override
public void mouseReleased(MouseEvent e){
released = true;
repaint();
}
@Override
public void mouseEntered(MouseEvent e){}
@Override
public void mouseExited(MouseEvent e){}
@Override
public void mouseClicked(MouseEvent e){}
@Override
public void mouseMoved(MouseEvent e){}
}
}
看这里,{{Voilà}}
只需创造一个简单的导航