Index: src/org/openstreetmap/josm/data/validation/tests/Highways.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/Highways.java	(revision 18080)
+++ src/org/openstreetmap/josm/data/validation/tests/Highways.java	(working copy)
@@ -4,6 +4,12 @@
 import static org.openstreetmap.josm.data.validation.tests.CrossingWays.HIGHWAY;
 import static org.openstreetmap.josm.tools.I18n.tr;
 
+import javax.swing.*;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import java.awt.*;
+import java.awt.event.ActionListener;
+import java.text.DecimalFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -15,6 +21,8 @@
 import java.util.stream.Collectors;
 
 import org.openstreetmap.josm.command.ChangePropertyCommand;
+import org.openstreetmap.josm.data.osm.BBox;
+import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.OsmUtils;
@@ -22,6 +30,9 @@
 import org.openstreetmap.josm.data.validation.Severity;
 import org.openstreetmap.josm.data.validation.Test;
 import org.openstreetmap.josm.data.validation.TestError;
+import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.spi.preferences.IPreferences;
+import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.Utils;
 
@@ -38,9 +49,25 @@
     protected static final int SOURCE_MAXSPEED_CONTEXT_MISMATCH_VS_MAXSPEED = 2705;
     protected static final int SOURCE_MAXSPEED_CONTEXT_MISMATCH_VS_HIGHWAY = 2706;
     protected static final int SOURCE_WRONG_LINK = 2707;
+    protected static final int SOURCE_BUS_STOP_NEEDED = 2708;
 
     protected static final String SOURCE_MAXSPEED = "source:maxspeed";
+    protected static final String BUS = "bus";
+    protected static final String PUBLIC_TRANSPORT = "public_transport";
 
+    //Adding variables for #17188 : complain about bus stop position without nearby highway=bus_stop
+    private final JFormattedTextField bboxExpansionConstant;
+    private final JButton defaultButton;
+    private final JLabel warningLabel;
+    private final JLabel labelForBboxExpansion;
+    private static final String BBOX_EXPANSION_PREF_KEY = "bboxExpansionConstant";
+    private static final double EXPANSE_DISTANCE_DEFAULT = 0.00015;
+    private final IPreferences preferences = Config.getPref();
+
+    //Create listeners for bboxExpansionConstant and defaultButton specifically
+    private final ActionListener actionListener;
+    private final DocumentListener documentListener;
+
     /**
      * Classified highways in order of importance
      */
@@ -74,6 +101,37 @@
      */
     public Highways() {
         super(tr("Highways"), tr("Performs semantic checks on highways."));
+
+        // Variables for #17188
+        bboxExpansionConstant = new JFormattedTextField(new DecimalFormat("#.########"));
+        defaultButton = new JButton("Set Default (~15 meters)");
+        warningLabel = new JLabel("Entry must be a valid number.");
+        labelForBboxExpansion = new JLabel("Enter bbox expansion constant for searching nearby Nodes (LatLon degrees)");
+
+        //UI for #17188
+        warningLabel.setVisible(false);
+        warningLabel.setForeground(Color.RED);
+
+        //Listeners for #17188
+        documentListener = new DocumentListener() {
+            @Override
+            public void insertUpdate(DocumentEvent e) {
+                warn();
+            }
+
+            @Override
+            public void removeUpdate(DocumentEvent e) {
+                warn();
+            }
+
+            @Override
+            public void changedUpdate(DocumentEvent e) {
+                //unneeded for this listener's purpose.
+            }
+        };
+        actionListener = e -> {
+            bboxExpansionConstant.setValue(EXPANSE_DISTANCE_DEFAULT);
+        };
     }
 
     @Override
@@ -90,6 +148,10 @@
                 // as maxspeed is not set on highways here but on signs, speed cameras, etc.
                 testSourceMaxspeed(n, false);
             }
+            if ((IN_DOWNLOADED_AREA.test(n) || n.isNew()) && n.hasTag(BUS, "yes") && n.hasTag(PUBLIC_TRANSPORT, "stop_position")) {
+                // Test for 17188: complain about bus stop position without nearby highway=bus_stop
+                testMissingBusStopNode(n);
+            }
         }
     }
 
@@ -243,6 +305,56 @@
         }
     }
 
+    /**
+     * Tests for bus stop ("public_transport"="stop_position"/"bus"="yes") Nodes a long a way that are missing a related nearby Node with "highway=bus_stop"
+     *
+     * @param n Node being visited
+     */
+    public void testMissingBusStopNode(Node n) {
+        int countOfNodesWithProperTags = 0;
+        double expanseDistance = EXPANSE_DISTANCE_DEFAULT;
+
+        //Approximately 15 meters depending on Lat/Lon
+        String expanseDistanceFromPrefs = preferences.get(BBOX_EXPANSION_PREF_KEY);
+        if (expanseDistanceFromPrefs != null && !expanseDistanceFromPrefs.isEmpty()) {
+            expanseDistance = Double.parseDouble(expanseDistanceFromPrefs);
+        }
+
+        // Get nearby nodes within expanse distance
+        List<Node> nearbyNodesWithinExpanseDistance = getNearbyNodesWithinShortDistance(n, expanseDistance);
+        for (Node nearbyNodeWithinExpanseDistance : nearbyNodesWithinExpanseDistance) {
+            if (nearbyNodeWithinExpanseDistance.hasTag(HIGHWAY, "bus_stop") && nearbyNodeWithinExpanseDistance.hasTag(BUS, "yes")) {
+                countOfNodesWithProperTags += 1;
+            }
+        }
+
+        // If there are no nodes within expanse distance throw the error.
+        if (countOfNodesWithProperTags == 0) {
+            errors.add(TestError.builder(this, Severity.WARNING, SOURCE_BUS_STOP_NEEDED)
+                    .message(tr("Node needs a nearby related Node with tags: {0} and {1}.",
+                            "highway=bus_stop", "bus=yes"))
+                    .primitives(n)
+                    .build());
+        }
+    }
+
+    /**
+     * Gathers list of Nodes within specified approximate distance (takes double but unit is LatLon degrees) of Node n.
+     *
+     * @param n               Node being visited
+     * @param expanseDistance Distance to expand Node bounds. Units are in LatLon degrees.
+     * @return List of Nodes
+     */
+    public List<Node> getNearbyNodesWithinShortDistance(Node n, double expanseDistance) {
+        DataSet nodeDataSet = n.getDataSet();
+
+        // Expand bbox by expand distance
+        BBox nodeBBox = n.getBBox();
+        nodeBBox.addLatLon(nodeBBox.getCenter(), expanseDistance);
+
+        return nodeDataSet.searchNodes(nodeBBox);
+    }
+
     private void handleCarWay(Node n, Way w) {
         carsWays++;
         if (!w.isFirstLastNode(n) || carsWays > 1) {
@@ -295,4 +407,55 @@
             }
         }
     }
+
+    // Add UI for #17188
+    @Override
+    public void addGui(JPanel testPanel) {
+        super.addGui(testPanel);
+
+        // Check if there is a valid preference set for expansion value
+        String bboxExpansionPref = preferences.get(BBOX_EXPANSION_PREF_KEY);
+        if (bboxExpansionPref != null && !bboxExpansionPref.isEmpty()) {
+            try {
+                Double.parseDouble(bboxExpansionPref);
+                bboxExpansionConstant.setValue(Double.parseDouble(bboxExpansionPref));
+            }
+            catch (Exception exception) {
+                bboxExpansionConstant.setValue(EXPANSE_DISTANCE_DEFAULT);
+            }
+        }
+        else {
+            bboxExpansionConstant.setValue(EXPANSE_DISTANCE_DEFAULT);
+        }
+
+        bboxExpansionConstant.setColumns(5);
+        bboxExpansionConstant.getDocument().addDocumentListener(documentListener);
+        defaultButton.addActionListener(actionListener);
+
+        testPanel.add(labelForBboxExpansion, GBC.eol().insets(20,0,0,0));
+        testPanel.add(bboxExpansionConstant, GBC.eol().insets(20, 0, 0, 0));
+        testPanel.add(warningLabel, GBC.eol().insets(20,0,0,0));
+        testPanel.add(defaultButton, GBC.eol().insets(20,0,0,0));
+    }
+
+    /**
+     * This is the logic checking if the input value from user is valid. If not the warning is displayed.
+     */
+    public void warn() {
+        String expansionDegrees = bboxExpansionConstant.getText();
+        try {
+            Double.parseDouble(expansionDegrees);
+            warningLabel.setVisible(false);
+        }
+        catch (Exception exception) {
+            warningLabel.setVisible(true);
+        }
+    }
+
+    @Override
+    public boolean ok() {
+        //Uses most recent VALID entry for bbox expanse on OK
+        preferences.put(BBOX_EXPANSION_PREF_KEY, bboxExpansionConstant.getValue().toString());
+        return false;
+    }
 }
