First commit, added all projects
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<fileset-config file-format-version="1.2.0" simple-config="true" sync-formatter="false">
|
||||
<fileset name="all" enabled="true" check-config-name="CS480StyleGuide" local="false">
|
||||
<file-match-pattern match-pattern="." include-pattern="true"/>
|
||||
</fileset>
|
||||
</fileset-config>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>PA4</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>net.sf.eclipsecs.core.CheckstyleBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>net.sf.eclipsecs.core.CheckstyleNature</nature>
|
||||
</natures>
|
||||
<filteredResources>
|
||||
<filter>
|
||||
<id>1775716684331</id>
|
||||
<name></name>
|
||||
<type>30</type>
|
||||
<matcher>
|
||||
<id>org.eclipse.core.resources.regexFilterMatcher</id>
|
||||
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
|
||||
</matcher>
|
||||
</filter>
|
||||
</filteredResources>
|
||||
</projectDescription>
|
||||
@@ -0,0 +1,23 @@
|
||||
# Specifications
|
||||
|
||||
This section contains design specifications for some of the components in UML.txt. For the others, the UML should provide all of the information that you need.
|
||||
|
||||
## The Label Class
|
||||
|
||||
Label objects are used in label setting and label correcting algorithms. They maintain information about the shortest path to a particular Intersection that has been found thus far, including the length of the path and the incoming StreetSegment (i.e., the StreetSegment from the predecessor Intersection) on that path. The adjustValue() method must only update the value and predecessor if the possibleValue is less than the current value. Note that the isPermanent() and makePermanent() methods will only be used in label setting algorithms.
|
||||
|
||||
## Classes that Realize the LabelManager Interface
|
||||
|
||||
The adjustHeadValue() method in classes that realize the LabelManager interface must adjust the Label at the head node of the given StreetSegment. It must invoke the adjustValue() method of the appropriate Label object so that the Label is only updated if its value would be reduced.
|
||||
|
||||
## Classes that Realize the CandidateLabelManager Interface
|
||||
|
||||
The getCandidateLabel() method in classes that realize the CandidateLabelManager interface must return an appropriate candidate label. There are many ways to accomplish this; no particular algorithm has been specified.
|
||||
|
||||
## Classes that Realize the PermnanentLabelManager Interface
|
||||
|
||||
The getSmallestLabel() method in classes that realize the PermanentLabelManager interface must return a Label that has the minimum value among all non-permanent Label objects. A PermanentLabelList object must search through all non-permanent Label object in the List, a PermanentLabelHeap object must use a d-heap for this purpose, and a PermanentLabelBuckets object must use buckets for this purpose.
|
||||
|
||||
## Classes that Extend the AbstractShortestPathAlgorithm Class
|
||||
|
||||
Classes that extend the AbstractShortestPathAlgorithm class must use an appropriate LabelManager to manager the labels that are used in the findPath() method. The StreetSegment objects in the shortest path must be returned by the findPath() method. The findPath() method may (but is not required to) inform StreetSegmentObserver objects of StreetSegment objects that it has identified while performing the calculations. This functionality will not be used in the final product, but is useful when debugging and profililng.
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
@startuml
|
||||
skinparam classAttributeIconSize 0
|
||||
|
||||
' --- Interfaces ---
|
||||
interface StreetSegmentObserver <<Interface>> {
|
||||
}
|
||||
|
||||
interface StreetSegmentSubject <<Interface>> {
|
||||
}
|
||||
|
||||
interface LabelManager <<Interface>> {
|
||||
+ adjustHeadValue(segment : StreetSegment)
|
||||
+ getLabel(intersectionID : int) : Label
|
||||
}
|
||||
|
||||
interface CandidateLabelManager <<Interface>> {
|
||||
+ getCandidateLabel() : Label
|
||||
}
|
||||
|
||||
interface PermanentLabelManager <<Interface>> {
|
||||
+ getSmallestLabel() : Label
|
||||
+ makePermanent(intersectionID : int)
|
||||
}
|
||||
|
||||
interface ShortestPathAlgorithm <<Interface>> {
|
||||
+ findPath(origin : int, destination : int, net : StreetNetwork) : Map<String, StreetSegment>
|
||||
+ addStreetSegmentObserver(observer : StreetSegmentObserver)
|
||||
+ removeStreetSegmentObserver(observer : StreetSegmentObserver)
|
||||
+ notifyStreetSegmentObservers(segmentIDs : List<String>)
|
||||
}
|
||||
|
||||
' --- Main Classes ---
|
||||
|
||||
class Label {
|
||||
- permanent : boolean
|
||||
- value : double
|
||||
- id : int
|
||||
- predecessor : StreetSegment
|
||||
+ Label()
|
||||
+ Label(id : int)
|
||||
+ adjustValue(possibleValue : double, possiblePredecessor : StreetSegment)
|
||||
+ getID() : int
|
||||
+ getPredecessor() : StreetSegment
|
||||
+ getValue() : double
|
||||
+ isPermanent() : boolean
|
||||
+ makePermanent()
|
||||
+ setValue(value : double)
|
||||
}
|
||||
|
||||
class StreetNetwork {
|
||||
- intersections : List<Intersection>
|
||||
+ StreetNetwork()
|
||||
+ addIntersection(index : int, intersection : Intersection)
|
||||
+ getIntersection(index : int) : Intersection
|
||||
+ size() : int
|
||||
+ createStreetNetwork(streets : Map<String, Street>) : StreetNetwork
|
||||
}
|
||||
|
||||
abstract class AbstractLabelManager {
|
||||
# labels : Label[]
|
||||
+ AbstractLabelManager(networkSize : int)
|
||||
+ adjustHeadValue(segment : StreetSegment)
|
||||
+ getLabel(intersectionID : int) : Label
|
||||
}
|
||||
|
||||
class CandidateLabelList {
|
||||
+ NEWEST : String = "N"
|
||||
+ OLDEST : String = "O"
|
||||
- candidates : List<Integer>
|
||||
- policy : String
|
||||
+ CandidateLabelList(policy : String, networkSize : int)
|
||||
+ adjustHeadValue(segment : StreetSegment)
|
||||
+ getCandidateLabel() : Label
|
||||
}
|
||||
|
||||
class PermanentLabelList {
|
||||
+ PermanentLabelList(networkSize : int)
|
||||
+ adjustHeadValue(segment : StreetSegment)
|
||||
+ getSmallestLabel() : Label
|
||||
+ makePermanent(intersectionID : int)
|
||||
}
|
||||
|
||||
class PermanentLabelHeap {
|
||||
- <<Private Attributes As Needed>>
|
||||
+ PermanentLabelHeap(d : int, networkSize : int)
|
||||
+ adjustHeadValue(segment : StreetSegment)
|
||||
+ getSmallestLabel() : Label
|
||||
+ makePermanent(intersectionID : int)
|
||||
- <<Private Methods As Needed>>
|
||||
}
|
||||
|
||||
class PermanentLabelBuckets {
|
||||
- <<Private Attributes As Needed>>
|
||||
+ PermanentLabelBuckets(networkSize : int)
|
||||
+ adjustHeadValue(segment : StreetSegment)
|
||||
+ getSmallestLabel() : Label
|
||||
+ makePermanent(intersectionID : int)
|
||||
- <<Private Methods As Needed>>
|
||||
}
|
||||
|
||||
abstract class AbstractShortestPathAlgorithm {
|
||||
- observers : Collection<StreetSegmentObserver>
|
||||
+ AbstractShortestPathAlgorithm()
|
||||
+ findPath(origin : int, destination : int, net : StreetNetwork) : Map<String, StreetSegment>
|
||||
+ addStreetSegmentObserver(observer : StreetSegmentObserver)
|
||||
+ removeStreetSegmentObserver(observer : StreetSegmentObserver)
|
||||
+ notifyStreetSegmentObservers(segmentIDs : List<String>)
|
||||
}
|
||||
|
||||
class LabelCorrectingAlgorithm {
|
||||
- labels : CandidateLabelManager
|
||||
+ LabelCorrectingAlgorithm(labels : CandidateLabelManager)
|
||||
+ findPath(origin : int, destination : int, net : StreetNetwork) : Map<String, StreetSegment>
|
||||
}
|
||||
|
||||
class LabelSettingAlgorithm {
|
||||
- labels : PermanentLabelManager
|
||||
+ LabelSettingAlgorithm(labels : PermanentLabelManager)
|
||||
+ findPath(origin : int, destination : int, net : StreetNetwork) : Map<String, StreetSegment>
|
||||
}
|
||||
|
||||
' --- GUI / Worker Classes ---
|
||||
|
||||
class SwingWorker<T, V> {
|
||||
}
|
||||
|
||||
class PathFindingWorker {
|
||||
}
|
||||
|
||||
class BackgroundTaskDialog {
|
||||
}
|
||||
|
||||
abstract class AbstractModalDialog {
|
||||
}
|
||||
|
||||
class "javax.swing.JDialog" as JDialog {
|
||||
}
|
||||
|
||||
' --- Updated Relationships ---
|
||||
|
||||
' Correction: ShortestPathAlgorithm extends StreetSegmentSubject
|
||||
StreetSegmentSubject <|-- ShortestPathAlgorithm
|
||||
|
||||
' Correction: PathFindingWorker implements StreetSegmentObserver
|
||||
StreetSegmentObserver <|.. PathFindingWorker
|
||||
|
||||
' Standard Hierarchy
|
||||
LabelManager <|-- CandidateLabelManager
|
||||
LabelManager <|-- PermanentLabelManager
|
||||
LabelManager <|.. AbstractLabelManager
|
||||
|
||||
AbstractLabelManager <|-- CandidateLabelList
|
||||
CandidateLabelManager <|.. CandidateLabelList
|
||||
|
||||
AbstractLabelManager <|-- PermanentLabelList
|
||||
PermanentLabelManager <|.. PermanentLabelList
|
||||
|
||||
AbstractLabelManager <|-- PermanentLabelHeap
|
||||
PermanentLabelManager <|.. PermanentLabelHeap
|
||||
|
||||
AbstractLabelManager <|-- PermanentLabelBuckets
|
||||
PermanentLabelManager <|.. PermanentLabelBuckets
|
||||
|
||||
ShortestPathAlgorithm <|.. AbstractShortestPathAlgorithm
|
||||
AbstractShortestPathAlgorithm <|-- LabelCorrectingAlgorithm
|
||||
AbstractShortestPathAlgorithm <|-- LabelSettingAlgorithm
|
||||
|
||||
SwingWorker <|-- PathFindingWorker
|
||||
PathFindingWorker -> ShortestPathAlgorithm : executes
|
||||
BackgroundTaskDialog -left-|> SwingWorker
|
||||
AbstractModalDialog -left-|> BackgroundTaskDialog
|
||||
JDialog -left-|> AbstractModalDialog
|
||||
|
||||
@enduml
|
||||
Binary file not shown.
Symlink
+1
@@ -0,0 +1 @@
|
||||
../assets/rockingham-streets-2024.geo
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../assets/rockingham-streets-2024.str
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 122 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 134 KiB |
@@ -0,0 +1,264 @@
|
||||
package app;
|
||||
|
||||
import geography.*;
|
||||
import graph.*;
|
||||
import gui.*;
|
||||
import java.awt.event.*;
|
||||
import java.beans.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import dataprocessing.Geocoder;
|
||||
import feature.*;
|
||||
|
||||
// import com.formdev.flatlaf.*;
|
||||
|
||||
/**
|
||||
* The application for PA5.
|
||||
*
|
||||
* @author Prof. David Bernstein, James Madison University
|
||||
* @version 1.0
|
||||
*/
|
||||
public class PA5App
|
||||
implements ActionListener, Runnable, StreetSegmentObserver, PropertyChangeListener
|
||||
{
|
||||
private static final int SET_DESTINATION = 0;
|
||||
private static final int SET_ORIGIN = 1;
|
||||
private static final int CALCULATE_PATH = 2;
|
||||
|
||||
private static final String CALCULATE = "Calculate";
|
||||
private static final String EXIT = "Exit";
|
||||
private static final String DESTINATION = "Destination";
|
||||
private static final String ORIGIN = "Origin";
|
||||
|
||||
private static final String TAB = "\t";
|
||||
|
||||
private CartographyPanel<StreetSegment> panel;
|
||||
private CartographyDocument<StreetSegment> document;
|
||||
private GeocodeDialog dialog;
|
||||
private int mode;
|
||||
private JFrame frame;
|
||||
private ShortestPathAlgorithm alg;
|
||||
private PathFindingWorker task;
|
||||
private StreetSegment originSegment, destinationSegment;
|
||||
private StreetNetwork network;
|
||||
|
||||
/**
|
||||
* Handle actionPerformed() messages.
|
||||
*
|
||||
* @param evt
|
||||
* The event that generated the message
|
||||
*/
|
||||
public void actionPerformed(final ActionEvent evt)
|
||||
{
|
||||
String ac = evt.getActionCommand();
|
||||
|
||||
if (ac.equals(ORIGIN))
|
||||
mode = SET_ORIGIN;
|
||||
else if (ac.equals(DESTINATION))
|
||||
mode = SET_DESTINATION;
|
||||
else if (ac.equals(CALCULATE))
|
||||
mode = CALCULATE_PATH;
|
||||
|
||||
if (ac.equals(ORIGIN) || ac.equals(DESTINATION))
|
||||
{
|
||||
if (!dialog.isVisible())
|
||||
{
|
||||
dialog.setLocation((int) frame.getBounds().getMaxX(), (int) frame.getBounds().getY());
|
||||
dialog.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (ac.equals(CALCULATE))
|
||||
{
|
||||
// TODO CONSTRUCT THE ALGORITHM -- Use a Label Setting Algorithm
|
||||
// PermanentLabelManager labels = new PermanentLabelList(network.size());
|
||||
// PermanentLabelManager labels = new PermanentLabelBuckets(network.size());
|
||||
// PermanentLabelManager labels = new PermanentLabelHeap(5, network.size());
|
||||
// alg = new LabelSettingAlgorithm(labels);
|
||||
|
||||
// TODO CONSTRUCT THE ALGORITHM -- Use a LabelCorrecting Algorithm
|
||||
CandidateLabelManager labels = new CandidateLabelList(CandidateLabelList.NEWEST,
|
||||
network.size());
|
||||
alg = new LabelCorrectingAlgorithm(labels);
|
||||
|
||||
// Construct the SwingWorker
|
||||
task = new PathFindingWorker(alg, originSegment.getHead(), destinationSegment.getHead(),
|
||||
network, document, panel);
|
||||
task.addPropertyChangeListener(this);
|
||||
task.shouldShowIntermediateResults(false); // TODO SET TO true IF YOU WANT TO SEE INTERMEDIATE
|
||||
// RESULTS
|
||||
dialog.setVisible(false);
|
||||
|
||||
// Construct the dialog box
|
||||
BackgroundTaskDialog<Map<String, StreetSegment>, String> btd = new BackgroundTaskDialog<Map<String, StreetSegment>, String>(
|
||||
frame, "Calculating...", task);
|
||||
|
||||
// Execute
|
||||
btd.execute();
|
||||
}
|
||||
|
||||
if (ac.equals(EXIT))
|
||||
{
|
||||
dialog.dispose();
|
||||
frame.dispose();
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle propertyChange() messages.
|
||||
*
|
||||
* @param evt
|
||||
* The event that generated the message
|
||||
*/
|
||||
public void propertyChange(final PropertyChangeEvent evt)
|
||||
{
|
||||
if (evt.getPropertyName().equals("progress"))
|
||||
{
|
||||
System.out.println("progress...");
|
||||
}
|
||||
else if (evt.getPropertyName().equals("state"))
|
||||
{
|
||||
if (evt.getNewValue().equals(SwingWorker.StateValue.DONE))
|
||||
{
|
||||
try
|
||||
{
|
||||
Map<String, StreetSegment> path = task.get();
|
||||
document.setHighlighted(path);
|
||||
panel.repaint();
|
||||
task = null;
|
||||
}
|
||||
catch (InterruptedException | ExecutionException e)
|
||||
{
|
||||
JOptionPane.showMessageDialog(frame, "Interrupted", "Exception",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The code to be executed in the event dispatch thread.
|
||||
*/
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
// try {
|
||||
// UIManager.setLookAndFeel( new FlatLightLaf() );
|
||||
// } catch( Exception ex ) {
|
||||
// System.err.println( "Failed to initialize LaF" );
|
||||
// }
|
||||
|
||||
// CHOOSE A .geo FILE
|
||||
InputStream isgeo = new FileInputStream(new File("rockingham-streets-2024.geo"));
|
||||
// InputStream isgeo = new FileInputStream(new File("rockingham-streets.geo"));
|
||||
// InputStream isgeo = new FileInputStream(new File("virginia-streets.geo"));
|
||||
// InputStream isgeo = new FileInputStream(new File("va-streets-2024.geo"));
|
||||
AbstractMapProjection proj = new ConicalEqualAreaProjection(-96.0, 37.5, 29.5, 45.5);
|
||||
GeographicShapesReader gsReader = new GeographicShapesReader(isgeo, proj);
|
||||
CartographyDocument<GeographicShape> geographicShapes = gsReader.read();
|
||||
System.out.println("Read the .geo file");
|
||||
|
||||
// CHOOSE A .str FILE
|
||||
InputStream iss = new FileInputStream(new File("rockingham-streets-2024.str"));
|
||||
// InputStream iss = new FileInputStream(new File("rockingham-streets.str"));
|
||||
// InputStream iss = new FileInputStream(new File("virginia-streets.str"));
|
||||
// InputStream iss = new FileInputStream(new File("va-streets-2024.str"));
|
||||
StreetsReader sReader = new StreetsReader(iss, geographicShapes);
|
||||
Map<String, Street> streets = new HashMap<String, Street>();
|
||||
document = sReader.read(streets);
|
||||
System.out.println("Read the .str file");
|
||||
|
||||
panel = new CartographyPanel<StreetSegment>(document, new StreetSegmentCartographer());
|
||||
frame = new JFrame("Map");
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setSize(600, 600);
|
||||
|
||||
network = StreetNetwork.createStreetNetwork(streets);
|
||||
|
||||
JMenuBar menuBar = new JMenuBar();
|
||||
frame.setJMenuBar(menuBar);
|
||||
JMenuItem item;
|
||||
JMenu menu;
|
||||
|
||||
menu = new JMenu("File");
|
||||
menuBar.add(menu);
|
||||
|
||||
item = new JMenuItem(EXIT);
|
||||
item.addActionListener(this);
|
||||
menu.add(item);
|
||||
|
||||
menu = new JMenu("Geocode");
|
||||
menuBar.add(menu);
|
||||
|
||||
item = new JMenuItem(ORIGIN);
|
||||
item.addActionListener(this);
|
||||
menu.add(item);
|
||||
item = new JMenuItem(DESTINATION);
|
||||
item.addActionListener(this);
|
||||
menu.add(item);
|
||||
|
||||
menu = new JMenu("Path");
|
||||
menuBar.add(menu);
|
||||
|
||||
item = new JMenuItem(CALCULATE);
|
||||
item.addActionListener(this);
|
||||
menu.add(item);
|
||||
|
||||
frame.setContentPane(panel);
|
||||
frame.setVisible(true);
|
||||
|
||||
Geocoder geocoder = new Geocoder(geographicShapes, document, streets);
|
||||
dialog = new GeocodeDialog(frame, geocoder);
|
||||
dialog.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
dialog.addStreetSegmentObserver(this);
|
||||
dialog.setLocation((int) frame.getBounds().getMaxX(), (int) frame.getBounds().getY());
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
JOptionPane.showMessageDialog(frame, ioe.toString(), "Error", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a collection of StreetSegment objects.
|
||||
*
|
||||
* @param segmentIDs
|
||||
* The IDs of the StreetSegment objects
|
||||
*/
|
||||
@Override
|
||||
public void handleStreetSegments(final List<String> segmentIDs)
|
||||
{
|
||||
HashMap<String, StreetSegment> highlighted = new HashMap<String, StreetSegment>();
|
||||
for (String id : segmentIDs)
|
||||
{
|
||||
highlighted.put(id, document.getElement(id));
|
||||
}
|
||||
document.setHighlighted(highlighted);
|
||||
panel.repaint();
|
||||
|
||||
if (segmentIDs.size() > 0)
|
||||
{
|
||||
if (mode == SET_ORIGIN)
|
||||
{
|
||||
dialog.setVisible(true);
|
||||
originSegment = highlighted.get(segmentIDs.get(0));
|
||||
System.out.println(originSegment + TAB + destinationSegment);
|
||||
}
|
||||
else if (mode == SET_DESTINATION)
|
||||
{
|
||||
dialog.setVisible(true);
|
||||
destinationSegment = highlighted.get(segmentIDs.get(0));
|
||||
System.out.println(originSegment + TAB + destinationSegment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package app;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import javax.swing.*;
|
||||
|
||||
/**
|
||||
* The main class for PA5.
|
||||
*
|
||||
* @author Prof. David Bernstein, James Madison University
|
||||
* @version 1.0
|
||||
*/
|
||||
public class PA5Driver
|
||||
{
|
||||
/**
|
||||
* The entry point for the application.
|
||||
*
|
||||
* @param args The command-line arguments (IGNORED)
|
||||
* @throws InterruptedException if something goes wrong with Swing
|
||||
* @throws InvocationTargetException if something goes wrong with Swing
|
||||
*/
|
||||
public static void main(final String[] args)
|
||||
throws InterruptedException, InvocationTargetException
|
||||
{
|
||||
SwingUtilities.invokeAndWait(new PA5App());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package app;
|
||||
|
||||
import geography.*;
|
||||
import graph.*;
|
||||
import gui.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import javax.swing.*;
|
||||
|
||||
import feature.*;
|
||||
|
||||
/**
|
||||
* A driver for testing shortest path algorithms that does not make use of a SwingWorker.
|
||||
*
|
||||
* @author Prof. David Bernstein, James Madison University
|
||||
* @version 1.0
|
||||
*/
|
||||
public class ShortestPathDriver
|
||||
{
|
||||
|
||||
private static CartographyDocument<StreetSegment> document;
|
||||
private static CartographyPanel<StreetSegment> panel;
|
||||
|
||||
/**
|
||||
* The entry point of the application.
|
||||
*
|
||||
* @param args
|
||||
* The command-line arguments (which are ignored)
|
||||
* @throws IOException
|
||||
* If something goes wrong
|
||||
*/
|
||||
public static void main(final String[] args) throws IOException
|
||||
{
|
||||
// TODO SELECT A .geo FILE
|
||||
InputStream isgeo = new FileInputStream(new File("rockingham-streets-2024.geo"));
|
||||
// InputStream isgeo = new FileInputStream(new File("va-streets-2024.geo"));
|
||||
AbstractMapProjection proj = new ConicalEqualAreaProjection(-96.0, 37.5, 29.5, 45.5);
|
||||
GeographicShapesReader gsReader = new GeographicShapesReader(isgeo, proj);
|
||||
CartographyDocument<GeographicShape> geographicShapes = gsReader.read();
|
||||
|
||||
// TODO SELECT A .str FILE
|
||||
InputStream iss = new FileInputStream(new File("rockingham-streets-2024.str"));
|
||||
// InputStream iss = new FileInputStream(new File("va-streets-2024.str"));
|
||||
StreetsReader sReader = new StreetsReader(iss, geographicShapes);
|
||||
Map<String, Street> streets = new HashMap<String, Street>();
|
||||
document = sReader.read(streets);
|
||||
|
||||
panel = new CartographyPanel<StreetSegment>(document, new StreetSegmentCartographer());
|
||||
|
||||
StreetNetwork network = StreetNetwork.createStreetNetwork(streets);
|
||||
ShortestPathAlgorithm alg;
|
||||
|
||||
// TODO CONSTRUCT THE ALGORITHM -- Use a Label Setting Algorithm
|
||||
// PermanentLabelManager labels = new PermanentLabelList(network.size());
|
||||
PermanentLabelManager labels = new PermanentLabelBuckets(network.size());
|
||||
// PermanentLabelManager labels = new PermanentLabelHeap(5, network.size());
|
||||
alg = new LabelSettingAlgorithm(labels);
|
||||
|
||||
// TODO CONSTRUCT THE ALGORITHM -- Use a LabelCorrecting Algorithm
|
||||
// CandidateLabelManager labels = new CandidateLabelList(CandidateLabelList.NEWEST,
|
||||
// network.size());
|
||||
// alg = new LabelCorrectingAlgorithm(labels);
|
||||
|
||||
// // To show intermediate results
|
||||
// alg.addStreetSegmentObserver(new Observer());
|
||||
|
||||
// TODO CHOOSE AN ORIGIN AND DESTINATION
|
||||
Map<String, StreetSegment> path = alg.findPath(251, 300, network); // For Rockingham
|
||||
// Map<String, StreetSegment> path = alg.findPath(45387, 20000, network); // For virginia
|
||||
|
||||
// Show the path
|
||||
document.setHighlighted(path);
|
||||
JFrame f = new JFrame();
|
||||
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
f.setSize(600, 600);
|
||||
f.setContentPane(panel);
|
||||
f.setVisible(true);
|
||||
}
|
||||
|
||||
// private static final class Observer implements StreetSegmentObserver
|
||||
// {
|
||||
// public Observer()
|
||||
// {
|
||||
// }
|
||||
//
|
||||
// public void handleStreetSegments(List<String> segments)
|
||||
// {
|
||||
// HashMap<String, StreetSegment> highlighted = new HashMap<String, StreetSegment>();
|
||||
// for (String id: segments) highlighted.put(id, document.getElement(id));
|
||||
// document.setHighlighted(highlighted);
|
||||
// panel.repaint();
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package dataprocessing;
|
||||
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.PathIterator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import feature.Street;
|
||||
import feature.StreetSegment;
|
||||
import geography.GeographicShape;
|
||||
import gui.CartographyDocument;
|
||||
|
||||
/**
|
||||
* Maps a street name and number to coordinates along matching street segments.
|
||||
*/
|
||||
public class Geocoder
|
||||
{
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final CartographyDocument<GeographicShape> shapes;
|
||||
@SuppressWarnings("unused")
|
||||
private final CartographyDocument<StreetSegment> segments;
|
||||
private final Map<String, Street> streets;
|
||||
|
||||
/**
|
||||
* @param shapes
|
||||
* @param segments
|
||||
* @param streets
|
||||
*/
|
||||
public Geocoder(final CartographyDocument<GeographicShape> shapes,
|
||||
final CartographyDocument<StreetSegment> segments, final Map<String, Street> streets)
|
||||
{
|
||||
this.shapes = shapes;
|
||||
this.segments = segments;
|
||||
this.streets = streets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns interpolated coordinates for the given address. Matching segment IDs are added to
|
||||
* {@code segmentIDs}.
|
||||
*
|
||||
* @param canonicalName
|
||||
* street name
|
||||
* @param streetNumber
|
||||
* address number
|
||||
* @param segmentIDs
|
||||
* output list populated with matching segment IDs
|
||||
* @return list of coordinates
|
||||
*/
|
||||
public List<double[]> geocode(final String canonicalName, final int streetNumber,
|
||||
final List<String> segmentIDs)
|
||||
{
|
||||
List<double[]> locations = new ArrayList<>();
|
||||
Street street = streets.get(canonicalName);
|
||||
|
||||
if (street == null)
|
||||
{
|
||||
return locations;
|
||||
}
|
||||
|
||||
for (StreetSegment segment : street.getSegments(streetNumber))
|
||||
{
|
||||
segmentIDs.add(segment.getID());
|
||||
locations.add(interpolateAddress(segment, streetNumber));
|
||||
}
|
||||
|
||||
return locations;
|
||||
}
|
||||
|
||||
private double[] interpolateAddress(final StreetSegment segment, final int streetNumber)
|
||||
{
|
||||
int low = segment.getLowAddress();
|
||||
int high = segment.getHighAddress();
|
||||
double ratio = (streetNumber - low) / (double) (high - low);
|
||||
|
||||
List<double[]> points = extractPoints(segment.getGeographicShape().getShape());
|
||||
double totalLength = lineLength(points);
|
||||
double targetLength = ratio * totalLength;
|
||||
|
||||
return walkToTarget(points, targetLength);
|
||||
}
|
||||
|
||||
private List<double[]> extractPoints(final Shape shape)
|
||||
{
|
||||
List<double[]> points = new ArrayList<>();
|
||||
double[] coords = new double[6];
|
||||
PathIterator it = shape.getPathIterator(null);
|
||||
|
||||
while (!it.isDone())
|
||||
{
|
||||
int type = it.currentSegment(coords);
|
||||
if (type != PathIterator.SEG_CLOSE)
|
||||
{
|
||||
points.add(new double[] {coords[0], coords[1]});
|
||||
}
|
||||
it.next();
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
private double lineLength(final List<double[]> points)
|
||||
{
|
||||
double total = 0.0;
|
||||
for (int i = 1; i < points.size(); i++)
|
||||
{
|
||||
total += distance(points.get(i - 1), points.get(i));
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
private double[] walkToTarget(final List<double[]> points, final double targetLength)
|
||||
{
|
||||
double travelled = 0.0;
|
||||
|
||||
for (int i = 1; i < points.size(); i++)
|
||||
{
|
||||
double[] start = points.get(i - 1);
|
||||
double[] end = points.get(i);
|
||||
double segLen = distance(start, end);
|
||||
|
||||
if (travelled + segLen >= targetLength)
|
||||
{
|
||||
double t = (targetLength - travelled) / segLen;
|
||||
return new double[] {start[0] + t * (end[0] - start[0]),
|
||||
start[1] + t * (end[1] - start[1])};
|
||||
}
|
||||
|
||||
travelled += segLen;
|
||||
}
|
||||
|
||||
return points.get(points.size() - 1);
|
||||
}
|
||||
|
||||
private double distance(final double[] a, final double[] b)
|
||||
{
|
||||
double dx = b[0] - a[0];
|
||||
double dy = b[1] - a[1];
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package feature;
|
||||
|
||||
import geography.GeographicShape;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractFeature implements Feature
|
||||
{
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* @param id
|
||||
*/
|
||||
public AbstractFeature(final String id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getID()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract GeographicShape getGeographicShape();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package feature;
|
||||
|
||||
import geography.GeographicShape;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public interface Feature
|
||||
{
|
||||
/**
|
||||
* Get the ID of this feature.
|
||||
*
|
||||
* @return The ID
|
||||
*/
|
||||
public String getID();
|
||||
|
||||
|
||||
/**
|
||||
* @return The geographic shape of this feature
|
||||
*/
|
||||
public GeographicShape getGeographicShape();
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package feature;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A class representing an intersection of streets. Each intersection has a list of inbound and
|
||||
* outbound StreetSegments.
|
||||
*/
|
||||
public class Intersection
|
||||
{
|
||||
private List<StreetSegment> inbound;
|
||||
private List<StreetSegment> outbound;
|
||||
|
||||
/**
|
||||
* Constructor. Initializes the inbound and outbound lists to empty lists.
|
||||
*/
|
||||
public Intersection()
|
||||
{
|
||||
this.inbound = new ArrayList<>();
|
||||
this.outbound = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param segment The StreetSegment to add to the list of inbound segments.
|
||||
*/
|
||||
public void addInbound(final StreetSegment segment)
|
||||
{
|
||||
inbound.add(segment);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param segment The StreetSegment to add to the list of outbound segments.
|
||||
*/
|
||||
public void addOutbound(final StreetSegment segment)
|
||||
{
|
||||
outbound.add(segment);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The list of inbound StreetSegments.
|
||||
*/
|
||||
public List<StreetSegment> getInbound()
|
||||
{
|
||||
return inbound;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The list of outbound StreetSegments.
|
||||
*/
|
||||
public List<StreetSegment> getOutbound()
|
||||
{
|
||||
return outbound;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package feature;
|
||||
|
||||
import geography.GeographicShape;
|
||||
import geography.PiecewiseLinearCurve;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a street containing one or more segments.
|
||||
*/
|
||||
public class Street extends AbstractFeature
|
||||
{
|
||||
private static final String SPACE = " ";
|
||||
|
||||
private PiecewiseLinearCurve shape;
|
||||
@SuppressWarnings("unused")
|
||||
private String category;
|
||||
@SuppressWarnings("unused")
|
||||
private String code;
|
||||
@SuppressWarnings("unused")
|
||||
private String name;
|
||||
@SuppressWarnings("unused")
|
||||
private String prefix;
|
||||
@SuppressWarnings("unused")
|
||||
private String suffix;
|
||||
private List<StreetSegment> segments;
|
||||
|
||||
|
||||
/**
|
||||
* @param prefix
|
||||
* @param name
|
||||
* @param category
|
||||
* @param suffix
|
||||
* @param code
|
||||
*/
|
||||
public Street(final String prefix, final String name, final String category, final String suffix,
|
||||
final String code)
|
||||
{
|
||||
super(code);
|
||||
this.shape = new PiecewiseLinearCurve(code);
|
||||
this.prefix = prefix;
|
||||
this.name = name;
|
||||
this.category = category;
|
||||
this.suffix = suffix;
|
||||
this.code = code;
|
||||
this.segments = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param segment
|
||||
*/
|
||||
public void addSegment(final StreetSegment segment)
|
||||
{
|
||||
segments.add(segment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a canonical name from street components.
|
||||
*
|
||||
* @param prefix
|
||||
* The street prefix (e.g., "N", "E")
|
||||
* @param name
|
||||
* The street name
|
||||
* @param category
|
||||
* The street category (e.g., "St", "Ave")
|
||||
* @param suffix
|
||||
* The street suffix (e.g., "NW", "SE")
|
||||
* @return The canonical street name
|
||||
*/
|
||||
public static String createCanonicalName(final String prefix, final String name,
|
||||
final String category, final String suffix)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (prefix != null)
|
||||
{
|
||||
sb.append(prefix).append(SPACE);
|
||||
}
|
||||
|
||||
if (name != null)
|
||||
{
|
||||
sb.append(name).append(SPACE);
|
||||
}
|
||||
|
||||
if (category != null)
|
||||
{
|
||||
sb.append(category).append(SPACE);
|
||||
}
|
||||
|
||||
if (suffix != null)
|
||||
{
|
||||
sb.append(suffix);
|
||||
}
|
||||
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the segments of this street.
|
||||
*
|
||||
* @param number
|
||||
* @return The list of segments
|
||||
*/
|
||||
public List<StreetSegment> getSegments(final int number)
|
||||
{
|
||||
List<StreetSegment> numSegments = new ArrayList<>();
|
||||
for (StreetSegment segment : this.segments)
|
||||
{
|
||||
if (segment.getLowAddress() <= number && segment.getHighAddress() >= number)
|
||||
{
|
||||
numSegments.add(segment);
|
||||
}
|
||||
}
|
||||
|
||||
return numSegments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The segments
|
||||
*/
|
||||
public Iterator<StreetSegment> getSegments()
|
||||
{
|
||||
return segments.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeographicShape getGeographicShape()
|
||||
{
|
||||
return shape;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The size
|
||||
*/
|
||||
public int getSize()
|
||||
{
|
||||
return segments.size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package feature;
|
||||
|
||||
import geography.GeographicShape;
|
||||
|
||||
/**
|
||||
* Represents a street segment (a portion of a street between two points).
|
||||
*/
|
||||
public class StreetSegment extends AbstractFeature
|
||||
{
|
||||
private double length;
|
||||
private int tail;
|
||||
private int head;
|
||||
private int highAddress;
|
||||
private int lowAddress;
|
||||
private GeographicShape shape;
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* @param id
|
||||
* @param code
|
||||
* @param shape
|
||||
* @param lowAddress
|
||||
* @param highAddress
|
||||
* @param tail
|
||||
* @param head
|
||||
* @param length
|
||||
*/
|
||||
public StreetSegment(final String id, final String code, final GeographicShape shape,
|
||||
final int lowAddress, final int highAddress, final int tail, final int head,
|
||||
final double length)
|
||||
{
|
||||
super(id);
|
||||
this.length = length;
|
||||
this.tail = tail;
|
||||
this.head = head;
|
||||
this.highAddress = highAddress;
|
||||
this.lowAddress = lowAddress;
|
||||
this.shape = shape;
|
||||
this.code = (code != null && code.length() >= 2) ? code.substring(0, 2) : code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The length of this street segment
|
||||
*/
|
||||
public double getLength()
|
||||
{
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The tail of this street segment
|
||||
*/
|
||||
public int getTail()
|
||||
{
|
||||
return tail;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The head of this street segment
|
||||
*/
|
||||
public int getHead()
|
||||
{
|
||||
return head;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The high address of this street segment
|
||||
*/
|
||||
public int getHighAddress()
|
||||
{
|
||||
return highAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The low address of this street segment
|
||||
*/
|
||||
public int getLowAddress()
|
||||
{
|
||||
return lowAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeographicShape getGeographicShape()
|
||||
{
|
||||
return shape;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The code of this street segment
|
||||
*/
|
||||
public String getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package feature;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An observer of events related to StreetSegment objects.
|
||||
*
|
||||
* @author Prof. David Bernstein, James Madison University
|
||||
* @version 1.0
|
||||
*/
|
||||
public interface StreetSegmentObserver
|
||||
{
|
||||
/**
|
||||
* Respond to handleStreetSegments() messages.
|
||||
*
|
||||
* @param segmentIDs The IDs of the StreetSegment objects
|
||||
*/
|
||||
public abstract void handleStreetSegments(List<String> segmentIDs);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package feature;
|
||||
|
||||
/**
|
||||
* An generator of events related to StreetSegment objects.
|
||||
*
|
||||
* @author Prof. David Bernstein, James Madison University
|
||||
* @version 1.0
|
||||
*/
|
||||
public interface StreetSegmentSubject
|
||||
{
|
||||
/**
|
||||
* Add a StreetSegmentObserver.
|
||||
*
|
||||
* @param observer The observer
|
||||
*/
|
||||
public abstract void addStreetSegmentObserver(final StreetSegmentObserver observer);
|
||||
|
||||
/**
|
||||
* Remove a StreetSegmentObserver.
|
||||
*
|
||||
* @param observer The observer
|
||||
*/
|
||||
public abstract void removeStreetSegmentObserver(final StreetSegmentObserver observer);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package feature;
|
||||
import geography.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
/**
|
||||
* A ThemeLibrary for use with Street objects.
|
||||
*
|
||||
* @author Prof. David Bernstein, James Madison University
|
||||
* @version 1
|
||||
*/
|
||||
public class StreetThemeLibrary implements ThemeLibrary
|
||||
{
|
||||
private static final Theme DEFAULT_THEME = new Theme(Color.BLACK, new BasicStroke());
|
||||
|
||||
private Theme highlightTheme;
|
||||
private Map<String, Theme> themes;
|
||||
|
||||
/**
|
||||
* Default Constructor.
|
||||
*/
|
||||
public StreetThemeLibrary()
|
||||
{
|
||||
themes = new HashMap<String, Theme>();
|
||||
|
||||
// Interstate, U.S., and other highways
|
||||
themes.put("A1", new Theme(new Color( 0, 153, 204), new BasicStroke(3.0f)));
|
||||
themes.put("A2", new Theme(new Color(102, 153, 204), new BasicStroke(1.0f)));
|
||||
|
||||
// Secondary and connecting roads
|
||||
themes.put("A3", new Theme(new Color(102, 153, 102), new BasicStroke(3.0f)));
|
||||
|
||||
// Local roads and special purpose roads
|
||||
themes.put("A4", new Theme(new Color(153, 153, 153), new BasicStroke(1.0f)));
|
||||
themes.put("A6", new Theme(new Color(153, 153, 153), new BasicStroke(1.0f)));
|
||||
|
||||
// Dirt roads, alleys, and unclassified roads
|
||||
float[] pattern = new float[2];
|
||||
pattern[0] = 10.0f;
|
||||
pattern[1] = 10.0f;
|
||||
themes.put("A5", new Theme(new Color(211,211,211),
|
||||
new BasicStroke(1.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER,
|
||||
10.0f, pattern, 0.0f)));
|
||||
themes.put("A7", new Theme(new Color(211,211,211),
|
||||
new BasicStroke(1.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER,
|
||||
10.0f, pattern, 0.0f)));
|
||||
|
||||
// Highlighted elements
|
||||
highlightTheme = new Theme(new Color( 0, 255, 0, 128), new BasicStroke(5.0f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Theme to use for highlighted elements.
|
||||
*
|
||||
* @return The Theme
|
||||
*/
|
||||
@Override
|
||||
public Theme getHighlightTheme()
|
||||
{
|
||||
return highlightTheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Theme to use for the given code.
|
||||
*
|
||||
* @param code The code
|
||||
* @return The Theme
|
||||
*/
|
||||
@Override
|
||||
public Theme getTheme(final String code)
|
||||
{
|
||||
Theme result = themes.get(code);
|
||||
if (result == null) result = DEFAULT_THEME;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package feature;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import geography.GeographicShape;
|
||||
import gui.CartographyDocument;
|
||||
|
||||
/**
|
||||
* Reader for street data files (.str files).
|
||||
*/
|
||||
public class StreetsReader
|
||||
{
|
||||
private BufferedReader in;
|
||||
private CartographyDocument<GeographicShape> geographicShapes;
|
||||
|
||||
/**
|
||||
* Explicit Value Constructor.
|
||||
*
|
||||
* @param is
|
||||
* The input stream for the .str file
|
||||
* @param shapes
|
||||
* The geographic shapes document
|
||||
*/
|
||||
public StreetsReader(final InputStream is, final CartographyDocument<GeographicShape> shapes)
|
||||
{
|
||||
this.in = new BufferedReader(new InputStreamReader(is));
|
||||
this.geographicShapes = shapes;
|
||||
}
|
||||
|
||||
private static String safeGet(final String[] arr, final int index)
|
||||
{
|
||||
if (arr == null || index < 0 || index >= arr.length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
String value = arr[index];
|
||||
return (value == null || value.isBlank()) ? null : value.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the streets.
|
||||
*
|
||||
* @param streets
|
||||
* The map to store streets
|
||||
* @return The cartography document containing street segments
|
||||
*/
|
||||
public CartographyDocument<StreetSegment> read(final Map<String, Street> streets)
|
||||
throws IOException
|
||||
{
|
||||
Map<String, StreetSegment> segments = new HashMap<String, StreetSegment>();
|
||||
String currentLine;
|
||||
while ((currentLine = in.readLine()) != null)
|
||||
{
|
||||
String[] values = currentLine.trim().split("\t");
|
||||
int tail = Integer.parseInt(values[0].trim());
|
||||
int head = Integer.parseInt(values[1].trim());
|
||||
double length = Double.parseDouble(values[2].trim());
|
||||
String code = values[3].trim();
|
||||
String id = values[4].trim();
|
||||
String prefix = safeGet(values, 5);
|
||||
String name = safeGet(values, 6);
|
||||
String category = safeGet(values, 7);
|
||||
String suffix = safeGet(values, 8);
|
||||
int lowAddress = Integer.MIN_VALUE;
|
||||
int highAddress = Integer.MAX_VALUE;
|
||||
|
||||
if (safeGet(values, 9) != null && safeGet(values, 10) != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
int tailAddress = Integer.parseInt(values[9].trim());
|
||||
int headAddress = Integer.parseInt(values[10].trim());
|
||||
lowAddress = Math.min(tailAddress, headAddress);
|
||||
highAddress = Math.max(tailAddress, headAddress);
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
StreetSegment segment = new StreetSegment(id, code, geographicShapes.getElement(id),
|
||||
lowAddress, highAddress, tail, head, length);
|
||||
|
||||
String canonicalName = Street.createCanonicalName(prefix, name, category, suffix);
|
||||
Street street = streets.get(canonicalName);
|
||||
if (street == null)
|
||||
{
|
||||
street = new Street(prefix, name, category, suffix, code);
|
||||
streets.put(canonicalName, street);
|
||||
}
|
||||
|
||||
street.addSegment(segment);
|
||||
segments.put(id, segment);
|
||||
}
|
||||
|
||||
return new CartographyDocument<StreetSegment>(segments, geographicShapes.getBounds());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package geography;
|
||||
|
||||
import java.awt.Shape;
|
||||
|
||||
/**
|
||||
* A GeographicShape is a shape that can be associated with a geographic location.
|
||||
*/
|
||||
public abstract class AbstractGeographicShape implements GeographicShape
|
||||
{
|
||||
private String id;
|
||||
|
||||
|
||||
/**
|
||||
* @param id The ID of the shape.
|
||||
*/
|
||||
public AbstractGeographicShape(final String id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getID()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract Shape getShape();
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package geography;
|
||||
/**
|
||||
* An abstract class that is extended to construct different types
|
||||
* map projections.
|
||||
*
|
||||
* @author Prof. David Bernstein, James Madison University
|
||||
* @version 1.0
|
||||
*/
|
||||
public abstract class AbstractMapProjection implements MapProjection
|
||||
{
|
||||
// Radius of spherical Earth (in km)
|
||||
protected static final double R = 6369.0;
|
||||
|
||||
protected static final double PI = Math.PI;
|
||||
protected static final double TWO_PI = 2.0*Math.PI;
|
||||
protected static final double PI_OVER_TWO = Math.PI/2.0;
|
||||
protected static final double PI_OVER_FOUR = Math.PI/4.0;
|
||||
|
||||
protected static final double RADIANS_PER_DEGREE = 2.0*Math.PI/360.0;
|
||||
|
||||
protected static final double DEGREES_PER_RADIAN = 1.0 / RADIANS_PER_DEGREE;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The forward transformation (i.e., from Longitude/Latitude in
|
||||
* __degrees__ to kilometers above the equator and to the west of the
|
||||
* reference meridian).
|
||||
*
|
||||
* @param ll The longitude and latitude in __degrees__ (in that order)
|
||||
* @return KMs west of reference and north of equator (in that order)
|
||||
*/
|
||||
@Override
|
||||
public double[] forward(final double[] ll)
|
||||
{
|
||||
double lambda = ll[0] * RADIANS_PER_DEGREE;
|
||||
double phi = ll[1] * RADIANS_PER_DEGREE;
|
||||
|
||||
return forward(lambda, phi);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The forward transformation (i.e., from Longitude/Latitude in
|
||||
* __radians__ to kilometers above the equator and to the west of the
|
||||
* reference meridian).
|
||||
*
|
||||
* @param lambda The longitude in __radians__
|
||||
* @param phi The latitude in __radians__
|
||||
* @return KMs west of reference and north of equator (in that order)
|
||||
*/
|
||||
@Override
|
||||
public abstract double[] forward(final double lambda, final double phi);
|
||||
|
||||
|
||||
/**
|
||||
* The inverse transformation from kilometers (above the
|
||||
* equator and to the west of the reference meridian)
|
||||
* to Longitude/Latitude in __degrees__.
|
||||
*
|
||||
* @param km The point in above,west coordinates (in that order)
|
||||
* @return The longitude and latitude in __degrees__ (in that order)
|
||||
*/
|
||||
@Override
|
||||
public double[] inverse(final double[] km)
|
||||
{
|
||||
double[] ll = inverse(km[0], km[1]);// In radians
|
||||
ll[0] = ll[0] * DEGREES_PER_RADIAN; // In degrees
|
||||
ll[1] = ll[1] * DEGREES_PER_RADIAN; // In degrees
|
||||
|
||||
return ll;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The inverse transformation from kilometers (above the
|
||||
* equator and to the west of the reference meridian)
|
||||
* to Longitude/Latitude in __radians__.
|
||||
*
|
||||
* @param ew The distance east/west in kilometers
|
||||
* @param ns The distance north/south in kilometers
|
||||
* @return The longitude and latitude in __radians__ (in that order)
|
||||
*/
|
||||
@Override
|
||||
public abstract double[] inverse(final double ew, final double ns);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package geography;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ConicalEqualAreaProjection extends AbstractMapProjection
|
||||
{
|
||||
private double lamda0;
|
||||
private double phi0;
|
||||
private double phi1;
|
||||
private double phi2;
|
||||
|
||||
private double n;
|
||||
private double c;
|
||||
private double rho0;
|
||||
|
||||
/**
|
||||
* @param refM The reference meridian (in degrees)
|
||||
* @param refP The reference parallel (in degrees)
|
||||
* @param refP1 The first standard parallel (in degrees)
|
||||
* @param refP2 The second standard parallel (in degrees)
|
||||
*/
|
||||
public ConicalEqualAreaProjection(final double refM, final double refP, final double refP1,
|
||||
final double refP2)
|
||||
{
|
||||
this.lamda0 = refM * RADIANS_PER_DEGREE;
|
||||
this.phi0 = refP * RADIANS_PER_DEGREE;
|
||||
this.phi1 = refP1 * RADIANS_PER_DEGREE;
|
||||
this.phi2 = refP2 * RADIANS_PER_DEGREE;
|
||||
|
||||
n = 0.5 * (Math.sin(phi1) + Math.sin(phi2));
|
||||
c = Math.cos(phi1) * Math.cos(phi1) + 2.0 * n * Math.sin(phi1);
|
||||
rho0 = Math.sqrt(c - 2.0 * n * Math.sin(phi0)) / n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double[] forward(final double lambda, final double phi)
|
||||
{
|
||||
double rho = Math.sqrt(c - 2.0 * n * Math.sin(phi)) / n;
|
||||
double theta = n * (lambda - lamda0);
|
||||
|
||||
return new double[] {R * rho * Math.sin(theta), R * (rho0 - rho * Math.cos(theta))};
|
||||
}
|
||||
|
||||
@Override
|
||||
public double[] inverse(final double ew, final double ns)
|
||||
{
|
||||
double a = Math.sqrt(Math.pow((ew / R), 2) + Math.pow((rho0 - ns / R), 2));
|
||||
double b = Math.atan((ew / R) / (rho0 - ns / R));
|
||||
|
||||
return new double[] {lamda0 + b / n, Math.asin((c - Math.pow(a * n, 2)) / (2.0 * n))};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package geography;
|
||||
|
||||
import java.awt.Shape;
|
||||
|
||||
/**
|
||||
* A GeographicShape is a shape that can be associated with a geographic location.
|
||||
*/
|
||||
public interface GeographicShape
|
||||
{
|
||||
/**
|
||||
* @return The ID of the shape.
|
||||
*/
|
||||
public String getID();
|
||||
|
||||
/**
|
||||
* @return The shape.
|
||||
*/
|
||||
public Shape getShape();
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package geography;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import gui.CartographyDocument;
|
||||
|
||||
/**
|
||||
* A class for reading geographic shapes from an input stream and creating a CartographyDocument
|
||||
* containing those shapes.
|
||||
*/
|
||||
public class GeographicShapesReader
|
||||
{
|
||||
private BufferedReader in;
|
||||
private MapProjection proj;
|
||||
|
||||
/**
|
||||
* @param is
|
||||
* The input stream to read the shapes from
|
||||
* @param proj
|
||||
* The map projection to use when converting geographic coordinates to Cartesian
|
||||
* coordinates
|
||||
*/
|
||||
public GeographicShapesReader(final InputStream is, final MapProjection proj)
|
||||
{
|
||||
this.in = new BufferedReader(new InputStreamReader(is));
|
||||
this.proj = proj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A CartographyDocument containing the shapes read from the input stream.
|
||||
*/
|
||||
public CartographyDocument<GeographicShape> read()
|
||||
{
|
||||
double maxX = Double.NEGATIVE_INFINITY;
|
||||
double maxY = Double.NEGATIVE_INFINITY;
|
||||
double minX = Double.POSITIVE_INFINITY;
|
||||
double minY = Double.POSITIVE_INFINITY;
|
||||
|
||||
Map<String, GeographicShape> shapes = new HashMap<>();
|
||||
|
||||
String currentLine;
|
||||
try
|
||||
{
|
||||
while ((currentLine = in.readLine()) != null)
|
||||
{
|
||||
Map<String, String> parsed = new LinkedHashMap<>();
|
||||
String[] tokens = currentLine.trim().split("(?=Type:|ID:|Code:)");
|
||||
|
||||
for (String token : tokens)
|
||||
{
|
||||
if (token.isBlank())
|
||||
continue;
|
||||
int colonIdx = token.indexOf(':');
|
||||
String key = token.substring(0, colonIdx).trim();
|
||||
String value = token.substring(colonIdx + 1).trim();
|
||||
parsed.put(key, value);
|
||||
}
|
||||
|
||||
String type = parsed.get("Type");
|
||||
String id = parsed.get("ID");
|
||||
|
||||
PiecewiseLinearCurve shape;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case "PiecewiseLinearCurve":
|
||||
shape = new PiecewiseLinearCurve(id);
|
||||
break;
|
||||
case "Polygon":
|
||||
shape = new Polygon(id);
|
||||
break;
|
||||
case "Comment":
|
||||
in.readLine(); // skip the END
|
||||
continue;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
while ((currentLine = in.readLine()) != null)
|
||||
{
|
||||
currentLine = currentLine.trim();
|
||||
|
||||
if (currentLine.equals("END"))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
String[] strCoords = currentLine.trim().split("\\s+");
|
||||
double[] coords = new double[] {Double.parseDouble(strCoords[0]),
|
||||
Double.parseDouble(strCoords[1])};
|
||||
|
||||
double[] projCoords = proj.forward(coords);
|
||||
shape.add(projCoords);
|
||||
|
||||
maxX = Math.max(maxX, projCoords[0]);
|
||||
maxY = Math.max(maxY, projCoords[1]);
|
||||
minX = Math.min(minX, projCoords[0]);
|
||||
minY = Math.min(minY, projCoords[1]);
|
||||
}
|
||||
shapes.put(id, shape);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
Rectangle2D.Double bounds = new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
|
||||
return new CartographyDocument<>(shapes, bounds);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package geography;
|
||||
|
||||
/**
|
||||
* The requirements of a map projection.
|
||||
*
|
||||
* @author Prof. David Bernstein, James Madison University
|
||||
* @version 1.0
|
||||
*/
|
||||
public interface MapProjection
|
||||
{
|
||||
|
||||
/**
|
||||
* The forward transformation (i.e., from Longitude/Latitude in
|
||||
* __degrees__ to kilometers above the equator and to the west of the
|
||||
* reference meridian).
|
||||
*
|
||||
* @param ll The longitude and latitude in __degrees__ (in that order)
|
||||
* @return KMs west of reference and north of equator (in that order)
|
||||
*/
|
||||
public abstract double[] forward(double[] ll);
|
||||
|
||||
|
||||
/**
|
||||
* The forward transformation (i.e., from Longitude/Latitude in
|
||||
* __radians__ to kilometers above the equator and to the west of the
|
||||
* reference meridian).
|
||||
*
|
||||
* @param lambda The longitude in __radians__
|
||||
* @param phi The latitude in __radians__
|
||||
* @return KMs west of reference and north of equator (in that order)
|
||||
*/
|
||||
public abstract double[] forward(double lambda, double phi);
|
||||
|
||||
|
||||
/**
|
||||
* The inverse transformation from kilometers (above the
|
||||
* equator and to the west of the reference meridian)
|
||||
* to Longitude/Latitude in __degrees__.
|
||||
*
|
||||
* @param km The point in above,west coordinates (in that order)
|
||||
* @return The longitude and latitude in __degrees__ (in that order)
|
||||
*/
|
||||
public abstract double[] inverse(double[] km);
|
||||
|
||||
/**
|
||||
* The inverse transformation from kilometers (above the
|
||||
* equator and to the west of the reference meridian)
|
||||
* to Longitude/Latitude in __radians__.
|
||||
*
|
||||
* @param ew The distance east/west in kilometers
|
||||
* @param ns The distance north/south in kilometers
|
||||
* @return The longitude and latitude in __radians__ (in that order)
|
||||
*/
|
||||
public abstract double[] inverse(double ew, double ns);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package geography;
|
||||
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.Path2D;
|
||||
|
||||
/**
|
||||
* A class representing a piecewise linear curve.
|
||||
*/
|
||||
public class PiecewiseLinearCurve extends AbstractGeographicShape
|
||||
{
|
||||
protected Path2D.Double shape;
|
||||
|
||||
/**
|
||||
* @param id
|
||||
* The id of the curve
|
||||
*/
|
||||
public PiecewiseLinearCurve(final String id)
|
||||
{
|
||||
super(id);
|
||||
shape = new Path2D.Double();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id
|
||||
* The id of the curve
|
||||
* @param path
|
||||
* The path of the curve
|
||||
*/
|
||||
public PiecewiseLinearCurve(final String id, final Path2D.Double path)
|
||||
{
|
||||
super(id);
|
||||
this.shape = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getShape()
|
||||
{
|
||||
return shape;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param point
|
||||
* The point to add to the curve
|
||||
*/
|
||||
public void add(final double[] point)
|
||||
{
|
||||
if (shape.getCurrentPoint() == null)
|
||||
{
|
||||
shape.moveTo(point[0], point[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
shape.lineTo(point[0], point[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param addition
|
||||
* The shape to add to the curve
|
||||
* @param connect
|
||||
* Whether to connect the current curve to the addition with a line segment (if true, the
|
||||
* addition will be connected to the current curve with a line segment; if false, the
|
||||
* addition will be added as a separate shape)
|
||||
*/
|
||||
public void append(final Shape addition, final boolean connect)
|
||||
{
|
||||
if (connect && shape.getCurrentPoint() != null)
|
||||
{
|
||||
shape.lineTo(addition.getBounds2D().getX(), addition.getBounds2D().getY());
|
||||
}
|
||||
shape.append(addition, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package geography;
|
||||
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.Path2D;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class Polygon extends PiecewiseLinearCurve
|
||||
{
|
||||
/**
|
||||
* @param id The id of the polygon
|
||||
*/
|
||||
public Polygon(final String id)
|
||||
{
|
||||
super(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id The id of the polygon
|
||||
* @param path The path of the polygon
|
||||
*/
|
||||
public Polygon(final String id, final Path2D.Double path)
|
||||
{
|
||||
super(id, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getShape()
|
||||
{
|
||||
shape.closePath();
|
||||
return shape;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package geography;
|
||||
|
||||
/**
|
||||
* A map projection that maps the globe onto a plane using the sinusoidal projection.
|
||||
*/
|
||||
public class SinusoidalProjection extends AbstractMapProjection
|
||||
{
|
||||
|
||||
@Override
|
||||
public double[] forward(final double lambda, final double phi)
|
||||
{
|
||||
return new double[] {R * lambda * Math.cos(phi), R * phi};
|
||||
}
|
||||
|
||||
@Override
|
||||
public double[] inverse(final double ew, final double ns)
|
||||
{
|
||||
double phi = ns / R;
|
||||
return new double[] {ew / (R * Math.cos(phi)), phi};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package geography;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
|
||||
/**
|
||||
* A theme for rendering geographic elements.
|
||||
*/
|
||||
public class Theme
|
||||
{
|
||||
private Color color;
|
||||
private BasicStroke stroke;
|
||||
|
||||
/**
|
||||
* Explicit Value Constructor.
|
||||
*
|
||||
* @param color
|
||||
* The color
|
||||
* @param stroke
|
||||
* The stroke
|
||||
*/
|
||||
public Theme(final Color color, final BasicStroke stroke)
|
||||
{
|
||||
this.color = color;
|
||||
this.stroke = stroke;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the color.
|
||||
*
|
||||
* @return The color
|
||||
*/
|
||||
public Color getColor()
|
||||
{
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stroke.
|
||||
*
|
||||
* @return The stroke
|
||||
*/
|
||||
public BasicStroke getStroke()
|
||||
{
|
||||
return stroke;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package geography;
|
||||
|
||||
/**
|
||||
* Interface for theme libraries that provide rendering themes.
|
||||
*/
|
||||
public interface ThemeLibrary
|
||||
{
|
||||
/**
|
||||
* Get the theme for highlighted elements.
|
||||
*
|
||||
* @return The highlight theme
|
||||
*/
|
||||
Theme getHighlightTheme();
|
||||
|
||||
/**
|
||||
* Get the theme for a given code.
|
||||
*
|
||||
* @param code
|
||||
* The theme code
|
||||
* @return The theme
|
||||
*/
|
||||
Theme getTheme(final String code);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package graph;
|
||||
|
||||
import feature.StreetSegment;
|
||||
|
||||
/**
|
||||
* Base implementation for label managers.
|
||||
*/
|
||||
public abstract class AbstractLabelManager implements LabelManager
|
||||
{
|
||||
protected Label[] labels;
|
||||
|
||||
/**
|
||||
* @param networkSize size of the street network
|
||||
*/
|
||||
public AbstractLabelManager(final int networkSize)
|
||||
{
|
||||
labels = new Label[networkSize];
|
||||
for (int i = 0; i < networkSize; i++)
|
||||
{
|
||||
labels[i] = new Label(i);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void adjustHeadValue(final StreetSegment segment)
|
||||
{
|
||||
if (segment == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int tailID = segment.getTail();
|
||||
int headID = segment.getHead();
|
||||
if (!isValidIndex(tailID) || !isValidIndex(headID))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Label tailLabel = labels[tailID];
|
||||
Label headLabel = labels[headID];
|
||||
if (tailLabel == null || headLabel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double possibleValue = tailLabel.getValue() + segment.getLength();
|
||||
headLabel.adjustValue(possibleValue, segment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Label getLabel(final int intersectionID)
|
||||
{
|
||||
if (!isValidIndex(intersectionID))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return labels[intersectionID];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param intersectionID index to check
|
||||
* @return true iff index is valid
|
||||
*/
|
||||
protected boolean isValidIndex(final int intersectionID)
|
||||
{
|
||||
return intersectionID >= 0 && intersectionID < labels.length;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package graph;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import feature.StreetSegment;
|
||||
import feature.StreetSegmentObserver;
|
||||
|
||||
/**
|
||||
* Base implementation for shortest-path algorithms.
|
||||
*/
|
||||
public abstract class AbstractShortestPathAlgorithm implements ShortestPathAlgorithm
|
||||
{
|
||||
private Collection<StreetSegmentObserver> observers;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public AbstractShortestPathAlgorithm()
|
||||
{
|
||||
observers = new ArrayList<StreetSegmentObserver>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract Map<String, StreetSegment> findPath(int origin, int destination,
|
||||
StreetNetwork net);
|
||||
|
||||
@Override
|
||||
public void addStreetSegmentObserver(final StreetSegmentObserver observer)
|
||||
{
|
||||
if (observer != null)
|
||||
{
|
||||
observers.add(observer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeStreetSegmentObserver(final StreetSegmentObserver observer)
|
||||
{
|
||||
observers.remove(observer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyStreetSegmentObservers(final List<String> segmentIDs)
|
||||
{
|
||||
for (StreetSegmentObserver observer : observers)
|
||||
{
|
||||
observer.handleStreetSegments(segmentIDs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param segmentID the segment id
|
||||
*/
|
||||
protected void notifyStreetSegmentObservers(final String segmentID)
|
||||
{
|
||||
List<String> one = new ArrayList<String>();
|
||||
one.add(segmentID);
|
||||
notifyStreetSegmentObservers(one);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param labels the labels
|
||||
* @param origin the origin id
|
||||
* @param destination the destination id
|
||||
* @return ordered path map
|
||||
*/
|
||||
protected Map<String, StreetSegment> buildPath(final LabelManager labels, final int origin,
|
||||
final int destination)
|
||||
{
|
||||
LinkedHashMap<String, StreetSegment> path =
|
||||
new LinkedHashMap<String, StreetSegment>();
|
||||
|
||||
if (labels == null)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
Label destinationLabel = labels.getLabel(destination);
|
||||
if (destinationLabel == null || Double.isInfinite(destinationLabel.getValue()))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
ArrayList<StreetSegment> reversed = new java.util.ArrayList<StreetSegment>();
|
||||
Label current = destinationLabel;
|
||||
while (current != null && current.getID() != origin)
|
||||
{
|
||||
StreetSegment predecessor = current.getPredecessor();
|
||||
if (predecessor == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
reversed.add(predecessor);
|
||||
current = labels.getLabel(predecessor.getTail());
|
||||
}
|
||||
|
||||
for (int i = reversed.size() - 1; i >= 0; i--)
|
||||
{
|
||||
StreetSegment segment = reversed.get(i);
|
||||
path.put(segment.getID(), segment);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package graph;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import feature.StreetSegment;
|
||||
|
||||
/**
|
||||
* Candidate label manager backed by a list.
|
||||
*/
|
||||
public class CandidateLabelList extends AbstractLabelManager implements CandidateLabelManager
|
||||
{
|
||||
public static final String NEWEST = "N";
|
||||
public static final String OLDEST = "O";
|
||||
|
||||
private List<Integer> candidates;
|
||||
private String policy;
|
||||
|
||||
/**
|
||||
* Explicit value constructor.
|
||||
*
|
||||
* @param policy candidate selection policy (NEWEST or OLDEST)
|
||||
* @param networkSize network size
|
||||
*/
|
||||
public CandidateLabelList(final String policy, final int networkSize)
|
||||
{
|
||||
super(networkSize);
|
||||
this.candidates = new ArrayList<Integer>();
|
||||
if (NEWEST.equals(policy) || OLDEST.equals(policy))
|
||||
{
|
||||
this.policy = policy;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.policy = NEWEST;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void adjustHeadValue(final StreetSegment segment)
|
||||
{
|
||||
if (segment == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int headID = segment.getHead();
|
||||
Label head = getLabel(headID);
|
||||
if (head == null || head.isPermanent())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double previous = head.getValue();
|
||||
super.adjustHeadValue(segment);
|
||||
if (head.getValue() < previous && !candidates.contains(headID))
|
||||
{
|
||||
candidates.add(headID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Label getCandidateLabel()
|
||||
{
|
||||
while (!candidates.isEmpty())
|
||||
{
|
||||
int index;
|
||||
if (NEWEST.equals(policy))
|
||||
{
|
||||
index = candidates.remove(candidates.size() - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
index = candidates.remove(0);
|
||||
}
|
||||
|
||||
Label label = getLabel(index);
|
||||
if (label != null && !label.isPermanent())
|
||||
{
|
||||
return label;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package graph;
|
||||
|
||||
/**
|
||||
* A LabelManager that returns candidate labels.
|
||||
*/
|
||||
public interface CandidateLabelManager extends LabelManager
|
||||
{
|
||||
/**
|
||||
* Get a candidate label.
|
||||
*
|
||||
* @return the candidate label, or null when none exist
|
||||
*/
|
||||
public abstract Label getCandidateLabel();
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package graph;
|
||||
|
||||
import feature.StreetSegment;
|
||||
|
||||
/**
|
||||
* A label used by shortest-path algorithms.
|
||||
*/
|
||||
public class Label
|
||||
{
|
||||
private boolean permanent;
|
||||
private double value;
|
||||
private int id;
|
||||
private StreetSegment predecessor;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public Label()
|
||||
{
|
||||
this(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id the intersection id
|
||||
*/
|
||||
public Label(final int id)
|
||||
{
|
||||
this.id = id;
|
||||
this.permanent = false;
|
||||
this.value = Double.POSITIVE_INFINITY;
|
||||
this.predecessor = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param possibleValue possible new value
|
||||
* @param possiblePredecessor possible predecessor segment
|
||||
*/
|
||||
public void adjustValue(final double possibleValue,
|
||||
final StreetSegment possiblePredecessor)
|
||||
{
|
||||
if (possibleValue < value)
|
||||
{
|
||||
value = possibleValue;
|
||||
predecessor = possiblePredecessor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the intersection id
|
||||
*/
|
||||
public int getID()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the predecessor segment
|
||||
*/
|
||||
public StreetSegment getPredecessor()
|
||||
{
|
||||
return predecessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current label value
|
||||
*/
|
||||
public double getValue()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true iff permanent
|
||||
*/
|
||||
public boolean isPermanent()
|
||||
{
|
||||
return permanent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this label as permanent.
|
||||
*/
|
||||
public void makePermanent()
|
||||
{
|
||||
permanent = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the label value.
|
||||
*
|
||||
* @param value the value
|
||||
*/
|
||||
public void setValue(final double value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package graph;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import feature.Intersection;
|
||||
import feature.StreetSegment;
|
||||
|
||||
/**
|
||||
* Label-correcting shortest-path algorithm.
|
||||
*/
|
||||
public class LabelCorrectingAlgorithm extends AbstractShortestPathAlgorithm
|
||||
{
|
||||
private CandidateLabelManager labels;
|
||||
|
||||
/**
|
||||
* Explicit value constructor.
|
||||
*
|
||||
* @param labels candidate label manager
|
||||
*/
|
||||
public LabelCorrectingAlgorithm(final CandidateLabelManager labels)
|
||||
{
|
||||
this.labels = labels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, StreetSegment> findPath(final int origin, final int destination,
|
||||
final StreetNetwork net)
|
||||
{
|
||||
if (labels == null || net == null)
|
||||
{
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Label originLabel = labels.getLabel(origin);
|
||||
if (originLabel == null)
|
||||
{
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
originLabel.setValue(0.0);
|
||||
|
||||
Intersection originIntersection = net.getIntersection(origin);
|
||||
if (originIntersection != null)
|
||||
{
|
||||
for (StreetSegment segment : originIntersection.getOutbound())
|
||||
{
|
||||
labels.adjustHeadValue(segment);
|
||||
notifyStreetSegmentObservers(segment.getID());
|
||||
}
|
||||
}
|
||||
|
||||
Label current = labels.getCandidateLabel();
|
||||
while (current != null)
|
||||
{
|
||||
Intersection intersection = net.getIntersection(current.getID());
|
||||
if (intersection != null)
|
||||
{
|
||||
for (StreetSegment segment : intersection.getOutbound())
|
||||
{
|
||||
labels.adjustHeadValue(segment);
|
||||
notifyStreetSegmentObservers(segment.getID());
|
||||
}
|
||||
}
|
||||
|
||||
current = labels.getCandidateLabel();
|
||||
}
|
||||
|
||||
return buildPath(labels, origin, destination);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package graph;
|
||||
|
||||
import feature.StreetSegment;
|
||||
|
||||
/**
|
||||
* Manages labels used by shortest-path algorithms.
|
||||
*/
|
||||
public interface LabelManager
|
||||
{
|
||||
/**
|
||||
* Attempt to relax the label at the head of the segment.
|
||||
*
|
||||
* @param segment the segment being considered
|
||||
*/
|
||||
public abstract void adjustHeadValue(StreetSegment segment);
|
||||
|
||||
/**
|
||||
* Get the label for the given intersection.
|
||||
*
|
||||
* @param intersectionID the intersection id
|
||||
* @return the label
|
||||
*/
|
||||
public abstract Label getLabel(int intersectionID);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package graph;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import feature.Intersection;
|
||||
import feature.StreetSegment;
|
||||
|
||||
/**
|
||||
* Label-setting shortest-path algorithm.
|
||||
*/
|
||||
public class LabelSettingAlgorithm extends AbstractShortestPathAlgorithm
|
||||
{
|
||||
private PermanentLabelManager labels;
|
||||
|
||||
/**
|
||||
* @param labels permanent label manager
|
||||
*/
|
||||
public LabelSettingAlgorithm(final PermanentLabelManager labels)
|
||||
{
|
||||
this.labels = labels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, StreetSegment> findPath(final int origin, final int destination,
|
||||
final StreetNetwork net)
|
||||
{
|
||||
if (labels == null || net == null)
|
||||
{
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Label originLabel = labels.getLabel(origin);
|
||||
if (originLabel == null)
|
||||
{
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
originLabel.setValue(0.0);
|
||||
|
||||
Label current = originLabel;
|
||||
while (current != null)
|
||||
{
|
||||
int currentID = current.getID();
|
||||
labels.makePermanent(currentID);
|
||||
if (currentID == destination)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Intersection intersection = net.getIntersection(currentID);
|
||||
if (intersection != null)
|
||||
{
|
||||
for (StreetSegment segment : intersection.getOutbound())
|
||||
{
|
||||
labels.adjustHeadValue(segment);
|
||||
notifyStreetSegmentObservers(segment.getID());
|
||||
}
|
||||
}
|
||||
|
||||
current = labels.getSmallestLabel();
|
||||
if (current != null && Double.isInfinite(current.getValue()))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return buildPath(labels, origin, destination);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package graph;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import javax.swing.*;
|
||||
|
||||
import feature.StreetSegment;
|
||||
import feature.StreetSegmentObserver;
|
||||
import gui.*;
|
||||
|
||||
/**
|
||||
* A SwingWorker that uses a path finding algorithm to find a shortest path in
|
||||
* a worker thread.
|
||||
*
|
||||
* @author Prof. David Bernstein, James Madison University
|
||||
* @version 1.0
|
||||
*/
|
||||
public class PathFindingWorker extends SwingWorker<Map<String, StreetSegment>, String>
|
||||
implements StreetSegmentObserver
|
||||
{
|
||||
private int destination, origin;
|
||||
private ShortestPathAlgorithm alg;
|
||||
private StreetNetwork net;
|
||||
private CartographyDocument<StreetSegment> document;
|
||||
private CartographyPanel<StreetSegment> panel;
|
||||
|
||||
/**
|
||||
* Explicit Value Constructor.
|
||||
*
|
||||
* @param alg The algorithm to use
|
||||
* @param origin The origin Intersection
|
||||
* @param destination The destination Intersection
|
||||
* @param net The StreetNetwork
|
||||
* @param document The CartographyDocument
|
||||
* @param panel The CartographyPanel
|
||||
*/
|
||||
public PathFindingWorker(final ShortestPathAlgorithm alg,
|
||||
final int origin, final int destination, final StreetNetwork net,
|
||||
final CartographyDocument<StreetSegment> document,
|
||||
final CartographyPanel<StreetSegment> panel)
|
||||
{
|
||||
this.alg = alg;
|
||||
this.origin = origin;
|
||||
this.destination = destination;
|
||||
this.net = net;
|
||||
this.document = document;
|
||||
this.panel = panel;
|
||||
}
|
||||
|
||||
/**
|
||||
* The code to execute in the worker thread.
|
||||
*/
|
||||
@Override
|
||||
public Map<String, StreetSegment> doInBackground()
|
||||
{
|
||||
return alg.findPath(origin, destination, net);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a collection of StreetSegment objects.
|
||||
*
|
||||
* @param segmentIDs The IDs of the StreetSegment objects
|
||||
*/
|
||||
@Override
|
||||
public void handleStreetSegments(final List<String> segmentIDs)
|
||||
{
|
||||
publish(segmentIDs.toArray(new String[segmentIDs.size()]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle intermediate results.
|
||||
*/
|
||||
@Override
|
||||
public void process(final List<String> segmentIDs)
|
||||
{
|
||||
Map<String, StreetSegment> highlighted = new HashMap<String, StreetSegment>();
|
||||
for (String id: segmentIDs) highlighted.put(id, document.getElement(id));
|
||||
|
||||
document.setHighlighted(highlighted);
|
||||
panel.repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether intermediate results should be displayed
|
||||
* on the map.
|
||||
*
|
||||
* @param show true to show intermediate results; false otherwise
|
||||
*/
|
||||
public void shouldShowIntermediateResults(final boolean show)
|
||||
{
|
||||
if (show) alg.addStreetSegmentObserver(this);
|
||||
else alg.removeStreetSegmentObserver(this);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package graph;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import feature.StreetSegment;
|
||||
|
||||
/**
|
||||
* Permanent label manager backed by exact-value ordered buckets.
|
||||
*/
|
||||
public class PermanentLabelBuckets extends AbstractLabelManager implements PermanentLabelManager
|
||||
{
|
||||
private TreeMap<Double, Deque<Integer>> buckets;
|
||||
|
||||
/**
|
||||
* @param networkSize network size
|
||||
*/
|
||||
public PermanentLabelBuckets(final int networkSize)
|
||||
{
|
||||
super(networkSize);
|
||||
this.buckets = new TreeMap<Double, Deque<Integer>>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void adjustHeadValue(final StreetSegment segment)
|
||||
{
|
||||
if (segment == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int headID = segment.getHead();
|
||||
Label head = getLabel(headID);
|
||||
if (head == null || head.isPermanent())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double previous = head.getValue();
|
||||
super.adjustHeadValue(segment);
|
||||
if (head.getValue() < previous)
|
||||
{
|
||||
addToBucket(headID, head.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Label getSmallestLabel()
|
||||
{
|
||||
while (!buckets.isEmpty())
|
||||
{
|
||||
Double smallestKey = buckets.firstKey();
|
||||
Deque<Integer> ids = buckets.get(smallestKey);
|
||||
while (ids != null && !ids.isEmpty())
|
||||
{
|
||||
int id = ids.removeFirst();
|
||||
Label label = getLabel(id);
|
||||
if (label == null || label.isPermanent())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Double.compare(label.getValue(), smallestKey.doubleValue()) == 0)
|
||||
{
|
||||
if (ids.isEmpty())
|
||||
{
|
||||
buckets.remove(smallestKey);
|
||||
}
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
buckets.remove(smallestKey);
|
||||
}
|
||||
|
||||
Label smallest = null;
|
||||
for (Label label : labels)
|
||||
{
|
||||
if (label == null || label.isPermanent())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (smallest == null || label.getValue() < smallest.getValue())
|
||||
{
|
||||
smallest = label;
|
||||
}
|
||||
}
|
||||
return smallest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void makePermanent(final int intersectionID)
|
||||
{
|
||||
Label label = getLabel(intersectionID);
|
||||
if (label != null)
|
||||
{
|
||||
label.makePermanent();
|
||||
}
|
||||
}
|
||||
|
||||
private void addToBucket(final int intersectionID, final double value)
|
||||
{
|
||||
Deque<Integer> ids = buckets.get(value);
|
||||
if (ids == null)
|
||||
{
|
||||
ids = new ArrayDeque<Integer>();
|
||||
buckets.put(value, ids);
|
||||
}
|
||||
ids.addLast(intersectionID);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package graph;
|
||||
|
||||
import java.util.PriorityQueue;
|
||||
|
||||
import feature.StreetSegment;
|
||||
|
||||
/**
|
||||
* Permanent label manager backed by a d-heap strategy.
|
||||
*/
|
||||
public class PermanentLabelHeap extends AbstractLabelManager implements PermanentLabelManager
|
||||
{
|
||||
private static class HeapEntry implements Comparable<HeapEntry>
|
||||
{
|
||||
private final int id;
|
||||
private final double value;
|
||||
|
||||
HeapEntry(final int id, final double value)
|
||||
{
|
||||
this.id = id;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(final HeapEntry other)
|
||||
{
|
||||
return Double.compare(value, other.value);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private int d;
|
||||
private PriorityQueue<HeapEntry> heap;
|
||||
|
||||
/**
|
||||
* @param d arity of the conceptual heap
|
||||
* @param networkSize network size
|
||||
*/
|
||||
public PermanentLabelHeap(final int d, final int networkSize)
|
||||
{
|
||||
super(networkSize);
|
||||
this.d = Math.max(2, d);
|
||||
this.heap = new PriorityQueue<HeapEntry>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void adjustHeadValue(final StreetSegment segment)
|
||||
{
|
||||
if (segment == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int headID = segment.getHead();
|
||||
Label head = getLabel(headID);
|
||||
if (head == null || head.isPermanent())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double previous = head.getValue();
|
||||
super.adjustHeadValue(segment);
|
||||
if (head.getValue() < previous)
|
||||
{
|
||||
heap.add(new HeapEntry(headID, head.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Label getSmallestLabel()
|
||||
{
|
||||
while (!heap.isEmpty())
|
||||
{
|
||||
HeapEntry entry = heap.poll();
|
||||
Label label = getLabel(entry.id);
|
||||
if (label == null || label.isPermanent())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Double.compare(label.getValue(), entry.value) == 0)
|
||||
{
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
Label smallest = null;
|
||||
for (Label label : labels)
|
||||
{
|
||||
if (label == null || label.isPermanent())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (smallest == null || label.getValue() < smallest.getValue())
|
||||
{
|
||||
smallest = label;
|
||||
}
|
||||
}
|
||||
return smallest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void makePermanent(final int intersectionID)
|
||||
{
|
||||
Label label = getLabel(intersectionID);
|
||||
if (label != null)
|
||||
{
|
||||
label.makePermanent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package graph;
|
||||
|
||||
import feature.StreetSegment;
|
||||
|
||||
/**
|
||||
* Permanent label manager that linearly scans labels.
|
||||
*/
|
||||
public class PermanentLabelList extends AbstractLabelManager implements PermanentLabelManager
|
||||
{
|
||||
/**
|
||||
* @param networkSize network size
|
||||
*/
|
||||
public PermanentLabelList(final int networkSize)
|
||||
{
|
||||
super(networkSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void adjustHeadValue(final StreetSegment segment)
|
||||
{
|
||||
if (segment == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Label head = getLabel(segment.getHead());
|
||||
if (head == null || head.isPermanent())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
super.adjustHeadValue(segment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Label getSmallestLabel()
|
||||
{
|
||||
Label smallest = null;
|
||||
for (Label label : labels)
|
||||
{
|
||||
if (label == null || label.isPermanent())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (smallest == null || label.getValue() < smallest.getValue())
|
||||
{
|
||||
smallest = label;
|
||||
}
|
||||
}
|
||||
return smallest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void makePermanent(final int intersectionID)
|
||||
{
|
||||
Label label = getLabel(intersectionID);
|
||||
if (label != null)
|
||||
{
|
||||
label.makePermanent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package graph;
|
||||
|
||||
/**
|
||||
* A LabelManager that can select and finalize permanent labels.
|
||||
*/
|
||||
public interface PermanentLabelManager extends LabelManager
|
||||
{
|
||||
/**
|
||||
* @return the smallest non-permanent label, or null if none remain
|
||||
*/
|
||||
public abstract Label getSmallestLabel();
|
||||
|
||||
/**
|
||||
* @param intersectionID the intersection id
|
||||
*/
|
||||
public abstract void makePermanent(int intersectionID);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package graph;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import feature.StreetSegment;
|
||||
import feature.StreetSegmentObserver;
|
||||
import feature.StreetSegmentSubject;
|
||||
|
||||
/**
|
||||
* Finds shortest paths in a street network.
|
||||
*/
|
||||
public interface ShortestPathAlgorithm extends StreetSegmentSubject
|
||||
{
|
||||
/**
|
||||
* @param origin the origin intersection id
|
||||
* @param destination the destination intersection id
|
||||
* @param net the street network
|
||||
* @return the path as an ordered map of segment id to segment
|
||||
*/
|
||||
public abstract Map<String, StreetSegment> findPath(int origin, int destination,
|
||||
StreetNetwork net);
|
||||
|
||||
@Override
|
||||
public abstract void addStreetSegmentObserver(StreetSegmentObserver observer);
|
||||
|
||||
@Override
|
||||
public abstract void removeStreetSegmentObserver(StreetSegmentObserver observer);
|
||||
|
||||
/**
|
||||
* @param segmentIDs the segment ids
|
||||
*/
|
||||
public abstract void notifyStreetSegmentObservers(List<String> segmentIDs);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package graph;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import feature.Intersection;
|
||||
import feature.Street;
|
||||
import feature.StreetSegment;
|
||||
|
||||
/**
|
||||
* A graph of intersections connected by street segments.
|
||||
*/
|
||||
public class StreetNetwork
|
||||
{
|
||||
private List<Intersection> intersections;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public StreetNetwork()
|
||||
{
|
||||
intersections = new ArrayList<Intersection>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param index the index
|
||||
* @param intersection the intersection
|
||||
*/
|
||||
public void addIntersection(final int index, final Intersection intersection)
|
||||
{
|
||||
while (intersections.size() <= index)
|
||||
{
|
||||
intersections.add(null);
|
||||
}
|
||||
intersections.set(index, intersection);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param index the index
|
||||
* @return the intersection
|
||||
*/
|
||||
public Intersection getIntersection(final int index)
|
||||
{
|
||||
if (index < 0 || index >= intersections.size())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return intersections.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of intersection slots
|
||||
*/
|
||||
public int size()
|
||||
{
|
||||
return intersections.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param streets the streets
|
||||
* @return the network
|
||||
*/
|
||||
public static StreetNetwork createStreetNetwork(final Map<String, Street> streets)
|
||||
{
|
||||
StreetNetwork net = new StreetNetwork();
|
||||
|
||||
for (Street street : streets.values())
|
||||
{
|
||||
for (java.util.Iterator<StreetSegment> it = street.getSegments(); it.hasNext();)
|
||||
{
|
||||
StreetSegment segment = it.next();
|
||||
int tail = segment.getTail();
|
||||
int head = segment.getHead();
|
||||
|
||||
Intersection tailIntersection = net.getIntersection(tail);
|
||||
if (tailIntersection == null)
|
||||
{
|
||||
tailIntersection = new Intersection();
|
||||
net.addIntersection(tail, tailIntersection);
|
||||
}
|
||||
|
||||
Intersection headIntersection = net.getIntersection(head);
|
||||
if (headIntersection == null)
|
||||
{
|
||||
headIntersection = new Intersection();
|
||||
net.addIntersection(head, headIntersection);
|
||||
}
|
||||
|
||||
tailIntersection.addOutbound(segment);
|
||||
headIntersection.addInbound(segment);
|
||||
}
|
||||
}
|
||||
|
||||
return net;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
package gui;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
/**
|
||||
* An abstract modal JDialog that can be extended to create
|
||||
* dialogs for specific purposes.
|
||||
*
|
||||
* @author Prof. David Bernstein, James Madison University
|
||||
* @version 1.0
|
||||
*/
|
||||
public abstract class AbstractModalDialog extends JDialog implements ActionListener, WindowListener
|
||||
{
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String CANCEL_KEY = "Cancel";
|
||||
private static final String OK_KEY = "OK";
|
||||
|
||||
public static final int CANCEL_OPTION = JOptionPane.CANCEL_OPTION;
|
||||
public static final int OK_OPTION = JOptionPane.OK_OPTION;
|
||||
|
||||
private boolean includeCancel, includeOK, layoutRequired;
|
||||
private Component owner;
|
||||
private int returnStatus;
|
||||
private JButton cancelButton, okButton;
|
||||
|
||||
|
||||
/**
|
||||
* Explicit Value Constructor.
|
||||
*
|
||||
* @param owner The parent Dialog
|
||||
* @param title The title of this JDialog
|
||||
*/
|
||||
public AbstractModalDialog(final Dialog owner, final String title)
|
||||
{
|
||||
super(owner, title, true);
|
||||
this.owner = owner;
|
||||
performCommonConstructionTasks(title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicit Value Constructor.
|
||||
*
|
||||
* @param owner The parent Frame
|
||||
* @param title The title of this JDialog
|
||||
*/
|
||||
public AbstractModalDialog(final Frame owner, final String title)
|
||||
{
|
||||
super(owner, title, true);
|
||||
this.owner = owner;
|
||||
performCommonConstructionTasks(title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle actionPerformed messages.
|
||||
*
|
||||
* @param e The event that generated the message
|
||||
*/
|
||||
public void actionPerformed(final ActionEvent e)
|
||||
{
|
||||
String ac = e.getActionCommand();
|
||||
if (ac.equals(CANCEL_KEY))
|
||||
{
|
||||
returnStatus = CANCEL_OPTION;
|
||||
setVisible(false);
|
||||
}
|
||||
else if (ac.equals(OK_KEY))
|
||||
{
|
||||
returnStatus = OK_OPTION;
|
||||
setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the main pane in this JDialog
|
||||
* (must be implemented by concrete children).
|
||||
*
|
||||
* @return The main pane
|
||||
*/
|
||||
protected abstract JComponent createMainPane();
|
||||
|
||||
/**
|
||||
* Perform tasks required by all constructors.
|
||||
*
|
||||
* @param title The title of the dialog
|
||||
*/
|
||||
private void performCommonConstructionTasks(final String title)
|
||||
{
|
||||
cancelButton = new JButton(CANCEL_KEY);
|
||||
okButton = new JButton(OK_KEY);
|
||||
|
||||
includeCancel = true;
|
||||
includeOK = true;
|
||||
|
||||
if (title != null) setTitle(title);
|
||||
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
|
||||
|
||||
cancelButton.setActionCommand(CANCEL_KEY);
|
||||
cancelButton.addActionListener(this);
|
||||
|
||||
okButton.setActionCommand(OK_KEY);
|
||||
okButton.addActionListener(this);
|
||||
|
||||
addWindowListener(this);
|
||||
layoutRequired = true;
|
||||
|
||||
setSize(400,400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout this JDialog.
|
||||
*/
|
||||
private void performLayout()
|
||||
{
|
||||
JComponent cp, mainPane;
|
||||
JPanel buttonPanel;
|
||||
|
||||
cp = (JComponent)getContentPane();
|
||||
cp.setLayout(new BorderLayout());
|
||||
|
||||
mainPane = createMainPane();
|
||||
cp.add(mainPane, BorderLayout.CENTER);
|
||||
|
||||
buttonPanel = new JPanel();
|
||||
if (includeOK && includeCancel)
|
||||
buttonPanel.setLayout(new FlowLayout(FlowLayout.TRAILING));
|
||||
else
|
||||
buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
|
||||
if (includeOK) buttonPanel.add(okButton);
|
||||
if (includeCancel) buttonPanel.add(cancelButton);
|
||||
cp.add(buttonPanel, BorderLayout.SOUTH);
|
||||
|
||||
pack();
|
||||
|
||||
layoutRequired = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether the Cancel button should be included.
|
||||
*
|
||||
* (Note: This method must be called before the first call
|
||||
* to showDialog)
|
||||
*
|
||||
* @param include true to include; false otherwise
|
||||
*/
|
||||
protected void setIncludeCancel(final boolean include)
|
||||
{
|
||||
includeCancel = include;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether the OK button should be included.
|
||||
*
|
||||
* (Note: This method must be called before the first call
|
||||
* to showDialog)
|
||||
*
|
||||
* @param include true to include; false otherwise
|
||||
*/
|
||||
protected void setIncludeOK(final boolean include)
|
||||
{
|
||||
includeOK = include;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show this JDialog (and block the thread of execution until
|
||||
* the user responds).
|
||||
*
|
||||
* @return Either CANCEL_OPTION or OK_OPTION
|
||||
*/
|
||||
public int showDialog()
|
||||
{
|
||||
int x, y;
|
||||
Rectangle r;
|
||||
|
||||
|
||||
if (layoutRequired) performLayout();
|
||||
|
||||
returnStatus = CANCEL_OPTION;
|
||||
|
||||
r = owner.getBounds();
|
||||
x = (int)(r.getX() + r.getWidth()/2 - getWidth()/2);
|
||||
y = (int)(r.getY() + r.getHeight()/2 - getHeight()/2);
|
||||
setLocation(x, y);
|
||||
|
||||
setVisible(true);
|
||||
|
||||
return returnStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle windowActivated messages.
|
||||
*
|
||||
* @param e The relevant WindowEvent
|
||||
*/
|
||||
public void windowActivated(final WindowEvent e)
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle windowClosed messages.
|
||||
*
|
||||
* @param e The relevant WindowEvent
|
||||
*/
|
||||
public void windowClosed(final WindowEvent e)
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle windowClosing messages.
|
||||
*
|
||||
* @param e The relevant WindowEvent
|
||||
*/
|
||||
public void windowClosing(final WindowEvent e)
|
||||
{
|
||||
returnStatus = CANCEL_OPTION;
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle windowDeactivated messages.
|
||||
*
|
||||
* @param e The relevant WindowEvent
|
||||
*/
|
||||
public void windowDeactivated(final WindowEvent e)
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle windowDeiconified messages.
|
||||
*
|
||||
* @param e The relevant WindowEvent
|
||||
*/
|
||||
public void windowDeiconified(final WindowEvent e)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle windowIconified messages.
|
||||
*
|
||||
* @param e The relevant WindowEvent
|
||||
*/
|
||||
public void windowIconified(final WindowEvent e)
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle windowOpened messages.
|
||||
*
|
||||
* @param e The relevant WindowEvent
|
||||
*/
|
||||
public void windowOpened(final WindowEvent e)
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
package gui;
|
||||
|
||||
import java.awt.*;
|
||||
import java.beans.*;
|
||||
import java.util.*;
|
||||
import javax.swing.*;
|
||||
|
||||
/**
|
||||
* A modal JDialog that can be used to cancel and show the status
|
||||
* of a background task (i.e., a task being executed using a
|
||||
* SwingWorker).
|
||||
*
|
||||
* @param <V> The type returned by the SwingWorker
|
||||
* @param <T> The type of intermediate values generated by the SwingWorker
|
||||
*
|
||||
* @author Prof. David Bernstein, James Madison University
|
||||
* @version 1.0
|
||||
*/
|
||||
public class BackgroundTaskDialog<T, V> extends AbstractModalDialog
|
||||
implements PropertyChangeListener
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private long timeout;
|
||||
private SwingWorker<T, V> task;
|
||||
private java.util.Timer countdown;
|
||||
|
||||
|
||||
/**
|
||||
* Explicit Value Constructor.
|
||||
*
|
||||
* @param owner The parent Dialog
|
||||
* @param title The title of this JDialog
|
||||
* @param task The background task
|
||||
*/
|
||||
public BackgroundTaskDialog(final Dialog owner, final String title, final SwingWorker<T, V> task)
|
||||
{
|
||||
super(owner, title);
|
||||
performCommonConstructionTasks(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicit Value Constructor.
|
||||
*
|
||||
* @param owner The parent Frame
|
||||
* @param title The title of this JDialog
|
||||
* @param task The background task
|
||||
*/
|
||||
public BackgroundTaskDialog(final Frame owner, final String title, final SwingWorker<T,V> task)
|
||||
{
|
||||
super(owner, title);
|
||||
performCommonConstructionTasks(task);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cancel the countdown timer (if there is one).
|
||||
*/
|
||||
private void cancelCountdownTimer()
|
||||
{
|
||||
if (countdown != null)
|
||||
{
|
||||
countdown.cancel();
|
||||
countdown = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the main pane in this JDialog.
|
||||
*/
|
||||
@Override
|
||||
protected JComponent createMainPane()
|
||||
{
|
||||
JPanel result = new JPanel();
|
||||
result.setLayout(new BorderLayout());
|
||||
|
||||
JProgressBar progressBar = new JProgressBar();
|
||||
progressBar.setIndeterminate(true);
|
||||
progressBar.setOrientation(SwingConstants.HORIZONTAL);
|
||||
progressBar.setBorderPainted(true);
|
||||
|
||||
result.add(progressBar, BorderLayout.SOUTH);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The code to execute in the event dispatch thread.
|
||||
*/
|
||||
public void execute()
|
||||
{
|
||||
int status;
|
||||
|
||||
status = OK_OPTION;
|
||||
|
||||
// Execute the background task (in a worker thread)
|
||||
task.execute();
|
||||
|
||||
// If the task should timeout, start the countdown timer
|
||||
if (timeout > 0) startCountdownTimer();
|
||||
|
||||
// Show the dialog (unless the task has already completed somehow)
|
||||
if (!task.isDone()) status = showDialog();
|
||||
|
||||
// Cancel the countdown timer (if there is one)
|
||||
cancelCountdownTimer();
|
||||
|
||||
// If the dialog was canceled (i.e., the task didn't complete)
|
||||
// then cancel the task
|
||||
if (status == CANCEL_OPTION)
|
||||
{
|
||||
if (task != null) task.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Perform common construction tasks.
|
||||
*
|
||||
* @param swtask The SwingWorker
|
||||
*/
|
||||
private void performCommonConstructionTasks(final SwingWorker<T,V> swtask)
|
||||
{
|
||||
|
||||
setIncludeCancel(true);
|
||||
setIncludeOK(false);
|
||||
this.task = swtask;
|
||||
task.addPropertyChangeListener(this);
|
||||
|
||||
timeout = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Handle propertyChange messages.
|
||||
*
|
||||
* Note: This method should only be called by the SwingWorker, and
|
||||
* the SwingWorker always calls this method on the event dispatch
|
||||
* thread.
|
||||
*
|
||||
* @param evt The PropertyChangeEvent associated with the message
|
||||
*/
|
||||
@Override
|
||||
public void propertyChange(final PropertyChangeEvent evt)
|
||||
{
|
||||
SwingWorker.StateValue state;
|
||||
|
||||
state = task.getState();
|
||||
|
||||
if (state.equals(SwingWorker.StateValue.DONE))
|
||||
{
|
||||
// Cancel the countdown timer
|
||||
cancelCountdownTimer();
|
||||
|
||||
// Hide this dialog
|
||||
setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the timeout for the task.
|
||||
|
||||
* The timeout must be positive. A value of 0 or less
|
||||
* is treated as an infinite timeout.
|
||||
*
|
||||
* @param taskTimeout The timeout (in milliseconds)
|
||||
*/
|
||||
public void setTaskTimeout(final long taskTimeout)
|
||||
{
|
||||
this.timeout = taskTimeout;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Start the countdown timer.
|
||||
*/
|
||||
private void startCountdownTimer()
|
||||
{
|
||||
countdown = new java.util.Timer();
|
||||
|
||||
countdown.schedule(new TimeoutTask(), timeout);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A TimerTask for handling time-outs.
|
||||
*/
|
||||
private class TimeoutTask extends TimerTask
|
||||
{
|
||||
/**
|
||||
* The action to be performed by this TimerTask.
|
||||
*/
|
||||
public void run()
|
||||
{
|
||||
// Too much time has elapsed so cancel the task
|
||||
task.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package gui;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.AffineTransform;
|
||||
|
||||
/**
|
||||
* @param <T> The type of elements in the document
|
||||
*/
|
||||
public interface Cartographer<T>
|
||||
{
|
||||
/**
|
||||
* @param model The document to paint
|
||||
* @param g2 The graphics context to paint on
|
||||
* @param at The affine transform to apply to the graphics context before painting
|
||||
*/
|
||||
public void paintHighlights(final CartographyDocument<T> model, final Graphics2D g2,
|
||||
final AffineTransform at);
|
||||
|
||||
/**
|
||||
* @param model The document to paint
|
||||
* @param g2 The graphics context to paint on
|
||||
* @param at The affine transform to apply to the graphics context before painting
|
||||
*/
|
||||
public void paintShapes(final CartographyDocument<T> model, final Graphics2D g2,
|
||||
final AffineTransform at);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package gui;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @param <T> The type of elements in the document
|
||||
*/
|
||||
public class CartographyDocument<T> implements Iterable<T>
|
||||
{
|
||||
private Map<String, T> highlighted;
|
||||
private Map<String, T> elements;
|
||||
private Rectangle2D.Double bounds;
|
||||
|
||||
/**
|
||||
* @param elements The elements in the document, mapped by their id
|
||||
* @param bounds The bounds of the document
|
||||
*/
|
||||
public CartographyDocument(final Map<String, T> elements, final Rectangle2D.Double bounds)
|
||||
{
|
||||
this.elements = elements;
|
||||
this.bounds = bounds;
|
||||
this.highlighted = Map.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The bounds of the document
|
||||
*/
|
||||
public Rectangle2D.Double getBounds()
|
||||
{
|
||||
return bounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id The id of the element to get
|
||||
* @return The element with the given id, or null if no such element exists
|
||||
*/
|
||||
public T getElement(final String id)
|
||||
{
|
||||
return elements.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The iterator of highlighted elements in the document
|
||||
*/
|
||||
public Iterator<T> highlighted()
|
||||
{
|
||||
return highlighted.values().iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The iterator of all elements in the document
|
||||
*/
|
||||
public Iterator<T> iterator()
|
||||
{
|
||||
return elements.values().iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param highlighted The new highlighted elements, mapped by their id
|
||||
*/
|
||||
public void setHighlighted(final Map<String, T> highlighted)
|
||||
{
|
||||
this.highlighted = highlighted;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package gui;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import java.awt.geom.*;
|
||||
import java.util.*;
|
||||
|
||||
import math.*;
|
||||
|
||||
/**
|
||||
* A GUI component that can be extended to draw maps of various kinds.
|
||||
*
|
||||
* @param <T> The type of the data
|
||||
*
|
||||
* @author Prof. David Bernstein, James Madison University
|
||||
* @version 1.0
|
||||
*/
|
||||
public class CartographyPanel<T> extends JPanel implements MouseListener, MouseMotionListener
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected DisplayCoordinatesTransformation displayTransform;
|
||||
protected LinkedList<Rectangle2D.Double> zoomStack;
|
||||
|
||||
private CartographyDocument<T> model;
|
||||
private Cartographer<T> cartographer;
|
||||
private int[] rbMax, rbMin, rbStart, rbStop; // For the rubber-band-box
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Explicit Value Constructor.
|
||||
*
|
||||
* @param model The model to use
|
||||
* @param cartographer The cartographer to use
|
||||
*/
|
||||
public CartographyPanel(final CartographyDocument<T> model,
|
||||
final Cartographer<T> cartographer)
|
||||
{
|
||||
displayTransform = new DisplayCoordinatesTransformation();
|
||||
|
||||
this.model = model;
|
||||
zoomStack = new LinkedList<Rectangle2D.Double>();
|
||||
zoomStack.add(model.getBounds());
|
||||
|
||||
this.cartographer = cartographer;
|
||||
|
||||
rbStart = new int[2];
|
||||
rbStop = new int[2];
|
||||
rbMax = new int[2];
|
||||
rbMin = new int[2];
|
||||
|
||||
addMouseListener(this);
|
||||
addMouseMotionListener(this);
|
||||
|
||||
setDoubleBuffered(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a de-selection event (i.e., a zoom-out).
|
||||
*/
|
||||
public void handleDeselect()
|
||||
{
|
||||
if (zoomStack.size() > 1)
|
||||
{
|
||||
zoomStack.removeFirst();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a selection event (i.e., a zoom-in).
|
||||
*
|
||||
* @param min The upper-left of the selection (in screen coordinates)
|
||||
* @param max The lower-right of the selection (in screen coordinates)
|
||||
*/
|
||||
protected void handleSelect(final double[] min, final double[] max)
|
||||
{
|
||||
double scale = displayTransform.getLastTransform().getScaleX();
|
||||
double translateX = displayTransform.getLastTransform().getTranslateX();
|
||||
double translateY = displayTransform.getLastTransform().getTranslateY();
|
||||
|
||||
double x = (min[0] - translateX) / scale;
|
||||
double y = (min[1] - translateY) / scale;
|
||||
double width = (max[0] - translateX) / scale - x;
|
||||
double height = (max[1] - translateY) / scale - y;
|
||||
|
||||
Rectangle2D.Double r = new Rectangle2D.Double(x, y, width, height);
|
||||
zoomStack.addFirst((Rectangle2D.Double)
|
||||
(displayTransform.getLastReflection().createTransformedShape(r).getBounds2D()));
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the zoom stack.
|
||||
*
|
||||
* @param bounds The initial bounds
|
||||
*/
|
||||
private void initializeZoomStack(final Rectangle2D.Double bounds)
|
||||
{
|
||||
zoomStack = new LinkedList<Rectangle2D.Double>();
|
||||
zoomStack.add(bounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle mouseClicked events.
|
||||
*
|
||||
* @param evt The MouseEvent
|
||||
*/
|
||||
@Override
|
||||
public void mouseClicked(final MouseEvent evt)
|
||||
{
|
||||
if (evt.getClickCount() > 1)
|
||||
{
|
||||
handleDeselect();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle mouseDragged events.
|
||||
*
|
||||
* @param evt The MouseEvent
|
||||
*/
|
||||
@Override
|
||||
public void mouseDragged(final MouseEvent evt)
|
||||
{
|
||||
int over = evt.getX();
|
||||
int down = evt.getY();
|
||||
Graphics g = getGraphics();
|
||||
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
|
||||
|
||||
// Erase the old rubber-band-box
|
||||
g.setXORMode(getBackground());
|
||||
g.setColor(Color.green);
|
||||
g.drawRect(rbMin[0], rbMin[1], rbMax[0]-rbMin[0], rbMax[1]-rbMin[1]);
|
||||
|
||||
// Calculate the coordinates of the new rubber-band-box
|
||||
rbStop[0] = over;
|
||||
rbStop[1] = down;
|
||||
rbMin[0] = Math.min(rbStart[0],rbStop[0]);
|
||||
rbMin[1] = Math.min(rbStart[1],rbStop[1]);
|
||||
rbMax[0] = Math.max(rbStart[0],rbStop[0]);
|
||||
rbMax[1] = Math.max(rbStart[1],rbStop[1]);
|
||||
|
||||
// Draw the new rubber-band-box
|
||||
g.drawRect(rbMin[0], rbMin[1], rbMax[0]-rbMin[0], rbMax[1]-rbMin[1]);
|
||||
g.setPaintMode();
|
||||
g.dispose();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Handle mouseEntered events.
|
||||
*
|
||||
* @param evt The MouseEvent
|
||||
*/
|
||||
@Override
|
||||
public void mouseEntered(final MouseEvent evt)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle mouseExited events.
|
||||
*
|
||||
* @param evt The MouseEvent
|
||||
*/
|
||||
@Override
|
||||
public void mouseExited(final MouseEvent evt)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle mouseMoved events.
|
||||
*
|
||||
* @param evt The MouseEvent
|
||||
*/
|
||||
@Override
|
||||
public void mouseMoved(final MouseEvent evt)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle mousePressed events.
|
||||
*
|
||||
* @param evt The MouseEvent
|
||||
*/
|
||||
@Override
|
||||
public void mousePressed(final MouseEvent evt)
|
||||
{
|
||||
int over = evt.getX();
|
||||
int down = evt.getY();
|
||||
|
||||
rbStart[0] = over;
|
||||
rbStart[1] = down;
|
||||
rbStop[0] = over;
|
||||
rbStop[1] = down;
|
||||
rbMin[0] = over;
|
||||
rbMin[1] = down;
|
||||
rbMax[0] = over;
|
||||
rbMax[1] = down;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle mouseReleased events.
|
||||
*
|
||||
* @param evt The MouseEvent
|
||||
*/
|
||||
@Override
|
||||
public void mouseReleased(final MouseEvent evt)
|
||||
{
|
||||
Graphics g = getGraphics();
|
||||
Dimension d = getSize();
|
||||
g.setClip(0, 0, d.width, d.height);
|
||||
|
||||
|
||||
// Erase the old line
|
||||
g.setXORMode(getBackground());
|
||||
g.setColor(Color.green);
|
||||
g.drawRect(rbMin[0], rbMin[1], rbMax[0]-rbMin[0], rbMax[1]-rbMin[1]);
|
||||
|
||||
// Determine the selected region
|
||||
double[] min = new double[2];
|
||||
min[0] = (double)(rbMin[0]);
|
||||
min[1] = (double)(rbMin[1] - 1);
|
||||
|
||||
double[] max = new double[2];
|
||||
max[0] = (double)(rbMax[0]);
|
||||
max[1] = (double)(rbMax[1] - 1);
|
||||
|
||||
|
||||
// Reset the graphics environment
|
||||
g.setPaintMode();
|
||||
g.dispose();
|
||||
setCursor(Cursor.getDefaultCursor());
|
||||
|
||||
// Notify any children of the selection
|
||||
if ((min[0] != max[0]) || (min[1] != max[1]))
|
||||
{
|
||||
handleSelect(min, max);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Render this component.
|
||||
*
|
||||
* @param g The rendering engine to use
|
||||
*/
|
||||
@Override
|
||||
public void paint(final Graphics g)
|
||||
{
|
||||
Graphics2D g2 = (Graphics2D)g;
|
||||
Rectangle screenBounds = g2.getClipBounds();
|
||||
g2.setColor(getBackground());
|
||||
g2.fill(screenBounds);
|
||||
g2.setColor(Color.BLACK);
|
||||
|
||||
Rectangle2D.Double bounds = zoomStack.getFirst();
|
||||
|
||||
AffineTransform at = displayTransform.getTransform(screenBounds, bounds);
|
||||
|
||||
cartographer.paintShapes(model, g2, at);
|
||||
cartographer.paintHighlights(model, g2, at);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the model.
|
||||
*
|
||||
* @param model The model
|
||||
*/
|
||||
public void setModel(final CartographyDocument<T> model)
|
||||
{
|
||||
this.model = model;
|
||||
initializeZoomStack(model.getBounds());
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
package gui;
|
||||
|
||||
import dataprocessing.*;
|
||||
import feature.Street;
|
||||
import feature.StreetSegmentObserver;
|
||||
import feature.StreetSegmentSubject;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.*;
|
||||
|
||||
/**
|
||||
* A dialog box for a Geocoder.
|
||||
*
|
||||
* @author Prof. David Bernstein, James Madison University
|
||||
* @version 1.0
|
||||
*/
|
||||
public class GeocodeDialog extends JDialog implements
|
||||
ActionListener, ListSelectionListener, StreetSegmentSubject
|
||||
{
|
||||
// Constants with package visibility
|
||||
static final String CATEGORY = "Category";
|
||||
static final String GEOCODE = "Geocode";
|
||||
static final String NAME = "Name";
|
||||
static final String NUMBER = "Number";
|
||||
static final String PREFIX = "Prefix";
|
||||
static final String SUFFIX = "Suffix";
|
||||
|
||||
// Constants with private visibility
|
||||
private static final String QUOTE = "\"";
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// Attributes
|
||||
private List<String> ids;
|
||||
private List<StreetSegmentObserver> observers;
|
||||
|
||||
private Geocoder geocoder;
|
||||
private JButton geocodeButton;
|
||||
private JComboBox<String> categoryField, prefixField, suffixField;
|
||||
private JList<String> resultsArea;
|
||||
private JTextField nameField, numberField;
|
||||
|
||||
/**
|
||||
* Explicit Value Constructor.
|
||||
*
|
||||
* @param owner The owning JFrame
|
||||
* @param geocoder The Geocoder
|
||||
*/
|
||||
public GeocodeDialog(final JFrame owner, final Geocoder geocoder)
|
||||
{
|
||||
super(owner, "Geocoder", false);
|
||||
this.geocoder = geocoder;
|
||||
|
||||
observers = new ArrayList<StreetSegmentObserver>();
|
||||
|
||||
numberField = createJTextField();
|
||||
prefixField = createJComboBox(null);
|
||||
nameField = createJTextField();
|
||||
categoryField = createJComboBox(null);
|
||||
suffixField = createJComboBox(null);
|
||||
|
||||
geocodeButton = new JButton(GEOCODE);
|
||||
geocodeButton.addActionListener(this);
|
||||
|
||||
resultsArea = new JList<String>(new DefaultListModel<String>());
|
||||
resultsArea.addListSelectionListener(this);
|
||||
|
||||
String[] directions = new String[] {"E","N","NE","NW","S","SE","SW","W"};
|
||||
populate(prefixField, directions);
|
||||
populate(categoryField, new String[] {"Aly","Arc","Ave","Blvd","Br","Brg","Byp","Cir",
|
||||
"Cres","Cswy","Ct","Ctr","Cv","Dr","Expy","Fwy","Grd","Hwy","Ln","Loop","Mal","Pass",
|
||||
"Path","Pike","Pky","Pl","Plz","Ramp","Rd","Row","Rte","Rue","Run","Spur","Sq","St",
|
||||
"Ter","Tpke","Trce","Trl","Tunl","Walk","Way","Xing"});
|
||||
populate(suffixField, directions);
|
||||
|
||||
|
||||
performLayout();
|
||||
setSize(800, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle actionPerformed() messages.
|
||||
*
|
||||
* @param evt The event that generated the message.
|
||||
*/
|
||||
public void actionPerformed(final ActionEvent evt)
|
||||
{
|
||||
DefaultListModel<String> listModel = (DefaultListModel<String>)resultsArea.getModel();
|
||||
listModel.clear();
|
||||
|
||||
String prefix = prefixField.getItemAt(prefixField.getSelectedIndex());
|
||||
String name = nameField.getText();
|
||||
String category = categoryField.getItemAt(categoryField.getSelectedIndex());
|
||||
String suffix = suffixField.getItemAt(suffixField.getSelectedIndex());
|
||||
int number = 0;
|
||||
try
|
||||
{
|
||||
number = Integer.parseInt(numberField.getText());
|
||||
String canonicalName = Street.createCanonicalName(prefix, name, category, suffix);
|
||||
ids = new ArrayList<String>();
|
||||
List<double[]> locations = geocoder.geocode(canonicalName, number, ids);
|
||||
List<String> lonlat = new ArrayList<String>();
|
||||
for (double[] location: locations) lonlat.add(location[0] + "," + location[1]);
|
||||
displayResults(lonlat);
|
||||
}
|
||||
catch (NumberFormatException nfe)
|
||||
{
|
||||
JOptionPane.showMessageDialog(this, QUOTE+numberField.getText()+QUOTE
|
||||
+ " is not a valid street number!",
|
||||
"Input Error", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a StreetSegmentObserver to this subject.
|
||||
*
|
||||
* @param observer The observer
|
||||
*/
|
||||
@Override
|
||||
public void addStreetSegmentObserver(final StreetSegmentObserver observer)
|
||||
{
|
||||
observers.add(observer);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a JPanel containing a Jcomponent and a titled-border.
|
||||
*
|
||||
* @param title The title
|
||||
* @param component The JComponent
|
||||
* @return The JPanel
|
||||
*/
|
||||
private JPanel createTitledComponent(final String title, final JComponent component)
|
||||
{
|
||||
JPanel result = new JPanel();
|
||||
result.setLayout(new BorderLayout());
|
||||
result.add(component, BorderLayout.CENTER);
|
||||
result.setBorder(BorderFactory.createTitledBorder(title));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a JComboBox.
|
||||
*
|
||||
* @param items The items to include (after the blank)
|
||||
* @return The JComboBox
|
||||
*/
|
||||
private JComboBox<String> createJComboBox(final String[] items)
|
||||
{
|
||||
JComboBox<String> result = new JComboBox<String>();
|
||||
result.setEditable(false);
|
||||
populate(result, items);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JTextField.
|
||||
*
|
||||
* @return The JTextField
|
||||
*/
|
||||
private JTextField createJTextField()
|
||||
{
|
||||
JTextField result = new JTextField("", 6);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display result.
|
||||
*
|
||||
* @param results The results to display
|
||||
*/
|
||||
private void displayResults(final List<String> results)
|
||||
{
|
||||
DefaultListModel<String> model = (DefaultListModel<String>)resultsArea.getModel();
|
||||
model.clear();
|
||||
if (results != null)
|
||||
{
|
||||
for (String line: results) model.addElement(line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all of the StreetSegmentObserver objects of a
|
||||
* handleStreetSegment() message.
|
||||
*
|
||||
* @param segmendIDs The IDs of the StreetSegment objects
|
||||
*/
|
||||
private void notifyStreetSegmentObservers(final List<String> segmendIDs)
|
||||
{
|
||||
for (StreetSegmentObserver observer: observers) observer.handleStreetSegments(segmendIDs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout this Component.
|
||||
*
|
||||
*/
|
||||
private void performLayout()
|
||||
{
|
||||
JPanel contentPane = (JPanel)getContentPane();
|
||||
|
||||
contentPane.setLayout(new BorderLayout());
|
||||
|
||||
JToolBar toolBar = new JToolBar();
|
||||
toolBar.setFloatable(false);
|
||||
toolBar.addSeparator();
|
||||
toolBar.add(geocodeButton);
|
||||
contentPane.add(toolBar, BorderLayout.SOUTH);
|
||||
|
||||
Row entryPanel = new Row();
|
||||
entryPanel.add(createTitledComponent(NUMBER, numberField), 1, 0.0);
|
||||
entryPanel.add(createTitledComponent(PREFIX, prefixField), 1, 0.0);
|
||||
entryPanel.add(createTitledComponent(NAME, nameField), 3, 100.0);
|
||||
entryPanel.add(createTitledComponent(CATEGORY, categoryField), 1, 0.0);
|
||||
entryPanel.add(createTitledComponent(SUFFIX, suffixField), 1, 0.0);
|
||||
|
||||
|
||||
JPanel center = new JPanel();
|
||||
center.setLayout(new BorderLayout());
|
||||
center.add(entryPanel, BorderLayout.NORTH);
|
||||
JScrollPane sp = new JScrollPane(resultsArea);
|
||||
sp.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
|
||||
sp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
|
||||
center.add(sp, BorderLayout.CENTER);
|
||||
|
||||
contentPane.add(center, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate a JComboBox.
|
||||
*
|
||||
* @param comboBox The JComboBox to populate
|
||||
* @param items The items to populate it with (or null to reset)
|
||||
*/
|
||||
private void populate(final JComboBox<String> comboBox, final String[] items)
|
||||
{
|
||||
comboBox.removeAllItems();
|
||||
comboBox.addItem("");
|
||||
if (items != null)
|
||||
{
|
||||
Arrays.sort(items);
|
||||
for (String item: items) comboBox.addItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a StreetSegmentObserver from this subject.
|
||||
*
|
||||
* @param observer The observer
|
||||
*/
|
||||
@Override
|
||||
public void removeStreetSegmentObserver(final StreetSegmentObserver observer)
|
||||
{
|
||||
observers.remove(observer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the selection mode (e.g., ListSelectionModel.SINGLE_SELECTION) for the JList.
|
||||
*
|
||||
* @param mode The selection mode
|
||||
*/
|
||||
public void setSelectionMode(final int mode)
|
||||
{
|
||||
resultsArea.setSelectionMode(mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle valueChanged() messages.
|
||||
*
|
||||
* @param evt The event that generated the message
|
||||
*/
|
||||
@Override
|
||||
public void valueChanged(final ListSelectionEvent evt)
|
||||
{
|
||||
if (!evt.getValueIsAdjusting())
|
||||
{
|
||||
int indexes[] = resultsArea.getSelectedIndices();
|
||||
List<String> selectedIDs = new ArrayList<String>();
|
||||
for (int i=0; i<indexes.length; i++)
|
||||
{
|
||||
selectedIDs.add(ids.get(indexes[i]));
|
||||
}
|
||||
notifyStreetSegmentObservers(selectedIDs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.AffineTransform;
|
||||
|
||||
import geography.GeographicShape;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class GeographicShapeCartographer implements Cartographer<GeographicShape>
|
||||
{
|
||||
private Color color;
|
||||
|
||||
/**
|
||||
* @param color
|
||||
* The color to use when rendering the shapes.
|
||||
*/
|
||||
public GeographicShapeCartographer(final Color color)
|
||||
{
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintHighlights(final CartographyDocument<GeographicShape> model, final Graphics2D g2,
|
||||
final AffineTransform at)
|
||||
{
|
||||
g2.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 128));
|
||||
GeographicShape shape;
|
||||
while (model.highlighted().hasNext())
|
||||
{
|
||||
shape = model.highlighted().next();
|
||||
g2.fill(at.createTransformedShape(shape.getShape()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintShapes(final CartographyDocument<GeographicShape> model, final Graphics2D g2,
|
||||
final AffineTransform at)
|
||||
{
|
||||
g2.setColor(color);
|
||||
for (GeographicShape shape : model)
|
||||
{
|
||||
g2.draw(at.createTransformedShape(shape.getShape()));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package gui;
|
||||
|
||||
import java.awt.*;
|
||||
import javax.swing.*;
|
||||
|
||||
/**
|
||||
* A JPanel that is layed-out in the horizontal dimension.
|
||||
* Individual elements can be sized relative to each other and
|
||||
* their "expandability" can be specified.
|
||||
*
|
||||
* @author Prof. David Bernstein, James Madsion University
|
||||
* @version 1.0
|
||||
*/
|
||||
public class Row extends JPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private GridBagLayout layout;
|
||||
private int column;
|
||||
|
||||
|
||||
/**
|
||||
* Default Constructor.
|
||||
*/
|
||||
public Row()
|
||||
{
|
||||
super();
|
||||
column = 0;
|
||||
layout = new GridBagLayout();
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Component to this Row.
|
||||
* Note: The expandability of all Component objects normally sum to 100.
|
||||
*
|
||||
* @param component The Component to add
|
||||
* @param width The width (in cells) of this Component
|
||||
* @param expandability The percentage of extra space that this Component should use
|
||||
*/
|
||||
public void add(final Component component, final int width, final double expandability)
|
||||
{
|
||||
GridBagConstraints gbc = new GridBagConstraints();
|
||||
gbc.gridx = column;
|
||||
gbc.gridy = 0;
|
||||
gbc.gridwidth = width;
|
||||
gbc.gridheight = 1;
|
||||
if (expandability > 0.0) gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||
else gbc.fill = GridBagConstraints.NONE;
|
||||
gbc.weightx = expandability;
|
||||
layout.setConstraints(component, gbc);
|
||||
add(component);
|
||||
|
||||
column += width;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package gui;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.util.Iterator;
|
||||
|
||||
import feature.StreetSegment;
|
||||
import feature.StreetThemeLibrary;
|
||||
import geography.Theme;
|
||||
import geography.ThemeLibrary;
|
||||
|
||||
/**
|
||||
* A cartographer for rendering street segments.
|
||||
*/
|
||||
public class StreetSegmentCartographer implements Cartographer<StreetSegment>
|
||||
{
|
||||
private ThemeLibrary themeLibrary = new StreetThemeLibrary();
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public StreetSegmentCartographer()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintHighlights(final CartographyDocument<StreetSegment> model, final Graphics2D g2,
|
||||
final AffineTransform at)
|
||||
{
|
||||
StreetSegment segment;
|
||||
Iterator<StreetSegment> it = model.highlighted();
|
||||
while (it.hasNext())
|
||||
{
|
||||
segment = it.next();
|
||||
Theme theme = themeLibrary.getHighlightTheme();
|
||||
g2.setColor(theme.getColor());
|
||||
g2.setStroke(theme.getStroke());
|
||||
g2.draw(at.createTransformedShape(segment.getGeographicShape().getShape()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintShapes(final CartographyDocument<StreetSegment> model, final Graphics2D g2,
|
||||
final AffineTransform at)
|
||||
{
|
||||
for (StreetSegment segment : model)
|
||||
{
|
||||
Theme theme = themeLibrary.getTheme(segment.getCode());
|
||||
g2.setColor(theme.getColor());
|
||||
g2.setStroke(theme.getStroke());
|
||||
g2.draw(at.createTransformedShape(segment.getGeographicShape().getShape()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package math;
|
||||
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
/**
|
||||
* A view transformation that maps content coordinates to display coordinates, while maintaining the
|
||||
* aspect ratio of the content. The content is reflected across the horizontal axis to match the
|
||||
* typical display coordinate system where the y-axis increases downwards. The transformation also
|
||||
* centers the content within the display bounds.
|
||||
*/
|
||||
public class DisplayCoordinatesTransformation implements ViewTransformation
|
||||
{
|
||||
private AffineTransform lastReflection = new AffineTransform();
|
||||
private AffineTransform lastTransform = new AffineTransform();
|
||||
|
||||
@Override
|
||||
public AffineTransform getLastReflection()
|
||||
{
|
||||
return new AffineTransform(lastReflection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AffineTransform getLastTransform()
|
||||
{
|
||||
return new AffineTransform(lastTransform);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AffineTransform getTransform(final Rectangle2D displayBounds,
|
||||
final Rectangle2D contentBounds)
|
||||
{
|
||||
double scaleX = displayBounds.getWidth() / contentBounds.getWidth();
|
||||
double scaleY = displayBounds.getHeight() / contentBounds.getHeight();
|
||||
double scale = Math.min(scaleX, scaleY);
|
||||
|
||||
double scaledHeight = contentBounds.getHeight() * scale;
|
||||
|
||||
lastReflection = new AffineTransform();
|
||||
lastReflection.translate(0.0, contentBounds.getMinY() + contentBounds.getMaxY());
|
||||
lastReflection.scale(1.0, -1.0);
|
||||
|
||||
double scaledWidth = contentBounds.getWidth() * scale;
|
||||
|
||||
double offsetX = displayBounds.getMinX() + (displayBounds.getWidth() - scaledWidth) / 2.0;
|
||||
double offsetY = displayBounds.getMinY() + (displayBounds.getHeight() - scaledHeight) / 2.0;
|
||||
|
||||
double translateX = offsetX - contentBounds.getMinX() * scale;
|
||||
double translateY = offsetY - contentBounds.getMinY() * scale;
|
||||
|
||||
lastTransform = AffineTransform.getTranslateInstance(translateX, translateY);
|
||||
lastTransform.scale(scale, scale);
|
||||
|
||||
AffineTransform combined = new AffineTransform(lastTransform);
|
||||
combined.concatenate(lastReflection);
|
||||
|
||||
return combined;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package math;
|
||||
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public interface ViewTransformation
|
||||
{
|
||||
/**
|
||||
* @return The last reflection.
|
||||
*/
|
||||
public AffineTransform getLastReflection();
|
||||
|
||||
/**
|
||||
* @return The last transform.
|
||||
*/
|
||||
public AffineTransform getLastTransform();
|
||||
|
||||
/**
|
||||
* @param displayBounds
|
||||
* The bounds of the display area
|
||||
* @param contentBounds
|
||||
* The bounds of the content area
|
||||
* @return The transform that maps content coordinates to display coordinates, while maintaining
|
||||
* the aspect ratio of the content. The content is reflected across the horizontal axis to
|
||||
* match the typical display coordinate system where the y-axis increases downwards. The
|
||||
* transformation also centers the content within the display bounds.
|
||||
*/
|
||||
public AffineTransform getTransform(final Rectangle2D displayBounds,
|
||||
final Rectangle2D contentBounds);
|
||||
}
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../assets/usa48-counties.geo
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../assets/usa48-states.geo
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../assets/va-blocks.geo
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../assets/va-counties.geo
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../assets/va-streets-2024.geo
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../assets/va-streets-2024.str
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../assets/world-countries.geo
|
||||
Reference in New Issue
Block a user