From 023ec80bb9ccedc98b6bdc79c84e78c7afe71a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Sch=C3=A4fer?= Date: Mon, 24 Aug 2020 10:08:19 +0200 Subject: [PATCH] Start creating the routing helper rewrite, at the moment just a bit of UI that doesn't do much yet --- gradle.properties | 2 +- .../routinghelper/BusTransportMode.java | 44 +++++++++ .../actions/routinghelper/ITransportMode.java | 43 ++++++++ .../routinghelper/RoutingHelperAction.java | 78 +++++++++++++++ .../routinghelper/RoutingHelperPanel.java | 97 +++++++++++++++++++ .../routinghelper/WayTraversalDirection.java | 48 +++++++++ .../pt_assistant/utils/BoundsUtils.java | 10 ++ 7 files changed, 321 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/openstreetmap/josm/plugins/pt_assistant/actions/routinghelper/BusTransportMode.java create mode 100644 src/main/java/org/openstreetmap/josm/plugins/pt_assistant/actions/routinghelper/ITransportMode.java create mode 100644 src/main/java/org/openstreetmap/josm/plugins/pt_assistant/actions/routinghelper/RoutingHelperAction.java create mode 100644 src/main/java/org/openstreetmap/josm/plugins/pt_assistant/actions/routinghelper/RoutingHelperPanel.java create mode 100644 src/main/java/org/openstreetmap/josm/plugins/pt_assistant/actions/routinghelper/WayTraversalDirection.java diff --git a/gradle.properties b/gradle.properties index 914aa89a..98c7099d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,6 +10,6 @@ plugin.canloadatruntime=true plugin.icon=images/bus.svg # Minimum JOSM version (any numeric version) -plugin.main.version=15238 +plugin.main.version=15418 # JOSM version to compile against (any numeric version available for download, or the special values "tested" or "latest") plugin.compile.version=17084 diff --git a/src/main/java/org/openstreetmap/josm/plugins/pt_assistant/actions/routinghelper/BusTransportMode.java b/src/main/java/org/openstreetmap/josm/plugins/pt_assistant/actions/routinghelper/BusTransportMode.java new file mode 100644 index 00000000..003d17ef --- /dev/null +++ b/src/main/java/org/openstreetmap/josm/plugins/pt_assistant/actions/routinghelper/BusTransportMode.java @@ -0,0 +1,44 @@ +package org.openstreetmap.josm.plugins.pt_assistant.actions.routinghelper; + +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import com.drew.lang.annotations.NotNull; +import org.openstreetmap.josm.data.osm.IRelation; +import org.openstreetmap.josm.data.osm.IWay; +import org.openstreetmap.josm.data.osm.Node; +import org.openstreetmap.josm.data.osm.OsmPrimitiveType; +import org.openstreetmap.josm.data.osm.Relation; +import org.openstreetmap.josm.data.osm.Way; + +public class BusTransportMode implements ITransportMode { + @Override + public boolean canTraverseWay(@NotNull final IWay way, @NotNull final WayTraversalDirection direction) { + final String onewayValue = way.get("oneway"); + return way.hasTag("highway", "primary", "secondary", "tertiary", "residential") && ( + onewayValue == null || "no".equals(way.get("oneway:bus")) || + ("yes".equals(onewayValue) && direction == WayTraversalDirection.FORWARD) || + ("-1".equals(onewayValue) && direction == WayTraversalDirection.BACKWARD) + ); + } + + @Override + public boolean canBeUsedForRelation(@NotNull final IRelation relation) { + return relation.hasTag("route", "bus"); + } + + @Override + public boolean canTurn(@NotNull final Way from, @NotNull final Node via, @NotNull final Way to) { + final Set restrictionRelations = from.getReferrers().stream() + .map(it -> it.getType() == OsmPrimitiveType.RELATION ? (Relation) it : null) + .filter(Objects::nonNull) + .filter(it -> "restriction".equals(it.get("type"))) + .filter(it -> it.findRelationMembers("from").contains(from)) + .filter(it -> it.findRelationMembers("via").contains(via)) + .filter(it -> it.findRelationMembers("to").contains(to)) + .collect(Collectors.toSet()); + // TODO: Use the `restrictionRelations` to figure out the turning restrictions that apply + return from.containsNode(via) && to.containsNode(via); + } +} diff --git a/src/main/java/org/openstreetmap/josm/plugins/pt_assistant/actions/routinghelper/ITransportMode.java b/src/main/java/org/openstreetmap/josm/plugins/pt_assistant/actions/routinghelper/ITransportMode.java new file mode 100644 index 00000000..3f6f9b15 --- /dev/null +++ b/src/main/java/org/openstreetmap/josm/plugins/pt_assistant/actions/routinghelper/ITransportMode.java @@ -0,0 +1,43 @@ +package org.openstreetmap.josm.plugins.pt_assistant.actions.routinghelper; + +import com.drew.lang.annotations.NotNull; +import org.openstreetmap.josm.data.osm.IRelation; +import org.openstreetmap.josm.data.osm.IWay; +import org.openstreetmap.josm.data.osm.Node; +import org.openstreetmap.josm.data.osm.Way; + +public interface ITransportMode { + /** + * Just a convenience method for {@link #canTraverseWay(IWay, WayTraversalDirection)} that assumes {@link WayTraversalDirection#FORWARD} + * @param way the way for which we check, if it can be traversed by the transport mode + * @return {@code true} if the transport mode can travel along the way in the forward direction. Otherwise {@code false}. + */ + default boolean canTraverseWay(final Way way) { + return canTraverseWay(way, WayTraversalDirection.FORWARD); + } + + /** + * @param way the way that is checked, if the transport mode can traverse + * @param direction the travel direction for which we check + * @return {@code true} iff the transport mode can travel along the given way in the given direction. Otherwise {@code false}. + */ + boolean canTraverseWay(@NotNull IWay way, @NotNull WayTraversalDirection direction); + + /** + * Checks if this transport mode should be used for the given relation + * @param relation the relation that is checked, if it is suitable for the transport mode + * @return {@code true} if the transport mode is suitable for the relation. Otherwise {@code false}. + */ + boolean canBeUsedForRelation(@NotNull final IRelation relation); + + /** + * @param from the way from which the vehicle is coming + * @param via the node that the vehicle travels through, must be part of {@code from} and {@code to} ways, + * or this method will return false + * @param to the way onto which the vehicle makes the turn + * @return {@code true} iff the transport mode can make a turn from the given {@code from} way, + * via the given {@code via} node to the given {@code to} way. Otherwise {@code false}. + * This method assumes that both ways can be traversed by the transport mode, it does not check that. + */ + boolean canTurn(@NotNull final Way from, @NotNull final Node via, @NotNull final Way to); +} diff --git a/src/main/java/org/openstreetmap/josm/plugins/pt_assistant/actions/routinghelper/RoutingHelperAction.java b/src/main/java/org/openstreetmap/josm/plugins/pt_assistant/actions/routinghelper/RoutingHelperAction.java new file mode 100644 index 00000000..fd7743b9 --- /dev/null +++ b/src/main/java/org/openstreetmap/josm/plugins/pt_assistant/actions/routinghelper/RoutingHelperAction.java @@ -0,0 +1,78 @@ +package org.openstreetmap.josm.plugins.pt_assistant.actions.routinghelper; + +import java.awt.event.ActionEvent; +import java.util.Collections; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import javax.swing.JOptionPane; + +import com.drew.lang.annotations.NotNull; +import com.drew.lang.annotations.Nullable; +import org.openstreetmap.josm.data.osm.Relation; +import org.openstreetmap.josm.data.osm.Way; +import org.openstreetmap.josm.gui.MainApplication; +import org.openstreetmap.josm.gui.MapFrame; +import org.openstreetmap.josm.gui.dialogs.relation.actions.AbstractRelationEditorAction; +import org.openstreetmap.josm.gui.dialogs.relation.actions.IRelationEditorActionAccess; +import org.openstreetmap.josm.gui.dialogs.relation.actions.IRelationEditorUpdateOn; +import org.openstreetmap.josm.plugins.pt_assistant.utils.BoundsUtils; +import org.openstreetmap.josm.tools.I18n; +import org.openstreetmap.josm.tools.ImageProvider; + +public class RoutingHelperAction extends AbstractRelationEditorAction { + private static final Set TRANSPORT_MODES = Collections.singleton(new BusTransportMode()); + + private Optional activeTransportMode; + + private final RoutingHelperPanel routingHelperPanel = new RoutingHelperPanel(this); + + public RoutingHelperAction(IRelationEditorActionAccess editorAccess) { + super(editorAccess, IRelationEditorUpdateOn.TAG_CHANGE); + new ImageProvider("dialogs/relation", "routing_assistance.svg").getResource().attachImageIcon(this, true); + putValue(SHORT_DESCRIPTION, I18n.tr("Routing helper")); + } + + @Override + protected void updateEnabledState() { + final Relation currentRelation = getEditor().getRelation(); + final Optional newActiveTransportMode = TRANSPORT_MODES.stream() + .filter(mode -> mode.canBeUsedForRelation(currentRelation)) + .findFirst(); + this.activeTransportMode = newActiveTransportMode; + setEnabled(newActiveTransportMode.isPresent() && MainApplication.getMap().getTopPanel(RoutingHelperPanel.class) == null); + } + + @Override + public void actionPerformed(@NotNull final ActionEvent actionEvent) { + final MapFrame mapFrame = MainApplication.getMap(); + + if (mapFrame.getTopPanel(RoutingHelperPanel.class) == null) { + mapFrame.addTopPanel(routingHelperPanel); + updateEnabledState(); + } + + final Way currentWay = editorAccess.getEditor().getRelation().getMembers().stream().map(it -> it.isWay() ? it.getWay() : null).filter(Objects::nonNull).findFirst().orElse(null); + if (currentWay != null) { + MainApplication.getMap().mapView.zoomTo(BoundsUtils.fromBBox(currentWay.getBBox())); + } + routingHelperPanel.onCurrentWayChange(currentWay); + } + + public void goToPreviousGap() { + JOptionPane.showMessageDialog(routingHelperPanel, "Not implemented yet", "Not implemented", JOptionPane.ERROR_MESSAGE); + } + + public void goToPreviousWay() { + JOptionPane.showMessageDialog(routingHelperPanel, "Not implemented yet", "Not implemented", JOptionPane.ERROR_MESSAGE); + } + + public void goToNextWay() { + JOptionPane.showMessageDialog(routingHelperPanel, "Not implemented yet", "Not implemented", JOptionPane.ERROR_MESSAGE); + } + + public void goToNextGap() { + JOptionPane.showMessageDialog(routingHelperPanel, "Not implemented yet", "Not implemented", JOptionPane.ERROR_MESSAGE); + } +} diff --git a/src/main/java/org/openstreetmap/josm/plugins/pt_assistant/actions/routinghelper/RoutingHelperPanel.java b/src/main/java/org/openstreetmap/josm/plugins/pt_assistant/actions/routinghelper/RoutingHelperPanel.java new file mode 100644 index 00000000..8296bec4 --- /dev/null +++ b/src/main/java/org/openstreetmap/josm/plugins/pt_assistant/actions/routinghelper/RoutingHelperPanel.java @@ -0,0 +1,97 @@ +package org.openstreetmap.josm.plugins.pt_assistant.actions.routinghelper; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Cursor; +import java.awt.FlowLayout; +import java.util.Optional; + +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.border.EmptyBorder; + +import com.drew.lang.annotations.Nullable; +import org.openstreetmap.josm.data.osm.Way; +import org.openstreetmap.josm.gui.MainApplication; +import org.openstreetmap.josm.gui.MapFrame; +import org.openstreetmap.josm.tools.I18n; +import org.openstreetmap.josm.tools.ImageProvider; + +/** + * The top panel that is added via {@link MapFrame#addTopPanel(Component)}. + * This class should just handle the display and input. The state of the routing helper should be handled in {@link RoutingHelperAction}. + */ +public class RoutingHelperPanel extends JPanel { + + private final JLabel wayLabel = new JLabel("Way"); + + private final RoutingHelperAction routingHelperAction; + + public RoutingHelperPanel(final RoutingHelperAction routingHelperAction) { + this.routingHelperAction = routingHelperAction; + + + // Style main label + wayLabel.setBorder(new EmptyBorder(10, 10, 10, 10)); + + JButton closeButton = new JButton(ImageProvider.get("misc", "black_x")); + closeButton.setContentAreaFilled(false); + closeButton.setRolloverEnabled(true); + closeButton.setBorderPainted(false); + closeButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + closeButton.setToolTipText(I18n.tr("Close the routing helper")); + closeButton.addActionListener(e -> + Optional.ofNullable(MainApplication.getMap()) + .ifPresent(map -> { + map.removeTopPanel(RoutingHelperPanel.class); + routingHelperAction.updateEnabledState(); + }) + ); + + final JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.setOpaque(false); + mainPanel.add(wayLabel, BorderLayout.CENTER); + mainPanel.add(closeButton, BorderLayout.EAST); + + // build the left and right button panels + final JPanel buttonLeftPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + buttonLeftPanel.setOpaque(false); + final JButton prevGapButton = new JButton("« to previous gap"); + prevGapButton.addActionListener(e -> routingHelperAction.goToPreviousGap()); + buttonLeftPanel.add(prevGapButton); + final JButton prevButton = new JButton("‹ to previous way"); + prevButton.addActionListener(e -> routingHelperAction.goToPreviousWay()); + buttonLeftPanel.add(prevButton); + + final JPanel buttonRightPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + buttonRightPanel.setOpaque(false); + final JButton nextButton = new JButton("to next way ›"); + nextButton.addActionListener(e -> routingHelperAction.goToNextWay()); + buttonRightPanel.add(nextButton); + final JButton nextgapButton = new JButton("to next gap »"); + nextgapButton.addActionListener(e -> routingHelperAction.goToNextGap()); + buttonRightPanel.add(nextgapButton); + + // Combine both button panels into one + final JPanel buttonPanel = new JPanel(new BorderLayout()); + buttonPanel.setOpaque(false); + buttonPanel.add(buttonLeftPanel, BorderLayout.WEST); + buttonPanel.add(buttonRightPanel, BorderLayout.EAST); + + // put everything together + setBackground(new Color(0xFF9966)); + setLayout(new BorderLayout()); + add(mainPanel, BorderLayout.CENTER); + add(buttonPanel, BorderLayout.SOUTH); + } + + public void onCurrentWayChange(@Nullable final Way way) { + if (way == null) { + wayLabel.setText(I18n.tr("No way found in relation")); + } else { + wayLabel.setText(I18n.tr("Active way: {0} ({1} nodes)", way.getId(), way.getNodesCount())); + } + } +} diff --git a/src/main/java/org/openstreetmap/josm/plugins/pt_assistant/actions/routinghelper/WayTraversalDirection.java b/src/main/java/org/openstreetmap/josm/plugins/pt_assistant/actions/routinghelper/WayTraversalDirection.java new file mode 100644 index 00000000..603d0b96 --- /dev/null +++ b/src/main/java/org/openstreetmap/josm/plugins/pt_assistant/actions/routinghelper/WayTraversalDirection.java @@ -0,0 +1,48 @@ +package org.openstreetmap.josm.plugins.pt_assistant.actions.routinghelper; + +import java.util.function.Function; + +import com.drew.lang.annotations.NotNull; +import com.drew.lang.annotations.Nullable; +import org.openstreetmap.josm.data.osm.INode; +import org.openstreetmap.josm.data.osm.IWay; +import org.openstreetmap.josm.data.osm.Way; + +public enum WayTraversalDirection { + FORWARD(IWay::firstNode, IWay::lastNode), + BACKWARD(IWay::lastNode, IWay::firstNode); + + private final Function, INode> startNodeGetter; + private final Function, INode> endNodeGetter; + + WayTraversalDirection( + @NotNull final Function, INode> startNodeGetter, + @NotNull final Function, INode> endNodeGetter + ) { + this.startNodeGetter = startNodeGetter; + this.endNodeGetter = endNodeGetter; + } + + /** + * Finds the first node that you come across when traversin the given way. + * + * @param way the way for which the first or last node is returned, depending on the direction + * @return {@link #FORWARD} returns the {@link Way#firstNode()}, {@link #BACKWARD} returns the {@link Way#lastNode()} + */ + @Nullable + public INode getStartNodeFor(@NotNull final IWay way) { + return startNodeGetter.apply(way); + } + + + /** + * Finds the node where traversal of the given way ends. + * + * @param way the way for which the first or last node is returned, depending on the direction + * @return {@link #FORWARD} returns the {@link Way#lastNode()}, {@link #BACKWARD} returns the {@link Way#firstNode()} + */ + @Nullable + public INode getEndNodeFor(@NotNull final IWay way) { + return endNodeGetter.apply(way); + } +} diff --git a/src/main/java/org/openstreetmap/josm/plugins/pt_assistant/utils/BoundsUtils.java b/src/main/java/org/openstreetmap/josm/plugins/pt_assistant/utils/BoundsUtils.java index 9cca26a3..be800a2f 100644 --- a/src/main/java/org/openstreetmap/josm/plugins/pt_assistant/utils/BoundsUtils.java +++ b/src/main/java/org/openstreetmap/josm/plugins/pt_assistant/utils/BoundsUtils.java @@ -4,6 +4,7 @@ import java.util.Collection; import java.util.Optional; +import com.drew.lang.annotations.NotNull; import org.openstreetmap.josm.data.Bounds; import org.openstreetmap.josm.data.osm.BBox; import org.openstreetmap.josm.data.osm.Way; @@ -53,4 +54,13 @@ public static Optional createBoundsWithPadding(final BBox bbox, final do ); }); } + + /** + * Converts the given {@link BBox} to {@link Bounds} + * @param bbox the bbox to convert + * @return the bounds equivalent to the passed bbox + */ + public static Bounds fromBBox(@NotNull final BBox bbox) { + return new Bounds(bbox.getBottomRightLat(), bbox.getTopLeftLon(), bbox.getTopLeftLat(), bbox.getBottomRightLon()); + } }