package aimax.osm.gui.fx.applications;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import aima.core.agent.*;
import aima.core.environment.map.MapAgent;
import aima.core.environment.map.MapEnvironment;
import aima.core.environment.map.MapFunctionFactory;
import aima.core.environment.map.MoveToAction;
import aima.core.search.framework.Metrics;
import aima.core.search.framework.SearchForActions;
import aima.core.search.framework.evalfunc.HeuristicFunction;
import aima.core.util.CancelableThread;
import aima.core.util.math.geom.shapes.Point2D;
import aima.gui.fx.framework.IntegrableApplication;
import aima.gui.fx.framework.Parameter;
import aima.gui.fx.framework.SimulationPaneBuilder;
import aima.gui.fx.framework.SimulationPaneCtrl;
import aima.gui.fx.views.SimpleEnvironmentViewCtrl;
import aima.gui.util.SearchFactory;
import aimax.osm.data.DataResource;
import aimax.osm.data.MapWayAttFilter;
import aimax.osm.data.Position;
import aimax.osm.data.entities.MapNode;
import aimax.osm.gui.fx.viewer.MapPaneCtrl;
import aimax.osm.routing.MapAdapter;
import javafx.application.Platform;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
/**
* Simple OSM route finding agent application which can be used as base class
* for more advanced OSM agent applications. For example, by overriding some of
* the factory methods, it is easy to add parameters and change agent as well as
* environment implementations.
*
* @author Ruediger Lunde
*
*/
public class OsmAgentBaseApp extends IntegrableApplication {
public static void main(String[] args) {
launch(args);
}
public static String PARAM_WAY_SELECTION = "waySelection";
public static String PARAM_SEARCH = "search";
public static String PARAM_Q_SEARCH_IMPL = "qsearch";
public static String PARAM_HEURISTIC = "heuristic";
public static String TRACK_NAME = "Track";
protected MapPaneCtrl mapPaneCtrl;
protected SimpleEnvironmentViewCtrl envViewCtrl;
protected SimulationPaneCtrl simPaneCtrl;
protected MapAdapter map;
protected MapEnvironment env;
@Override
public String getTitle() {
return "OSM Agent Base App";
}
/** Loads a map of the city of Ulm, Germany. Override to change the map. */
protected void loadMap() {
mapPaneCtrl.loadMap(DataResource.getULMFileResource());
}
/** Defines the parameters to be shown in the simulation pane tool bar. */
protected List<Parameter> createParameters() {
Parameter p1 = new Parameter(PARAM_WAY_SELECTION, "Use any way", "Travel by car", "Travel by bicycle");
Parameter p2 = new Parameter(PARAM_SEARCH, (Object[]) SearchFactory.getInstance().getSearchStrategyNames());
p2.setDefaultValueIndex(5);
Parameter p3 = new Parameter(PARAM_Q_SEARCH_IMPL, (Object[]) SearchFactory.getInstance().getQSearchImplNames());
p3.setDefaultValueIndex(1);
p3.setDependency(PARAM_SEARCH, "Depth First", "Breadth First", "Uniform Cost", "Greedy Best First", "A*");
Parameter p4 = new Parameter(PARAM_HEURISTIC, "0", "SLD");
p4.setDefaultValueIndex(1);
p4.setDependency(PARAM_SEARCH, "Greedy Best First", "A*", "Recursive Best First",
"Recursive Best First No Loops", "Hill Climbing");
return Arrays.asList(p1, p2, p3, p4);
}
/**
* Factory method which creates a search strategy based on the current
* parameter settings.
*/
protected SearchForActions createSearch(List<String> locations) {
HeuristicFunction heuristic;
switch (simPaneCtrl.getParamValueIndex(PARAM_HEURISTIC)) {
case 0:
heuristic = MapFunctionFactory.getZeroHeuristicFunction();
break;
default:
heuristic = MapFunctionFactory.getSLDHeuristicFunction(locations.get(1), map);
}
return SearchFactory.getInstance().createSearch(simPaneCtrl.getParamValueIndex(PARAM_SEARCH),
simPaneCtrl.getParamValueIndex(PARAM_Q_SEARCH_IMPL), heuristic);
}
/**
* Factory method which creates a new agent based on the current parameter
* settings.
*/
protected Agent createAgent(SearchForActions search, List<String> locations) {
return new MapAgent(map, search, locations.get(1), envViewCtrl::notify);
}
/**
* Factory method which creates a new environment based on the current
* parameter settings.
*/
protected MapEnvironment createEnvironment() {
return new MapEnvironment(map);
}
/**
* Defines state view, parameters, and call-back functions and calls the
* simulation pane builder to create layout and controller objects.
*/
@Override
public Pane createRootPane() {
BorderPane root = new BorderPane();
List<Parameter> params = createParameters();
StackPane mapPane = new StackPane();
mapPaneCtrl = new MapPaneCtrl(mapPane);
loadMap();
StackPane envView = new StackPane();
envViewCtrl = new SimpleEnvironmentViewCtrl(envView, mapPane, 0.75);
SimulationPaneBuilder builder = new SimulationPaneBuilder();
builder.defineParameters(params);
builder.defineStateView(envView);
builder.defineInitMethod(this::initialize);
builder.defineSimMethod(this::simulate);
simPaneCtrl = builder.getResultFor(root);
simPaneCtrl.setParam(SimulationPaneCtrl.PARAM_SIM_SPEED, 0);
return root;
}
/**
* Is called after each parameter selection change. This implementation
* prepares the map for different kinds of vehicles and clears the currently
* displayed track.
*/
@Override
public void initialize() {
map = new MapAdapter(mapPaneCtrl.getMap());
switch (simPaneCtrl.getParamValueIndex(PARAM_WAY_SELECTION)) {
case 0:
map.setMapWayFilter(MapWayAttFilter.createAnyWayFilter());
map.ignoreOneways(true);
break;
case 1:
map.setMapWayFilter(MapWayAttFilter.createCarWayFilter());
map.ignoreOneways(false);
break;
case 2:
map.setMapWayFilter(MapWayAttFilter.createBicycleWayFilter());
map.ignoreOneways(false);
break;
}
map.getOsmMap().clearTrack(TRACK_NAME);
}
/** Starts the experiment. */
public void simulate() {
List<MapNode> markers = map.getOsmMap().getMarkers();
if (markers.size() < 2) {
simPaneCtrl.setStatus("Error: Please set at least two markers with mouse-left.");
} else {
List<String> locations = new ArrayList<>(markers.size());
for (MapNode node : markers) {
Point2D pt = new Point2D(node.getLon(), node.getLat());
locations.add(map.getNearestLocation(pt));
}
SearchForActions search = createSearch(locations);
Agent agent = createAgent(search, locations);
env = createEnvironment();
env.addEnvironmentView(new TrackUpdater());
env.addAgent(agent, locations.get(0));
if (simPaneCtrl.getParam(PARAM_SEARCH).isPresent())
env.notifyViews("Using " + simPaneCtrl.getParamValue(PARAM_SEARCH));
while (!env.isDone() && !CancelableThread.currIsCanceled()) {
env.step();
simPaneCtrl.waitAfterStep();
}
envViewCtrl.notify("");
// simPaneCtrl.setStatus(search.getMetrics().toString());
}
}
@Override
public void cleanup() {
simPaneCtrl.cancelSimulation();
}
/** Visualizes agent positions. Call from simulation thread. */
private void updateTrack(Agent agent, Metrics metrics) {
MapAdapter map = (MapAdapter) env.getMap();
MapNode node = map.getWayNode(env.getAgentLocation(agent));
if (node != null) {
Platform.runLater(() -> map.getOsmMap().addToTrack(TRACK_NAME, new Position(node.getLat(), node.getLon())));
}
simPaneCtrl.setStatus(metrics.toString());
}
// helper classes...
private class TrackUpdater implements EnvironmentView {
int actionCounter = 0;
@Override
public void notify(String msg) {
envViewCtrl.notify(msg);
}
@Override
public void agentAdded(Agent agent, Environment source) {
updateTrack(agent, new Metrics());
}
/**
* Reacts on environment changes and updates the tracks.
*/
@Override
public void agentActed(Agent agent, Percept percept, Action command, Environment source) {
if (command instanceof MoveToAction) {
Metrics metrics = new Metrics();
Double travelDistance = env.getAgentTravelDistance(env.getAgents().get(0));
if (travelDistance != null)
metrics.set("travelDistance[km]", travelDistance);
metrics.set("actions", ++actionCounter);
updateTrack(agent, metrics);
}
}
}
}