Index: src/org/openstreetmap/josm/gui/dialogs/layer/CycleLayerDownAction.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/layer/CycleLayerDownAction.java	(nonexistent)
+++ src/org/openstreetmap/josm/gui/dialogs/layer/CycleLayerDownAction.java	(working copy)
@@ -0,0 +1,69 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.layer;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.layer.ImageryLayer;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.MainLayerManager;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ * Allow users to cycle between adjacent layers easily
+ *
+ * @author Taylor Smock
+ * @since xxx
+ */
+public class CycleLayerDownAction extends JosmAction {
+    private static final long serialVersionUID = -7094806029703064750L;
+    protected static final int KEYDOWN = KeyEvent.VK_CLOSE_BRACKET;
+    protected static final int MODIFIER = Shortcut.SHIFT;
+    private static Shortcut cycleDown = Shortcut.registerShortcut("core:cyclelayerdown", tr("Cycle layers down"), KEYDOWN, MODIFIER);
+
+    /**
+     * Create a CycleLayerDownAction that cycles through layers that are in the
+     * model
+     */
+    public CycleLayerDownAction() {
+        super(tr("Cycle layers"), "dialogs/next", tr("Cycle through layers"), cycleDown, true, "cycle-layer", false);
+        new ImageProvider("dialogs", "next").getResource().attachImageIcon(this, true);
+        putValue(SHORT_DESCRIPTION, tr("Cycle through visible layers."));
+        putValue(NAME, tr("Cycle layers"));
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        MainLayerManager manager = MainApplication.getLayerManager();
+        List<Layer> managerLayers = manager.getLayers().stream().filter(layer -> !(layer instanceof ImageryLayer))
+                .collect(Collectors.toList());
+        if (managerLayers.isEmpty()) {
+            return;
+        }
+
+        List<Layer> layers = new ArrayList<>(managerLayers);
+        Collections.reverse(layers);
+        int index = layers.indexOf(manager.getActiveLayer());
+        int sublist = index < managerLayers.size() - 1 ? index + 1 : 0;
+        layers = layers.subList(sublist, layers.size());
+
+        Layer layer = layers.stream().filter(Layer::isVisible).filter(tlayer -> !(tlayer instanceof ImageryLayer))
+                .findFirst().orElse(manager.getActiveLayer());
+
+        manager.setActiveLayer(layer);
+    }
+
+    @Override
+    public void destroy() {
+        super.destroy();
+    }
+}
Index: src/org/openstreetmap/josm/gui/dialogs/layer/CycleLayerUpAction.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/layer/CycleLayerUpAction.java	(nonexistent)
+++ src/org/openstreetmap/josm/gui/dialogs/layer/CycleLayerUpAction.java	(working copy)
@@ -0,0 +1,67 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.layer;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.layer.ImageryLayer;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.MainLayerManager;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ * Allow users to cycle between adjacent layers easily
+ *
+ * @author Taylor Smock
+ * @since xxx
+ */
+public class CycleLayerUpAction extends JosmAction {
+    private static final long serialVersionUID = -4041662823217465004L;
+    protected static final int KEYUP = KeyEvent.VK_OPEN_BRACKET;
+    protected static final int MODIFIER = Shortcut.SHIFT;
+    private static Shortcut cycleUp = Shortcut.registerShortcut("core:cyclelayerup", tr("Cycle layers up"), KEYUP, MODIFIER);
+
+    /**
+     * Create a CycleLayerDownAction that cycles through layers that are in the
+     * model
+     */
+    public CycleLayerUpAction() {
+        super(tr("Cycle layer up"), "dialogs/next", tr("Cycle up through layers"), cycleUp, true, "cycle-layer", false);
+        new ImageProvider("dialogs", "next").getResource().attachImageIcon(this, true);
+        putValue(SHORT_DESCRIPTION, tr("Cycle through visible layers."));
+        putValue(NAME, tr("Cycle layers"));
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        MainLayerManager manager = MainApplication.getLayerManager();
+        List<Layer> managerLayers = manager.getLayers().stream().filter(layer -> !(layer instanceof ImageryLayer))
+                .collect(Collectors.toList());
+        if (managerLayers.isEmpty()) {
+            return;
+        }
+        int index = managerLayers.indexOf(manager.getActiveLayer());
+        int sublist = index < managerLayers.size() ? index + 1 : index;
+        if (index >= managerLayers.size() - 1) {
+            index = 0;
+            sublist = 0;
+        }
+        List<Layer> layers = managerLayers.subList(sublist, managerLayers.size());
+        Layer layer = layers.stream().filter(Layer::isVisible).filter(tlayer -> !(tlayer instanceof ImageryLayer))
+                .findFirst().orElse(manager.getActiveLayer());
+
+        manager.setActiveLayer(layer);
+    }
+
+    @Override
+    public void destroy() {
+        super.destroy();
+    }
+}
Index: test/unit/org/openstreetmap/josm/gui/dialogs/layer/CycleLayerActionTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/gui/dialogs/layer/CycleLayerActionTest.java	(nonexistent)
+++ test/unit/org/openstreetmap/josm/gui/dialogs/layer/CycleLayerActionTest.java	(working copy)
@@ -0,0 +1,116 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.layer;
+
+import static org.junit.Assert.assertEquals;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.openstreetmap.josm.data.imagery.ImageryInfo;
+import org.openstreetmap.josm.data.imagery.ImageryLayerInfo;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.layer.ImageryLayer;
+import org.openstreetmap.josm.gui.layer.MainLayerManager;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * Test class for {@link CycleLayerDownAction}
+ *
+ * @author Taylor Smock
+ */
+public class CycleLayerActionTest {
+    /** Layers need a projection */
+    @Rule
+    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+    public JOSMTestRules test = new JOSMTestRules().main().preferences().projection().fakeImagery();
+
+    CycleLayerDownAction cycleDown;
+    CycleLayerUpAction cycleUp;
+    MainLayerManager manager;
+
+    /**
+     * Set up common items (make layers, etc.)
+     */
+    @Before
+    public void setUp() {
+        cycleDown = new CycleLayerDownAction();
+        cycleUp = new CycleLayerUpAction();
+        manager = MainApplication.getLayerManager();
+        for (int i = 0; i < 10; i++) {
+            manager.addLayer(new OsmDataLayer(new DataSet(), tr("Layer {0}", i), null));
+        }
+    }
+
+    /**
+     * Test going down from the bottom
+     */
+    @Test
+    public void testDownBottom() {
+        manager.setActiveLayer(manager.getLayers().get(0));
+        cycleDown.actionPerformed(null);
+        assertEquals(manager.getLayers().size() - 1, manager.getLayers().indexOf(manager.getActiveLayer()));
+    }
+
+    /**
+     * Check going up from the top
+     */
+    @Test
+    public void testUpTop() {
+        manager.setActiveLayer(manager.getLayers().get(manager.getLayers().size() - 1));
+        cycleUp.actionPerformed(null);
+        assertEquals(0, manager.getLayers().indexOf(manager.getActiveLayer()));
+    }
+
+    /**
+     * Check going down
+     */
+    @Test
+    public void testDown() {
+        manager.setActiveLayer(manager.getLayers().get(3));
+        cycleDown.actionPerformed(null);
+        assertEquals(2, manager.getLayers().indexOf(manager.getActiveLayer()));
+    }
+
+    /**
+     * Check going up
+     */
+    @Test
+    public void testUp() {
+        manager.setActiveLayer(manager.getLayers().get(3));
+        cycleUp.actionPerformed(null);
+        assertEquals(4, manager.getLayers().indexOf(manager.getActiveLayer()));
+    }
+
+    /**
+     * Test no layers
+     */
+    @Test
+    public void testNoLayers() {
+        manager.getLayers().forEach(manager::removeLayer);
+        cycleUp.actionPerformed(null);
+        cycleDown.actionPerformed(null);
+        assertEquals(0, manager.getLayers().size());
+    }
+
+    /**
+     * Test with an aerial imagery layer
+     */
+    @Test
+    public void testWithAerialImagery() {
+        final ImageryInfo magentaTilesInfo = ImageryLayerInfo.instance.getLayers().stream()
+                .filter(i -> i.getName().equals("Magenta Tiles")).findAny().get();
+        ImageryLayer imageryLayer = ImageryLayer.create(magentaTilesInfo);
+        manager.addLayer(imageryLayer);
+        manager.moveLayer(imageryLayer, 5);
+        manager.setActiveLayer(manager.getLayers().get(4));
+        cycleUp.actionPerformed(null);
+        assertEquals(6, manager.getLayers().indexOf(manager.getActiveLayer()));
+        cycleDown.actionPerformed(null);
+        assertEquals(4, manager.getLayers().indexOf(manager.getActiveLayer()));
+    }
+}
