Index: trunk/src/org/openstreetmap/josm/gui/ImageryMenu.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/ImageryMenu.java	(revision 15053)
+++ trunk/src/org/openstreetmap/josm/gui/ImageryMenu.java	(revision 15054)
@@ -12,8 +12,11 @@
 import java.util.Collection;
 import java.util.Comparator;
-import java.util.Iterator;
+import java.util.EnumMap;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Optional;
+import java.util.stream.Collectors;
 
 import javax.swing.Action;
@@ -30,4 +33,5 @@
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.imagery.ImageryInfo;
+import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryCategory;
 import org.openstreetmap.josm.data.imagery.ImageryLayerInfo;
 import org.openstreetmap.josm.data.imagery.Shape;
@@ -39,4 +43,6 @@
 import org.openstreetmap.josm.gui.preferences.imagery.ImageryPreference;
 import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
+import org.openstreetmap.josm.tools.Logging;
 
 /**
@@ -139,4 +145,15 @@
 
     /**
+     * For layers containing complex shapes, check that center is in one of its shapes (fix #7910)
+     * @param info layer info
+     * @param pos center
+     * @return {@code true} if center is in one of info shapes
+     */
+    private static boolean isPosInOneShapeIfAny(ImageryInfo info, LatLon pos) {
+        List<Shape> shapes = info.getBounds().getShapes();
+        return shapes == null || shapes.stream().anyMatch(s -> s.contains(pos));
+    }
+
+    /**
      * Refresh imagery menu.
      *
@@ -147,5 +164,5 @@
         removeDynamicItems();
 
-        addDynamic(offsetMenuItem);
+        addDynamic(offsetMenuItem, null);
         addDynamicSeparator();
 
@@ -154,40 +171,33 @@
         savedLayers.sort(alphabeticImageryComparator);
         for (final ImageryInfo u : savedLayers) {
-            addDynamic(trackJosmAction(new AddImageryLayerAction(u)));
-        }
-
-        // list all imagery entries where the current map location
-        // is within the imagery bounds
+            addDynamic(trackJosmAction(new AddImageryLayerAction(u)), null);
+        }
+
+        // list all imagery entries where the current map location is within the imagery bounds
         if (MainApplication.isDisplayingMapView()) {
             MapView mv = MainApplication.getMap().mapView;
             LatLon pos = mv.getProjection().eastNorth2latlon(mv.getCenter());
-            final List<ImageryInfo> inViewLayers = new ArrayList<>();
-
-            for (ImageryInfo i : ImageryLayerInfo.instance.getDefaultLayers()) {
-                if (i.getBounds() != null && i.getBounds().contains(pos)) {
-                    inViewLayers.add(i);
-                }
-            }
-            // Do not suggest layers already in use
-            inViewLayers.removeAll(ImageryLayerInfo.instance.getLayers());
-            // For layers containing complex shapes, check that center is in one
-            // of its shapes (fix #7910)
-            for (Iterator<ImageryInfo> iti = inViewLayers.iterator(); iti.hasNext();) {
-                List<Shape> shapes = iti.next().getBounds().getShapes();
-                if (shapes != null && !shapes.isEmpty()) {
-                    boolean found = false;
-                    for (Iterator<Shape> its = shapes.iterator(); its.hasNext() && !found;) {
-                        found = its.next().contains(pos);
-                    }
-                    if (!found) {
-                        iti.remove();
-                    }
-                }
-            }
+            final List<ImageryInfo> alreadyInUse = ImageryLayerInfo.instance.getLayers();
+            final List<ImageryInfo> inViewLayers = ImageryLayerInfo.instance.getDefaultLayers()
+                    .stream().filter(i -> i.getBounds() != null && i.getBounds().contains(pos)
+                        && !alreadyInUse.contains(i) && isPosInOneShapeIfAny(i, pos))
+                    .sorted(alphabeticImageryComparator)
+                    .collect(Collectors.toList());
             if (!inViewLayers.isEmpty()) {
-                inViewLayers.sort(alphabeticImageryComparator);
                 addDynamicSeparator();
                 for (ImageryInfo i : inViewLayers) {
-                    addDynamic(trackJosmAction(new AddImageryLayerAction(i)));
+                    addDynamic(trackJosmAction(new AddImageryLayerAction(i)), i.getImageryCategory());
+                }
+            }
+            if (!dynamicNonPhotoItems.isEmpty()) {
+                addDynamicSeparator();
+                for (Entry<ImageryCategory, List<JMenuItem>> e : dynamicNonPhotoItems.entrySet()) {
+                    ImageryCategory cat = e.getKey();
+                    JMenuItem categoryMenu = new JMenu(cat.getDescription());
+                    categoryMenu.setIcon(cat.getIcon(ImageSizes.MENU));
+                    for (JMenuItem it : e.getValue()) {
+                        categoryMenu.add(it);
+                    }
+                    dynamicNonPhotoMenus.add(add(categoryMenu));
                 }
             }
@@ -201,9 +211,9 @@
             int n = subMenu.getItemCount();
             for (int i = 0; i < n; i++) {
-                addDynamic(subMenu.getItem(i).getAction());
+                addDynamic(subMenu.getItem(i).getAction(), null);
             }
         } else {
             // or add the submenu itself
-            addDynamic(subMenu);
+            addDynamic(subMenu, null);
         }
     }
@@ -257,9 +267,18 @@
 
     /**
-     * Collection to store temporary menu items. They will be deleted
+     * List to store temporary "photo" menu items. They will be deleted
      * (and possibly recreated) when refreshImageryMenu() is called.
-     * @since 5803
      */
     private final List<Object> dynamicItems = new ArrayList<>(20);
+    /**
+     * Map to store temporary "not photo" menu items. They will be deleted
+     * (and possibly recreated) when refreshImageryMenu() is called.
+     */
+    private final Map<ImageryCategory, List<JMenuItem>> dynamicNonPhotoItems = new EnumMap<>(ImageryCategory.class);
+    /**
+     * List to store temporary "not photo" submenus. They will be deleted
+     * (and possibly recreated) when refreshImageryMenu() is called.
+     */
+    private final List<JMenuItem> dynamicNonPhotoMenus = new ArrayList<>(20);
     private final List<JosmAction> dynJosmActions = new ArrayList<>(20);
 
@@ -271,15 +290,22 @@
         dynJosmActions.forEach(JosmAction::destroy);
         dynJosmActions.clear();
-        for (Object item : dynamicItems) {
-            if (item instanceof JMenuItem) {
-                Optional.ofNullable(((JMenuItem) item).getAction()).ifPresent(MainApplication.getToolbar()::unregister);
-                remove((JMenuItem) item);
-            } else if (item instanceof MenuComponent) {
-                remove((MenuComponent) item);
-            } else if (item instanceof Component) {
-                remove((Component) item);
-            }
-        }
+        dynamicItems.forEach(this::removeDynamicItem);
         dynamicItems.clear();
+        dynamicNonPhotoMenus.forEach(this::removeDynamicItem);
+        dynamicItems.clear();
+        dynamicNonPhotoItems.clear();
+    }
+
+    private void removeDynamicItem(Object item) {
+        if (item instanceof JMenuItem) {
+            Optional.ofNullable(((JMenuItem) item).getAction()).ifPresent(MainApplication.getToolbar()::unregister);
+            remove((JMenuItem) item);
+        } else if (item instanceof MenuComponent) {
+            remove((MenuComponent) item);
+        } else if (item instanceof Component) {
+            remove((Component) item);
+        } else {
+            Logging.error("Unknown imagery menu item type: {0}", item);
+        }
     }
 
@@ -290,10 +316,20 @@
     }
 
-    private void addDynamic(Action a) {
-        dynamicItems.add(this.add(a));
-    }
-
-    private void addDynamic(JMenuItem it) {
-        dynamicItems.add(this.add(it));
+    private void addDynamic(Action a, ImageryCategory category) {
+        JMenuItem item = createActionComponent(a);
+        item.setAction(a);
+        doAddDynamic(item, category);
+    }
+
+    private void addDynamic(JMenuItem it, ImageryCategory category) {
+        doAddDynamic(it, category);
+    }
+
+    private void doAddDynamic(JMenuItem item, ImageryCategory category) {
+        if (category == null || category == ImageryCategory.PHOTO) {
+            dynamicItems.add(this.add(item));
+        } else {
+            dynamicNonPhotoItems.computeIfAbsent(category, x -> new ArrayList<>()).add(item);
+        }
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/util/GuiHelper.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/util/GuiHelper.java	(revision 15053)
+++ trunk/src/org/openstreetmap/josm/gui/util/GuiHelper.java	(revision 15054)
@@ -190,5 +190,6 @@
     /**
      * Executes asynchronously a runnable in
-     * <a href="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html">Event Dispatch Thread</a>.
+     * <a href="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html">Event Dispatch Thread</a>,
+     * except if we're already in the EDT: in this case the runnable is executed synchronously.
      * @param task The runnable to execute
      * @see SwingUtilities#invokeLater
