First commit, added all projects

This commit is contained in:
2026-05-31 14:39:57 -04:00
commit d2930f7af5
247 changed files with 22376354 additions and 0 deletions
+264
View File
@@ -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);
}
}
}
}
+26
View File
@@ -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());
}
}
+95
View File
@@ -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();
// }
// }
}
+142
View File
@@ -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);
}
}
+28
View File
@@ -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();
}
+22
View File
@@ -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();
}
+55
View File
@@ -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;
}
}
+141
View File
@@ -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();
}
}
+95
View File
@@ -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);
}
+24
View File
@@ -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);
}
+81
View File
@@ -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;
}
}
+103
View File
@@ -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))};
}
}
+19
View File
@@ -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);
}
}
+55
View File
@@ -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);
}
}
+34
View File
@@ -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};
}
}
+47
View File
@@ -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;
}
}
+23
View File
@@ -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);
}
+68
View File
@@ -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;
}
}
+85
View File
@@ -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;
}
}
+14
View File
@@ -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();
}
+97
View File
@@ -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);
}
}
+24
View File
@@ -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);
}
+69
View File
@@ -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);
}
}
+97
View File
@@ -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);
}
}
+112
View File
@@ -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);
}
}
+110
View File
@@ -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();
}
}
}
+63
View File
@@ -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();
}
}
}
+17
View File
@@ -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);
}
+34
View File
@@ -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);
}
+97
View File
@@ -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;
}
}
+264
View File
@@ -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
}
}
+202
View File
@@ -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);
}
}
}
+26
View File
@@ -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);
}
+67
View File
@@ -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;
}
}
+288
View File
@@ -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();
}
}
+292
View File
@@ -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()));
}
}
}
+56
View File
@@ -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;
}
}
+33
View File
@@ -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);
}