Skip to content

Commit

Permalink
Add options for routing types
Browse files Browse the repository at this point in the history
Signed-off-by: Taylor Smock <[email protected]>
  • Loading branch information
tsmock committed Dec 16, 2024
1 parent 75ec6af commit a0768ba
Show file tree
Hide file tree
Showing 10 changed files with 272 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.routing2;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.UncheckedIOException;
import java.util.Base64;
import java.util.Objects;

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import org.openstreetmap.josm.spi.preferences.Config;
import org.openstreetmap.josm.tools.Logging;

/**
* A class for reading/writing preferences
*/
public final class PreferenceHelper {
private PreferenceHelper() {
// Hide the constructor
}

/**
* Read a record from preferences
* @param preference The preference to read the record from
* @param def The default value
* @return The record from preferences, or the default
* @param <R> The record type
*/
@Nonnull
public static <R extends Record> R readRecord(@Nonnull String preference, @Nonnull R def) {
Objects.requireNonNull(def, "default value can not be null");
Objects.requireNonNull(preference, "preference can not be null");
final String pref = Config.getPref().get(preference);
if (pref != null && !pref.isEmpty()) {
try (ByteArrayInputStream bais = new ByteArrayInputStream(Base64.getDecoder().decode(pref));
ObjectInputStream ois = new ObjectInputStream(bais)) {
Object read = ois.readObject();
if (def.getClass().isInstance(read)) {
return (R) def.getClass().cast(read);
}
} catch (IOException e) {
// Should never happen with ByteArrayInputStream
throw new UncheckedIOException(e);
} catch (ClassNotFoundException e) {
Logging.error(e);
Config.getPref().put(preference, null); // Reset the preference, just in case.
}
}
return def;
}

/**
* Write a record to preferences
* @param preference The preference to write to
* @param rec The record to write
* @param <R> The record type
*/
public static <R extends Record> void writeRecord(@Nonnull String preference, @Nullable R rec) {
Objects.requireNonNull(preference, "preference can not be null");
if (rec == null) {
Config.getPref().put(preference, null);
} else {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(rec);
oos.flush();
Config.getPref().put(preference, Base64.getEncoder().encodeToString(baos.toByteArray()));
} catch (IOException e) {
// This shouldn't happen with a ByteArrayOutputStream
throw new UncheckedIOException(e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import org.openstreetmap.josm.gui.MainApplication;
import org.openstreetmap.josm.gui.MapFrame;
import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
import org.openstreetmap.josm.plugins.Plugin;
import org.openstreetmap.josm.plugins.PluginInformation;
import org.openstreetmap.josm.tools.Destroyable;
Expand Down Expand Up @@ -41,6 +42,11 @@ public void destroy() {
}
}

@Override
public PreferenceSetting getPreferenceSetting() {
return new RoutingPreferences();
}

/**
* Get the information for the plugin
* @return The plugin information
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Objects;
import java.util.function.Consumer;
Expand Down Expand Up @@ -48,7 +49,7 @@ public class RoutingDialog extends ToggleDialog {
public RoutingDialog() {
super(tr("Routing"), "routing", tr("Generate routes between points"), Shortcut
.registerShortcut("routing:dialog", tr("Routing Dialog"), KeyEvent.CHAR_UNDEFINED, Shortcut.NONE), 200,
false, null, false);
false, RoutingPreferences.class, false);
build();
}

Expand All @@ -63,6 +64,8 @@ private void build() {
false, false) {
@Override
public void actionPerformed(ActionEvent e) {
new ArrayList<>(MainApplication.getLayerManager().getLayersOfType(RoutingLayer.class))
.forEach(MainApplication.getLayerManager()::removeLayer);
final RoutingLayer layer = new RoutingLayer("Route", LatLonParser.parse(start.getText()),
LatLonParser.parse(end.getText()));
layer.addTripListener(instructions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.openstreetmap.josm.plugins.routing2.lib.generic.Maneuver;
import org.openstreetmap.josm.plugins.routing2.lib.generic.SetupException;
import org.openstreetmap.josm.plugins.routing2.lib.generic.Trip;
import org.openstreetmap.josm.plugins.routing2.lib.valhalla.ValhallaConfig;
import org.openstreetmap.josm.plugins.routing2.lib.valhalla.ValhallaServer;
import org.openstreetmap.josm.tools.JosmRuntimeException;
import org.openstreetmap.josm.tools.ListenerList;
Expand Down Expand Up @@ -242,6 +243,8 @@ public void commandChanged(int queueSize, int redoSize) {
}
if (!monitor.isCanceled()) {
this.setTrip(valhallaServer.generateRoute(MainApplication.getLayerManager().getActiveDataLayer(),
valhallaServer.generateDefaultTripConfig(
PreferenceHelper.readRecord("routing2.valhalla.config", ValhallaConfig.DEFAULT)),
this.start, this.end));
}
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.routing2;

import static org.openstreetmap.josm.tools.I18n.tr;

import java.util.EnumSet;

import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;

import org.openstreetmap.josm.gui.preferences.ExtensibleTabPreferenceSetting;
import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
import org.openstreetmap.josm.gui.widgets.JosmComboBox;
import org.openstreetmap.josm.gui.widgets.JosmComboBoxModel;
import org.openstreetmap.josm.plugins.routing2.lib.valhalla.TransportationType;
import org.openstreetmap.josm.plugins.routing2.lib.valhalla.ValhallaConfig;
import org.openstreetmap.josm.tools.ImageProvider;

/**
* Preferences for the routing engine(s)
*/
public class RoutingPreferences extends ExtensibleTabPreferenceSetting {
private ValhallaConfig valhallaConfig;

RoutingPreferences() {
super("dialogs/routing", tr("Routing2"), tr("Various settings that influence the routing plugin."), false);
this.valhallaConfig = PreferenceHelper.readRecord("routing2.valhalla.config", ValhallaConfig.DEFAULT);
}

@Override
public boolean ok() {
PreferenceHelper.writeRecord("routing2.valhalla.config",
ValhallaConfig.DEFAULT.equals(valhallaConfig) ? null : valhallaConfig);
return false;
}

@Override
public ImageIcon getIcon(ImageProvider.ImageSizes size) {
return super.getIcon(size);
}

@Override
public void addGui(PreferenceTabbedPane gui) {
final JTabbedPane tabbedPane = getTabPane();
tabbedPane.add(tr("Valhalla"), buildValhallaPane());
super.addGui(gui);
}

private JPanel buildValhallaPane() {
final JPanel jPanel = new JPanel();
final JosmComboBoxModel<TransportationType> model = new JosmComboBoxModel<>();
final JosmComboBox<TransportationType> valhalla = new JosmComboBox<>(model);
model.addAllElements(EnumSet.allOf(TransportationType.class));
valhalla.setSelectedItem(this.valhallaConfig.transportationType());
jPanel.add(new JLabel(tr("Transportation type")));
jPanel.add(valhalla);
valhalla.addItemListener(e -> {
if (e.getItem()instanceof TransportationType type
&& !type.equals(this.valhallaConfig.transportationType())) {
this.valhallaConfig = new ValhallaConfig(type, this.valhallaConfig.systemOfMeasurement(),
this.valhallaConfig.alternates());
}
});
return jPanel;
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.routing2.lib.generic;

import jakarta.json.JsonObject;
import org.openstreetmap.josm.data.coor.ILatLon;
import org.openstreetmap.josm.gui.layer.OsmDataLayer;
import org.openstreetmap.josm.gui.progress.ProgressMonitor;

/**
* An interface for communicating with routers
* @param <C> The class holding the routing config
*/
public interface IRouter {
public interface IRouter<C extends Record> {
/**
* Check if setup should be performed
* @return {@code true} if {@link #performSetup(ProgressMonitor)} should be called
Expand All @@ -25,8 +27,16 @@ public interface IRouter {
/**
* Generate a route
* @param layer The layer to do routing on
* @param tripConfig The configuration for the trip
* @param locations The locations (at least two locations must be specified; the start and end points)
* @throws TripException when trip calculations fail
*/
Trip generateRoute(OsmDataLayer layer, ILatLon... locations) throws TripException;
Trip generateRoute(OsmDataLayer layer, JsonObject tripConfig, ILatLon... locations) throws TripException;

/**
* Generate a default configuration for the given transportation type
* @param config The current configuration for the router
* @return The default trip config
*/
JsonObject generateDefaultTripConfig(C config);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.routing2.lib.valhalla;

/**
* The transportation type
*/
public enum TransportationType {
/** Default */
AUTO,
/** Bicycle */
BICYCLE,
/** Bus */
BUS,
/** Bike share */
BIKE_SHARE,
/** Truck */
TRUCK,
/** Taxi */
TAXI,
/** Scooter */
MOTOR_SCOOTER,
/** Motorcycle */
MOTORCYCLE,
/** Multimodel (e.g. ped + public transit) */
MULTIMODEL,
/** Pedestrian */
PEDESTRIAN,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.routing2.lib.valhalla;

import java.io.Serializable;

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import org.openstreetmap.josm.data.SystemOfMeasurement;

/**
* A configuration record for valhalla
* @param transportationType The transportation type to use
* @param systemOfMeasurement The system of measurement to use, or the default for the download area
* @param alternates The number of alternate routes to generate
*/
public record ValhallaConfig(@Nonnull TransportationType transportationType, @Nullable SystemOfMeasurement systemOfMeasurement, int alternates) implements Serializable {
/** The default configuration */
public static ValhallaConfig DEFAULT = new ValhallaConfig(TransportationType.AUTO, null, 3);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ForkJoinPool;
Expand All @@ -29,6 +30,7 @@
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.io.file.Counters;
import org.apache.commons.io.file.DeletingPathVisitor;
import org.openstreetmap.josm.data.SystemOfMeasurement;
import org.openstreetmap.josm.data.coor.ILatLon;
import org.openstreetmap.josm.gui.Notification;
import org.openstreetmap.josm.gui.layer.OsmDataLayer;
Expand Down Expand Up @@ -60,7 +62,7 @@
/**
* Make calls to a local install of Valhalla
*/
public final class ValhallaServer implements IRouter {
public final class ValhallaServer implements IRouter<ValhallaConfig> {
private static final String valhallaVersion = "3.5.1";

@Override
Expand Down Expand Up @@ -150,7 +152,38 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx
}

@Override
public Trip generateRoute(OsmDataLayer layer, ILatLon... locations) {
public JsonObject generateDefaultTripConfig(ValhallaConfig config) {
JsonObjectBuilder builder = Json.createObjectBuilder();
builder.add("costing", switch (config.transportationType()) {
case AUTO -> "auto";
case BUS -> "bus";
case TAXI -> "taxi";
case TRUCK -> "truck";
case BICYCLE -> "bicycle";
case MOTORCYCLE -> "motorcycle";
case MULTIMODEL -> "multimodal";
case PEDESTRIAN -> "pedestrian";
case BIKE_SHARE -> "bikeshare";
case MOTOR_SCOOTER -> "motor_scooter";
});
final SystemOfMeasurement som;
if (config.systemOfMeasurement() != null) {
som = config.systemOfMeasurement();
} else {
som = SystemOfMeasurement.getSystemOfMeasurement();
}
if ("mph".equals(som.speedName)) {
builder.add("directions_options", Json.createObjectBuilder().add("units", "miles")
.add("language", Locale.getDefault().getLanguage()).add("alternates", config.alternates()));
} else {// else default to km
builder.add("directions_options", Json.createObjectBuilder()
.add("language", Locale.getDefault().getLanguage()).add("alternates", config.alternates()));
}
return builder.build();
}

@Override
public Trip generateRoute(OsmDataLayer layer, JsonObject tripConfig, ILatLon... locations) {
final Path config = generateConfig();
final Path dataPath = writeDataSet(layer);
try {
Expand All @@ -168,8 +201,7 @@ public Trip generateRoute(OsmDataLayer layer, ILatLon... locations) {
generateAdmins(config, dataPath);
generateTiles(config, dataPath);
generateExtract(config);
JsonObjectBuilder builder = Json.createObjectBuilder();
builder.add("costing", "auto").add("directions_options", Json.createObjectBuilder().add("units", "miles"));
JsonObjectBuilder builder = Json.createObjectBuilder(tripConfig);
JsonArrayBuilder locationsArray = Json.createArrayBuilder();
for (ILatLon location : locations) {
locationsArray.add(Json.createObjectBuilder().add("lat", location.lat()).add("lon", location.lon()));
Expand Down
18 changes: 18 additions & 0 deletions src/main/resources/images/dialogs/routing.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit a0768ba

Please sign in to comment.