Skip to content

Instantly share code, notes, and snippets.

@robinhouston
Last active December 20, 2015 21:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robinhouston/6200770 to your computer and use it in GitHub Desktop.
Save robinhouston/6200770 to your computer and use it in GitHub Desktop.
A family of equal-area projections

There is a family of pseudocylindrical equal-area projections that starts with the cylindrical equal area and ends with the sinusoidal, parameterised by t ∈ [0,1].

The formulas are:

x = λ cos^t φ

y = ∫_0^φ cos^(1-t) φ

where the integral has no closed form in general.

Update: It turns out this family was described, briefly, by Waldo Tolber in 1973: he calls it “a weighted geometrical mean of the meridians from the Lambert and sinusoidal projections”.

<!DOCTYPE html>
<meta charset="utf-8">
<title>A family of equal-area projections</title>
<script charset="utf-8" src="http://d3js.org/d3.v3.min.js"></script>
<script charset="utf-8" src="http://d3js.org/topojson.v1.min.js"></script>
<script charset="utf-8" src="integrate.js"></script>
<style>
.graticule { fill: none; stroke: #777; stroke-width: .5px; stroke-opacity: .5; }
.outline { fill: none; stroke: #777; stroke-width: 2px; stroke-opacity: 1; }
.land { fill: #222; }
.boundary { fill: none; stroke: #fff; stroke-width: .5px; }
#controls { width: 100%; position: relative; height: 48px; }
#slider { position: absolute; left: 80px; width: 800px; }
</style>
<div id="map"></div>
<div id="controls">
<input id="slider" type="range" min="0" max="1" step="0.01" value="0.5">
</div>
<script>
var width = 960,
height = 500;
var t, scale;
function set_t(new_t) {
t = new_t;
// Set the scale so the map has 2-to-1 proportions
var max_y = integrate(dy, 0, Math.PI/2, [t]);
scale = Math.sqrt(2*max_y/Math.PI);
}
function dy(φ, args) {
var t = args[0];
return Math.pow(Math.cos(φ), 1-t);
}
set_t(0.5);
var projection = d3.geo.projection(function(λ, φ) {
return [
λ * Math.pow(Math.cos(φ), t) * scale,
integrate(dy, 0, Math.abs(φ), [t]) * (φ < 0 ? -1 : 1) / scale
];
});
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule();
var svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
svg.append("path")
.datum(graticule.outline())
.attr("class", "outline")
.attr("d", path);
d3.json("/mbostock/raw/4090846/world-50m.json", function(error, world) {
svg.insert("path", ".graticule")
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path);
svg.insert("path", ".graticule")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
});
d3.select(self.frameElement).style("height", (height + 48) + "px");
d3.select("#slider").on("change", function() {
set_t(parseFloat(this.value));
svg.selectAll("path").transition().attr("d", path);
});
</script>
(function() {
// Simple Simpson’s rule integration
var DELTA = 0.1;
function integrate_one_slice(f, a, b, args) {
return (b-a) * (
f(a, args)
+ 4*f((a+b)/2, args)
+ f(b, args)
) / 6;
}
function integrate(f, a, b, args) {
var x = a,
integral = 0;
while (x < b) {
integral += integrate_one_slice(f, x, Math.min(x+DELTA, b), args);
x += DELTA;
}
return integral;
}
window.integrate = integrate;
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment