diff --git a/doc/contributions.txt b/doc/contributions.txt
index 6d93eca14ca65f333cd2a50dc2383ce1c10ccd4c..a21dab5469c45193bec38029dec9efdea167ce1f 100644
--- a/doc/contributions.txt
+++ b/doc/contributions.txt
@@ -49,6 +49,7 @@ Aimee Trescothick
 	VWR-12631
 	VWR-12696
 	VWR-12748
+	VWR-13221
 	VWR-14087
 	VWR-14267
 	VWR-14278
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 9d911777d12f2000448e790b61cdc7a9491b387f..46ae879aeca2f3922c1873903cf022f66f20a4d4 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -4669,6 +4669,17 @@
       <key>Value</key>
       <integer>1</integer>
     </map>
+    <key>MiniMapAutoCenter</key>
+    <map>
+      <key>Comment</key>
+      <string>Center the focal point of the minimap.</string>
+      <key>Persist</key>
+      <integer>0</integer>
+      <key>Type</key>
+      <string>Boolean</string>
+      <key>Value</key>
+      <integer>1</integer>
+    </map>
     <key>Marker</key>
     <map>
       <key>Comment</key>
diff --git a/indra/newview/llfloatermap.cpp b/indra/newview/llfloatermap.cpp
index 0f8b709f29dd4657206af1f323de1a5c468ffa08..c9d7eff02b95d124b5628c2b17b2b81b22d8178f 100644
--- a/indra/newview/llfloatermap.cpp
+++ b/indra/newview/llfloatermap.cpp
@@ -47,7 +47,10 @@
 //
 // Constants
 //
-const F32 MAP_MINOR_DIR_THRESHOLD = 0.08f;
+
+// The minor cardinal direction labels are hidden if their height is more
+// than this proportion of the map.
+const F32 MAP_MINOR_DIR_THRESHOLD = 0.07f;
 const S32 MAP_PADDING_LEFT = 0;
 const S32 MAP_PADDING_TOP = 2;
 const S32 MAP_PADDING_RIGHT = 2;
@@ -93,7 +96,7 @@ BOOL LLFloaterMap::postBuild()
 	mTextBoxNorthWest = getChild<LLTextBox> ("floater_map_northwest");
 
 	LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
-	
+
 	registrar.add("Minimap.Zoom", boost::bind(&LLFloaterMap::handleZoom, this, _2));
 	registrar.add("Minimap.Tracker", boost::bind(&LLFloaterMap::handleStopTracking, this, _2));
 
@@ -255,7 +258,7 @@ void LLFloaterMap::reshape(S32 width, S32 height, BOOL called_from_parent)
 void LLFloaterMap::handleZoom(const LLSD& userdata)
 {
 	std::string level = userdata.asString();
-	
+
 	F32 scale = 0.0f;
 	if (level == std::string("close"))
 		scale = LLNetMap::MAP_SCALE_MAX;
diff --git a/indra/newview/llnetmap.cpp b/indra/newview/llnetmap.cpp
index e4a96cca145bfcd913cd53e02c9edbe43cae8c85..12f77313d9d2bd979ee5443db3d14e3eaee45f0b 100644
--- a/indra/newview/llnetmap.cpp
+++ b/indra/newview/llnetmap.cpp
@@ -55,6 +55,7 @@
 #include "llviewermenu.h"
 #include "llviewerobjectlist.h"
 #include "llviewerregion.h"
+#include "llviewerwindow.h"
 #include "llworld.h"
 #include "llworldmapview.h"		// shared draw code
 
@@ -69,6 +70,7 @@ const F32 MAP_SCALE_ZOOM_FACTOR = 1.04f; // Zoom in factor per click of scroll w
 const F32 MIN_DOT_RADIUS = 3.5f;
 const F32 DOT_SCALE = 0.75f;
 const F32 MIN_PICK_SCALE = 2.f;
+const S32 MOUSE_DRAG_SLOP = 2;		// How far the mouse needs to move before we think it's a drag
 
 LLNetMap::LLNetMap (const Params & p)
 :	LLUICtrl (p),
@@ -77,11 +79,12 @@ LLNetMap::LLNetMap (const Params & p)
 	mPixelsPerMeter( MAP_SCALE_MID / REGION_WIDTH_METERS ),
 	mObjectMapTPM(0.f),
 	mObjectMapPixels(0.f),
-	mTargetPanX(0.f),
-	mTargetPanY(0.f),
-	mCurPanX(0.f),
-	mCurPanY(0.f),
-	mUpdateNow(FALSE),
+	mTargetPan(0.f, 0.f),
+	mCurPan(0.f, 0.f),
+	mStartPan(0.f, 0.f),
+	mMouseDown(0, 0),
+	mPanning(false),
+	mUpdateNow(false),
 	mObjectImageCenterGlobal( gAgentCamera.getCameraPositionGlobal() ),
 	mObjectRawImagep(),
 	mObjectImagep(),
@@ -98,7 +101,9 @@ LLNetMap::~LLNetMap()
 
 void LLNetMap::setScale( F32 scale )
 {
-	mScale = llclamp(scale, 0.1f, 16.f*1024.f); // [reasonably small , unreasonably large]
+	scale = llclamp(scale, MAP_SCALE_MIN, MAP_SCALE_MAX);
+	mCurPan *= scale / mScale;
+	mScale = scale;
 	
 	if (mObjectImagep.notNull())
 	{
@@ -115,13 +120,7 @@ void LLNetMap::setScale( F32 scale )
 	mPixelsPerMeter = mScale / REGION_WIDTH_METERS;
 	mDotRadius = llmax(DOT_SCALE * mPixelsPerMeter, MIN_DOT_RADIUS);
 
-	mUpdateNow = TRUE;
-}
-
-void LLNetMap::translatePan( F32 delta_x, F32 delta_y )
-{
-	mTargetPanX += delta_x;
-	mTargetPanY += delta_y;
+	mUpdateNow = true;
 }
 
 
@@ -141,9 +140,12 @@ void LLNetMap::draw()
 	{
 		createObjectImage();
 	}
-	
-	mCurPanX = lerp(mCurPanX, mTargetPanX, LLCriticalDamp::getInterpolant(0.1f));
-	mCurPanY = lerp(mCurPanY, mTargetPanY, LLCriticalDamp::getInterpolant(0.1f));
+
+	static LLUICachedControl<bool> auto_center("MiniMapAutoCenter", true);
+	if (auto_center)
+	{
+		mCurPan = lerp(mCurPan, mTargetPan, LLCriticalDamp::getInterpolant(0.1f));
+	}
 
 	// Prepare a scissor region
 	F32 rotation = 0;
@@ -174,8 +176,8 @@ void LLNetMap::draw()
 		}
 
 		// region 0,0 is in the middle
-		S32 center_sw_left = getRect().getWidth() / 2 + llfloor(mCurPanX);
-		S32 center_sw_bottom = getRect().getHeight() / 2 + llfloor(mCurPanY);
+		S32 center_sw_left = getRect().getWidth() / 2 + llfloor(mCurPan.mV[VX]);
+		S32 center_sw_bottom = getRect().getHeight() / 2 + llfloor(mCurPan.mV[VY]);
 
 		gGL.pushMatrix();
 
@@ -256,26 +258,24 @@ void LLNetMap::draw()
 			}
 			gGL.setAlphaRejectSettings(LLRender::CF_DEFAULT);
 		}
-		
-
-		LLVector3d old_center = mObjectImageCenterGlobal;
-		LLVector3d new_center = gAgentCamera.getCameraPositionGlobal();
-
-		new_center.mdV[0] = (5.f/mObjectMapTPM)*floor(0.2f*mObjectMapTPM*new_center.mdV[0]);
-		new_center.mdV[1] = (5.f/mObjectMapTPM)*floor(0.2f*mObjectMapTPM*new_center.mdV[1]);
-		new_center.mdV[2] = 0.f;
 
+		// Redraw object layer periodically
 		if (mUpdateNow || (map_timer.getElapsedTimeF32() > 0.5f))
 		{
-			mUpdateNow = FALSE;
-			mObjectImageCenterGlobal = new_center;
+			mUpdateNow = false;
+
+			// Locate the centre of the object layer, accounting for panning
+			LLVector3 new_center = globalPosToView(gAgentCamera.getCameraPositionGlobal());
+			new_center.mV[VX] -= mCurPan.mV[VX];
+			new_center.mV[VY] -= mCurPan.mV[VY];
+			new_center.mV[VZ] = 0.f;
+			mObjectImageCenterGlobal = viewPosToGlobal(new_center.mV[VX], new_center.mV[VY]);
 
-			// Center moved enough.
 			// Create the base texture.
 			U8 *default_texture = mObjectRawImagep->getData();
 			memset( default_texture, 0, mObjectImagep->getWidth() * mObjectImagep->getHeight() * mObjectImagep->getComponents() );
 
-			// Draw buildings
+			// Draw objects
 			gObjectList.renderObjectsForMap(*this);
 
 			mObjectImagep->setSubImage(mObjectRawImagep, 0, 0, mObjectImagep->getWidth(), mObjectImagep->getHeight());
@@ -392,12 +392,16 @@ void LLNetMap::draw()
 		// Draw dot for self avatar position
 		pos_global = gAgent.getPositionGlobal();
 		pos_map = globalPosToView(pos_global);
-		LLUIImagePtr you = LLWorldMapView::sAvatarYouLargeImage;
 		S32 dot_width = llround(mDotRadius * 2.f);
-		you->draw(llround(pos_map.mV[VX] - mDotRadius),
-				  llround(pos_map.mV[VY] - mDotRadius),
-				  dot_width,
-				  dot_width);
+		LLUIImagePtr you = LLWorldMapView::sAvatarYouLargeImage;
+		if (you)
+		{
+			you->draw(llround(pos_map.mV[VX] - mDotRadius),
+					  llround(pos_map.mV[VY] - mDotRadius),
+					  dot_width,
+					  dot_width);
+		}
+
 
 		// Draw frustum
 		F32 meters_to_pixels = mScale/ LLWorld::getInstance()->getRegionWidthInMeters();
@@ -472,8 +476,8 @@ LLVector3 LLNetMap::globalPosToView( const LLVector3d& global_pos )
 		pos_local.rotVec( rot );
 	}
 
-	pos_local.mV[VX] += getRect().getWidth() / 2 + mCurPanX;
-	pos_local.mV[VY] += getRect().getHeight() / 2 + mCurPanY;
+	pos_local.mV[VX] += getRect().getWidth() / 2 + mCurPan.mV[VX];
+	pos_local.mV[VY] += getRect().getHeight() / 2 + mCurPan.mV[VY];
 
 	return pos_local;
 }
@@ -506,8 +510,8 @@ void LLNetMap::drawTracking(const LLVector3d& pos_global, const LLColor4& color,
 
 LLVector3d LLNetMap::viewPosToGlobal( S32 x, S32 y )
 {
-	x -= llround(getRect().getWidth() / 2 + mCurPanX);
-	y -= llround(getRect().getHeight() / 2 + mCurPanY);
+	x -= llround(getRect().getWidth() / 2 + mCurPan.mV[VX]);
+	y -= llround(getRect().getHeight() / 2 + mCurPan.mV[VY]);
 
 	LLVector3 pos_local( (F32)x, (F32)y, 0 );
 
@@ -532,10 +536,20 @@ LLVector3d LLNetMap::viewPosToGlobal( S32 x, S32 y )
 BOOL LLNetMap::handleScrollWheel(S32 x, S32 y, S32 clicks)
 {
 	// note that clicks are reversed from what you'd think: i.e. > 0  means zoom out, < 0 means zoom in
-	F32 scale = mScale;
-        
-	scale *= pow(MAP_SCALE_ZOOM_FACTOR, -clicks);
-	setScale(llclamp(scale, MAP_SCALE_MIN, MAP_SCALE_MAX));
+	F32 new_scale = mScale * pow(MAP_SCALE_ZOOM_FACTOR, -clicks);
+	F32 old_scale = mScale;
+
+	setScale(new_scale);
+
+	static LLUICachedControl<bool> auto_center("MiniMapAutoCenter", true);
+	if (!auto_center)
+	{
+		// Adjust pan to center the zoom on the mouse pointer
+		LLVector2 zoom_offset;
+		zoom_offset.mV[VX] = x - getRect().getWidth() / 2;
+		zoom_offset.mV[VY] = y - getRect().getHeight() / 2;
+		mCurPan -= zoom_offset * mScale / old_scale - zoom_offset;
+	}
 
 	return TRUE;
 }
@@ -715,5 +729,99 @@ void LLNetMap::createObjectImage()
 		mObjectImagep = LLViewerTextureManager::getLocalTexture( mObjectRawImagep.get(), FALSE);
 	}
 	setScale(mScale);
-	mUpdateNow = TRUE;
+	mUpdateNow = true;
+}
+
+BOOL LLNetMap::handleMouseDown( S32 x, S32 y, MASK mask )
+{
+	if (!(mask & MASK_SHIFT)) return FALSE;
+
+	// Start panning
+	gFocusMgr.setMouseCapture(this);
+
+	mStartPan = mCurPan;
+	mMouseDown.mX = x;
+	mMouseDown.mY = y;
+	return TRUE;
+}
+
+BOOL LLNetMap::handleMouseUp( S32 x, S32 y, MASK mask )
+{
+	if (hasMouseCapture())
+	{
+		if (mPanning)
+		{
+			// restore mouse cursor
+			S32 local_x, local_y;
+			local_x = mMouseDown.mX + llfloor(mCurPan.mV[VX] - mStartPan.mV[VX]);
+			local_y = mMouseDown.mY + llfloor(mCurPan.mV[VY] - mStartPan.mV[VY]);
+			LLRect clip_rect = getRect();
+			clip_rect.stretch(-8);
+			clip_rect.clipPointToRect(mMouseDown.mX, mMouseDown.mY, local_x, local_y);
+			LLUI::setMousePositionLocal(this, local_x, local_y);
+
+			// finish the pan
+			mPanning = false;
+
+			mMouseDown.set(0, 0);
+
+			// auto centre
+			mTargetPan.setZero();
+		}
+		gViewerWindow->showCursor();
+		gFocusMgr.setMouseCapture(NULL);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+// static
+bool LLNetMap::outsideSlop( S32 x, S32 y, S32 start_x, S32 start_y, S32 slop )
+{
+	S32 dx = x - start_x;
+	S32 dy = y - start_y;
+
+	return (dx <= -slop || slop <= dx || dy <= -slop || slop <= dy);
+}
+
+BOOL LLNetMap::handleHover( S32 x, S32 y, MASK mask )
+{
+	if (hasMouseCapture())
+	{
+		if (mPanning || outsideSlop(x, y, mMouseDown.mX, mMouseDown.mY, MOUSE_DRAG_SLOP))
+		{
+			if (!mPanning)
+			{
+				// just started panning, so hide cursor
+				mPanning = true;
+				gViewerWindow->hideCursor();
+			}
+
+			LLVector2 delta(static_cast<F32>(gViewerWindow->getCurrentMouseDX()),
+							static_cast<F32>(gViewerWindow->getCurrentMouseDY()));
+
+			// Set pan to value at start of drag + offset
+			mCurPan += delta;
+			mTargetPan = mCurPan;
+
+			gViewerWindow->moveCursorToCenter();
+		}
+
+		// Doesn't really matter, cursor should be hidden
+		gViewerWindow->setCursor( UI_CURSOR_TOOLPAN );
+	}
+	else
+	{
+		if (mask & MASK_SHIFT)
+		{
+			// If shift is held, change the cursor to hint that the map can be dragged
+			gViewerWindow->setCursor( UI_CURSOR_TOOLPAN );
+		}
+		else
+		{
+			gViewerWindow->setCursor( UI_CURSOR_CROSS );
+		}
+	}
+
+	return TRUE;
 }
diff --git a/indra/newview/llnetmap.h b/indra/newview/llnetmap.h
index 6808642505824fd0f2ea82b932007d46499d5d0c..c5860ca82ce665fa06aab5490c338d706dc9102d 100644
--- a/indra/newview/llnetmap.h
+++ b/indra/newview/llnetmap.h
@@ -37,7 +37,6 @@
 class LLColor4U;
 class LLCoordGL;
 class LLImageRaw;
-class LLTextBox;
 class LLViewerTexture;
 
 class LLNetMap : public LLUICtrl
@@ -66,17 +65,17 @@ public:
 
 	/*virtual*/ void	draw();
 	/*virtual*/ BOOL	handleScrollWheel(S32 x, S32 y, S32 clicks);
+	/*virtual*/ BOOL	handleMouseDown(S32 x, S32 y, MASK mask);
+	/*virtual*/ BOOL	handleMouseUp(S32 x, S32 y, MASK mask);
+	/*virtual*/ BOOL	handleHover( S32 x, S32 y, MASK mask );
 	/*virtual*/ BOOL	handleToolTip( S32 x, S32 y, MASK mask);
 	/*virtual*/ void	reshape(S32 width, S32 height, BOOL called_from_parent = TRUE);
 	
 	void			setScale( F32 scale );
 	void			setToolTipMsg(const std::string& msg) { mToolTipMsg = msg; }
 	void			renderScaledPointGlobal( const LLVector3d& pos, const LLColor4U &color, F32 radius );
-	
-private:
-	void			translatePan( F32 delta_x, F32 delta_y );
-	void			setPan( F32 x, F32 y )			{ mTargetPanX = x; mTargetPanY = y; }
 
+private:
 	const LLVector3d& getObjectImageCenterGlobal()	{ return mObjectImageCenterGlobal; }
 	void 			renderPoint(const LLVector3 &pos, const LLColor4U &color, 
 								S32 diameter, S32 relative_height = 0);
@@ -91,6 +90,10 @@ private:
 	void			createObjectImage();
 
 private:
+	static bool		outsideSlop(S32 x, S32 y, S32 start_x, S32 start_y, S32 slop);
+
+	bool			mUpdateNow;
+
 	LLUIColor		mBackgroundColor;
 
 	F32				mScale;					// Size of a region in pixels
@@ -98,11 +101,13 @@ private:
 	F32				mObjectMapTPM;			// texels per meter on map
 	F32				mObjectMapPixels;		// Width of object map in pixels
 	F32				mDotRadius;				// Size of avatar markers
-	F32				mTargetPanX;
-	F32				mTargetPanY;
-	F32				mCurPanX;
-	F32				mCurPanY;
-	BOOL			mUpdateNow;
+
+	bool			mPanning;			// map is being dragged
+	LLVector2		mTargetPan;
+	LLVector2		mCurPan;
+	LLVector2		mStartPan;		// pan offset at start of drag
+	LLCoordGL		mMouseDown;			// pointer position at start of drag
+
 	LLVector3d		mObjectImageCenterGlobal;
 	LLPointer<LLImageRaw> mObjectRawImagep;
 	LLPointer<LLViewerTexture>	mObjectImagep;
diff --git a/indra/newview/skins/default/xui/en/floater_map.xml b/indra/newview/skins/default/xui/en/floater_map.xml
index efd96624ab0026d0cbf298824050fd6fadbca19e..5495e1101568ddbcc561d9950ad7dde7e7dada50 100644
--- a/indra/newview/skins/default/xui/en/floater_map.xml
+++ b/indra/newview/skins/default/xui/en/floater_map.xml
@@ -7,8 +7,8 @@
  follows="top|right"
  height="174"
  layout="topleft"
- min_height="174"
- min_width="174"
+ min_height="128"
+ min_width="128"
  name="Map"
  title=""
  help_topic="map"
@@ -18,41 +18,9 @@
  left="0"
  top="0"
  width="200">
-    <floater.string
-     name="mini_map_north">
-        N
-    </floater.string>
-    <floater.string
-     name="mini_map_east">
-        E
-    </floater.string>
-    <floater.string
-     name="mini_map_west">
-        W
-    </floater.string>
-    <floater.string
-     name="mini_map_south">
-        S
-    </floater.string>
-    <floater.string
-     name="mini_map_southeast">
-        SE
-    </floater.string>
-    <floater.string
-     name="mini_map_northeast">
-        NE
-    </floater.string>
-    <floater.string
-     name="mini_map_southwest">
-        SW
-    </floater.string>
-    <floater.string
-     name="mini_map_northwest">
-        NW
-    </floater.string>
     <floater.string
      name="ToolTipMsg">
-        [AGENT][REGION](Double-click to open Map)
+        [AGENT][REGION](Double-click to open Map, shift-drag to pan)
     </floater.string>
     <floater.string name="mini_map_caption">
 	MINIMAP
diff --git a/indra/newview/skins/default/xui/en/menu_mini_map.xml b/indra/newview/skins/default/xui/en/menu_mini_map.xml
index f5ea3e735bb86d025340e6c92e0299d2362522f3..8fe89d39343b3b75139bd74ca3ef921f7f4054e4 100644
--- a/indra/newview/skins/default/xui/en/menu_mini_map.xml
+++ b/indra/newview/skins/default/xui/en/menu_mini_map.xml
@@ -29,6 +29,7 @@
          function="Minimap.Zoom"
          parameter="far" />
     </menu_item_call>
+    <menu_item_separator />
     <menu_item_check
        label="Rotate Map"
        name="Rotate Map">
@@ -38,6 +39,15 @@
              function="ToggleControl"
              parameter="MiniMapRotate" />
     </menu_item_check>
+    <menu_item_check
+       label="Auto Center"
+       name="Auto Center">
+          <menu_item_check.on_check
+             control="MiniMapAutoCenter" />
+          <menu_item_check.on_click
+             function="ToggleControl"
+             parameter="MiniMapAutoCenter" />
+    </menu_item_check>
     <menu_item_separator />
     <menu_item_call
      label="Stop Tracking"