package aimax.osm.applications;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import aima.core.util.CancelableThread;
import aimax.osm.data.DataResource;
import aimax.osm.data.EntityClassifier;
import aimax.osm.data.OsmMap;
import aimax.osm.data.Position;
import aimax.osm.data.entities.EntityViewInfo;
import aimax.osm.data.entities.MapNode;
import aimax.osm.data.entities.Track;
import aimax.osm.data.impl.DefaultMap;
import aimax.osm.gps.GpsFix;
import aimax.osm.gps.GpsLocator;
import aimax.osm.gps.GpsPositionListener;
import aimax.osm.gps.NmeaReader.MessageToFileListener;
import aimax.osm.routing.RouteCalculator;
import aimax.osm.viewer.AbstractEntityRenderer;
import aimax.osm.viewer.MapStyleFactory;
import aimax.osm.viewer.MapViewFrame;
/**
* Configurable application which shows a map and location information from a
* GPS connection, and additionally provides routing functionality.
* <p>
* This implementation provides a simple reflection-based mechanism which allows
* to replace the standard implementations of all fundamental components by
* other implementations. For example, to replace the route calculator just
* create a class <code>x.y.ZRouteCalculator</code> as subclass of
* <code>RouteCalculator</code> and add in the main method the line
* <code>System.setProperty(MiniNaviApp.MAP_CLASS_PROPERTY, "x.y.ZRouteCalculator</code>
* . Analogously, renderer, entity classifier, and even the map representation
* itself can be replaced. Note, that system properties can also be set by
* VM argument (-Dpropertyname=value).
* </p>
* <p>
* To enable the GPS interface, download the rs232 serial port library from
* http://www.rxtx.org, install it correctly, and rename file
* <code>NmeaSerialPortReader.txt</code> in package <code>aimax.osm.gps</code>
* to <code>NmeaSerialPortReader.java</code>. One possible choice for
* installation is, to add the jar-file to the class path (project properties,
* add jar), to store the DDL locally (e.g. in project-root/lib), and start the
* application with VM argument <code>-Djava.library.path=lib -Xmx1500M</code>.
* </p>
*
* @author Ruediger Lunde
*/
public class MiniNaviApp implements ActionListener {
protected static Logger LOG = Logger.getLogger("aimax.osm");
public final static String MAP_CLASS_PROPERTY = "aimax.osm.navi.mapclass";
public final static String CLASSIFIER_CLASS_PROPERTY = "aimax.osm.navi.classifierclass";
public final static String RENDERER_CLASS_PROPERTY = "aimax.osm.navi.rendererclass";
public final static String ROUTECALCULATOR_CLASS_PROPERTY = "aimax.osm.navi.routecalculatorclass";
public final static String ROUTE_TRACK_NAME = "Route";
public final static String GPS_TRACK_NAME = "GPS";
protected MapViewFrame frame;
protected GpsLocator locator;
protected MessageToFileListener nmeaLogger;
protected RouteCalculator routeCalculator;
protected RoutingThread routingThread;
protected JComboBox gpsCombo;
protected JFileChooser gpsFileChooser;
protected JComboBox waySelection;
protected JButton calcButton;
public MiniNaviApp(String[] args) {
initFrame(args);
locator = new GpsLocator();
locator.addGpsPositionListener(new MyGpsPositionListener());
routeCalculator = (RouteCalculator) createComponent(
ROUTECALCULATOR_CLASS_PROPERTY, RouteCalculator.class);
if (routeCalculator == null)
routeCalculator = new RouteCalculator();
JToolBar toolbar = frame.getToolbar();
gpsCombo = new JComboBox(new String[] { "GPS Off", "GPS On",
"GPS Center", "GPS Cen+Log", "Read Log" });
gpsCombo.addActionListener(this);
toolbar.addSeparator();
toolbar.add(gpsCombo);
waySelection = new JComboBox(routeCalculator.getWaySelectionOptions());
toolbar.add(waySelection);
toolbar.addSeparator();
calcButton = new JButton("Calculate Route");
toolbar.add(calcButton);
calcButton.addActionListener(this);
}
protected void initFrame(String[] args) {
frame = new ConfigurableMapViewFrame(args);
frame.setTitle("MiniNavi");
}
public MapViewFrame getFrame() {
return frame;
}
public OsmMap getMap() {
return frame.getMap();
}
public void showFrame() {
frame.setSize(800, 600);
frame.setVisible(true);
}
@Override
public void actionPerformed(ActionEvent event) {
if (event.getSource() == gpsCombo) {
getMap().clearTrack(GPS_TRACK_NAME);
try {
if (gpsFileChooser == null)
gpsFileChooser = new JFileChooser();
if (nmeaLogger != null) {
nmeaLogger.closeFile();
locator.getNmeaReader().removeListener(nmeaLogger);
nmeaLogger = null;
}
if (gpsCombo.getSelectedIndex() < 1) { // GPS Off
locator.closeConnection();
} else if (gpsCombo.getSelectedIndex() < 4) { // GPS On
locator.openSerialPortConnection();
if (locator.isConnected()) {
if (gpsCombo.getSelectedIndex() == 3
&& gpsFileChooser.showSaveDialog(frame) == JFileChooser.APPROVE_OPTION) {
nmeaLogger = new MessageToFileListener();
locator.getNmeaReader().addListener(nmeaLogger);
nmeaLogger.openFile(gpsFileChooser
.getSelectedFile());
}
} else { // Establishing connection to GPS failed
gpsCombo.setSelectedIndex(0);
}
} else if (gpsFileChooser.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION) { // simulate
// GPS
locator
.openFileConnection(gpsFileChooser
.getSelectedFile());
}
} catch (Exception e) {
e.printStackTrace();
}
} else if (event.getSource() == calcButton) {
if (routingThread != null) {
routingThread.cancel();
} else {
List<MapNode> markers = getMap().getMarkers();
if (!markers.isEmpty()) {
List<MapNode> routeMarkers = new ArrayList<MapNode>();
Track gpsTrack = getMap().getTrack(GPS_TRACK_NAME);
if (gpsTrack != null) {
routeMarkers.add(gpsTrack.getLastNode());
routeMarkers.add(markers.get(0));
} else {
routeMarkers.addAll(markers);
}
routingThread = new RoutingThread(routeMarkers);
updateEnabledState();
routingThread.start();
}
}
}
}
protected void updateEnabledState() {
frame.getLoadButton().setEnabled(routingThread == null);
frame.getSaveButton().setEnabled(routingThread == null);
calcButton.setText(routingThread == null ? "Calculate Route"
: "Cancel Calculation");
}
protected static Object createComponent(String property, Class<?> oclass) {
Object result = null;
String className = System.getProperty(property);
if (className != null) {
try {
Class<?> c = Class.forName(className);
result = c.newInstance();
if (!oclass.isInstance(result)) {
LOG.warning("Component instantiation failed. Class "
+ className + " is not of type "
+ oclass.getCanonicalName() + ".");
}
} catch (Exception e) {
LOG.warning("Component instantiation failed. Class "
+ className + " could not be loaded correctly.");
}
}
return result;
}
// ///////////////////////////////////////////////////////////////
// inner classes
class RoutingThread extends CancelableThread {
List<MapNode> routeMarkers;
List<Position> positions;
public RoutingThread(List<MapNode> routeMarkers) {
this.routeMarkers = routeMarkers;
}
@Override
public void interrupt() {
cancel();
super.interrupt();
}
@Override
public void run() {
try {
positions = routeCalculator.calculateRoute(routeMarkers, frame
.getMap(), waySelection.getSelectedIndex());
} catch (Exception e) {
e.printStackTrace(); // for debugging
}
try {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame.getMap().createTrack(ROUTE_TRACK_NAME, positions);
routingThread = null;
updateEnabledState();
}
});
} catch (Exception e) {
e.printStackTrace(); // for debugging
}
}
}
class MyGpsPositionListener implements GpsPositionListener {
@Override
public void positionUpdated(GpsFix pos) {
if (pos.isPosOk()) {
OsmMap mapData = frame.getMap();
Track track = mapData.getTrack(GPS_TRACK_NAME);
MapNode node = null;
if (track != null)
node = track.getLastNode();
if (node == null || pos.getDistKM(node) > 0.01) {
mapData.addToTrack(GPS_TRACK_NAME, pos);
if (gpsCombo.getSelectedIndex() == 2
|| gpsCombo.getSelectedIndex() == 3)
frame.getView().adjustToCenter(pos.getLat(),
pos.getLon());
}
}
}
}
static class ConfigurableMapViewFrame extends MapViewFrame {
private static final long serialVersionUID = 1L;
ConfigurableMapViewFrame(String[] args) {
super(args);
}
@SuppressWarnings("unchecked")
@Override
protected void initMapAndClassifier() {
OsmMap map = (OsmMap) createComponent(MAP_CLASS_PROPERTY,
OsmMap.class);
if (map == null)
map = new DefaultMap();
view.setMap(map);
classifier = (EntityClassifier<EntityViewInfo>) createComponent(
CLASSIFIER_CLASS_PROPERTY, EntityClassifier.class);
if (classifier == null)
classifier = new MapStyleFactory().createDefaultClassifier();
AbstractEntityRenderer renderer = (AbstractEntityRenderer) createComponent(
RENDERER_CLASS_PROPERTY, AbstractEntityRenderer.class);
if (renderer != null)
view.setRenderer(renderer);
}
}
// ///////////////////////////////////////////////////////////////
// application starter
/**
* Start application with VM arg <code>-Djava.library.path=lib</code> and
* program arg <code>-screenwidth=xx</code> (with xx the width in cm) or
* <code>-screensize=yy</code> (with yy measured diagonally in inch).
*/
public static void main(String[] args) {
// indicates progress when reading large maps (for testing only)
Logger.getLogger("aimax.osm").setLevel(Level.FINEST);
Logger.getLogger("").getHandlers()[0].setLevel(Level.FINE);
Locale.setDefault(Locale.US);
// System.setProperty(MiniNaviApp.MAP_CLASS_PROPERTY,
// "rl.osm.data.perst.PerstMap");
MiniNaviApp demo = new MiniNaviApp(args);
demo.getFrame().readMap(DataResource.getULMFileResource());
// demo.getFrame().readMap(new File("maps/ulm.osm"));
demo.showFrame();
}
}