toolkit/javascript/d3/src/geo/albers.js
changeset 47 c0b4a8b5a012
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolkit/javascript/d3/src/geo/albers.js	Thu Apr 10 14:20:23 2014 +0200
@@ -0,0 +1,126 @@
+// Derived from Tom Carden's Albers implementation for Protovis.
+// http://gist.github.com/476238
+// http://mathworld.wolfram.com/AlbersEqual-AreaConicProjection.html
+
+d3.geo.albers = function() {
+  var origin = [-98, 38],
+      parallels = [29.5, 45.5],
+      scale = 1000,
+      translate = [480, 250],
+      lng0, // d3_geo_radians * origin[0]
+      n,
+      C,
+      p0;
+
+  function albers(coordinates) {
+    var t = n * (d3_geo_radians * coordinates[0] - lng0),
+        p = Math.sqrt(C - 2 * n * Math.sin(d3_geo_radians * coordinates[1])) / n;
+    return [
+      scale * p * Math.sin(t) + translate[0],
+      scale * (p * Math.cos(t) - p0) + translate[1]
+    ];
+  }
+
+  albers.invert = function(coordinates) {
+    var x = (coordinates[0] - translate[0]) / scale,
+        y = (coordinates[1] - translate[1]) / scale,
+        p0y = p0 + y,
+        t = Math.atan2(x, p0y),
+        p = Math.sqrt(x * x + p0y * p0y);
+    return [
+      (lng0 + t / n) / d3_geo_radians,
+      Math.asin((C - p * p * n * n) / (2 * n)) / d3_geo_radians
+    ];
+  };
+
+  function reload() {
+    var phi1 = d3_geo_radians * parallels[0],
+        phi2 = d3_geo_radians * parallels[1],
+        lat0 = d3_geo_radians * origin[1],
+        s = Math.sin(phi1),
+        c = Math.cos(phi1);
+    lng0 = d3_geo_radians * origin[0];
+    n = .5 * (s + Math.sin(phi2));
+    C = c * c + 2 * n * s;
+    p0 = Math.sqrt(C - 2 * n * Math.sin(lat0)) / n;
+    return albers;
+  }
+
+  albers.origin = function(x) {
+    if (!arguments.length) return origin;
+    origin = [+x[0], +x[1]];
+    return reload();
+  };
+
+  albers.parallels = function(x) {
+    if (!arguments.length) return parallels;
+    parallels = [+x[0], +x[1]];
+    return reload();
+  };
+
+  albers.scale = function(x) {
+    if (!arguments.length) return scale;
+    scale = +x;
+    return albers;
+  };
+
+  albers.translate = function(x) {
+    if (!arguments.length) return translate;
+    translate = [+x[0], +x[1]];
+    return albers;
+  };
+
+  return reload();
+};
+
+// A composite projection for the United States, 960x500. The set of standard
+// parallels for each region comes from USGS, which is published here:
+// http://egsc.usgs.gov/isb/pubs/MapProjections/projections.html#albers
+// TODO allow the composite projection to be rescaled?
+d3.geo.albersUsa = function() {
+  var lower48 = d3.geo.albers();
+
+  var alaska = d3.geo.albers()
+      .origin([-160, 60])
+      .parallels([55, 65]);
+
+  var hawaii = d3.geo.albers()
+      .origin([-160, 20])
+      .parallels([8, 18]);
+
+  var puertoRico = d3.geo.albers()
+      .origin([-60, 10])
+      .parallels([8, 18]);
+
+  function albersUsa(coordinates) {
+    var lon = coordinates[0],
+        lat = coordinates[1];
+    return (lat > 50 ? alaska
+        : lon < -140 ? hawaii
+        : lat < 21 ? puertoRico
+        : lower48)(coordinates);
+  }
+
+  albersUsa.scale = function(x) {
+    if (!arguments.length) return lower48.scale();
+    lower48.scale(x);
+    alaska.scale(x * .6);
+    hawaii.scale(x);
+    puertoRico.scale(x * 1.5);
+    return albersUsa.translate(lower48.translate());
+  };
+
+  albersUsa.translate = function(x) {
+    if (!arguments.length) return lower48.translate();
+    var dz = lower48.scale() / 1000,
+        dx = x[0],
+        dy = x[1];
+    lower48.translate(x);
+    alaska.translate([dx - 400 * dz, dy + 170 * dz]);
+    hawaii.translate([dx - 190 * dz, dy + 200 * dz]);
+    puertoRico.translate([dx + 580 * dz, dy + 430 * dz]);
+    return albersUsa;
+  };
+
+  return albersUsa.scale(lower48.scale());
+};