Skip to content

Instantly share code, notes, and snippets.

@memoryfull
Created October 11, 2015 21:37
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 memoryfull/946a8172d074a865c4ff to your computer and use it in GitHub Desktop.
Save memoryfull/946a8172d074a865c4ff to your computer and use it in GitHub Desktop.
Affine transformation in d3.geo()

A demonstration of an Affine transformation in d3.geo().

For reference: an Affine transformation with same parameters in QGIS text

<!DOCTYPE html>
<!-- based on http://bl.ocks.org/mbostock/raw/5912673/ -->
<meta charset="utf-8">
<style>
.stroke {
fill: none;
stroke: #000;
stroke-width: 1px;
}
.fill {
fill: #fff;
}
.land {
fill: #ddd;
}
.boundary {
fill: none;
stroke: #fff;
stroke-width: 1px;
}
</style>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-geo-projection/0.2.9/d3.geo.projection.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/queue-async/1.0.7/queue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js"></script>
<script>
var width = 960,
height = 550;
var projection = d3.geo.robinson()
.scale(700)
.center([12,42])
.translate([width / 2, height / 2])
.precision(.1);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("use")
.attr("class", "stroke")
.attr("xlink:href", "#sphere");
queue()
.defer(d3.json, "https://bl.ocks.org/mbostock/raw/4090846/world-50m.json")
.await(ready);
function ready(error, world) {
if (error) throw error;
var country = svg.insert("g", ".graticule")
.attr("class", "land")
.selectAll("path")
.data(topojson.feature(world, world.objects.countries).features)
.enter().append("path")
.attr("d", path);
// Select a polygon for Italy by ISO code 380
country.filter(function(d) { return d.id === 380; })
.style("fill", "#545454")
// Rotate the points of the polygon,
// applying stream transform.
// This produces an Affine transform that is
// "too large".
//
// http://stackoverflow.com/a/31647135
.attr("d", d3.geo.path().projection(
{
stream: function(s) {
return projection.stream(
// Rotate Italy -7.2 degrees around
// a point with λ=0.5, φ=49.9
//
// See http://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations
d3.geo.transform({
point: function(x, y) { this.stream.point(0.992 * x + 0.125 * y - 6.25,
-0.125 * x + 0.992 * y + 0.456); }
// For reference: this produces a valid one-one x,y mapping:
//point: function(x, y) { this.stream.point(1 * x + 0 * y + 0, 0 * x + 1 * y + 0); }
}).stream(s)
);
}
}
))
// The straightforward alternative below is not working
// as well (NB: the negation of λ and φ as suggested in
// https://www.jasondavies.com/maps/rotate/)
// .attr("d", d3.geo.path().projection(projection.rotate([0.5, 49.9, -7.2])))
;
// For reference: in QGIS plugin
// Python code is as follows
// self.a=self.spinA.value()
// self.b=self.spinB.value()
// self.tx=self.spinTx.value()
// self.c=self.spinC.value()
// self.d=self.spinD.value()
// self.ty=self.spinTy.value()
// # x' = a x + b y + tx
// # y' = c x + d y + ty
// newx=self.a*vertex.x()+self.b*vertex.y()+self.tx
// newy=self.c*vertex.x()+self.d*vertex.y()+self.ty
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 + "px");
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment