// License: GPL. For details, see LICENSE file. package ptl; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.event.ActionEvent; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.swing.JOptionPane; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.actions.JosmAction; import org.openstreetmap.josm.data.SystemOfMeasurement; import org.openstreetmap.josm.data.coor.LatLon; 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.RelationMember; import org.openstreetmap.josm.gui.DefaultNameFormatter; import org.openstreetmap.josm.gui.ExtendedDialog; import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType; import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionTypeCalculator; import org.openstreetmap.josm.gui.util.GuiHelper; import org.openstreetmap.josm.gui.widgets.JosmTextArea; import org.openstreetmap.josm.tools.CheckParameterUtil; import org.openstreetmap.josm.tools.Utils; public class DistanceBetweenStops extends JosmAction { public DistanceBetweenStops() { super(tr("Distance between stops"), null, null, null, false); } static String calculateDistanceBetweenStops(final Relation route) { CheckParameterUtil.ensureThat(isRouteSupported(route), "A valid public_transport:version=2 route is required"); final List<Node> stopNodes = new ArrayList<>(); final List<RelationMember> routeSegments = new ArrayList<>(); final List<Node> routeNodes = new ArrayList<>(); for (final RelationMember member : route.getMembers()) { if (member.hasRole("stop", "stop_exit_only", "stop_entry_only") && OsmPrimitiveType.NODE.equals(member.getType())) { stopNodes.add(member.getNode()); } else if (member.hasRole("") && OsmPrimitiveType.WAY.equals(member.getType())) { routeSegments.add(member); } } final WayConnectionTypeCalculator connectionTypeCalculator = new WayConnectionTypeCalculator(); final List<WayConnectionType> links = connectionTypeCalculator.updateLinks(routeSegments); for (int i = 0; i < links.size(); i++) { final WayConnectionType link = links.get(i); final List<Node> nodes = routeSegments.get(i).getWay().getNodes(); switch (link.direction) { case BACKWARD: Collections.reverse(nodes); // fall through case FORWARD: routeNodes.addAll(link.linkPrev ? nodes.subList(1, nodes.size()) : nodes); break; default: break; } } final StringBuilder sb = new StringBuilder(); Node lastN = null; List<Node> remainingRouteNodes = routeNodes; double totalLength = 0.0; int lengthN = 0; final boolean onlyLowerUnit = Main.pref.getBoolean("system_of_measurement.use_only_lower_unit", false); Main.pref.put("system_of_measurement.use_only_lower_unit", true); try { for (Node n : stopNodes) { final double length; if (lastN != null) { if (remainingRouteNodes.indexOf(lastN) > 0) { remainingRouteNodes = remainingRouteNodes.subList(remainingRouteNodes.indexOf(lastN), remainingRouteNodes.size()); } if (remainingRouteNodes.indexOf(n) > 0) { final List<Node> segmentBetweenStops = remainingRouteNodes.subList(0, remainingRouteNodes.indexOf(n) + 1); length = getLength(segmentBetweenStops); totalLength += length; lengthN++; } else { length = Double.NaN; } } else { length = 0.0; } sb.append(SystemOfMeasurement.getSystemOfMeasurement().getDistText(length, new DecimalFormat("0"), -1)); sb.append("\t"); sb.append(n.getDisplayName(DefaultNameFormatter.getInstance())); sb.append("\n"); lastN = n; } sb.insert(0, "\n"); sb.insert(0, route.getDisplayName(DefaultNameFormatter.getInstance())); sb.insert(0, "\t"); sb.insert(0, SystemOfMeasurement.getSystemOfMeasurement().getDistText(totalLength / lengthN, new DecimalFormat("0"), -1)); } finally { Main.pref.put("system_of_measurement.use_only_lower_unit", onlyLowerUnit); } return sb.toString(); } private static boolean isRouteSupported(Relation route) { return !route.hasIncompleteMembers() && route.hasTag("type", "route") && route.hasKey("route") && route.hasTag("public_transport:version", "2"); } private static double getLength(Iterable<Node> nodes) { double length = 0; Node lastN = null; for (Node n : nodes) { if (lastN != null) { LatLon lastNcoor = lastN.getCoor(); LatLon coor = n.getCoor(); if (lastNcoor != null && coor != null) { length += coor.greatCircleDistance(lastNcoor); } } lastN = n; } return length; } @Override public void actionPerformed(ActionEvent e) { if (getLayerManager().getEditDataSet() == null) { return; } final StringBuilder sb = new StringBuilder(); for (Relation relation : getLayerManager().getEditDataSet().getSelectedRelations()) { if (!isRouteSupported(relation)) { JOptionPane.showMessageDialog(Main.parent, "<html>" + tr("A valid public_transport:version=2 route is required") + Utils.joinAsHtmlUnorderedList(Collections.singleton(relation.getDisplayName(DefaultNameFormatter.getInstance()))), tr("Invalid selection"), JOptionPane.WARNING_MESSAGE); continue; } sb.append("\n"); sb.append(calculateDistanceBetweenStops(relation)).append("\n"); } new ExtendedDialog(Main.parent, getValue(NAME).toString(), new String[]{tr("Close")}) { { setButtonIcons(new String[]{"ok.png"}); final JosmTextArea jte = new JosmTextArea(); jte.setFont(GuiHelper.getMonospacedFont(jte)); jte.setEditable(false); jte.append(sb.toString()); jte.setSelectionStart(0); jte.setSelectionEnd(0); setContent(jte); } }.showDialog(); } }