diff --git a/package-lock.json b/package-lock.json
index 4471d4f357f..d4d2123367d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -209,9 +209,9 @@
       }
     },
     "@plotly/d3": {
-      "version": "3.6.1",
-      "resolved": "https://registry.npmjs.org/@plotly/d3/-/d3-3.6.1.tgz",
-      "integrity": "sha512-lM2dmUqRX1qGtrWczC7QNbQ4Bdgp9sII9i7NV6Hokw06kLH1++x0Ehlj193+PkLYvi1us1IcYjC7IIw+h6GywA=="
+      "version": "3.7.0",
+      "resolved": "https://registry.npmjs.org/@plotly/d3/-/d3-3.7.0.tgz",
+      "integrity": "sha512-uSVIiWXmc1RKmVXOLAR8wS6xuTvLsOPkB+ZSDernOOc774zG/3hX91qsaXWOQXQJkdPNCMFM4uSFGiBcM6DsnA=="
     },
     "@plotly/d3-sankey": {
       "version": "0.7.2",
@@ -2457,6 +2457,25 @@
         "d3-timer": "1"
       }
     },
+    "d3-geo": {
+      "version": "1.12.1",
+      "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.1.tgz",
+      "integrity": "sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==",
+      "requires": {
+        "d3-array": "1"
+      }
+    },
+    "d3-geo-projection": {
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-2.9.0.tgz",
+      "integrity": "sha512-ZULvK/zBn87of5rWAfFMc9mJOipeSo57O+BBitsKIXmU4rTVAnX1kSsJkE0R+TxY8pGNoM1nbyRRE7GYHhdOEQ==",
+      "requires": {
+        "commander": "2",
+        "d3-array": "1",
+        "d3-geo": "^1.12.0",
+        "resolve": "^1.1.10"
+      }
+    },
     "d3-hierarchy": {
       "version": "1.1.9",
       "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz",
diff --git a/package.json b/package.json
index 6f254075b21..27688415040 100644
--- a/package.json
+++ b/package.json
@@ -62,7 +62,7 @@
     ]
   },
   "dependencies": {
-    "@plotly/d3": "^3.6.1",
+    "@plotly/d3": "3.7.0",
     "@plotly/d3-sankey": "0.7.2",
     "@plotly/d3-sankey-circular": "0.33.1",
     "@plotly/point-cluster": "^3.1.9",
@@ -78,6 +78,8 @@
     "convex-hull": "^1.0.3",
     "country-regex": "^1.1.0",
     "d3-force": "^1.2.1",
+    "d3-geo": "^1.12.1",
+    "d3-geo-projection": "^2.9.0",
     "d3-hierarchy": "^1.1.9",
     "d3-interpolate": "^1.4.0",
     "d3-time-format": "^2.2.3",
diff --git a/src/plots/geo/constants.js b/src/plots/geo/constants.js
index ccd39c19298..9e92f65f352 100644
--- a/src/plots/geo/constants.js
+++ b/src/plots/geo/constants.js
@@ -2,7 +2,6 @@
 
 // projection names to d3 function name
 exports.projNames = {
-    // d3.geo.projection
     'equirectangular': 'equirectangular',
     'mercator': 'mercator',
     'orthographic': 'orthographic',
@@ -24,7 +23,107 @@ exports.projNames = {
     'albers usa': 'albersUsa',
     'winkel tripel': 'winkel3',
     'aitoff': 'aitoff',
-    'sinusoidal': 'sinusoidal'
+    'sinusoidal': 'sinusoidal',
+/*
+    // potential projections that could be added to the API
+
+    'airy': 'airy',
+    // 'albers': 'albers',
+    'armadillo': 'armadillo',
+    'august': 'august',
+    'baker': 'baker',
+    'berghaus': 'berghaus',
+    'bertin1953': 'bertin1953',
+    'boggs': 'boggs',
+    'bonne': 'bonne',
+    'bottomley': 'bottomley',
+    'bromley': 'bromley',
+    // 'chamberlin': 'chamberlin',
+    'chamberlin africa': 'chamberlinAfrica',
+    'collignon': 'collignon',
+    'craig': 'craig',
+    'craster': 'craster',
+    'cylindrical equal area': 'cylindricalEqualArea',
+    'cylindrical stereographic': 'cylindricalStereographic',
+    'eckert1': 'eckert1',
+    'eckert2': 'eckert2',
+    'eckert3': 'eckert3',
+    'eckert5': 'eckert5',
+    'eckert6': 'eckert6',
+    'eisenlohr': 'eisenlohr',
+    'fahey': 'fahey',
+    'foucaut': 'foucaut',
+    'foucaut sinusoidal': 'foucautSinusoidal',
+    'gilbert': 'gilbert',
+    'gingery': 'gingery',
+    'ginzburg4': 'ginzburg4',
+    'ginzburg5': 'ginzburg5',
+    'ginzburg6': 'ginzburg6',
+    'ginzburg8': 'ginzburg8',
+    'ginzburg9': 'ginzburg9',
+    'gringorten': 'gringorten',
+    'guyou': 'guyou',
+    'hammer retroazimuthal': 'hammerRetroazimuthal',
+    'healpix': 'healpix',
+    'hill': 'hill',
+    'homolosine': 'homolosine',
+    'hufnagel': 'hufnagel',
+    'hyperelliptical': 'hyperelliptical',
+    'lagrange': 'lagrange',
+    'larrivee': 'larrivee',
+    'laskowski': 'laskowski',
+    'littrow': 'littrow',
+    'loximuthal': 'loximuthal',
+    // 'modified stereographic': 'modifiedStereographic',
+    'modified stereographic alaska': 'modifiedStereographicAlaska',
+    'modified stereographic gs48': 'modifiedStereographicGs48',
+    'modified stereographic gs50': 'modifiedStereographicGs50',
+    'modified stereographic miller': 'modifiedStereographicMiller',
+    'modified stereographic lee': 'modifiedStereographicLee',
+    'mt flat polar parabolic': 'mtFlatPolarParabolic',
+    'mt flat polar quartic': 'mtFlatPolarQuartic',
+    'mt flat polar sinusoidal': 'mtFlatPolarSinusoidal',
+    'natural earth1': 'naturalEarth1',
+    'natural earth2': 'naturalEarth2',
+    'nell hammer': 'nellHammer',
+    'nicolosi': 'nicolosi',
+    'patterson': 'patterson',
+    'polyconic': 'polyconic',
+    'rectangular polyconic': 'rectangularPolyconic',
+    'satellite': 'satellite',
+    'sinu mollweide': 'sinuMollweide',
+    'times': 'times',
+    // 'two point azimuthal': 'twoPointAzimuthal',
+    // 'two point azimuthalUsa': 'twoPointAzimuthalUsa',
+    // 'two point equidistant': 'twoPointEquidistant',
+    // 'two point equidistantUsa': 'twoPointEquidistantUsa',
+    'van der grinten': 'vanDerGrinten',
+    'van der grinten2': 'vanDerGrinten2',
+    'van der grinten3': 'vanDerGrinten3',
+    'van der grinten4': 'vanDerGrinten4',
+    // 'wagner': 'wagner',
+    'wagner4': 'wagner4',
+    'wagner6': 'wagner6',
+    // 'wagner7': 'wagner7',
+    'wiechel': 'wiechel',
+    'winkel3': 'winkel3',
+
+    // 'interrupt': 'interrupt',
+    'interrupted homolosine': 'interruptedHomolosine',
+    'interrupted sinusoidal': 'interruptedSinusoidal',
+    'interrupted boggs': 'interruptedBoggs',
+    'interrupted sinu mollweide': 'interruptedSinuMollweide',
+    'interrupted mollweide': 'interruptedMollweide',
+    'interrupted mollweide hemispheres': 'interruptedMollweideHemispheres',
+    'interrupted quartic authalic': 'interruptedQuarticAuthalic',
+
+    'polyhedral butterfly': 'polyhedralButterfly',
+    'polyhedral collignon': 'polyhedralCollignon',
+    'polyhedral waterman': 'polyhedralWaterman',
+
+    'gringorten quincuncial': 'gringortenQuincuncial',
+    'peirce quincuncial': 'peirceQuincuncial',
+*/
 };
 
 // name of the axes
diff --git a/src/plots/geo/geo.js b/src/plots/geo/geo.js
index 5ed53ca379f..0e44c35be81 100644
--- a/src/plots/geo/geo.js
+++ b/src/plots/geo/geo.js
@@ -3,6 +3,10 @@
 /* global PlotlyGeoAssets:false */
 
 var d3 = require('@plotly/d3');
+var geo = require('d3-geo');
+var geoPath = geo.geoPath;
+var geoDistance = geo.geoDistance;
+var geoProjection = require('d3-geo-projection');
 
 var Registry = require('../../registry');
 var Lib = require('../../lib');
@@ -25,8 +29,6 @@ var geoUtils = require('../../lib/geo_location_utils');
 var topojsonUtils = require('../../lib/topojson_utils');
 var topojsonFeature = require('topojson-client').feature;
 
-require('./projections')(d3);
-
 function Geo(opts) {
     this.id = opts.id;
     this.graphDiv = opts.graphDiv;
@@ -247,29 +249,6 @@ proto.updateProjection = function(geoCalcData, fullLayout) {
     var s = this.fitScale = projection.scale();
     var t = projection.translate();
 
-    if(
-        !isFinite(b[0][0]) || !isFinite(b[0][1]) ||
-        !isFinite(b[1][0]) || !isFinite(b[1][1]) ||
-        isNaN(t[0]) || isNaN(t[0])
-    ) {
-        var attrToUnset = ['fitbounds', 'projection.rotation', 'center', 'lonaxis.range', 'lataxis.range'];
-        var msg = 'Invalid geo settings, relayout\'ing to default view.';
-        var updateObj = {};
-
-        // clear all attributes that could cause invalid bounds,
-        // clear viewInitial to update reset-view behavior
-
-        for(var i = 0; i < attrToUnset.length; i++) {
-            updateObj[this.id + '.' + attrToUnset[i]] = null;
-        }
-
-        this.viewInitial = null;
-
-        Lib.warn(msg);
-        gd._promises.push(Registry.call('relayout', gd, updateObj));
-        return msg;
-    }
-
     if(geoLayout.fitbounds) {
         var b2 = projection.getBounds(makeRangeBox(axLon.range, axLat.range));
         var k2 = Math.min(
@@ -508,7 +487,7 @@ proto.updateFx = function(fullLayout, geoLayout) {
     bgRect.on('mousemove', function() {
         var lonlat = _this.projection.invert(Lib.getPositionFromD3Event());
 
-        if(!lonlat || isNaN(lonlat[0]) || isNaN(lonlat[1])) {
+        if(!lonlat) {
             return dragElement.unhover(gd, d3.event);
         }
 
@@ -648,9 +627,8 @@ proto.render = function() {
     }
 };
 
-// Helper that wraps d3.geo[/* projection name /*]() which:
+// Helper that wraps d3[geo + /* Projection name /*]() which:
 //
-// - adds 'fitExtent' (available in d3 v4)
 // - adds 'getPath', 'getBounds' convenience methods
 // - scopes logic related to 'clipAngle'
 // - adds 'isLonLatOverEdges' method
@@ -663,7 +641,11 @@ function getProjection(geoLayout) {
     var projLayout = geoLayout.projection;
     var projType = projLayout.type;
 
-    var projection = d3.geo[constants.projNames[projType]]();
+    var projName = constants.projNames[projType];
+    // uppercase the first letter and add geo to the start of method name
+    projName = 'geo' + projName.charAt(0).toUpperCase() + projName.slice(1);
+    var projFn = geo[projName] || geoProjection[projName];
+    var projection = projFn();
 
     var clipAngle = geoLayout._isClipped ?
         constants.lonaxisSpan[projType] / 2 :
@@ -686,7 +668,7 @@ function getProjection(geoLayout) {
 
         if(clipAngle) {
             var r = projection.rotate();
-            var angle = d3.geo.distance(lonlat, [-r[0], -r[1]]);
+            var angle = geoDistance(lonlat, [-r[0], -r[1]]);
             var maxAngle = clipAngle * Math.PI / 180;
             return angle > maxAngle;
         } else {
@@ -695,38 +677,13 @@ function getProjection(geoLayout) {
     };
 
     projection.getPath = function() {
-        return d3.geo.path().projection(projection);
+        return geoPath().projection(projection);
     };
 
     projection.getBounds = function(object) {
         return projection.getPath().bounds(object);
     };
 
-    // adapted from d3 v4:
-    // https://github.com/d3/d3-geo/blob/master/src/projection/fit.js
-    projection.fitExtent = function(extent, object) {
-        var w = extent[1][0] - extent[0][0];
-        var h = extent[1][1] - extent[0][1];
-        var clip = projection.clipExtent && projection.clipExtent();
-
-        projection
-            .scale(150)
-            .translate([0, 0]);
-
-        if(clip) projection.clipExtent(null);
-
-        var b = projection.getBounds(object);
-        var k = Math.min(w / (b[1][0] - b[0][0]), h / (b[1][1] - b[0][1]));
-        var x = +extent[0][0] + (w - k * (b[1][0] + b[0][0])) / 2;
-        var y = +extent[0][1] + (h - k * (b[1][1] + b[0][1])) / 2;
-
-        if(clip) projection.clipExtent(clip);
-
-        return projection
-            .scale(k * 150)
-            .translate([x, y]);
-    };
-
     projection.precision(constants.precision);
 
     if(clipAngle) {
diff --git a/src/plots/geo/projections.js b/src/plots/geo/projections.js
deleted file mode 100644
index 45698e2bf08..00000000000
--- a/src/plots/geo/projections.js
+++ /dev/null
@@ -1,436 +0,0 @@
-/*
- * Generated by https://github.com/etpinard/d3-geo-projection-picker
- *
- * which is hand-picks projection from https://github.com/d3/d3-geo-projection
- *
- * into a CommonJS require-able module.
- */
-
-'use strict';
-
-/* eslint-disable */
-
-function addProjectionsToD3(d3) {
-  d3.geo.project = function(object, projection) {
-    var stream = projection.stream;
-    if (!stream) throw new Error("not yet supported");
-    return (object && d3_geo_projectObjectType.hasOwnProperty(object.type) ? d3_geo_projectObjectType[object.type] : d3_geo_projectGeometry)(object, stream);
-  };
-  function d3_geo_projectFeature(object, stream) {
-    return {
-      type: "Feature",
-      id: object.id,
-      properties: object.properties,
-      geometry: d3_geo_projectGeometry(object.geometry, stream)
-    };
-  }
-  function d3_geo_projectGeometry(geometry, stream) {
-    if (!geometry) return null;
-    if (geometry.type === "GeometryCollection") return {
-      type: "GeometryCollection",
-      geometries: object.geometries.map(function(geometry) {
-        return d3_geo_projectGeometry(geometry, stream);
-      })
-    };
-    if (!d3_geo_projectGeometryType.hasOwnProperty(geometry.type)) return null;
-    var sink = d3_geo_projectGeometryType[geometry.type];
-    d3.geo.stream(geometry, stream(sink));
-    return sink.result();
-  }
-  var d3_geo_projectObjectType = {
-    Feature: d3_geo_projectFeature,
-    FeatureCollection: function(object, stream) {
-      return {
-        type: "FeatureCollection",
-        features: object.features.map(function(feature) {
-          return d3_geo_projectFeature(feature, stream);
-        })
-      };
-    }
-  };
-  var d3_geo_projectPoints = [], d3_geo_projectLines = [];
-  var d3_geo_projectPoint = {
-    point: function(x, y) {
-      d3_geo_projectPoints.push([ x, y ]);
-    },
-    result: function() {
-      var result = !d3_geo_projectPoints.length ? null : d3_geo_projectPoints.length < 2 ? {
-        type: "Point",
-        coordinates: d3_geo_projectPoints[0]
-      } : {
-        type: "MultiPoint",
-        coordinates: d3_geo_projectPoints
-      };
-      d3_geo_projectPoints = [];
-      return result;
-    }
-  };
-  var d3_geo_projectLine = {
-    lineStart: d3_geo_projectNoop,
-    point: function(x, y) {
-      d3_geo_projectPoints.push([ x, y ]);
-    },
-    lineEnd: function() {
-      if (d3_geo_projectPoints.length) d3_geo_projectLines.push(d3_geo_projectPoints),
-      d3_geo_projectPoints = [];
-    },
-    result: function() {
-      var result = !d3_geo_projectLines.length ? null : d3_geo_projectLines.length < 2 ? {
-        type: "LineString",
-        coordinates: d3_geo_projectLines[0]
-      } : {
-        type: "MultiLineString",
-        coordinates: d3_geo_projectLines
-      };
-      d3_geo_projectLines = [];
-      return result;
-    }
-  };
-  var d3_geo_projectPolygon = {
-    polygonStart: d3_geo_projectNoop,
-    lineStart: d3_geo_projectNoop,
-    point: function(x, y) {
-      d3_geo_projectPoints.push([ x, y ]);
-    },
-    lineEnd: function() {
-      var n = d3_geo_projectPoints.length;
-      if (n) {
-        do d3_geo_projectPoints.push(d3_geo_projectPoints[0].slice()); while (++n < 4);
-        d3_geo_projectLines.push(d3_geo_projectPoints), d3_geo_projectPoints = [];
-      }
-    },
-    polygonEnd: d3_geo_projectNoop,
-    result: function() {
-      if (!d3_geo_projectLines.length) return null;
-      var polygons = [], holes = [];
-      d3_geo_projectLines.forEach(function(ring) {
-        if (d3_geo_projectClockwise(ring)) polygons.push([ ring ]); else holes.push(ring);
-      });
-      holes.forEach(function(hole) {
-        var point = hole[0];
-        polygons.some(function(polygon) {
-          if (d3_geo_projectContains(polygon[0], point)) {
-            polygon.push(hole);
-            return true;
-          }
-        }) || polygons.push([ hole ]);
-      });
-      d3_geo_projectLines = [];
-      return !polygons.length ? null : polygons.length > 1 ? {
-        type: "MultiPolygon",
-        coordinates: polygons
-      } : {
-        type: "Polygon",
-        coordinates: polygons[0]
-      };
-    }
-  };
-  var d3_geo_projectGeometryType = {
-    Point: d3_geo_projectPoint,
-    MultiPoint: d3_geo_projectPoint,
-    LineString: d3_geo_projectLine,
-    MultiLineString: d3_geo_projectLine,
-    Polygon: d3_geo_projectPolygon,
-    MultiPolygon: d3_geo_projectPolygon,
-    Sphere: d3_geo_projectPolygon
-  };
-  function d3_geo_projectNoop() {}
-  function d3_geo_projectClockwise(ring) {
-    if ((n = ring.length) < 4) return false;
-    var i = 0, n, area = ring[n - 1][1] * ring[0][0] - ring[n - 1][0] * ring[0][1];
-    while (++i < n) area += ring[i - 1][1] * ring[i][0] - ring[i - 1][0] * ring[i][1];
-    return area <= 0;
-  }
-  function d3_geo_projectContains(ring, point) {
-    var x = point[0], y = point[1], contains = false;
-    for (var i = 0, n = ring.length, j = n - 1; i < n; j = i++) {
-      var pi = ring[i], xi = pi[0], yi = pi[1], pj = ring[j], xj = pj[0], yj = pj[1];
-      if (yi > y ^ yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi) contains = !contains;
-    }
-    return contains;
-  }
-  var ε = 1e-6, ε2 = ε * ε, π = Math.PI, halfπ = π / 2, sqrtπ = Math.sqrt(π), radians = π / 180, degrees = 180 / π;
-  function sinci(x) {
-    return x ? x / Math.sin(x) : 1;
-  }
-  function sgn(x) {
-    return x > 0 ? 1 : x < 0 ? -1 : 0;
-  }
-  function asin(x) {
-    return x > 1 ? halfπ : x < -1 ? -halfπ : Math.asin(x);
-  }
-  function acos(x) {
-    return x > 1 ? 0 : x < -1 ? π : Math.acos(x);
-  }
-  function asqrt(x) {
-    return x > 0 ? Math.sqrt(x) : 0;
-  }
-  var projection = d3.geo.projection, projectionMutator = d3.geo.projectionMutator;
-  d3.geo.interrupt = function(project) {
-    var lobes = [ [ [ [ -π, 0 ], [ 0, halfπ ], [ π, 0 ] ] ], [ [ [ -π, 0 ], [ 0, -halfπ ], [ π, 0 ] ] ] ];
-    var bounds;
-    function forward(λ, φ) {
-      var sign = φ < 0 ? -1 : +1, hemilobes = lobes[+(φ < 0)];
-      for (var i = 0, n = hemilobes.length - 1; i < n && λ > hemilobes[i][2][0]; ++i) ;
-      var coordinates = project(λ - hemilobes[i][1][0], φ);
-      coordinates[0] += project(hemilobes[i][1][0], sign * φ > sign * hemilobes[i][0][1] ? hemilobes[i][0][1] : φ)[0];
-      return coordinates;
-    }
-    function reset() {
-      bounds = lobes.map(function(hemilobes) {
-        return hemilobes.map(function(lobe) {
-          var x0 = project(lobe[0][0], lobe[0][1])[0], x1 = project(lobe[2][0], lobe[2][1])[0], y0 = project(lobe[1][0], lobe[0][1])[1], y1 = project(lobe[1][0], lobe[1][1])[1], t;
-          if (y0 > y1) t = y0, y0 = y1, y1 = t;
-          return [ [ x0, y0 ], [ x1, y1 ] ];
-        });
-      });
-    }
-    if (project.invert) forward.invert = function(x, y) {
-      var hemibounds = bounds[+(y < 0)], hemilobes = lobes[+(y < 0)];
-      for (var i = 0, n = hemibounds.length; i < n; ++i) {
-        var b = hemibounds[i];
-        if (b[0][0] <= x && x < b[1][0] && b[0][1] <= y && y < b[1][1]) {
-          var coordinates = project.invert(x - project(hemilobes[i][1][0], 0)[0], y);
-          coordinates[0] += hemilobes[i][1][0];
-          return pointEqual(forward(coordinates[0], coordinates[1]), [ x, y ]) ? coordinates : null;
-        }
-      }
-    };
-    var projection = d3.geo.projection(forward), stream_ = projection.stream;
-    projection.stream = function(stream) {
-      var rotate = projection.rotate(), rotateStream = stream_(stream), sphereStream = (projection.rotate([ 0, 0 ]),
-      stream_(stream));
-      projection.rotate(rotate);
-      rotateStream.sphere = function() {
-        d3.geo.stream(sphere(), sphereStream);
-      };
-      return rotateStream;
-    };
-    projection.lobes = function(_) {
-      if (!arguments.length) return lobes.map(function(lobes) {
-        return lobes.map(function(lobe) {
-          return [ [ lobe[0][0] * 180 / π, lobe[0][1] * 180 / π ], [ lobe[1][0] * 180 / π, lobe[1][1] * 180 / π ], [ lobe[2][0] * 180 / π, lobe[2][1] * 180 / π ] ];
-        });
-      });
-      lobes = _.map(function(lobes) {
-        return lobes.map(function(lobe) {
-          return [ [ lobe[0][0] * π / 180, lobe[0][1] * π / 180 ], [ lobe[1][0] * π / 180, lobe[1][1] * π / 180 ], [ lobe[2][0] * π / 180, lobe[2][1] * π / 180 ] ];
-        });
-      });
-      reset();
-      return projection;
-    };
-    function sphere() {
-      var ε = 1e-6, coordinates = [];
-      for (var i = 0, n = lobes[0].length; i < n; ++i) {
-        var lobe = lobes[0][i], λ0 = lobe[0][0] * 180 / π, φ0 = lobe[0][1] * 180 / π, φ1 = lobe[1][1] * 180 / π, λ2 = lobe[2][0] * 180 / π, φ2 = lobe[2][1] * 180 / π;
-        coordinates.push(resample([ [ λ0 + ε, φ0 + ε ], [ λ0 + ε, φ1 - ε ], [ λ2 - ε, φ1 - ε ], [ λ2 - ε, φ2 + ε ] ], 30));
-      }
-      for (var i = lobes[1].length - 1; i >= 0; --i) {
-        var lobe = lobes[1][i], λ0 = lobe[0][0] * 180 / π, φ0 = lobe[0][1] * 180 / π, φ1 = lobe[1][1] * 180 / π, λ2 = lobe[2][0] * 180 / π, φ2 = lobe[2][1] * 180 / π;
-        coordinates.push(resample([ [ λ2 - ε, φ2 - ε ], [ λ2 - ε, φ1 + ε ], [ λ0 + ε, φ1 + ε ], [ λ0 + ε, φ0 - ε ] ], 30));
-      }
-      return {
-        type: "Polygon",
-        coordinates: [ d3.merge(coordinates) ]
-      };
-    }
-    function resample(coordinates, m) {
-      var i = -1, n = coordinates.length, p0 = coordinates[0], p1, dx, dy, resampled = [];
-      while (++i < n) {
-        p1 = coordinates[i];
-        dx = (p1[0] - p0[0]) / m;
-        dy = (p1[1] - p0[1]) / m;
-        for (var j = 0; j < m; ++j) resampled.push([ p0[0] + j * dx, p0[1] + j * dy ]);
-        p0 = p1;
-      }
-      resampled.push(p1);
-      return resampled;
-    }
-    function pointEqual(a, b) {
-      return Math.abs(a[0] - b[0]) < ε && Math.abs(a[1] - b[1]) < ε;
-    }
-    return projection;
-  };
-  function eckert4(λ, φ) {
-    var k = (2 + halfπ) * Math.sin(φ);
-    φ /= 2;
-    for (var i = 0, δ = Infinity; i < 10 && Math.abs(δ) > ε; i++) {
-      var cosφ = Math.cos(φ);
-      φ -= δ = (φ + Math.sin(φ) * (cosφ + 2) - k) / (2 * cosφ * (1 + cosφ));
-    }
-    return [ 2 / Math.sqrt(π * (4 + π)) * λ * (1 + Math.cos(φ)), 2 * Math.sqrt(π / (4 + π)) * Math.sin(φ) ];
-  }
-  eckert4.invert = function(x, y) {
-    var A = .5 * y * Math.sqrt((4 + π) / π), k = asin(A), c = Math.cos(k);
-    return [ x / (2 / Math.sqrt(π * (4 + π)) * (1 + c)), asin((k + A * (c + 2)) / (2 + halfπ)) ];
-  };
-  (d3.geo.eckert4 = function() {
-    return projection(eckert4);
-  }).raw = eckert4;
-  var hammerAzimuthalEqualArea = d3.geo.azimuthalEqualArea.raw;
-  function hammer(A, B) {
-    if (arguments.length < 2) B = A;
-    if (B === 1) return hammerAzimuthalEqualArea;
-    if (B === Infinity) return hammerQuarticAuthalic;
-    function forward(λ, φ) {
-      var coordinates = hammerAzimuthalEqualArea(λ / B, φ);
-      coordinates[0] *= A;
-      return coordinates;
-    }
-    forward.invert = function(x, y) {
-      var coordinates = hammerAzimuthalEqualArea.invert(x / A, y);
-      coordinates[0] *= B;
-      return coordinates;
-    };
-    return forward;
-  }
-  function hammerProjection() {
-    var B = 2, m = projectionMutator(hammer), p = m(B);
-    p.coefficient = function(_) {
-      if (!arguments.length) return B;
-      return m(B = +_);
-    };
-    return p;
-  }
-  function hammerQuarticAuthalic(λ, φ) {
-    return [ λ * Math.cos(φ) / Math.cos(φ /= 2), 2 * Math.sin(φ) ];
-  }
-  hammerQuarticAuthalic.invert = function(x, y) {
-    var φ = 2 * asin(y / 2);
-    return [ x * Math.cos(φ / 2) / Math.cos(φ), φ ];
-  };
-  (d3.geo.hammer = hammerProjection).raw = hammer;
-  function kavrayskiy7(λ, φ) {
-    return [ 3 * λ / (2 * π) * Math.sqrt(π * π / 3 - φ * φ), φ ];
-  }
-  kavrayskiy7.invert = function(x, y) {
-    return [ 2 / 3 * π * x / Math.sqrt(π * π / 3 - y * y), y ];
-  };
-  (d3.geo.kavrayskiy7 = function() {
-    return projection(kavrayskiy7);
-  }).raw = kavrayskiy7;
-  function miller(λ, φ) {
-    return [ λ, 1.25 * Math.log(Math.tan(π / 4 + .4 * φ)) ];
-  }
-  miller.invert = function(x, y) {
-    return [ x, 2.5 * Math.atan(Math.exp(.8 * y)) - .625 * π ];
-  };
-  (d3.geo.miller = function() {
-    return projection(miller);
-  }).raw = miller;
-  function mollweideBromleyθ(Cp) {
-    return function(θ) {
-      var Cpsinθ = Cp * Math.sin(θ), i = 30, δ;
-      do θ -= δ = (θ + Math.sin(θ) - Cpsinθ) / (1 + Math.cos(θ)); while (Math.abs(δ) > ε && --i > 0);
-      return θ / 2;
-    };
-  }
-  function mollweideBromley(Cx, Cy, Cp) {
-    var θ = mollweideBromleyθ(Cp);
-    function forward(λ, φ) {
-      return [ Cx * λ * Math.cos(φ = θ(φ)), Cy * Math.sin(φ) ];
-    }
-    forward.invert = function(x, y) {
-      var θ = asin(y / Cy);
-      return [ x / (Cx * Math.cos(θ)), asin((2 * θ + Math.sin(2 * θ)) / Cp) ];
-    };
-    return forward;
-  }
-  var mollweideθ = mollweideBromleyθ(π), mollweide = mollweideBromley(Math.SQRT2 / halfπ, Math.SQRT2, π);
-  (d3.geo.mollweide = function() {
-    return projection(mollweide);
-  }).raw = mollweide;
-  function naturalEarth(λ, φ) {
-    var φ2 = φ * φ, φ4 = φ2 * φ2;
-    return [ λ * (.8707 - .131979 * φ2 + φ4 * (-.013791 + φ4 * (.003971 * φ2 - .001529 * φ4))), φ * (1.007226 + φ2 * (.015085 + φ4 * (-.044475 + .028874 * φ2 - .005916 * φ4))) ];
-  }
-  naturalEarth.invert = function(x, y) {
-    var φ = y, i = 25, δ;
-    do {
-      var φ2 = φ * φ, φ4 = φ2 * φ2;
-      φ -= δ = (φ * (1.007226 + φ2 * (.015085 + φ4 * (-.044475 + .028874 * φ2 - .005916 * φ4))) - y) / (1.007226 + φ2 * (.015085 * 3 + φ4 * (-.044475 * 7 + .028874 * 9 * φ2 - .005916 * 11 * φ4)));
-    } while (Math.abs(δ) > ε && --i > 0);
-    return [ x / (.8707 + (φ2 = φ * φ) * (-.131979 + φ2 * (-.013791 + φ2 * φ2 * φ2 * (.003971 - .001529 * φ2)))), φ ];
-  };
-  (d3.geo.naturalEarth = function() {
-    return projection(naturalEarth);
-  }).raw = naturalEarth;
-  var robinsonConstants = [ [ .9986, -.062 ], [ 1, 0 ], [ .9986, .062 ], [ .9954, .124 ], [ .99, .186 ], [ .9822, .248 ], [ .973, .31 ], [ .96, .372 ], [ .9427, .434 ], [ .9216, .4958 ], [ .8962, .5571 ], [ .8679, .6176 ], [ .835, .6769 ], [ .7986, .7346 ], [ .7597, .7903 ], [ .7186, .8435 ], [ .6732, .8936 ], [ .6213, .9394 ], [ .5722, .9761 ], [ .5322, 1 ] ];
-  robinsonConstants.forEach(function(d) {
-    d[1] *= 1.0144;
-  });
-  function robinson(λ, φ) {
-    var i = Math.min(18, Math.abs(φ) * 36 / π), i0 = Math.floor(i), di = i - i0, ax = (k = robinsonConstants[i0])[0], ay = k[1], bx = (k = robinsonConstants[++i0])[0], by = k[1], cx = (k = robinsonConstants[Math.min(19, ++i0)])[0], cy = k[1], k;
-    return [ λ * (bx + di * (cx - ax) / 2 + di * di * (cx - 2 * bx + ax) / 2), (φ > 0 ? halfπ : -halfπ) * (by + di * (cy - ay) / 2 + di * di * (cy - 2 * by + ay) / 2) ];
-  }
-  robinson.invert = function(x, y) {
-    var yy = y / halfπ, φ = yy * 90, i = Math.min(18, Math.abs(φ / 5)), i0 = Math.max(0, Math.floor(i));
-    do {
-      var ay = robinsonConstants[i0][1], by = robinsonConstants[i0 + 1][1], cy = robinsonConstants[Math.min(19, i0 + 2)][1], u = cy - ay, v = cy - 2 * by + ay, t = 2 * (Math.abs(yy) - by) / u, c = v / u, di = t * (1 - c * t * (1 - 2 * c * t));
-      if (di >= 0 || i0 === 1) {
-        φ = (y >= 0 ? 5 : -5) * (di + i);
-        var j = 50, δ;
-        do {
-          i = Math.min(18, Math.abs(φ) / 5);
-          i0 = Math.floor(i);
-          di = i - i0;
-          ay = robinsonConstants[i0][1];
-          by = robinsonConstants[i0 + 1][1];
-          cy = robinsonConstants[Math.min(19, i0 + 2)][1];
-          φ -= (δ = (y >= 0 ? halfπ : -halfπ) * (by + di * (cy - ay) / 2 + di * di * (cy - 2 * by + ay) / 2) - y) * degrees;
-        } while (Math.abs(δ) > ε2 && --j > 0);
-        break;
-      }
-    } while (--i0 >= 0);
-    var ax = robinsonConstants[i0][0], bx = robinsonConstants[i0 + 1][0], cx = robinsonConstants[Math.min(19, i0 + 2)][0];
-    return [ x / (bx + di * (cx - ax) / 2 + di * di * (cx - 2 * bx + ax) / 2), φ * radians ];
-  };
-  (d3.geo.robinson = function() {
-    return projection(robinson);
-  }).raw = robinson;
-  function sinusoidal(λ, φ) {
-    return [ λ * Math.cos(φ), φ ];
-  }
-  sinusoidal.invert = function(x, y) {
-    return [ x / Math.cos(y), y ];
-  };
-  (d3.geo.sinusoidal = function() {
-    return projection(sinusoidal);
-  }).raw = sinusoidal;
-  function aitoff(λ, φ) {
-    var cosφ = Math.cos(φ), sinciα = sinci(acos(cosφ * Math.cos(λ /= 2)));
-    return [ 2 * cosφ * Math.sin(λ) * sinciα, Math.sin(φ) * sinciα ];
-  }
-  aitoff.invert = function(x, y) {
-    if (x * x + 4 * y * y > π * π + ε) return;
-    var λ = x, φ = y, i = 25;
-    do {
-      var sinλ = Math.sin(λ), sinλ_2 = Math.sin(λ / 2), cosλ_2 = Math.cos(λ / 2), sinφ = Math.sin(φ), cosφ = Math.cos(φ), sin_2φ = Math.sin(2 * φ), sin2φ = sinφ * sinφ, cos2φ = cosφ * cosφ, sin2λ_2 = sinλ_2 * sinλ_2, C = 1 - cos2φ * cosλ_2 * cosλ_2, E = C ? acos(cosφ * cosλ_2) * Math.sqrt(F = 1 / C) : F = 0, F, fx = 2 * E * cosφ * sinλ_2 - x, fy = E * sinφ - y, δxδλ = F * (cos2φ * sin2λ_2 + E * cosφ * cosλ_2 * sin2φ), δxδφ = F * (.5 * sinλ * sin_2φ - E * 2 * sinφ * sinλ_2), δyδλ = F * .25 * (sin_2φ * sinλ_2 - E * sinφ * cos2φ * sinλ), δyδφ = F * (sin2φ * cosλ_2 + E * sin2λ_2 * cosφ), denominator = δxδφ * δyδλ - δyδφ * δxδλ;
-      if (!denominator) break;
-      var δλ = (fy * δxδφ - fx * δyδφ) / denominator, δφ = (fx * δyδλ - fy * δxδλ) / denominator;
-      λ -= δλ, φ -= δφ;
-    } while ((Math.abs(δλ) > ε || Math.abs(δφ) > ε) && --i > 0);
-    return [ λ, φ ];
-  };
-  (d3.geo.aitoff = function() {
-    return projection(aitoff);
-  }).raw = aitoff;
-  function winkel3(λ, φ) {
-    var coordinates = aitoff(λ, φ);
-    return [ (coordinates[0] + λ / halfπ) / 2, (coordinates[1] + φ) / 2 ];
-  }
-  winkel3.invert = function(x, y) {
-    var λ = x, φ = y, i = 25;
-    do {
-      var cosφ = Math.cos(φ), sinφ = Math.sin(φ), sin_2φ = Math.sin(2 * φ), sin2φ = sinφ * sinφ, cos2φ = cosφ * cosφ, sinλ = Math.sin(λ), cosλ_2 = Math.cos(λ / 2), sinλ_2 = Math.sin(λ / 2), sin2λ_2 = sinλ_2 * sinλ_2, C = 1 - cos2φ * cosλ_2 * cosλ_2, E = C ? acos(cosφ * cosλ_2) * Math.sqrt(F = 1 / C) : F = 0, F, fx = .5 * (2 * E * cosφ * sinλ_2 + λ / halfπ) - x, fy = .5 * (E * sinφ + φ) - y, δxδλ = .5 * F * (cos2φ * sin2λ_2 + E * cosφ * cosλ_2 * sin2φ) + .5 / halfπ, δxδφ = F * (sinλ * sin_2φ / 4 - E * sinφ * sinλ_2), δyδλ = .125 * F * (sin_2φ * sinλ_2 - E * sinφ * cos2φ * sinλ), δyδφ = .5 * F * (sin2φ * cosλ_2 + E * sin2λ_2 * cosφ) + .5, denominator = δxδφ * δyδλ - δyδφ * δxδλ, δλ = (fy * δxδφ - fx * δyδφ) / denominator, δφ = (fx * δyδλ - fy * δxδλ) / denominator;
-      λ -= δλ, φ -= δφ;
-    } while ((Math.abs(δλ) > ε || Math.abs(δφ) > ε) && --i > 0);
-    return [ λ, φ ];
-  };
-  (d3.geo.winkel3 = function() {
-    return projection(winkel3);
-  }).raw = winkel3;
-}
-
-module.exports = addProjectionsToD3;
diff --git a/test/image/baselines/geo_fitbounds-locations.png b/test/image/baselines/geo_fitbounds-locations.png
index 2b51b2ebf5d..7b6215fa3be 100644
Binary files a/test/image/baselines/geo_fitbounds-locations.png and b/test/image/baselines/geo_fitbounds-locations.png differ
diff --git a/test/image/baselines/geo_stereographic.png b/test/image/baselines/geo_stereographic.png
index fb7b258283c..cf0b24814ca 100644
Binary files a/test/image/baselines/geo_stereographic.png and b/test/image/baselines/geo_stereographic.png differ
diff --git a/test/jasmine/tests/geo_test.js b/test/jasmine/tests/geo_test.js
index a9d9b0c0b4d..732406e0368 100644
--- a/test/jasmine/tests/geo_test.js
+++ b/test/jasmine/tests/geo_test.js
@@ -1362,8 +1362,8 @@ describe('Test geo interactions', function() {
                 .toBe(hoverLabelCnt, msg);
         }
 
-        var px = 390;
-        var py = 290;
+        var px = 200;
+        var py = 200;
         var cnt = 0;
 
         Plotly.newPlot(gd, fig).then(function() {
@@ -1374,14 +1374,14 @@ describe('Test geo interactions', function() {
 
             return new Promise(function(resolve) {
                 var interval = setInterval(function() {
-                    px += 2;
+                    py -= 2;
                     mouseEvent('mousemove', px, py);
 
-                    if(px < 402) {
-                        _assert('- px ' + px, 1);
+                    if(py > 175) {
+                        _assert('- py ' + py, 1);
                         expect(cnt).toBe(0, 'no plotly_unhover event so far');
                     } else {
-                        _assert('- px ' + px, 0);
+                        _assert('- py ' + py, 0);
                         expect(cnt).toBe(1, 'plotly_unhover event count');
 
                         clearInterval(interval);
@@ -1421,66 +1421,6 @@ describe('Test geo interactions', function() {
         .then(done, done.fail);
     });
 
-    it('should plot to scope defaults when user setting lead to NaN map bounds', function(done) {
-        var gd = createGraphDiv();
-
-        spyOn(Lib, 'warn');
-
-        Plotly.newPlot(gd, [{
-            type: 'scattergeo',
-            lon: [0],
-            lat: [0]
-        }], {
-            geo: {
-                projection: {
-                    type: 'kavrayskiy7',
-                    rotation: {
-                        lat: 38.794799,
-                        lon: -81.622334,
-                    }
-                },
-                center: {
-                    lat: -81
-                },
-                lataxis: {
-                    range: [38.794799, 45.122292]
-                },
-                lonaxis: {
-                    range: [-82.904731, -81.622334]
-                }
-            },
-            width: 700,
-            heigth: 500
-        })
-        .then(function() {
-            var geoLayout = gd._fullLayout.geo;
-            var geo = geoLayout._subplot;
-
-            expect(geoLayout.projection.rotation).toEqual({
-                lon: 0, lat: 0, roll: 0,
-            });
-            expect(geoLayout.center).toEqual({
-                lon: 0, lat: 0
-            });
-            expect(geoLayout.lonaxis.range).toEqual([-180, 180]);
-            expect(geoLayout.lataxis.range).toEqual([-90, 90]);
-
-            expect(geo.viewInitial).toEqual({
-                'fitbounds': false,
-                'projection.rotation.lon': 0,
-                'center.lon': 0,
-                'center.lat': 0,
-                'projection.scale': 1
-            });
-
-            expect(Lib.warn).toHaveBeenCalledTimes(1);
-            expect(Lib.warn).toHaveBeenCalledWith(
-                'Invalid geo settings, relayout\'ing to default view.'
-            );
-        })
-        .then(done, done.fail);
-    });
-
     it('should get hover right for choropleths involving landmasses that cross antimeridian', function(done) {
         var gd = createGraphDiv();