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> |