A demonstration of an Affine transformation in d3.geo().
For reference: an Affine transformation with same parameters in QGIS
<!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> |